]> git.parisson.com Git - teleforma.git/commitdiff
Cache handling and notify live conferences
authorYoan Le Clanche <yoanl@pilotsystems.net>
Wed, 18 Jan 2023 11:02:25 +0000 (12:02 +0100)
committerYoan Le Clanche <yoanl@pilotsystems.net>
Wed, 18 Jan 2023 11:02:25 +0000 (12:02 +0100)
app/settings.py
teleforma/management/commands/teleforma-build-cache.py [new file with mode: 0644]
teleforma/migrations/0022_conference_notified_live.py [new file with mode: 0644]
teleforma/models/core.py
teleforma/models/crfpa.py
teleforma/urls.py
teleforma/views/core.py

index 95526b1b7e01be16d68dd8b94db6f850909d543a..402b58d8906e460f74f797d5b3ddf8430845737f 100644 (file)
@@ -35,6 +35,7 @@ ALLOWED_HOSTS = ['localhost', 'crfpa.dockdev.pilotsystems.net',
     '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"
@@ -58,6 +59,9 @@ CHANNEL_LAYERS = {
     },
 }
 
+# channel access point from django
+CHANNEL_URL = "http://channels:8000"
+
 DATABASES = {
     'default': {
         # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
diff --git a/teleforma/management/commands/teleforma-build-cache.py b/teleforma/management/commands/teleforma-build-cache.py
new file mode 100644 (file)
index 0000000..dd15e8d
--- /dev/null
@@ -0,0 +1,52 @@
+
+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)
diff --git a/teleforma/migrations/0022_conference_notified_live.py b/teleforma/migrations/0022_conference_notified_live.py
new file mode 100644 (file)
index 0000000..0431fe5
--- /dev/null
@@ -0,0 +1,18 @@
+# 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'),
+        ),
+    ]
index 125e20182ab568bd68587d48a0431aec66ced2dd..c9b9fbba340f02b797780e6ca0798951f8ac64e8 100755 (executable)
@@ -39,6 +39,7 @@ import mimetypes
 import os
 import string
 import random
+import requests
 from teleforma.utils import guess_mimetypes
 
 import django.db.models as models
@@ -48,7 +49,7 @@ from django.core.paginator import InvalidPage
 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
@@ -406,6 +407,7 @@ class Conference(models.Model):
     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):
@@ -452,6 +454,14 @@ class Conference(models.Model):
         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):
index e3e5a4f8d52f4364144bc4a12cadee96ad4cd3bb..bbf58fae4dabbb640ec1c16cf14bdc826803e0a8 100755 (executable)
@@ -44,6 +44,7 @@ from django.db.models import signals
 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)
@@ -401,6 +402,7 @@ class Student(models.Model):
     def expiration_date(self):
         """ closing date of student period """
         return self.period.date_close_accounts
+
     
     class Meta(MetaCore):
         db_table = app_label + '_' + 'student'
@@ -419,11 +421,26 @@ def update_balance_signal(sender, instance, *args, **kwargs):
 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"
 
index dfc2575df6b4ba0629c4a1bd0184d1dfbb21c69f..9fe6dbf517cf5fcb9efb86c8cd0fdf537338e89c 100644 (file)
@@ -54,7 +54,7 @@ from teleforma.views.home import HomeView
 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,
@@ -272,4 +272,8 @@ urlpatterns = [
     # 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')
 ]
index 4b40403e6aee22b86ed89e2d7b852a0bb0511467..a718aa68d8fdf86f8fd75121df8a0542504577ce 100644 (file)
@@ -51,7 +51,7 @@ from django.contrib.sites.shortcuts import get_current_site
 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
@@ -67,8 +67,8 @@ from rest_framework.response import Response
 #
 # 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
@@ -82,6 +82,8 @@ from ..models.core import (Conference, ConferencePublication, Course, CourseType
 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)
@@ -968,6 +970,36 @@ class NotificationView(APIView):
         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"