'e-learning.crfpa.pre-barreau.com',
'prod.docker.e-learning.crfpa.pre-barreau.parisson.com',
'recovery.docker.e-learning.crfpa.pre-barreau.parisson.com',
+ 'channels'
]
ASGI_APPLICATION = "teleforma.ws.routing.application"
},
}
+# channel access point from django
+CHANNEL_URL = "http://channels:8000"
+
DATABASES = {
'default': {
# Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
--- /dev/null
+
+from django.core.management.base import BaseCommand, CommandError
+from django.template.defaultfilters import slugify
+from django.conf import settings
+from teleforma.models.crfpa import Student
+import logging
+
+from teleforma.views.core import get_courses
+
+
+class Logger:
+ """A logging object"""
+
+ def __init__(self, file):
+ self.logger = logging.getLogger('myapp')
+ if file:
+ self.hdlr = logging.FileHandler(file)
+ else:
+ self.hdlr = None
+ self.formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
+ if self.hdlr:
+ self.hdlr.setFormatter(self.formatter)
+ self.logger.addHandler(self.hdlr)
+ self.logger.addHandler(logging.StreamHandler())
+ self.logger.setLevel(logging.INFO)
+
+class Command(BaseCommand):
+ help = "Build get_courses cache"
+
+ def add_arguments(self, parser):
+ parser.add_argument('--logfile', type=str, required=False,
+ help='log file to use')
+
+ def handle(self, *args, **options):
+ logpath = options['logfile']
+ logger = Logger(logpath)
+
+ total = Student.objects.filter(user__is_active=True).count()
+ logger.logger.info(f'Found {total} active students...')
+ for i, student in enumerate(Student.objects.filter(user__is_active=True, platform_only=True)):
+ if i % 100 == 0:
+ logger.logger.info(f"build cache : {int(i * 100 / total)}%")
+ periods = [training.period for training in student.trainings.all()]
+ for period in periods:
+ for child in period.children.all():
+ periods.append(child)
+ for period in periods + [None]:
+ # for date_order in (True, False):
+ # for num_order in (True, False):
+ # for num_courses in (True, False):
+ # logger.logger.info(f"build cache for get_courses-{student.user.id}-{date_order}-{num_order}-{num_courses}-{period and period.id or None}")
+ get_courses(student.user, period=period)
--- /dev/null
+# Generated by Django 3.2.13 on 2023-01-17 14:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('teleforma', '0021_conferencepublication'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='conference',
+ name='notified_live',
+ field=models.BooleanField(default=False, verbose_name='Notification live'),
+ ),
+ ]
import os
import string
import random
+import requests
from teleforma.utils import guess_mimetypes
import django.db.models as models
from django.db import models
from django.forms.fields import FileField
from django.template.defaultfilters import slugify
-from django.urls import reverse_lazy
+from django.urls import reverse_lazy, reverse
from django.utils.translation import ugettext_lazy as _
# from quiz.models import Quiz
from sorl.thumbnail import default as sorl_default
web_class_group = models.ForeignKey('WebClassGroup', related_name='conferences', verbose_name=_('web class group'),
blank=True, null=True, on_delete=models.SET_NULL)
notified = models.BooleanField(_('notified'), default=False)
+ notified_live = models.BooleanField("Notifié live", default=False)
@property
def description(self):
if not self.public_id:
self.public_id = get_random_hash()
self.course.save()
+
+ if self.streaming and not self.notified_live:
+ # Notify live conferences by sending a signal to websocket.
+ # This signal will be catched by the channel instance to notify students
+ from teleforma.models.notification import notify
+ requests.post(f"{settings.CHANNEL_URL}{reverse('teleforma-live-conference-notify')}", {'id': self.id})
+ self.notified_live = True
+
super(Conference, self).save(*args, **kwargs)
def to_dict(self):
from django.urls.base import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from tinymce.models import HTMLField
+from django.core.cache import cache
from ..models.core import (Course, Media, MetaCore, payment_choices,
payment_schedule_choices)
def expiration_date(self):
""" closing date of student period """
return self.period.date_close_accounts
+
class Meta(MetaCore):
db_table = app_label + '_' + 'student'
def create_payment_objects(sender, instance, *args, **kwargs):
transaction.on_commit(instance.create_payment_objects)
+
+def purge_courses_cache(sender, instance, *args, **kwargs):
+ """ purge get_courses cache """
+ periods = [training.period for training in instance.trainings.all()]
+ for period in periods:
+ for child in period.children.all():
+ periods.append(child)
+ for period in periods + [None]:
+ for date_order in (True, False):
+ for num_order in (True, False):
+ for num_courses in (True, False):
+ cache_key = f"get_courses-{instance.user.id}-{date_order}-{num_order}-{num_courses}-{period and period.id or None}"
+ print(f"purging {cache_key}")
+ cache.delete(cache_key)
+
signals.post_save.connect(update_balance_signal)
signals.post_save.connect(create_payment_objects, sender=Student)
+signals.post_save.connect(purge_courses_cache, sender=Student)
signals.post_delete.connect(update_balance_signal)
-
class Profile(models.Model):
"User profile extension"
from .views.appointment import Appointments, cancel_appointment
from .views.core import (ChatMessageView, ConferenceListView, ConferenceView, CourseListView,
CoursePendingListView, CourseView, DocumentView,
- HelpView, HomeRedirectView, MediaTranscodedView,
+ HelpView, HomeRedirectView, LiveConferenceNotify, MediaTranscodedView,
MediaView, MediaViewEmbed, NotificationView)
from .views.crfpa import (AnnalsCourseView, AnnalsIEJView, AnnalsView,
CorrectorAddView, CorrectorCompleteView,
# notification
path('notification',
NotificationView.as_view(), name='teleforma-notification'),
+
+ # must be called on channels instance
+ path('live_conference_notify',
+ LiveConferenceNotify.as_view(), name='teleforma-live-conference-notify')
]
from django.template import loader
from django.urls import reverse
from django.utils.decorators import method_decorator
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import ugettext, ugettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import TemplateResponseMixin, TemplateView, View
from django.views.generic.detail import DetailView
#
# Authors: Guillaume Pellerin <yomguy@parisson.com>
from rest_framework.views import APIView
-from teleforma.models.crfpa import Home, Training
-from teleforma.models.notification import Notification
+from teleforma.models.crfpa import Home, Student, Training
+from teleforma.models.notification import Notification, notify
from teleforma.utils import guess_mimetypes
from ..decorators import access_required
from ..webclass.models import Webclass, WebclassRecord
from .pages import get_page_content
+import logging
+logger = logging.getLogger('teleforma')
# def render(request, template, data=None, mimetype=None):
# return django_render(template, data, context_instance=RequestContext(request),
# mimetype=mimetype)
return Response({'status': 'ok'})
+class LiveConferenceNotify(APIView):
+ def post(self, request):
+ """
+ notify users a new live conference is starting
+ """
+ conference_id = request.data.get('id')
+ if not conference_id:
+ raise Exception('No conference id in request')
+ conference = Conference.objects.get(pk=int(conference_id))
+ students = Student.objects.filter(period=conference.period, platform_only=True)
+ text = f"""Une conférence live "{conference.course.title}" commence"""
+ url = reverse('teleforma-conference-detail', kwargs={'period_id': conference.period.id, 'pk': conference.id})
+ for student in students:
+ try:
+ if student.user:
+ courses = get_courses(student.user, period=conference.period)
+ for course in courses:
+ if conference.course == course['course'] and \
+ conference.course_type in course['types']:
+ notify(student.user, text, url)
+ logger.info("Student notified: " + student.user.username)
+ except Exception as e:
+ logger.warning("Student NOT notified: " + str(student.id))
+ logger.warning(e)
+ return Response({'status': 'ok'})
+
+
+
+
+
# class ConferenceRecordView(FormView):
# "Conference record form : TeleCaster module required"