From: Yoan Le Clanche Date: Thu, 4 Jun 2020 13:59:20 +0000 (+0200) Subject: WIP X-Git-Tag: 1.4.3~75^2~4 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=e516eeec5f073f55e3ad48444c2a4f43f40d350e;p=teleforma.git WIP --- diff --git a/teleforma/exam/templates/exam/mass_score_form.html b/teleforma/exam/templates/exam/mass_score_form.html index a05bfbca..de9d4fcf 100644 --- a/teleforma/exam/templates/exam/mass_score_form.html +++ b/teleforma/exam/templates/exam/mass_score_form.html @@ -65,13 +65,13 @@
-{% if messages %} +{% comment %} {% if messages %}
{% for message in messages %} {{ message }}
{% endfor %} -{% endif %} +{% endif %} {% endcomment %}
{% csrf_token %} diff --git a/teleforma/exam/templates/exam/score_form.html b/teleforma/exam/templates/exam/score_form.html index c47faf47..6ba49a26 100644 --- a/teleforma/exam/templates/exam/score_form.html +++ b/teleforma/exam/templates/exam/score_form.html @@ -33,13 +33,13 @@

-{% if messages %} +{% comment %} {% if messages %}
{% for message in messages %} {{ message }}
{% endfor %} -{% endif %} +{% endif %} {% endcomment %}
Ce formulaire vous permet d'enregistrer la note d'une copie papier déjà corrigée hors de la plateforme e-learning.
diff --git a/teleforma/exam/templates/exam/scores.html b/teleforma/exam/templates/exam/scores.html index cc82af1d..7953c62f 100644 --- a/teleforma/exam/templates/exam/scores.html +++ b/teleforma/exam/templates/exam/scores.html @@ -50,13 +50,13 @@
-{% if messages %} +{% comment %} {% if messages %}
{% for message in messages %} {{ message }}
{% endfor %} -{% endif %} +{% endif %} {% endcomment %}
{% include_container data.chartcontainer 500 '100%' %} diff --git a/teleforma/exam/templates/exam/script_form.html b/teleforma/exam/templates/exam/script_form.html index e038a701..3f37aad3 100644 --- a/teleforma/exam/templates/exam/script_form.html +++ b/teleforma/exam/templates/exam/script_form.html @@ -38,13 +38,13 @@
-{% if messages %} +{% comment %} {% if messages %}
{% for message in messages %} {{ message }}
{% endfor %} -{% endif %} +{% endif %} {% endcomment %}
Ce formulaire vous permet de soumettre une copie non corrigée à la correction en ligne.
diff --git a/teleforma/exam/templates/exam/scripts.html b/teleforma/exam/templates/exam/scripts.html index 7bae5cef..1b618252 100644 --- a/teleforma/exam/templates/exam/scripts.html +++ b/teleforma/exam/templates/exam/scripts.html @@ -52,13 +52,13 @@ {% block answers %}
{{ title }}
-{% if messages %} +{% comment %} {% if messages %}
{% for message in messages %} {{ message }}
{% endfor %} -{% endif %} +{% endif %} {% endcomment %}
{% if profile > 0 %} diff --git a/teleforma/templates/postman/base_folder.html b/teleforma/templates/postman/base_folder.html index 1fd48140..7bd38b86 100644 --- a/teleforma/templates/postman/base_folder.html +++ b/teleforma/templates/postman/base_folder.html @@ -24,13 +24,13 @@ {% autopaginate pm_messages %}{% paginate %} -{% if messages %} +{% comment %} {% if messages %}
{% for message in messages %} {{ message }}
{% endfor %} -{% endif %} +{% endif %} {% endcomment %} {% csrf_token %} diff --git a/teleforma/templates/teleforma/appointments.html b/teleforma/templates/teleforma/appointments.html index 1231ad44..664d10e5 100644 --- a/teleforma/templates/teleforma/appointments.html +++ b/teleforma/templates/teleforma/appointments.html @@ -125,16 +125,6 @@ {% block content %} - {% if messages %} - - {% endif %} - - -

Êtes-vous sûr de vouloir réserver ce créneau ? diff --git a/teleforma/templates/teleforma/course.html b/teleforma/templates/teleforma/course.html index 81d940f3..aa1643e2 100644 --- a/teleforma/templates/teleforma/course.html +++ b/teleforma/templates/teleforma/course.html @@ -3,6 +3,7 @@ {% block courses %} +

diff --git a/teleforma/templates/teleforma/course_detail.html b/teleforma/templates/teleforma/course_detail.html index af1ba6e5..9e8d65e6 100644 --- a/teleforma/templates/teleforma/course_detail.html +++ b/teleforma/templates/teleforma/course_detail.html @@ -22,7 +22,11 @@ $(document).ready(function(){
- +
+ {% if webclass and not webclass_slot %} + Vous n'êtes pas inscrit à la webconférence de cette matière. Cliquez-ici pour choisir un créneau horaire. + {% endif %} +


@@ -69,6 +73,15 @@ $(document).ready(function(){
{% endfor %} + +
+
{{ course.title }} - Webclass{% if course.description %} - {{ course.description }}{% endif %} +
+ {% block webclass %} + TEST + {% include "webclass/inc/webclass_list.html" %} + {% endblock %} +
{% endwith %} {% endfor %} diff --git a/teleforma/templates/teleforma/inc/media_list.html b/teleforma/templates/teleforma/inc/media_list.html index 346bcfd5..76c2de5f 100644 --- a/teleforma/templates/teleforma/inc/media_list.html +++ b/teleforma/templates/teleforma/inc/media_list.html @@ -23,11 +23,14 @@ {% trans 'Click here' %} {% endthumbnail %} + {% else %} + {% trans 'Click here' %} {% endif %} {% endfor %} {% else %} - {% trans 'Click here' %} +
{% trans 'Click here' %}
{% endif %} +
{% trans 'Click here' %}
{{ course.title }}{% if course.description %} - {{ course.description }}{% endif %} diff --git a/teleforma/templates/telemeta/base.html b/teleforma/templates/telemeta/base.html index 00354345..9c1a3710 100644 --- a/teleforma/templates/telemeta/base.html +++ b/teleforma/templates/telemeta/base.html @@ -131,6 +131,10 @@ alt="logo" /> {% endif %} + {% if user.professor.count %} +
  • Webclass
  • + {% endif %} + {% if periods|length == 1 %}
  •  {% trans "Scores" %}
  • {% else %} @@ -187,6 +191,15 @@ alt="logo" /> {% block postman_menu %} {% endblock postman_menu %} + + {% if messages %} +
      + {% for message in messages %} + {{ message }} + {% endfor %} +
    + {% endif %} + {% block content %} {% endblock %} diff --git a/teleforma/urls.py b/teleforma/urls.py index eb0ea5a6..e2d00d4d 100644 --- a/teleforma/urls.py +++ b/teleforma/urls.py @@ -156,6 +156,8 @@ urlpatterns = patterns('', # EXAM url(r'^', include('teleforma.exam.urls')), + # WEBCLASS + url(r'^', include('teleforma.webclass.urls')), # Payment url(r'^payment/(?P.*)/start/$', PaymentStartView.as_view(), diff --git a/teleforma/views/core.py b/teleforma/views/core.py index 0d661b74..f6be6f16 100644 --- a/teleforma/views/core.py +++ b/teleforma/views/core.py @@ -73,6 +73,7 @@ from jsonrpc.proxy import ServiceProxy from teleforma.models import * from teleforma.forms import * from teleforma.models.appointment import AppointmentPeriod +from teleforma.webclass.models import Webclass, WebclassSlot from telemeta.views import * import jqchat.models from xlwt import Workbook @@ -398,7 +399,7 @@ class CourseView(CourseAccessMixin, DetailView): model = Course - def get_context_data(self, **kwargs): + def get_context_data(self, *args, **kwargs): context = super(CourseView, self).get_context_data(**kwargs) course = self.get_object() courses = [] @@ -411,6 +412,23 @@ class CourseView(CourseAccessMixin, DetailView): context['room'] = get_room(name=course.code, period=context['period'].name, content_type=content_type, id=course.id) + + # webclass + webclass = None + webclass_slot = None + student = self.request.user.student.all() + if student: + student = student[0] + + if student: + try: + webclass = Webclass.objects.get(period=self.period, course=course, iej=student.iej) + except Webclass.DoesNotExist: + pass + if webclass: + webclass_slot = webclass.get_slot(self.request.user) + context['webclass'] = webclass + context['webclass_slot'] = webclass_slot return context @method_decorator(login_required) @@ -469,6 +487,7 @@ class MediaView(CourseAccessMixin, DetailView): if not access: context['access_error'] = access_error context['message'] = contact_message + return context @method_decorator(login_required) diff --git a/teleforma/webclass/__init__.py b/teleforma/webclass/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/teleforma/webclass/admin.py b/teleforma/webclass/admin.py new file mode 100644 index 00000000..adaa3393 --- /dev/null +++ b/teleforma/webclass/admin.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +from teleforma.admin import * +from teleforma.webclass.models import * +from django.contrib import admin + +class BBBServerAdmin(admin.ModelAdmin): + model = BBBServer + list_display = ('url', 'api_key') + +class WebclassSlotInline(admin.StackedInline): + model = WebclassSlot + raw_id_fields = ('participants',) + readonly_fields = ('room_id', 'room_password') + extra = 5 + +class WebclassAdmin(admin.ModelAdmin): + inlines = [WebclassSlotInline] + list_filter = ('course', 'period', 'iej') + list_display = ('course', 'period') + filter_horizontal = ('iej',) + search_fields = ['id', 'course__code', 'course__title'] + +class WebclassRecordAdmin(admin.ModelAdmin): + list_filter = ('course', 'period') + list_display = ('course', 'period') + search_fields = ['id', 'course__code', 'course__title'] + + # def get_form(self, request, obj=None, **kwargs): + # form = super(WebclassRecordAdmin, self).get_form(request, obj, **kwargs) + # form.base_fields['url'] = forms.ChoiceField(choices=get_all_records()) + # return form + + + + +admin.site.register(BBBServer, BBBServerAdmin) +admin.site.register(Webclass, WebclassAdmin) +admin.site.register(WebclassRecord, WebclassRecordAdmin) \ No newline at end of file diff --git a/teleforma/webclass/migrations/0001_initial.py b/teleforma/webclass/migrations/0001_initial.py new file mode 100644 index 00000000..48b2a4dd --- /dev/null +++ b/teleforma/webclass/migrations/0001_initial.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'BBBServer' + db.create_table('teleforma_bbb_server', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('url', self.gf('django.db.models.fields.CharField')(max_length=100)), + ('api_key', self.gf('django.db.models.fields.CharField')(max_length=100)), + )) + db.send_create_signal('webclass', ['BBBServer']) + + # Adding model 'Webclass' + db.create_table('teleforma_webclass', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('department', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='webclass', null=True, on_delete=models.SET_NULL, to=orm['teleforma.Department'])), + ('period', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='webclass', null=True, on_delete=models.SET_NULL, to=orm['teleforma.Period'])), + ('course', self.gf('django.db.models.fields.related.ForeignKey')(related_name='webclass', to=orm['teleforma.Course'])), + ('bbb_server', self.gf('django.db.models.fields.related.ForeignKey')(related_name='webclass', to=orm['webclass.BBBServer'])), + ('duration', self.gf('telemeta.models.core.DurationField')(default='0', blank=True)), + ('max_participants', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('status', self.gf('django.db.models.fields.IntegerField')(default=2)), + )) + db.send_create_signal('webclass', ['Webclass']) + + # Adding M2M table for field iej on 'Webclass' + m2m_table_name = db.shorten_name('teleforma_webclass_iej') + db.create_table(m2m_table_name, ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('webclass', models.ForeignKey(orm['webclass.webclass'], null=False)), + ('iej', models.ForeignKey(orm['teleforma.iej'], null=False)) + )) + db.create_unique(m2m_table_name, ['webclass_id', 'iej_id']) + + # Adding model 'WebclassSlot' + db.create_table('teleforma_webclass_slot', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('webclass', self.gf('django.db.models.fields.related.ForeignKey')(related_name='slots', to=orm['webclass.Webclass'])), + ('day', self.gf('django.db.models.fields.IntegerField')()), + ('start_hour', self.gf('django.db.models.fields.TimeField')()), + ('professor', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='webclass_slot', null=True, on_delete=models.SET_NULL, to=orm['teleforma.Professor'])), + ('room_id', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + ('room_password', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + )) + db.send_create_signal('webclass', ['WebclassSlot']) + + # Adding M2M table for field participants on 'WebclassSlot' + m2m_table_name = db.shorten_name('teleforma_webclass_slot_participants') + db.create_table(m2m_table_name, ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('webclassslot', models.ForeignKey(orm['webclass.webclassslot'], null=False)), + ('user', models.ForeignKey(orm['auth.user'], null=False)) + )) + db.create_unique(m2m_table_name, ['webclassslot_id', 'user_id']) + + + def backwards(self, orm): + # Deleting model 'BBBServer' + db.delete_table('teleforma_bbb_server') + + # Deleting model 'Webclass' + db.delete_table('teleforma_webclass') + + # Removing M2M table for field iej on 'Webclass' + db.delete_table(db.shorten_name('teleforma_webclass_iej')) + + # Deleting model 'WebclassSlot' + db.delete_table('teleforma_webclass_slot') + + # Removing M2M table for field participants on 'WebclassSlot' + db.delete_table(db.shorten_name('teleforma_webclass_slot_participants')) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'quiz.category': { + 'Meta': {'object_name': 'Category'}, + 'category': ('django.db.models.fields.CharField', [], {'max_length': '250', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'quiz.quiz': { + 'Meta': {'object_name': 'Quiz'}, + 'answers_at_end': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['quiz.Category']", 'null': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'draft': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'exam_paper': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'fail_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'max_questions': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'pass_mark': ('django.db.models.fields.SmallIntegerField', [], {'default': '0', 'blank': 'True'}), + 'random_order': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'single_attempt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'success_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '60'}), + 'url': ('django.db.models.fields.SlugField', [], {'max_length': '60'}) + }, + 'teleforma.course': { + 'Meta': {'ordering': "['number']", 'object_name': 'Course'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}), + 'department': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'course'", 'to': "orm['teleforma.Department']"}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'has_exam_scripts': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_professor_sent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['teleforma.Professor']", 'null': 'True', 'blank': 'True'}), + 'magistral': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'number': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'obligation': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'oral_1': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'oral_2': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'oral_speciality': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'periods': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'courses'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['teleforma.Period']"}), + 'procedure': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'quiz': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['quiz.Quiz']", 'null': 'True', 'blank': 'True'}), + 'synthesis_note': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'title_tweeter': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'written_speciality': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'teleforma.department': { + 'Meta': {'object_name': 'Department'}, + 'default_period': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'departments'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['teleforma.Period']"}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'department'", 'to': "orm['teleforma.Organization']"}) + }, + 'teleforma.iej': { + 'Meta': {'ordering': "['name']", 'object_name': 'IEJ'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'teleforma.organization': { + 'Meta': {'object_name': 'Organization'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'teleforma.period': { + 'Meta': {'ordering': "['name']", 'object_name': 'Period'}, + 'date_begin': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'date_close_accounts': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'date_end': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'date_exam_end': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'date_inscription_end': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'date_inscription_start': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'date_password_init': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'department': ('telemeta.models.core.ForeignKey', [], {'default': 'None', 'related_name': "'period'", 'null': 'True', 'blank': 'True', 'to': "orm['teleforma.Department']"}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'message_local': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'message_platform': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'nb_script': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['teleforma.Period']"}) + }, + 'teleforma.professor': { + 'Meta': {'ordering': "['user__last_name']", 'object_name': 'Professor'}, + 'courses': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'professor'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['teleforma.Course']"}), + 'department': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'professor'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['teleforma.Department']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'professor'", 'unique': 'True', 'to': "orm['auth.User']"}) + }, + 'webclass.bbbserver': { + 'Meta': {'object_name': 'BBBServer', 'db_table': "'teleforma_bbb_server'"}, + 'api_key': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'webclass.webclass': { + 'Meta': {'object_name': 'Webclass', 'db_table': "'teleforma_webclass'"}, + 'bbb_server': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'webclass'", 'to': "orm['webclass.BBBServer']"}), + 'course': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'webclass'", 'to': "orm['teleforma.Course']"}), + 'department': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'webclass'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['teleforma.Department']"}), + 'duration': ('telemeta.models.core.DurationField', [], {'default': "'0'", 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'iej': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'webclass'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['teleforma.IEJ']"}), + 'max_participants': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'period': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'webclass'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['teleforma.Period']"}), + 'status': ('django.db.models.fields.IntegerField', [], {'default': '2'}) + }, + 'webclass.webclassslot': { + 'Meta': {'object_name': 'WebclassSlot', 'db_table': "'teleforma_webclass_slot'"}, + 'day': ('django.db.models.fields.IntegerField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'participants': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'webclass_slot'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'professor': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'webclass_slot'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['teleforma.Professor']"}), + 'room_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'room_password': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'start_hour': ('django.db.models.fields.TimeField', [], {}), + 'webclass': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'slots'", 'to': "orm['webclass.Webclass']"}) + } + } + + complete_apps = ['webclass'] \ No newline at end of file diff --git a/teleforma/webclass/migrations/0002_auto__add_webclassrecord.py b/teleforma/webclass/migrations/0002_auto__add_webclassrecord.py new file mode 100644 index 00000000..45114df9 --- /dev/null +++ b/teleforma/webclass/migrations/0002_auto__add_webclassrecord.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'WebclassRecord' + db.create_table('teleforma_webclass_record', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('period', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['teleforma.Period'])), + ('course', self.gf('django.db.models.fields.related.ForeignKey')(related_name='webclass_records', to=orm['teleforma.Course'])), + ('url', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('created', self.gf('django.db.models.fields.DateTimeField')()), + )) + db.send_create_signal('webclass', ['WebclassRecord']) + + + def backwards(self, orm): + # Deleting model 'WebclassRecord' + db.delete_table('teleforma_webclass_record') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'quiz.category': { + 'Meta': {'object_name': 'Category'}, + 'category': ('django.db.models.fields.CharField', [], {'max_length': '250', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'quiz.quiz': { + 'Meta': {'object_name': 'Quiz'}, + 'answers_at_end': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['quiz.Category']", 'null': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'draft': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'exam_paper': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'fail_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'max_questions': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'pass_mark': ('django.db.models.fields.SmallIntegerField', [], {'default': '0', 'blank': 'True'}), + 'random_order': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'single_attempt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'success_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '60'}), + 'url': ('django.db.models.fields.SlugField', [], {'max_length': '60'}) + }, + 'teleforma.course': { + 'Meta': {'ordering': "['number']", 'object_name': 'Course'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}), + 'department': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'course'", 'to': "orm['teleforma.Department']"}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'has_exam_scripts': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_professor_sent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['teleforma.Professor']", 'null': 'True', 'blank': 'True'}), + 'magistral': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'number': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'obligation': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'oral_1': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'oral_2': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'oral_speciality': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'periods': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'courses'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['teleforma.Period']"}), + 'procedure': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'quiz': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['quiz.Quiz']", 'null': 'True', 'blank': 'True'}), + 'synthesis_note': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'title_tweeter': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'written_speciality': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'teleforma.department': { + 'Meta': {'object_name': 'Department'}, + 'default_period': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'departments'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['teleforma.Period']"}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'department'", 'to': "orm['teleforma.Organization']"}) + }, + 'teleforma.iej': { + 'Meta': {'ordering': "['name']", 'object_name': 'IEJ'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'teleforma.organization': { + 'Meta': {'object_name': 'Organization'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'teleforma.period': { + 'Meta': {'ordering': "['name']", 'object_name': 'Period'}, + 'date_begin': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'date_close_accounts': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'date_end': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'date_exam_end': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'date_inscription_end': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'date_inscription_start': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'date_password_init': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'department': ('telemeta.models.core.ForeignKey', [], {'default': 'None', 'related_name': "'period'", 'null': 'True', 'blank': 'True', 'to': "orm['teleforma.Department']"}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'message_local': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'message_platform': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'nb_script': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['teleforma.Period']"}) + }, + 'teleforma.professor': { + 'Meta': {'ordering': "['user__last_name']", 'object_name': 'Professor'}, + 'courses': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'professor'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['teleforma.Course']"}), + 'department': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'professor'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['teleforma.Department']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'professor'", 'unique': 'True', 'to': "orm['auth.User']"}) + }, + 'webclass.bbbserver': { + 'Meta': {'object_name': 'BBBServer', 'db_table': "'teleforma_bbb_server'"}, + 'api_key': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'webclass.webclass': { + 'Meta': {'object_name': 'Webclass', 'db_table': "'teleforma_webclass'"}, + 'bbb_server': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'webclass'", 'to': "orm['webclass.BBBServer']"}), + 'course': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'webclass'", 'to': "orm['teleforma.Course']"}), + 'department': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'webclass'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['teleforma.Department']"}), + 'duration': ('telemeta.models.core.DurationField', [], {'default': "'0'", 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'iej': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'webclass'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['teleforma.IEJ']"}), + 'max_participants': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'period': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'webclass'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['teleforma.Period']"}), + 'status': ('django.db.models.fields.IntegerField', [], {'default': '2'}) + }, + 'webclass.webclassrecord': { + 'Meta': {'object_name': 'WebclassRecord', 'db_table': "'teleforma_webclass_record'"}, + 'course': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'webclass_records'", 'to': "orm['teleforma.Course']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'period': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['teleforma.Period']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'webclass.webclassslot': { + 'Meta': {'object_name': 'WebclassSlot', 'db_table': "'teleforma_webclass_slot'"}, + 'day': ('django.db.models.fields.IntegerField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'participants': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'webclass_slot'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'professor': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'webclass_slot'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['teleforma.Professor']"}), + 'room_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'room_password': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'start_hour': ('django.db.models.fields.TimeField', [], {}), + 'webclass': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'slots'", 'to': "orm['webclass.Webclass']"}) + } + } + + complete_apps = ['webclass'] \ No newline at end of file diff --git a/teleforma/webclass/migrations/__init__.py b/teleforma/webclass/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/teleforma/webclass/models.py b/teleforma/webclass/models.py new file mode 100644 index 00000000..62de7639 --- /dev/null +++ b/teleforma/webclass/models.py @@ -0,0 +1,320 @@ +# -*- coding: utf-8 -*- + +import datetime +from datetime import timedelta +import calendar +from unidecode import unidecode + +from django.db.models import * +from telemeta.models import * +from teleforma.fields import * +from teleforma.models.core import STATUS_CHOICES +import django.db.models as models +from django.utils.translation import ugettext_lazy as _ +from django.utils import translation +from django.template.defaultfilters import slugify +from django.db.models.signals import post_save +from django.dispatch import receiver +from bigbluebutton_api_python import BigBlueButton +from bigbluebutton_api_python.exception import BBBException +from jxmlease import XMLListNode, XMLDictNode + +translation.activate('fr') +app_label = 'teleforma' + +DAYS_CHOICES = [(i, _(calendar.day_name[i])) for i in range(7)] +# BBB_SERVER = "https://bbb.parisson.com/bigbluebutton/" +# BBB_SECRET = "uOzkrTnWly1jusr0PYcrlwhvKhZG1ZYDOrSvxgP70" + + +class MetaCore: + app_label = 'webclass' + +def get_all_records(): + all_records = [] + recordings = [] + recordings_xml = self.bbb.get_recordings(self.room_id).get_field('recordings') + if hasattr(recordings_xml, 'get'): + recordings = recordings_xml['recording'] + if type(recordings) is XMLDictNode: + recordings = [recordings] + for recording in recordings: + recording.prettyprint() + url = recording.get('playback', {}).get('format', {}).get('url') + if url: + url = url.decode() + data = { + 'start': int(recording['startTime'].decode()), + 'end': int(recording['endTime'].decode()), + 'url': url, + 'state': recording['state'].decode(), + } + data['duration'] = data['end'] - data['start'] + all_records.append(data) + + if not all_records: + return [] + + all_records = sorted(all_records, key=lambda record:-record['duration']) + vocabulary = [] + for record in all_records: + vocabulary.append((record['url'], record['duration'])) + return longest_record + + + +class BBBServer(models.Model): + url = models.CharField("Url du serveur BBB", max_length=100) + api_key = models.CharField("API Key", max_length=100) + + class Meta(MetaCore): + db_table = app_label + '_' + 'bbb_server' + verbose_name = _('BBB server') + verbose_name_plural = _('BBB servers') + + def get_instance(self): + return BigBlueButton(self.url, self.api_key) + + def __unicode__(self): + return "Serveur %d" % self.id + +class Webclass(models.Model): + + department = models.ForeignKey('teleforma.Department', related_name='webclass', verbose_name=_('department'), on_delete=models.SET_NULL, blank=True, null=True) + period = models.ForeignKey('teleforma.Period', related_name='webclass', verbose_name=_('period'), on_delete=models.SET_NULL, blank=True, null=True) + course = models.ForeignKey('teleforma.Course', related_name='webclass', verbose_name=_('course')) + iej = models.ManyToManyField('teleforma.IEJ', related_name='webclass', verbose_name=_('iej'), blank=True, null=True) + bbb_server = models.ForeignKey('BBBServer', related_name='webclass', verbose_name='Serveur BBB') + duration = DurationField('Durée de la conférence') + max_participants = models.IntegerField('Nombre maxium de participants par créneau', blank=True, null=True) + status = models.IntegerField(_('status'), choices=STATUS_CHOICES, default=2) + + + class Meta(MetaCore): + db_table = app_label + '_' + 'webclass' + verbose_name = _('webclass') + verbose_name_plural = _('webclass') + + def __unicode__(self): + return "Webclass %d : %s" % (self.id, self.course.title) + + def get_slot(self, user): + """ return webclass slot or None if user is not subscribed """ + try: + return WebclassSlot.objects.get(webclass=self, participants=user) + except WebclassSlot.DoesNotExist: + return None + + +class WebclassSlot(models.Model): + """ Webclass slot """ + webclass = models.ForeignKey('Webclass', related_name='slots') + day = models.IntegerField('Jour du créneau', choices=DAYS_CHOICES) + start_hour = models.TimeField('heure du créneau') + professor = models.ForeignKey('teleforma.Professor', related_name='webclass_slot', verbose_name=_('professor'), + on_delete=models.SET_NULL, blank=True, null=True) + participants = models.ManyToManyField(User, related_name="webclass_slot", verbose_name=_('participants'), + blank=True, null=True) + room_id = models.CharField('id de la conférence BBB (généré automatiquement)', blank=True, null=True, max_length=255) + room_password = models.CharField('password du modérateur (généré automatiquement)', blank=True, null=True, max_length=255) + + class Meta(MetaCore): + db_table = app_label + '_' + 'webclass_slot' + verbose_name = _('webclass slot') + + def __unicode__(self): + return "Webclass slot : " + str(self.id) + + @property + def participant_slot_available(self): + """ + is there any slot available for another participants + """ + nb_participants = self.participants.count() + return nb_participants < self.webclass.max_participants + + @property + def end_hour(self): + """ + start hour + duration + """ + date = datetime.datetime.combine(datetime.date.today(), self.start_hour) + timedelta(seconds=self.webclass.duration.as_seconds()) + return date.time() + + @property + def bbb(self): + return self.webclass.bbb_server.get_instance() + + def prepare_webclass(self): + """ + generate room id and moderator password + """ + if not self.room_id: + # not sure why, but the slug contains accent + room_id = "%s-w%d-s%d" % (unidecode(slugify(self.webclass.course.title)), self.webclass.id, self.id) + password = User.objects.make_random_password() + self.room_id = room_id + self.room_password = password + self.save() + + def create_webclass_room(self, request): + """ create a BBB room and generate meeting id and moderator password """ + if self.room_id: + try: + # check if meeting already exists + self.get_webclass_info() + except BBBException: + year = datetime.datetime.now().year + # site_url = 'https://' + request.get_host() + webclass = self.webclass + params = { + 'moderatorPW':self.room_password, + 'attendeePW':'pwattendee', + # 'maxParticipants':self.webclass_max_participants + 1, + 'welcome':"Pré-Barreau : Bienvenue sur la conférence \"%s\"." % (webclass.course.title.encode('utf-8'),), + '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" + } + print params + try: + result = self.bbb.create_meeting(self.room_id, params=params) + except BBBException as e: + print(e) + raise + + + def get_join_webclass_url(self, request, user, username=None): + """ + Get url to BBB meeting. + If user are professor or staff, provide the url with the moderator password + """ + self.create_webclass_room(request) + username = user.get_full_name() + is_professor = len(user.professor.all()) >= 1 + is_staff = user.is_staff or user.is_superuser + password = 'pwattendee' + if is_professor or is_staff: + password = self.room_password + params = {'userID': user.username} + return self.bbb.get_join_meeting_url(username, self.room_id, password, params) + + def get_fake_join_webclass_url(self, username): + """ + Fake join url for testing purpose + Get url to BBB meeting. + If user are professor or staff, provide the url with the moderator password + """ + self.create_webclass_room() + password = 'pwattendee' + params = {'userID': username} + return self.bbb.get_join_meeting_url(username, self.room_id, password, params) + + def next_webclass_date(self): + """ + get the next webclass date for this slot + (or today webclass if this is the current day) + """ + now = datetime.datetime.now() + days_ahead = self.day - now.weekday() + if days_ahead < 0: + days_ahead += 7 + next_date = now + datetime.timedelta(days_ahead) + next_date.replace(hour=self.start_hour.hour, minute=self.start_hour.minute) + return next_date + + @property + def status(self): + """ is webclass running, about to start, or finished ? + state : future, past, almost, ingoing + """ + now = datetime.datetime.now() + next_webclass_date_begin = self.next_webclass_date() + next_webclass_date_end = next_webclass_date_begin + timedelta(seconds=self.webclass.duration.as_seconds()) + begin_minus_1_hour = next_webclass_date_begin - timedelta(hours=1) + if now < begin_minus_1_hour: + # conference not yet started + status = "future" + elif next_webclass_date_end + timedelta(hours=1) < now: + # conference expired + status = "past" + elif begin_minus_1_hour < now < next_webclass_date_begin: + # conference can be joined + status = "almost" + else: + status = "ingoing" + + return status + + def is_webclass_running(self): + """ Is webclass currently running ? """ + # print(self.get_webclass_info()) + return self.bbb.is_meeting_running(self.room_id).get_field('running').decode() == 'true' or False + + def get_webclass_info(self): + """ """ + print(self.room_id) + print(self.bbb.get_meeting_info(self.room_id)) + return self.bbb.get_meeting_info(self.room_id) + + # def get_record(self): + # """ get longest published record for the current conference """ + # all_records = [] + # recordings = [] + # recordings_xml = self.bbb.get_recordings(self.room_id).get_field('recordings') + # if hasattr(recordings_xml, 'get'): + # recordings = recordings_xml['recording'] + # if type(recordings) is XMLDictNode: + # recordings = [recordings] + # for recording in recordings: + # recording.prettyprint() + # url = recording.get('playback', {}).get('format', {}).get('url') + # if url: + # url = url.decode() + # data = { + # 'start': int(recording['startTime'].decode()), + # 'end': int(recording['endTime'].decode()), + # 'url': url, + # 'state': recording['state'].decode(), + # } + # data['duration'] = data['end'] - data['start'] + # all_records.append(data) + + # if not all_records: + # return None + # all_records = sorted(all_records, key=lambda record:-record['duration']) + + # longest_record = all_records[0] + # if not longest_record['url'] or longest_record['state'] != 'published': + # return None + # return longest_record + + +@receiver(post_save, sender=WebclassSlot) +def create_webclass_room(sender, **kwargs): + instance = kwargs['instance'] + instance.prepare_webclass() + + + +class WebclassRecord(models.Model): + + period = models.ForeignKey('teleforma.Period', verbose_name=_('period')) + course = models.ForeignKey('teleforma.Course', related_name='webclass_records', verbose_name=_('course')) + url = models.CharField("Enregistrement BBB", max_length=255) + created = models.DateTimeField("Date de la conférence") + + class Meta(MetaCore): + db_table = app_label + '_' + 'webclass_record' + verbose_name = 'enregistrement' + verbose_name_plural = 'enregistrements' + + def __unicode__(self): + return "Enregistrement webclass %d" % self.id diff --git a/teleforma/webclass/templates/webclass/appointments.html b/teleforma/webclass/templates/webclass/appointments.html new file mode 100644 index 00000000..de9b8b90 --- /dev/null +++ b/teleforma/webclass/templates/webclass/appointments.html @@ -0,0 +1,65 @@ +{% extends "telemeta/base.html" %} +{% load teleforma_tags %} +{% load i18n %} + + +{% block extra_javascript %} + + + +{% endblock extra_javascript %} + + +{% block content %} + +
    +

    + Êtes-vous sûr de vouloir réserver ce créneau ? Vous ne pourrez plus modifier votre choix. +

    + +

    Créneau horaire

    + +
    + + + {% for slot in slots %} + + {{slot.day}} de {{slot.start_hour|date:"H\hi"}}}} à {{slot.end_hour|date:"H\hi"}}}} + {% csrf_token %} + + + + {% endfor %} + + +{% endblock content %} diff --git a/teleforma/webclass/templates/webclass/appointments_professor.html b/teleforma/webclass/templates/webclass/appointments_professor.html new file mode 100644 index 00000000..7dbe5615 --- /dev/null +++ b/teleforma/webclass/templates/webclass/appointments_professor.html @@ -0,0 +1,27 @@ +{% extends "telemeta/base.html" %} +{% load i18n %} +{% load telemeta_utils %} +{% load teleforma_tags %} + +{% block head_title %}Calendrier des Webclass{% endblock %} + +{% block title %} +Calendrier des Webclass +{% endblock %} + + +{% block infra_javascript %} +{% endblock infra_javascript %} + +{% block content %} +
    + {% for slot in slots %} +
    + {{slot.get_day_display}} de {{slot.start_hour|date:"H\hi"}} à {{slot.end_hour|date:"H\hi"}} : {{slot.participants.count}} participant{{slot.participants.count|pluralize}} + Rejoindre la conférence +
    + {% empty %} +

    Aucune webclasse programmée.

    + {% endfor %} +
    +{% endblock content %} diff --git a/teleforma/webclass/templates/webclass/inc/webclass_list.html b/teleforma/webclass/templates/webclass/inc/webclass_list.html new file mode 100644 index 00000000..e5aba91c --- /dev/null +++ b/teleforma/webclass/templates/webclass/inc/webclass_list.html @@ -0,0 +1,73 @@ +{% load teleforma_tags %} +{% load i18n %} + +{% if webclass_slot %} +
    +
    +

    Webclass live

    +
    + + + {% if webclass_slot %} + + + + {% endif %} + + {% for conference in conferences|from_period:period %} + {% for stream in conference.livestream.all %} + {% if stream.stream_type == 'webm' %} + + {% if stream.streaming %} + + + + {% else %} +
    + +
    + {% endif %} + + {% endif %} + {% endfor %} + {% endfor %} + +
    +

    Vous êtes inscrit pour les webconférence du + {{webclass_slot.get_day_display}} de + {{webclass_slot.start_hour|date:"H\hi"}} à + {{webclass_slot.end_hour|date:"H\hi"}} + avec le professeur {{webclass_slot.professor.user.last_name}}.

    + {{webclass_slot.status}} + {% if webclass_slot.status == 'past' %} +

    La webconférence est terminée.

    + {% elif webclass_slot.status == 'ingoing' %} +

    La webconférence est en cours.

    + Cliquez ici pour rejoindre la conférence + {% elif webclass_slot.status == 'almost' %} +

    Le webconférence est accessible mais elle n'a pas encore démarré.

    + Cliquez ici pour rejoindre la conférence + {% endif %} +
    + + {% trans 'Click here' %} + + +
    +
    +
    {% trans "Title" %}
    {{ stream.conference.course.title }}
    +
    {% trans "Session" %}
    {{ stream.conference.session }}
    + {% if stream.conference.professor.user.username %} +
    {% trans "Professor" %}
    {{ stream.conference.professor }}
    + {% endif %} +
    {% trans "Begin" %}
    {{ stream.conference.date_begin }}
    +
    +
    +
    + {% if stream.streaming %} + + + {% endif %} +
    +
    +{% endif %} diff --git a/teleforma/webclass/urls.py b/teleforma/webclass/urls.py new file mode 100644 index 00000000..29bb9b25 --- /dev/null +++ b/teleforma/webclass/urls.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2007-2012 Parisson SARL + +# This software is a computer program whose purpose is to backup, analyse, +# transcode and stream any audio content with its metadata over a web frontend. + +# This software is governed by the CeCILL license under French law and +# abiding by the rules of distribution of free software. You can use, +# modify and/ or redistribute the software under the terms of the CeCILL +# license as circulated by CEA, CNRS and INRIA at the following URL +# "http://www.cecill.info". + +# As a counterpart to the access to the source code and rights to copy, +# modify and redistribute granted by the license, users are provided only +# with a limited warranty and the software's author, the holder of the +# economic rights, and the successive licensors have only limited +# liability. + +# In this respect, the user's attention is drawn to the risks associated +# with loading, using, modifying and/or developing or reproducing the +# software by the user in light of its specific status of free software, +# that may mean that it is complicated to manipulate, and that also +# therefore means that it is reserved for developers and experienced +# professionals having in-depth computer knowledge. Users are therefore +# encouraged to load and test the software's suitability as regards their +# requirements in conditions enabling the security of their systems and/or +# data to be ensured and, more generally, to use and operate it in the +# same conditions as regards security. + +# The fact that you are presently reading this means that you have had +# knowledge of the CeCILL license and that you accept its terms. +# +# Authors: Guillaume Pellerin + +from django.conf.urls import patterns, url, include +from django.http import HttpResponse +from teleforma.webclass.views import * + + +urlpatterns = patterns('', + url(r'^desk/webclass_appointments/(?P.*)$', WebclassAppointment.as_view(), + name="teleforma-webclass-appointments"), + url(r'^desk/webclass_calendar/$', WebclassProfessorAppointments.as_view(), name="teleforma-webclass-professor"), + url(r'^desk/webclass/(?P.*)/join/$', + join_webclass, + name="teleforma-webclass-join"), + +) diff --git a/teleforma/webclass/views.py b/teleforma/webclass/views.py new file mode 100644 index 00000000..9b100053 --- /dev/null +++ b/teleforma/webclass/views.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- + +from django.views.generic import View, TemplateView +from django.contrib import messages +from django.http import HttpResponse +from django.shortcuts import redirect, get_object_or_404, render +from django.template.loader import render_to_string +from django.http import HttpResponse, HttpResponseRedirect +from django.core.urlresolvers import reverse, reverse_lazy +from django.db import IntegrityError +from django.core.mail import send_mail +from django.conf import settings +from django.core.cache import cache + +from teleforma.webclass.models import Webclass, WebclassSlot + +from teleforma.views.core import get_periods, get_courses + + +class WebclassProfessorAppointments(TemplateView): + template_name = 'webclass/appointments_professor.html' + + def get_context_data(self, **kwargs): + """ """ + context = super(WebclassProfessorAppointments, self).get_context_data(**kwargs) + + user = self.request.user + if not user.professor: + return HttpResponse('Unauthorized', status=401) + context['slots'] = WebclassSlot.objects.filter(professor=user.professor.get(), webclass__status=3).order_by('day', 'start_hour') + print(context['slots']) + return context + +class WebclassAppointment(View): + template_name = 'webclass/appointments.html' + + def check_rights(self, user, webclass): + if not user.is_authenticated(): + return HttpResponseRedirect(reverse('teleforma-login')) + student = user.student.all().count() + if not student: + return HttpResponse('Unauthorized', status=401) + # check period + period_id = webclass.period.id + periods = [ p for p in get_periods(user) if int(p.id) == period_id ] + if not periods: + return HttpResponse('Unauthorized', status=401) + # check courses + course_id = webclass.course.id + courses = [ c for c in get_courses(user) if int(c['course'].id) == course_id ] + if not courses: + return HttpResponse('Unauthorized', status=401) + # Student is in the right IEJ ? + if not student.iej in webclass.iej.all(): + return HttpResponse('Unauthorized', status=401) + return + + def render(self, request, webclass): + # Ensure user is logged in, a student, and has access to current period + user = request.user + student = user.student.all()[0] + + # Get info + slots = [] + for slot in webclass.slots.order_by('day', 'start_hour'): + slots.append({ + 'id': slot.id, + 'day': slot.get_day_display(), + 'start_hour':slot.start_hour, + 'end_hour': slot.end_hour, + 'professor': slot.professor, + 'available': slot.participant_slot_available + }) + return render(request, self.template_name, {'slots': slots}) + + def check_slot_validity(self, user, slot): + """ + Check if we can register to this exact slot + """ + student = user.student.all()[0] + + # Check if there is still space for one student + if not slot.participant_slot_available: + return u"Ce créneau n'est plus disponible." + + # # Check we don't have another appointment on this period + if webclass.get_slot(user): + return u"Vous êtes déjà inscrit." + + def post(self, request, pk): + webclass = get_object_or_404(Webclass, id = pk) + rights = self.check_rights(request.user, webclass) + if rights: + return rights + + user = request.user + slot_id = int(request.POST.get('slot_id')) + slot = WebclassSlot.objects.get(pk=slot_id) + + msg = self.check_slot_validity(user, slot) + + if not msg: + slot.participants.add(user) + slot.save() + # self.send_ap_mail(ap) + messages.add_message(request, messages.INFO, "Votre réservation a bien été prise en compte.") + return HttpResponseRedirect(reverse('teleforma-desk-period-course', kwargs={'period_id':webclass.period.id, 'pk':webclass.course.id})) + else: + messages.add_message(request, messages.ERROR, msg) + return self.render(request, webclass) + + def get(self, request, pk): + webclass = get_object_or_404(Webclass, id = pk) + rights = self.check_rights(request.user, webclass) + if rights: + return rights + return self.render(request, webclass) + + # def send_ap_mail(self, ap): + # """ + # Send the confirm mail to student + # """ + # data = { 'mfrom': settings.DEFAULT_FROM_EMAIL, + # 'mto': ap.student.email, + # 'jury_address': ap.jury.address, + # 'date': ap.real_date, + # 'student': ap.student, + # 'main_text': ap.appointment_period.appointment_mail_text } + # # DEBUG + # # data['mto'] = "yoanl@pilotsystems.net" + # # data['mto'] = "dorothee.lavalle@pre-barreau.com" + # # data['mto'] = "gael@pilotsystems.net" + + # subject_template = 'teleforma/messages/email_appointment_sujet.txt' + # message_template = 'teleforma/messages/email_appointment.txt' + # subject = render_to_string(subject_template, data) + # subject = ''.join(subject.splitlines()) + # message = render_to_string(message_template, data) + # send_mail(subject, message, data['mfrom'], [ data['mto'] ], + # fail_silently=False) + # return data + + + +def join_webclass(request, pk): + webclass_slot = WebclassSlot.objects.get(pk=int(pk)) + # webclass = webclass_slot.webclass + # fake debug links + # username = request.GET.get('username') + # if username: + # return redirect(webclass.get_fake_join_webclass_url(request, username)) + user = request.user + authorized = False + + # staff or professor ? + is_professor = len(user.professor.all()) >= 1 + is_staff = user.is_staff or user.is_superuser + if is_professor or is_staff: + authorized = True + + # student registered ? + if not authorized: + if user in webclass_slot.participants.all(): + authorized = True + + if authorized: + return redirect(webclass_slot.get_join_webclass_url(request, user)) + else: + return HttpResponse('Unauthorized', status=401) \ No newline at end of file