From 68119792d36d84960418b4c2f399febf1236c592 Mon Sep 17 00:00:00 2001 From: Yoan Le Clanche Date: Mon, 28 Apr 2025 14:10:22 +0200 Subject: [PATCH] Professor can now add webclass from the UI : --- ...forma-webclass-create-conferencerecords.py | 31 +++++++ teleforma/static/teleforma/css/teleforma.css | 14 ++- .../templates/teleforma/course_detail.html | 2 + .../migrations/0010_auto_20250424_1640.py | 23 +++++ .../0011_webclassrecord_professor.py | 20 +++++ teleforma/webclass/models.py | 8 +- .../templates/webclass/webclass_record.html | 7 +- teleforma/webclass/urls.py | 8 +- teleforma/webclass/views.py | 87 ++++++++++++++++++- 9 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 teleforma/management/commands/teleforma-webclass-create-conferencerecords.py create mode 100644 teleforma/webclass/migrations/0010_auto_20250424_1640.py create mode 100644 teleforma/webclass/migrations/0011_webclassrecord_professor.py diff --git a/teleforma/management/commands/teleforma-webclass-create-conferencerecords.py b/teleforma/management/commands/teleforma-webclass-create-conferencerecords.py new file mode 100644 index 00000000..81b0aa9d --- /dev/null +++ b/teleforma/management/commands/teleforma-webclass-create-conferencerecords.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +from optparse import make_option +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth.models import User +from ...models.core import Conference +from teleforma.webclass.models import * + + + +class Command(BaseCommand): + help = "Store record id for conferences" + + def handle(self, *args, **options): + bbb = BBBServer.objects.get(pk=2).get_instance() + + missing_records = WebclassRecord.objects.filter(record_id__isnull=True, room_id__isnull=False) + + for record in missing_records: + recordings = bbb.get_recordings(record.room_id).get_field('recordings') + if hasattr(recordings, 'get'): + recordings = recordings['recording'] + if type(recordings) is XMLDictNode: + recordings = [recordings] + if recordings: + # get last session id from the same course + record.record_id = recordings[0]['recordID'] + + print("Add record id %s for webclass record %s" % (record.record_id, record.id)) + record.save() \ No newline at end of file diff --git a/teleforma/static/teleforma/css/teleforma.css b/teleforma/static/teleforma/css/teleforma.css index 0efb41c8..09be8596 100644 --- a/teleforma/static/teleforma/css/teleforma.css +++ b/teleforma/static/teleforma/css/teleforma.css @@ -1728,8 +1728,18 @@ form.add_actus #id_text_parent{ padding: 0em; font-weight: bold; font-size: 1.2em; - } - + display: flex; + flex-direction: row; + justify-content: space-between; +} +.create-bbb-conference { + font-size: 0.8em; +} +.bbb-placeholder { + width: 100%; + height: 116px; + background-color: #dfdfdf; +} .course_content { background-color: #FFF; -moz-border-radius: 8px 0px 8px 8px; diff --git a/teleforma/templates/teleforma/course_detail.html b/teleforma/templates/teleforma/course_detail.html index 44793c31..adf3548b 100644 --- a/teleforma/templates/teleforma/course_detail.html +++ b/teleforma/templates/teleforma/course_detail.html @@ -82,6 +82,8 @@
{{ course.title }} - Corrections de copies{% if course.description %} - {{ course.description }}{% endif %} + {% if user.is_staff or user.professor.count %}Créer une correction de copie{% endif %} +
{% block webclass_corrections %} {% include "webclass/inc/webclass_corrections_list.html" %} diff --git a/teleforma/webclass/migrations/0010_auto_20250424_1640.py b/teleforma/webclass/migrations/0010_auto_20250424_1640.py new file mode 100644 index 00000000..966161e8 --- /dev/null +++ b/teleforma/webclass/migrations/0010_auto_20250424_1640.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.25 on 2025-04-24 16:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('webclass', '0009_auto_20250214_1201'), + ] + + operations = [ + migrations.AddField( + model_name='webclassrecord', + name='room_id', + field=models.CharField(blank=True, max_length=255, null=True, unique=True, verbose_name='id de la conférence BBB (généré automatiquement)'), + ), + migrations.AlterField( + model_name='webclassrecord', + name='record_id', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Enregistrement BBB'), + ), + ] diff --git a/teleforma/webclass/migrations/0011_webclassrecord_professor.py b/teleforma/webclass/migrations/0011_webclassrecord_professor.py new file mode 100644 index 00000000..10abc9db --- /dev/null +++ b/teleforma/webclass/migrations/0011_webclassrecord_professor.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.25 on 2025-04-24 16:46 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('teleforma', '0029_merge_0028_auto_20240318_1139_0028_auto_20240415_0414'), + ('webclass', '0010_auto_20250424_1640'), + ] + + operations = [ + migrations.AddField( + model_name='webclassrecord', + name='professor', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='webclass_records', to='teleforma.professor', verbose_name='professor'), + ), + ] diff --git a/teleforma/webclass/models.py b/teleforma/webclass/models.py index 0ff52a60..2b834112 100644 --- a/teleforma/webclass/models.py +++ b/teleforma/webclass/models.py @@ -429,13 +429,19 @@ class WebclassRecord(models.Model): period = models.ForeignKey('teleforma.Period', verbose_name=_('period'), on_delete=models.CASCADE) course = models.ForeignKey( 'teleforma.Course', related_name='webclass_records', verbose_name=_('course'), on_delete=models.CASCADE) - record_id = models.CharField("Enregistrement BBB", max_length=255) + record_id = models.CharField("Enregistrement BBB", max_length=255, blank=True, null=True) # not used for now, but may be handy if we need to optimize performance bbb_server = models.ForeignKey( 'BBBServer', related_name='webclass_records', verbose_name='Serveur BBB', on_delete=models.CASCADE) created = models.DateTimeField("Date de la conférence", auto_now_add=True) session = models.CharField( _('session'), choices=session_choices, max_length=16, blank=True, null=True) + + # Used when conference are created from webclass by teachers to retrive the record id + room_id = models.CharField( + 'id de la conférence BBB (généré automatiquement)', blank=True, null=True, max_length=255, unique=True) + professor = models.ForeignKey('teleforma.Professor', related_name='webclass_records', verbose_name=_('professor'), on_delete=models.SET_NULL, blank=True, null=True) + WEBCLASS = 'WC' CORRECTION = 'CC' diff --git a/teleforma/webclass/templates/webclass/webclass_record.html b/teleforma/webclass/templates/webclass/webclass_record.html index 3e6f839b..5f40c556 100644 --- a/teleforma/webclass/templates/webclass/webclass_record.html +++ b/teleforma/webclass/templates/webclass/webclass_record.html @@ -3,8 +3,11 @@ - {% trans 'Click here' %} -
Cliquez-ici
+ {% if record.preview %} + {% trans 'Click here' %} + {% else %} + {% trans 'Click here' %} + {% endif %}
diff --git a/teleforma/webclass/urls.py b/teleforma/webclass/urls.py index 7cc6b9cf..f9c8eaf9 100644 --- a/teleforma/webclass/urls.py +++ b/teleforma/webclass/urls.py @@ -36,7 +36,7 @@ from django.conf.urls import url from ..webclass.views import (WebclassAppointment, WebclassProfessorAppointments, - WebclassRecordsFormView, WebclassRecordView, + WebclassRecordsFormView, WebclassRecordView, create_cc_bbb_conference, join_webclass, unregister) urlpatterns = [ @@ -53,6 +53,8 @@ urlpatterns = [ name="teleforma-webclass-unregister"), url(r'^desk/webclass/(?P.*)/join/$', join_webclass, - name="teleforma-webclass-join") - + name="teleforma-webclass-join"), + url(r'^desk/webclass/(?P.*)/(?P.*)/create_cc_bbb_conference/$', + create_cc_bbb_conference, + name="teleforma-create-cc-bbb-conference") ] diff --git a/teleforma/webclass/views.py b/teleforma/webclass/views.py index 3cf8c1cc..6d2738b1 100644 --- a/teleforma/webclass/views.py +++ b/teleforma/webclass/views.py @@ -7,12 +7,19 @@ from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.decorators import method_decorator from django.views.generic import FormView, TemplateView, View +from bigbluebutton_api_python.exception import BBBException + from datetime import datetime +import string +import random + +from ..models.core import Conference, Course, Professor from ..decorators import access_required from ..views.core import get_courses, get_periods from ..webclass.forms import WebclassRecordsForm -from ..webclass.models import Webclass, WebclassSlot +from ..webclass.models import BBBServer, Webclass, WebclassRecord, WebclassSlot +from django.contrib.auth.models import User class WebclassProfessorAppointments(TemplateView): @@ -231,3 +238,81 @@ def unregister(request, pk): "Votre réservation a été annulé.") # redirect to register form return redirect(reverse("teleforma-webclass-appointments", kwargs={'pk':webclass_slot.webclass.id})) + + + +@access_required +def create_cc_bbb_conference(request, period_id, course_id): + """ + Join a bbb webclass for "correction de copie" (cc) with a random room id. + A cron script will later create a WebclassRecord object. + """ + + username = request.user.get_full_name() + is_professor = len(request.user.professor.all()) >= 1 + is_staff = request.user.is_staff or request.user.is_superuser + + if not is_professor and not is_staff: + raise ValueError("User is not a professor or staff") + course = Course.objects.get(pk=course_id) + + year = datetime.now().year + bbb = BBBServer.objects.get(pk=2).get_instance() + # generate password + password = User.objects.make_random_password() + # generate random room id + room_id = "".join(random.choices(string.ascii_letters + string.digits, k=20)) + + params = { + 'name': "Conférence %s" % course.title, + 'moderatorPW': password, + 'attendeePW': "PWATTENDEE", + # 'maxParticipants':self.webclass_max_participants + 1, + 'welcome': "Pré-Barreau : Bienvenue sur la conférence \"%s\"." % (course.title), + 'record': True, + 'autoStartRecording':True, + 'muteOnStart': True, + 'allowModsToUnmuteUsers': True, + 'logo':'https://e-learning.crfpa.pre-barreau.com/static/teleforma/images/logo_pb.png', + 'copyright': "© %d Pré-Barreau" % year, + # 'guestPolicy':'ALWAYS_ACCEPT' + 'bannerText': "Pré-Barreau - CRFPA", + 'bannerColor': "#003768", + # 'customStyleUrl': site_url+"/static/teleforma/css/bbb.css" + 'logoutURL': "https://e-learning.ae.pre-barreau.com", + 'endWhenNoModerator': True, + 'meetingLayout': "VIDEO_FOCUS", + "notifyRecordingIsOn": True, + } + + meta = { + 'origin': 'crfpa', + 'periodid': period_id, + 'courseid': course_id, + 'professorid': request.user.id, + 'type': 'cc', + } + + try: + result = bbb.create_meeting( + room_id, params=params, meta=meta) + except BBBException as e: + print(e) + raise + + try: + professor = request.user.professor.get() + except Professor.DoesNotExist: + professor = None + + WebclassRecord.objects.create( + room_id=room_id, + bbb_server=BBBServer.objects.get(pk=2), + period_id=period_id, + course_id=course_id, + category=WebclassRecord.CORRECTION, + professor=professor, + ) + + params = {'userID': request.user.username} + return redirect(bbb.get_join_meeting_url(username, room_id, password, params)) -- 2.39.5