]> git.parisson.com Git - teleforma.git/commitdiff
Create conference from BBB
authorYoan Le Clanche <yoanl@pilotsystems.net>
Mon, 21 Oct 2024 11:35:59 +0000 (13:35 +0200)
committerGuillaume Pellerin <guillaume.pellerin@parisson.com>
Mon, 21 Oct 2024 21:15:46 +0000 (23:15 +0200)
14 files changed:
teleforma/forms.py
teleforma/management/commands/teleforma-webclass-create-conferencerecords.py [new file with mode: 0644]
teleforma/migrations/0013_auto_20240926_1615.py [new file with mode: 0644]
teleforma/models/core.py
teleforma/static/teleforma/css/teleforma.css
teleforma/templates/teleforma/course_detail.html
teleforma/templates/teleforma/inc/media_list.html
teleforma/views/core.py
teleforma/webclass/admin.py
teleforma/webclass/migrations/0004_alter_webclassrecord_options.py [new file with mode: 0644]
teleforma/webclass/migrations/0005_bbbserver_api_version.py [new file with mode: 0644]
teleforma/webclass/models.py
teleforma/webclass/urls.py
teleforma/webclass/views.py

index 7923269fa3770a58094baacec7c809fd5ecbc8ae..57193a9127e081c8adf74d63a95c9fa06c053da5 100644 (file)
@@ -56,6 +56,7 @@ class BBBConferenceForm(ModelForm):
     class Meta:
         model = Conference
         fields = '__all__'
+        exclude = ['bbb_server']
 
 class UserProfileForm(ModelForm):
     class Meta:
diff --git a/teleforma/management/commands/teleforma-webclass-create-conferencerecords.py b/teleforma/management/commands/teleforma-webclass-create-conferencerecords.py
new file mode 100644 (file)
index 0000000..1ce0b21
--- /dev/null
@@ -0,0 +1,38 @@
+# -*- 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_conferences = Conference.objects.filter(bbb_record_id__isnull=True, bbb_room_id__isnull=False)
+
+        for conference in missing_conferences:
+            recordings = bbb.get_recordings(conference.bbb_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
+                sessions = Conference.objects.filter(course=conference.course, course_type=conference.course_type)
+                last_session = 0
+                for session in sessions:
+                    if session.session_as_int > last_session:
+                        last_session = session.session_as_int
+
+                conference.session = last_session + 1
+                conference.bbb_record_id = recordings[0]['recordID']
+
+                print("Add record id %s for conference %s" % (conference.bbb_record_id, conference.id))
+                conference.save()
\ No newline at end of file
diff --git a/teleforma/migrations/0013_auto_20240926_1615.py b/teleforma/migrations/0013_auto_20240926_1615.py
new file mode 100644 (file)
index 0000000..d5bbda4
--- /dev/null
@@ -0,0 +1,30 @@
+# Generated by Django 3.2.25 on 2024-09-26 16:15
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('webclass', '0003_alter_webclass_session'),
+        ('teleforma', '0012_auto_20240925_0929'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='conference',
+            name='bbb_record_id',
+            field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Enregistrement BBB'),
+        ),
+        migrations.AddField(
+            model_name='conference',
+            name='bbb_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.AddField(
+            model_name='conference',
+            name='bbb_server',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='conference_records', to='webclass.bbbserver', verbose_name='Serveur BBB'),
+        ),
+    ]
index a20fa0821d661aee5d8481833d11ab309c005dac..90e5f604eb4efe7ae8ccd26696a6a455a6d2abf1 100755 (executable)
@@ -412,6 +412,14 @@ class Conference(models.Model):
     notified = models.BooleanField(_('notified'), default=False)
     notified_live = models.BooleanField("Notifié live", default=False)
 
+    # bbb
+    bbb_room_id = models.CharField(
+        'id de la conférence BBB (généré automatiquement)', blank=True, null=True, max_length=255, unique=True)
+    bbb_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(
+        'webclass.BBBServer', related_name='conference_records', verbose_name='Serveur BBB', on_delete=models.CASCADE, null=True, blank=True)
+
     @property
     def description(self):
         return str(self)
index 15e037ca57a29311887fe091ba048c635028a241..deb06fb9e8ece70ec1def4a4ce2bb10e8a3a838a 100644 (file)
@@ -1727,7 +1727,15 @@ 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;
+    font-weight: normal;
+}
+
 
 .course_content {
     background-color: #FFF;
index 10336f933028aa9976a60efdcbb164a56aef2d68..7c4cfde9ca68c021cba357d0f31e3b08e5185b93 100644 (file)
   <div class="course">
     <div class="course_title">{{ course.title }} - {{ type }}{% if course.description %} -
       {{ course.description }}{% endif %}
+      {% if user.is_staff or user.professor.count %}<a href="{% url 'teleforma-create-bbb-conference' period.id course.id type.id %}" class="create-bbb-conference">Créer une conférence</a>{% endif %}
     </div>
 
+
+    
+
     {% if type.name == 'Quiz' %}
     <div class="course_content">
       {% if course.quiz.all %}
index 720994b79e982649f073902d50d0fc43ff31d311..9224be4c476af5ad9b4eca0dc1c71e78b081ad8d 100644 (file)
     <table class="listing" width="100%">
     <tbody>
         {% for conference in all_conferences %}
-          {% if conference.video %}
+        
+            
+          {% if conference.video or conference.record %}
             <tr>
-            <td {% if forloop.first %}class="border-top"{% endif %} width="230px" style="vertical-align:middle">
-            <a href="{% url 'teleforma-media-detail' period.id conference.video.id %}" title="{% trans "Play" %}">
-            {% if conference.video.poster_file %}
-               {% thumbnail conference.video.poster_file "168x96" as im %}
-                <div style="background: no-repeat url('{{ im.url }}') 0 1px; background-size: 100%; background-color: #dfdfdf;">
-                 <img src="/static/teleforma/images/play_168.png" width="100%" alt="{% trans 'Click here' %}" />
-                </div>
-               {% endthumbnail %}
+            {% if conference.record %}
+                <td {% if forloop.first %}class="border-top" {% endif %} style="width:200px">
+                    <a href="{% url 'teleforma-webclass-record' %}?url={{conference.record.url}}" title="{% trans "View" %}">
+                        {% if conference.record.preview %}
+                        <img src="{{ conference.record.preview }}" style="width:176px" alt="{% trans 'Click here' %}" /> xxx {{conference.record}}
+                        {% else %}
+                            <img src="/static/teleforma/images/play_168.png" width="100%" alt="{% trans 'Click here' %}" style="background-color: #dfdfdf;" />
+                        {% endif %}
+                    </a>
+                </td>
             {% else %}
-              <div>{% trans 'Click here' %}</div>
+
+                <td {% if forloop.first %}class="border-top"{% endif %} width="230px" style="vertical-align:middle">
+                <a href="{% url 'teleforma-media-detail' period.id conference.video.id %}" title="{% trans "Play" %}">
+                {% if conference.video.poster_file %}
+                {% thumbnail conference.video.poster_file "168x96" as im %}
+                    <div style="background: no-repeat url('{{ im.url }}') 0 1px; background-size: 100%; background-color: #dfdfdf;">
+                    <img src="/static/teleforma/images/play_168.png" width="100%" alt="{% trans 'Click here' %}" />
+                    </div>
+                {% endthumbnail %}
+                {% else %}
+                <div>{% trans 'Click here' %}</div>
+                {% endif %}
+                {% comment %}<div>{% trans 'Click here' %}</div>{% endcomment %}
+                </a>
+                </td>
             {% endif %}
-            {% comment %}<div>{% trans 'Click here' %}</div>{% endcomment %}
-            </a>
-            </td>
             {% conference_publication conference as publication %}
             <td {% if forloop.first %}class="border-top"{% endif %} width="60%" style="padding-left: 1em;">
                 <div>
index 1559d26a0d1e0778886d766668536112b29da9cc..d189e02172620203a05d94fddddccb8e1b92a908 100644 (file)
@@ -91,7 +91,7 @@ from ..models.core import (Conference, Course, CourseType, Department,
                            Document, DocumentType, Media, MediaTranscoded,
                            Organization, Period, Professor,
                            get_user_role)
-from ..webclass.models import Webclass, WebclassRecord
+from ..webclass.models import Webclass, WebclassRecord, get_records_from_bbb
 from .pages import get_page_content
 
 # def render(request, template, data=None, mimetype=None):
@@ -160,11 +160,25 @@ def get_course_conferences(period, course, course_type, status_min=3):
         course_type=course_type,
         status__gte=status_min).distinct()
 
+    record_ids = set()
     for conference in cc:
         # do not include conferences with publication rules
         if conference.id not in already_added:
             conferences.append(conference)
+
+        if conference.bbb_record_id:
+            record_ids.add(conference.bbb_record_id)
+        
     conferences = sorted(conferences, key=lambda c:-c.session_as_int)
+
+    if record_ids:
+        records = get_records_from_bbb(recording_id=','.join(record_ids))
+        records_dict = {record['id']: record for record in records}
+
+        # print(records)
+        for conference in conferences:
+            if conference.bbb_record_id:
+                conference.record = records_dict.get(conference.bbb_record_id)
     return conferences
 
 
index 8dc7c4afeb7c88682e79b7ad2c749a54e02bf3b2..46b267e0c24359ba926a5dd93c4cfd909442e6d4 100644 (file)
@@ -6,7 +6,7 @@ from teleforma.webclass.models import (BBBServer, Webclass, WebclassRecord,
 
 class BBBServerAdmin(admin.ModelAdmin):
     model = BBBServer
-    list_display = ('url', 'api_key')
+    list_display = ('url', 'api_key', 'api_version')
 
 class WebclassSlotInline(admin.StackedInline):
     model = WebclassSlot
diff --git a/teleforma/webclass/migrations/0004_alter_webclassrecord_options.py b/teleforma/webclass/migrations/0004_alter_webclassrecord_options.py
new file mode 100644 (file)
index 0000000..1183b83
--- /dev/null
@@ -0,0 +1,17 @@
+# Generated by Django 3.2.25 on 2024-09-26 16:17
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('webclass', '0003_alter_webclass_session'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='webclassrecord',
+            options={'verbose_name': 'enregistrement webclass', 'verbose_name_plural': 'enregistrements webclass'},
+        ),
+    ]
diff --git a/teleforma/webclass/migrations/0005_bbbserver_api_version.py b/teleforma/webclass/migrations/0005_bbbserver_api_version.py
new file mode 100644 (file)
index 0000000..29e1886
--- /dev/null
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-09-30 16:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('webclass', '0004_alter_webclassrecord_options'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='bbbserver',
+            name='api_version',
+            field=models.CharField(default='1', max_length=100, verbose_name='API Version'),
+        ),
+    ]
index 430ae8d7e15144c7b4b9dfb61c5d8dd6c79aacbb..0ae0791e372b3f4cbf50db40e9e858c7b49d98d7 100644 (file)
@@ -3,6 +3,8 @@
 import calendar
 import datetime
 from datetime import date, timedelta
+import random
+import string
 from unidecode import unidecode
 
 import django.db.models as models
@@ -48,7 +50,15 @@ def get_records_from_bbb(**kwargs):
             recordings = [recordings]
         for recording in recordings:
             # recording.prettyprint()
-            url = recording.get('playback', {}).get('format', {}).get('url')
+            format = {}
+            if server.api_version == '1':
+                format = recording.get('playback', {}).get('format', {})
+            else:
+                formats = recording.get('playback', {}).get('format', [])
+                for format in formats:
+                    if format.get('type') == 'presentation':
+                        break
+            url = format.get('url')
             if url:
                 url = str(url)
             else:
@@ -63,7 +73,7 @@ def get_records_from_bbb(**kwargs):
                 'end': end,
                 'end_date': datetime.datetime.fromtimestamp(end),
                 'url': url,
-                'preview': str(recording.get('playback', {}).get('format', {}).get('preview', {}).get('images', {}).get('image', [])),
+                'preview': str(format.get('preview', {}).get('images', {}).get('image', '')),
                 'state': str(recording['state']),
             }
             if recording['metadata'].get('periodid'):
@@ -113,6 +123,7 @@ def get_records(period_id=None, course_id=None, rooms=None, recording_id=None):
 class BBBServer(models.Model):
     url = models.CharField("Url du serveur BBB", max_length=100)
     api_key = models.CharField("API Key", max_length=100)
+    api_version = models.CharField("API Version", max_length=100, default="1")
 
     class Meta(MetaCore):
         db_table = app_label + '_' + 'bbb_server'
@@ -424,19 +435,22 @@ class WebclassRecord(models.Model):
 
     class Meta(MetaCore):
         db_table = app_label + '_' + 'webclass_record'
-        verbose_name = 'enregistrement'
-        verbose_name_plural = 'enregistrements'
+        verbose_name = 'enregistrement webclass'
+        verbose_name_plural = 'enregistrements webclass'
 
     def __str__(self):
         return "Enregistrement webclass %d" % self.id
 
-    @staticmethod
-    def get_records(period, course):
+    @classmethod
+    def get_records(cls, period, course):
         record_ids = set()
+        records_mapping = {}
         # id : category mapping
         category_mapping = {}
-        for record in WebclassRecord.objects.filter(period=period, course=course):
+        # get current class
+        for record in cls.objects.filter(period=period, course=course):
             record_ids.add(record.record_id)
+            records_mapping[record.record_id] = record
             category_mapping[record.record_id] = record.category
         if not record_ids:
             return {}
@@ -445,9 +459,121 @@ class WebclassRecord(models.Model):
         # group records by category
         categories = {}
         for record in records:
+            record['obj'] = records_mapping.get(record['id'])
             category = category_mapping[record['id']]
             if category not in categories:
                 categories[category] = []
             categories[category].append(record)
-
         return categories
+
+# class ConferenceRecord(WebclassRecord):
+
+#     room_id = models.CharField(
+#         'id de la conférence BBB (généré automatiquement)', blank=True, null=True, max_length=255, unique=True)
+    
+#     category = None
+#     course_type = models.ForeignKey('teleforma.CourseType', verbose_name=_('course type'), on_delete=models.CASCADE)
+
+
+#     class Meta(MetaCore):
+#         db_table = app_label + '_' + 'conference_record'
+#         verbose_name = 'enregistrement conférence'
+#         verbose_name_plural = 'enregistrements conférence'
+
+#     def __str__(self):
+#         return "Enregistrement conférence %d" % self.id
+
+#     @classmethod
+#     def get_bbb_instance(cls):
+#         return BBBServer.objects.get(pk=2).get_instance()
+
+
+#     @classmethod
+#     def create_bbb_room(cls, request, period_id, course_id, course_type_id):
+#         """ create a BBB room and generate meeting id and moderator password """
+
+#         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")
+
+
+#         year = datetime.datetime.now().year
+#         bbb = cls.get_bbb_instance()
+
+#         # generate password
+#         # password = User.objects.make_random_password()
+#         password = "test2"
+#         # generate random room id
+#         room_id = "".join(random.choices(string.ascii_letters + string.digits, k=20))
+
+#         params = {
+#             'name': "test",
+#             'moderatorPW': password,
+#             # 'attendeePW': password+"2",
+#             # 'maxParticipants':self.webclass_max_participants + 1,
+#             'welcome': "Pré-Barreau : Bienvenue sur la conférence \"%s\"." % ("xxxx"),
+#             '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",
+#             'bannerColor': "#003768",
+#             # 'customStyleUrl': site_url+"/static/teleforma/css/bbb.css"
+#         }
+#         meta = {
+#             'origin': 'ae',
+#             'periodid': period_id,
+#             'courseid': course_id,
+#             'course_type_id': course_type_id,
+#             'professorid': request.user.id,
+#         }
+#         try:
+#             result = bbb.create_meeting(
+#                 room_id, params=params, meta=meta)
+#         except BBBException as e:
+#             print(e)
+#             raise
+#         ConferenceRecord.objects.create(
+#             room_id=room_id,
+#             bbb_server=BBBServer.objects.get(pk=2),
+#             period_id=period_id,
+#             course_id=course_id,
+#             course_type_id=course_type_id,
+#         )
+
+
+#         params = {'userID': request.user.username}
+#         print(room_id)
+#         return bbb.get_join_meeting_url(username, room_id, password, params)
+    
+
+#     @classmethod
+#     def get_records(cls, period, course, course_type):
+#         record_ids = set()
+#         records_mapping = {}
+#         # id : category mapping
+#         category_mapping = {}
+#         # get current class
+#         for record in cls.objects.filter(period=period, course=course, course_type=course_type, record_id__isnull=False):
+#             record_ids.add(record.record_id)
+#             records_mapping[record.record_id] = record
+#             category_mapping[record.record_id] = record.category
+#         if not record_ids:
+#             return {}
+#         records = get_records_from_bbb(recording_id=','.join(record_ids))
+
+#         # group records by category
+#         categories = {}
+#         for record in records:
+#             record['obj'] = records_mapping.get(record['id'])
+#             category = category_mapping[record['id']]
+#             if category not in categories:
+#                 categories[category] = []
+#             categories[category].append(record)
+#         print(categories)
+#         return categories
\ No newline at end of file
index 7cc6b9cfd07b12da8c186c2b49bc203d1fade981..c4105ed82eca597bb1f62f2276f416c0cce7b128 100644 (file)
@@ -37,7 +37,7 @@ from django.conf.urls import url
 from ..webclass.views import (WebclassAppointment,
                               WebclassProfessorAppointments,
                               WebclassRecordsFormView, WebclassRecordView,
-                              join_webclass, unregister)
+                              join_webclass, unregister, create_bbb_conference)
 
 urlpatterns = [
     url(r'^desk/webclass_appointments/(?P<pk>.*)$', WebclassAppointment.as_view(),
@@ -53,6 +53,8 @@ urlpatterns = [
         name="teleforma-webclass-unregister"),
     url(r'^desk/webclass/(?P<pk>.*)/join/$',
         join_webclass,
-        name="teleforma-webclass-join")
-    
+        name="teleforma-webclass-join"),
+    url(r'^desk/webclass/(?P<period_id>.*)/(?P<course_id>.*)/(?P<course_type_id>.*)/create_conference/$',
+        create_bbb_conference,
+        name="teleforma-create-bbb-conference")
 ]
index 3a4c1788da89ed15610f0f433377715de60e9a9b..d7dff61500d660ef962268d26a6883e02127334a 100644 (file)
@@ -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 random
+import string
+
+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, WebclassSlot
+from django.contrib.auth.models import User
 
 
 class WebclassProfessorAppointments(TemplateView):
@@ -228,3 +235,73 @@ 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_bbb_conference(request, period_id, course_id, course_type_id):
+    """
+    Create a BBB conference
+    """
+
+    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': password+"2",
+        # 'maxParticipants':self.webclass_max_participants + 1,
+        'welcome': "Pré-Barreau : Bienvenue sur la conférence \"%s\"." % (course.title),
+        'record': False,
+        # '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",
+        'bannerColor': "#003768",
+        # 'customStyleUrl': site_url+"/static/teleforma/css/bbb.css"
+    }
+    meta = {
+        'origin': 'ae',
+        'periodid': period_id,
+        'courseid': course_id,
+        'course_type_id': course_type_id,
+        'professorid': request.user.id,
+    }
+    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
+    Conference.objects.create(
+        bbb_room_id=room_id,
+        bbb_server=BBBServer.objects.get(pk=2),
+        period_id=period_id,
+        course_id=course_id,
+        course_type_id=course_type_id,
+        professor=professor,
+    )
+
+    params = {'userID': request.user.username}
+    return redirect(bbb.get_join_meeting_url(username, room_id, password, params))
\ No newline at end of file