class Meta:
model = Conference
fields = '__all__'
+ exclude = ['bbb_server']
class UserProfileForm(ModelForm):
class Meta:
--- /dev/null
+# -*- 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
--- /dev/null
+# 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'),
+ ),
+ ]
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)
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;
<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 %}
<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>
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):
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
class BBBServerAdmin(admin.ModelAdmin):
model = BBBServer
- list_display = ('url', 'api_key')
+ list_display = ('url', 'api_key', 'api_version')
class WebclassSlotInline(admin.StackedInline):
model = WebclassSlot
--- /dev/null
+# 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'},
+ ),
+ ]
--- /dev/null
+# 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'),
+ ),
+ ]
import calendar
import datetime
from datetime import date, timedelta
+import random
+import string
from unidecode import unidecode
import django.db.models as models
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:
'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'):
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'
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 {}
# 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
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(),
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")
]
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):
"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