From fae14b4fbbdaf69a2013afefa16fa5ac0d1bfbce Mon Sep 17 00:00:00 2001 From: Yoan Le Clanche Date: Wed, 5 Nov 2025 14:37:23 +0100 Subject: [PATCH] Add testimonials for webconference --- teleforma/admin.py | 72 ++++-- .../commands/teleforma-revisions-from-bbb.py | 87 ++++++- .../0024_seminar_secondary_subject.py | 18 ++ .../migrations/0025_auto_20251105_1331.py | 41 ++++ teleforma/models/core.py | 2 + teleforma/models/pro.py | 30 ++- teleforma/static/teleforma/css/teleforma.css | 3 - .../teleforma/conference_testimonial.html | 63 ++++++ .../conference_testimonial_payback.html | 214 ++++++++++++++++++ .../templates/teleforma/testimonials.html | 17 +- teleforma/urls.py | 10 +- teleforma/views/pro.py | 161 ++++++++++--- 12 files changed, 652 insertions(+), 66 deletions(-) create mode 100644 teleforma/migrations/0024_seminar_secondary_subject.py create mode 100644 teleforma/migrations/0025_auto_20251105_1331.py create mode 100644 teleforma/templates/teleforma/conference_testimonial.html create mode 100644 teleforma/templates/teleforma/conference_testimonial_payback.html diff --git a/teleforma/admin.py b/teleforma/admin.py index 3cd48c1e..753fb6f0 100644 --- a/teleforma/admin.py +++ b/teleforma/admin.py @@ -262,9 +262,20 @@ class MediaItemMarkerAdmin(admin.ModelAdmin): class TestimonialAdmin(admin.ModelAdmin): - search_fields = ['seminar__course__title', 'seminar__sub_title', + search_fields = ['seminar__course__title', 'seminar__sub_title', 'conference__title', 'user__username', 'user__last_name'] - + list_display = ['get_title', 'user', 'date_added', 'date_modified'] + list_filter = ['date_added', 'date_modified'] + sortable_by = ['date_added', 'date_modified'] + + def get_title(self, obj): + """Affiche le titre de l'attestation""" + if obj.seminar: + return obj.seminar.title + elif obj.conference: + return obj.conference.title + return "N/A" + get_title.short_description = 'Titre' class AnswerAdmin(admin.ModelAdmin): search_fields = ['user__username', 'user__last_name'] @@ -274,10 +285,29 @@ class AnswerAdmin(admin.ModelAdmin): class SeminarRevisionAdmin(admin.ModelAdmin): - search_fields = ['user__username', 'seminar__title'] + search_fields = ['user__username', 'seminar__title', 'conference__title'] date_hierarchy = 'date_modified' readonly_fields=('date',) - + list_display = ['get_title', 'user', 'date', 'date_modified', 'get_type'] + list_filter = ['date', 'date_modified'] + + def get_title(self, obj): + """Affiche le titre du séminaire ou de la conférence""" + if obj.seminar: + return obj.seminar.title + elif obj.conference: + return obj.conference.title + return "N/A" + get_title.short_description = 'Formation' + + def get_type(self, obj): + """Affiche le type (séminaire ou conférence)""" + if obj.seminar: + return "Séminaire" + elif obj.conference: + return "Conférence" + return "N/A" + get_type.short_description = 'Type' actions = ['export_seminar_revisions'] @@ -297,24 +327,34 @@ class SeminarRevisionAdmin(admin.ModelAdmin): data = {} for rev in queryset.order_by('-date'): - seminar_id = rev.seminar.id - if seminar_id not in data: - data[seminar_id] = {'title': rev.seminar.pretty_title.encode('utf-8'), 'users': {}} + # Gère les révisions de séminaire et de conférence + if rev.seminar: + item_id = ('seminar', rev.seminar.id) + title = rev.seminar.pretty_title.encode('utf-8') + elif rev.conference: + item_id = ('conference', rev.conference.id) + title = rev.conference.title.encode('utf-8') + else: + continue + + if item_id not in data: + data[item_id] = {'title': title, 'users': {}} user = rev.user.email - if user not in data[seminar_id]['users']: - data[seminar_id]['users'][user] = [] + if user not in data[item_id]['users']: + data[item_id]['users'][user] = [] if rev.date_modified: - data[seminar_id]['users'][user].append({'start': rev.date, 'end': rev.date_modified}) + data[item_id]['users'][user].append({'start': rev.date, 'end': rev.date_modified}) writer = UnicodeWriter(response, delimiter=";") - writer.writerow(["seminaire", "email", "duree total", "date debut", "date fin", "duree"]) - for seminar in list(data.keys()): - title = data[seminar]['title'] - users = data[seminar]['users'] + writer.writerow(["formation", "type", "email", "duree total", "date debut", "date fin", "duree"]) + for item_key in list(data.keys()): + title = data[item_key]['title'] + item_type = "Séminaire" if item_key[0] == 'seminar' else "Conférence" + users = data[item_key]['users'] for user in list(users.keys()): - l = [title, user] + l = [title, item_type, user] revs = users[user] duration = datetime.timedelta() for rev in revs: @@ -325,7 +365,7 @@ class SeminarRevisionAdmin(admin.ModelAdmin): l.append(str(rev['end'])) l.append(pretty_duration(rev['end'] - rev['start']) if rev['end'] else 0) duration += rev['end'] - rev['start'] if rev['end'] else datetime.timedelta() - l.insert(2, pretty_duration(duration)) + l.insert(3, pretty_duration(duration)) writer.writerow(l) return response diff --git a/teleforma/management/commands/teleforma-revisions-from-bbb.py b/teleforma/management/commands/teleforma-revisions-from-bbb.py index 17315feb..e0abe432 100644 --- a/teleforma/management/commands/teleforma-revisions-from-bbb.py +++ b/teleforma/management/commands/teleforma-revisions-from-bbb.py @@ -28,7 +28,7 @@ class Command(BaseCommand): def handle(self, *args, **options): duration = options['duration'] logpath = options['logfile'] - sys.stdout = open(logpath, 'a', encoding='utf-8') + # sys.stdout = open(logpath, 'a', encoding='utf-8') end = datetime.datetime.now() start = end - datetime.timedelta(seconds = duration) @@ -49,12 +49,21 @@ class Command(BaseCommand): meeting_id = str(meeting['meetingID']) try: conf = Conference.objects.get(webclass_id = meeting_id) - seminar = Seminar.objects.get(conference = conf) + print(conf) except: - print(("Warning, can't find Seminar for %s" % meeting_id)) + print(("Warning, can't find Conference for %s" % meeting_id)) continue - sem_txt = "%s (%s)" % (seminar, seminar.pk) + # Essayer de trouver un séminaire associé + try: + seminar = Seminar.objects.get(conference = conf) + print(seminar) + sem_txt = "%s (%s)" % (seminar, seminar.pk) + except: + seminar = None + sem_txt = "Conference %s (%s)" % (conf.title, conf.pk) + print(("No Seminar for %s, using Conference directly" % meeting_id)) + attendees = as_list(meeting['attendees']['attendee']) for attendee in attendees: user_id = str(attendee['userID']) @@ -71,17 +80,71 @@ class Command(BaseCommand): continue conf.set_as_read(user) user_txt = "%s (%s)" % (user, user.pk) - rev = SeminarRevision.objects.filter(seminar = seminar, - user = user, - date_modified = None) + + # Chercher une révision ouverte (soit avec seminar, soit avec conference) + if seminar: + rev = SeminarRevision.objects.filter(seminar=seminar, + user=user, + date_modified=None) + else: + rev = SeminarRevision.objects.filter(conference=conf, + user=user, + date_modified=None) + if rev.count(): print(("User %s already has an open revision on %s" % (user_txt, sem_txt))) - else: + else: print(("Crediting %d seconds to %s on %s" % (duration, user_txt, sem_txt))) - sr = SeminarRevision(seminar = seminar, - user = user, - date = start, - date_modified = end) + sr = SeminarRevision(seminar=seminar, + conference=conf, + user=user, + date=start, + date_modified=end) sr.save() sr.date = start sr.save() + + # si la durée totale de la participation est supérieure à la durée de la formation (-20 minutes), on créé une attestation + # calcule le temps passé en webconférence + total_spent = datetime.timedelta() + + # Chercher toutes les révisions liées à cette conférence (via seminar ou directement) + if seminar: + revisions = SeminarRevision.objects.filter(seminar=seminar, user=user, conference=conf) + else: + revisions = SeminarRevision.objects.filter(conference=conf, user=user, seminar=None) + + for revision in revisions: + total_spent += revision.delta() + + required_duration = conf.duration.as_seconds() * 0.9 + # print("Required duration: %d seconds" % required_duration) + # print("Total spent: %d seconds" % total_spent.total_seconds()) + if total_spent.total_seconds() >= required_duration: + # Chercher une attestation existante + if seminar: + existing_testimonial = Testimonial.objects.filter( + seminar=seminar, + user=user, + conference=conf + ).first() + else: + existing_testimonial = Testimonial.objects.filter( + conference=conf, + user=user, + seminar=None + ).first() + # print("Existing testimonial: %s" % existing_testimonial) + + if not existing_testimonial: + print("Creating testimonial for %s" % conf.title + " for user " + user.username) + testimonial = Testimonial( + seminar=seminar, + conference=conf, + user=user, + date_added=start, + date_modified=end + ) + testimonial.save() + conf.viewers.add(user) + conf.save() diff --git a/teleforma/migrations/0024_seminar_secondary_subject.py b/teleforma/migrations/0024_seminar_secondary_subject.py new file mode 100644 index 00000000..25afc392 --- /dev/null +++ b/teleforma/migrations/0024_seminar_secondary_subject.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.23 on 2025-03-11 12:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teleforma', '0023_conference_fif_pl'), + ] + + operations = [ + migrations.AddField( + model_name='seminar', + name='secondary_subject', + field=models.CharField(blank=True, choices=[('', 'Rien'), ('article_98_1', 'Article 98-1'), ('article_98_1_oral_blanc', 'Article 98-1 - Oral blanc')], default='', max_length=50, verbose_name='Matière secondaire'), + ), + ] diff --git a/teleforma/migrations/0025_auto_20251105_1331.py b/teleforma/migrations/0025_auto_20251105_1331.py new file mode 100644 index 00000000..05cea7bf --- /dev/null +++ b/teleforma/migrations/0025_auto_20251105_1331.py @@ -0,0 +1,41 @@ +# Generated by Django 3.2.23 on 2025-11-05 13:31 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('teleforma', '0024_seminar_secondary_subject'), + ] + + operations = [ + migrations.AddField( + model_name='conference', + name='viewers', + field=models.ManyToManyField(blank=True, help_text='Utilisateurs ayant vu la webconférence', related_name='conference_viewers', to=settings.AUTH_USER_MODEL, verbose_name='viewers'), + ), + migrations.AddField( + model_name='seminarrevision', + name='conference', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='revision', to='teleforma.conference', verbose_name='conference'), + ), + migrations.AddField( + model_name='testimonial', + name='conference', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='testimonial', to='teleforma.conference', verbose_name='conference'), + ), + migrations.AlterField( + model_name='seminar', + name='secondary_subject', + field=models.CharField(blank=True, choices=[('', 'Aucune'), ('article_98_1', 'Article 98-1'), ('article_98_1_oral_blanc', 'Article 98-1 - Oral blanc')], default='', max_length=50, verbose_name='Matière secondaire'), + ), + migrations.AlterField( + model_name='seminarrevision', + name='seminar', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='revision', to='teleforma.seminar', verbose_name='seminar'), + ), + ] diff --git a/teleforma/models/core.py b/teleforma/models/core.py index 97994e01..0331e390 100755 --- a/teleforma/models/core.py +++ b/teleforma/models/core.py @@ -960,6 +960,8 @@ class Conference(Displayable, WebclassMixin, ProductCodeMixin, SuggestionsMixin) approved = models.BooleanField(_('approved'), default=True) readers = models.ManyToManyField(User, related_name="conference", verbose_name=_('readers'), blank=True) + viewers = models.ManyToManyField(User, help_text="Utilisateurs ayant vu la webconférence", related_name="conference_viewers", verbose_name=_('viewers'), + blank=True) docs_description = models.ManyToManyField(Document, related_name="conference_docs_description", verbose_name=_('description documents'), blank=True) diff --git a/teleforma/models/pro.py b/teleforma/models/pro.py index 187a1f5f..38e06cf4 100644 --- a/teleforma/models/pro.py +++ b/teleforma/models/pro.py @@ -65,7 +65,10 @@ class SeminarType(models.Model): verbose_name = _('Seminar type') - +class SecondarySubjectChoices(models.TextChoices): + NONE = '', _("Aucune") + ARTICLE_98_1 = 'article_98_1', _("Article 98-1") + ARTICLE_98_1_ORAL_BLANC = 'article_98_1_oral_blanc', _("Article 98-1 - Oral blanc") class Seminar(ClonableMixin, Displayable, ProductCodeMixin, SuggestionsMixin): @@ -74,6 +77,13 @@ class Seminar(ClonableMixin, Displayable, ProductCodeMixin, SuggestionsMixin): # status values : 1: draft, 2: published fif_pl = models.BooleanField(_('Eligible FIF-PL'), default=False) new = models.BooleanField(_('Nouveau'), default=False) + secondary_subject = models.CharField( + _("Matière secondaire"), + max_length=50, + choices=SecondarySubjectChoices.choices, + blank=True, + default=SecondarySubjectChoices.NONE + ) private = models.BooleanField(_('private'), default=False) upcoming = models.BooleanField("A venir (shop)", default=False) type = models.ForeignKey(SeminarType, related_name='seminar', verbose_name=_('type'), @@ -437,6 +447,8 @@ class Testimonial(models.Model): seminar = models.ForeignKey(Seminar, related_name="testimonial", verbose_name=_('seminar'), blank=True, null=True, on_delete=models.SET_NULL) + conference = models.ForeignKey(Conference, related_name="testimonial", verbose_name=_('conference'), + blank=True, null=True, on_delete=models.SET_NULL) user = models.ForeignKey(User, related_name="testimonial", verbose_name=_('user'), blank=True, null=True, on_delete=models.SET_NULL) template = models.ForeignKey(TestimonialTemplate, related_name="testimonial", @@ -448,6 +460,11 @@ class Testimonial(models.Model): title = models.CharField(_('title'), max_length=255, blank=True) sent = models.BooleanField("Envoyé par email ?", default=False) + @property + def webconf(self): + """Indique si l'attestation est pour une webconférence (si on a un champ conference renseigné)""" + return self.conference is not None + def get_title(self): if self.date_modified: date = self.date_modified @@ -541,13 +558,20 @@ class Auditor(models.Model): class SeminarRevision(models.Model): - seminar = models.ForeignKey(Seminar, related_name="revision", verbose_name=_('seminar'), on_delete=models.CASCADE) + seminar = models.ForeignKey(Seminar, related_name="revision", verbose_name=_('seminar'), blank=True, null=True, on_delete=models.CASCADE) + conference = models.ForeignKey(Conference, related_name="revision", verbose_name=_('conference'), blank=True, null=True, on_delete=models.CASCADE) user = models.ForeignKey(User, related_name="revision", verbose_name=_('user'), on_delete=models.CASCADE) date = models.DateTimeField(_('date added'), auto_now_add=True, null=True) date_modified = models.DateTimeField(_('date modified'), blank=True, null=True) def __str__(self): - return ' - '.join([self.seminar.title, self.user.username, str(self.date), str(self.date_modified)]) + title = self.seminar.title if self.seminar else (self.conference.title if self.conference else "N/A") + return ' - '.join([title, self.user.username, str(self.date), str(self.date_modified)]) + + @property + def webconf(self): + """Indique si la révision est due à une webconférence (si on a un champ conference renseigné)""" + return self.conference is not None class Meta(MetaCore): db_table = app_label + '_' + 'seminar_revisions' diff --git a/teleforma/static/teleforma/css/teleforma.css b/teleforma/static/teleforma/css/teleforma.css index 6056254d..11db2ad5 100644 --- a/teleforma/static/teleforma/css/teleforma.css +++ b/teleforma/static/teleforma/css/teleforma.css @@ -35,9 +35,6 @@ margin: 0 auto -58px; #footer { height: 30px; } - - - /*a:link, a:visited { color: #BB0000; text-decoration:none; diff --git a/teleforma/templates/teleforma/conference_testimonial.html b/teleforma/templates/teleforma/conference_testimonial.html new file mode 100644 index 00000000..89fcc34d --- /dev/null +++ b/teleforma/templates/teleforma/conference_testimonial.html @@ -0,0 +1,63 @@ + +{% load i18n %} +{% load teleforma_tags %} + +{% get_current_language as LANGUAGE_CODE %} +{% get_available_languages as LANGUAGES %} + + + + + +{%block head_title %}{% description %} - {% trans "Testimonial" %}{% endblock %} + +{% block stylesheets %} + +{% endblock %} + +{% block extra_stylesheets %}{% endblock %} + + + + +{% block layout %} +
+ + + +
+ + + + + + + + + +
{% trans "Last name" %} : {{ testimonial.user.last_name|upper }}
{% trans "First name" %} : {{ testimonial.user.first_name|upper }}
{% trans "Address" %} : {{ testimonial.user.auditor.all.0.address }} {{ testimonial.user.auditor.all.0.postal_code }} {{ testimonial.user.auditor.all.0.city }}
Durée de la formation : {{ conference.duration|hours }} {% trans "hours" %}
{% trans "Object" %} : {{ conference.title }}
Type de formation : Présentiel / Webconférence
Date de la formation : {{ first_revision.date|date:'j F Y' }}
+ + + + +
+ {{ conference.department.address|safe }} + + Pro-Barreau signature +
+ +
+ +{% block footer %} + +{% endblock %} + +
+{% endblock layout %} + + + diff --git a/teleforma/templates/teleforma/conference_testimonial_payback.html b/teleforma/templates/teleforma/conference_testimonial_payback.html new file mode 100644 index 00000000..562fcd33 --- /dev/null +++ b/teleforma/templates/teleforma/conference_testimonial_payback.html @@ -0,0 +1,214 @@ + +{% load i18n %} +{% load teleforma_tags %} + +{% get_current_language as LANGUAGE_CODE %} +{% get_available_languages as LANGUAGES %} + + + + + +{%block head_title %}{% description %} - {% trans "Testimonial" %}{% endblock %} + +{% block stylesheets %} + +{% endblock %} + + + +{% block extra_stylesheets %}{% endblock %} + + + + +{% block layout %} +
+
+ + +
+ {{ testimonial.user.first_name }} {{ testimonial.user.last_name }} +
+ + +
+ {{ title }} +
+ + + + +
+ {{ first_revision.date|date:'j' }} +
+
+ {{ first_revision.date|date:'m' }} +
+
+ {{ first_revision.date|date:'Y' }} +
+ +
+ {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'j' }}{% else %}{{ testimonial.date_added|date:'j' }}{% endif %} +
+
+ {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'m' }}{% else %}{{ testimonial.date_added|date:'m' }}{% endif %} +
+
+ {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'Y' }}{% else %}{{ testimonial.date_added|date:'Y' }}{% endif %} +
+ + +{% if blended %} +
+ {{ nb_semi_days }} +
+
+ {{ nb_days }} +
+
+ {{ hours_presentiel }} +
+{% endif %} + +
+ {{ hours_elearning|hours }} +
+ +
+ {{ nb_parts }} +
+ +
+ {{ price|floatformat:2 }} +
+ +
+ {{ price|floatformat:2 }} +
+ + +
+ {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'j F Y' }}{% else %}{{ testimonial.date_added|date:'j F Y' }}{% endif %} +
+ +
+ {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'j F Y' }}{% else %}{{ testimonial.date_added|date:'j F Y' }}{% endif %} +
+ +
+
+{% endblock layout %} + + + diff --git a/teleforma/templates/teleforma/testimonials.html b/teleforma/templates/teleforma/testimonials.html index 2aff09bc..1b1fe161 100644 --- a/teleforma/templates/teleforma/testimonials.html +++ b/teleforma/templates/teleforma/testimonials.html @@ -52,12 +52,13 @@ {% for testimonial in object_list %}
-

{% if testimonial.seminar %}{{ testimonial.seminar.title }}{% else %}{{ testimonial.title }}{% endif %}

+

{% if testimonial.seminar %}{{ testimonial.seminar.title }}{% elif testimonial.conference %}{{ testimonial.conference.title }}{% else %}{{ testimonial.title }}{% endif %}

+ {% if testimonial.seminar %} @@ -70,6 +71,20 @@ {% endif %} + {% elif testimonial.conference %} + + + + + + + {% if show_fifpl %} + + + + + {% endif %} + {% endif %}
{% trans "Training testimonial" %} {% trans "validated on" %} {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'j F Y' }}{% else %}{{ testimonial.date_added|date:'j F Y' }}{% endif %}
{% trans "Training testimonial" %}{% trans "validated on" %} {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'j F Y' }}{% else %}{{ testimonial.date_added|date:'j F Y' }}{% endif %}
{% trans "Payback testimonial" %} ({% trans "only to have support of the training costs" %}){% trans "validated on" %} {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'j F Y' }}{% else %}{{ testimonial.date_added|date:'j F Y' }}{% endif %}
diff --git a/teleforma/urls.py b/teleforma/urls.py index ee39bc08..544970b9 100644 --- a/teleforma/urls.py +++ b/teleforma/urls.py @@ -47,7 +47,7 @@ from django.contrib.auth.views import (LoginView, LogoutView, from django.views import static from teleforma.views.core import ConferenceRecordView, ConferenceView, CourseView, HelpView, MediaTranscodedView, MediaView, get_site_url, join_webclass from teleforma.views.crfpa import UserLoginView, UsersCourseView, UsersIejView, UsersTrainingView, UsersView -from teleforma.views.pro import AnswerDetailView, AnswerDetailViewTest, AnswerView, AnswersPendingView, AnswersTreatedView, QuizQuestionView, SeminarDocumentDownloadView, SeminarDocumentView, SeminarMediaView, SeminarView, SeminarsView, TestimonialDownloadView, TestimonialKnowledgeView, TestimonialListView, TestimonialPaybackView, TestimonialView, evaluation_form_detail, webclass_bbb_webhook +from teleforma.views.pro import AnswerDetailView, AnswerDetailViewTest, AnswerView, AnswersPendingView, AnswersTreatedView, ConferenceTestimonialDownloadView, ConferenceTestimonialPaybackView, ConferenceTestimonialView, QuizQuestionView, SeminarDocumentDownloadView, SeminarDocumentView, SeminarMediaView, SeminarView, SeminarsView, TestimonialDownloadView, TestimonialKnowledgeView, TestimonialListView, TestimonialPaybackView, TestimonialView, evaluation_form_detail, webclass_bbb_webhook from teleforma.views.profile import ProfileView from teleforma.views.home import HomeView @@ -157,6 +157,14 @@ urlpatterns = [ url(r'^desk/seminars/(?P.*)/testimonial-payback/$', TestimonialPaybackView.as_view(), name="teleforma-seminar-testimonial-payback"), + # Conference Testimonial (for conferences without seminars) + url(r'^desk/conferences/(?P.*)/testimonial/$', ConferenceTestimonialView.as_view(), + name="teleforma-conference-testimonial"), + url(r'^desk/conferences/(?P.*)/testimonial/download/$', ConferenceTestimonialDownloadView.as_view(), + name="teleforma-conference-testimonial-download"), + url(r'^desk/conferences/(?P.*)/testimonial-payback/$', ConferenceTestimonialPaybackView.as_view(), + name="teleforma-conference-testimonial-payback"), + # Postman url(r'^messages/', include('postman.urls')), diff --git a/teleforma/views/pro.py b/teleforma/views/pro.py index 28950e2f..66672157 100644 --- a/teleforma/views/pro.py +++ b/teleforma/views/pro.py @@ -816,37 +816,66 @@ class PDFTemplateResponseMixin(TemplateResponseMixin): def get_testimonial_context(testimonial): """ make it a stateless function so we can use it in django admin command to send testimonial pdf """ context = {'testimonial': testimonial} - seminar = testimonial.seminar - context['seminar'] = seminar user = testimonial.user - revisions = SeminarRevision.objects.filter( - seminar=seminar, user=user).order_by('date') - - if revisions: - context['first_revision'] = revisions[0] - - testimonials = Testimonial.objects.filter( - seminar=seminar, user=user) - if testimonials: - context['testimonial'] = testimonials[0] - - context['hours_presentiel'] = 0 - context['hours_elearning'] = seminar.duration - context['blended'] = False - if seminar.conference: - if seminar.conference.webclass and seminar.conference in user.auditor.get().conferences.all(): - context['blended'] = True - context['conference'] = seminar.conference - context['hours_presentiel'] = (seminar.duration.as_seconds() - seminar.conference.webclass_hours_complementary.as_seconds()) / 3600 - context['hours_elearning'] = seminar.conference.webclass_hours_complementary - - hours = seminar.duration.as_seconds() / 3600 - context['nb_parts'] = seminar.is_multipart and seminar.number_of_parts or 5 - context['nb_days'] = int(hours / 6) - context['nb_semi_days'] = (hours / 3 ) % 2 >= 1 and 1 or 0 - context['title'] = seminar.title - if seminar.level == "Spécialisation": - context['title'] = seminar.course.title + " - " + seminar.title + + # Gère les attestations de séminaire et de conférence + if testimonial.seminar: + seminar = testimonial.seminar + context['seminar'] = seminar + revisions = SeminarRevision.objects.filter( + seminar=seminar, user=user).order_by('date') + + if revisions: + context['first_revision'] = revisions[0] + + testimonials = Testimonial.objects.filter( + seminar=seminar, user=user) + if testimonials: + context['testimonial'] = testimonials[0] + + context['hours_presentiel'] = 0 + context['hours_elearning'] = seminar.duration + context['blended'] = False + if seminar.conference: + if seminar.conference.webclass and seminar.conference in user.auditor.get().conferences.all(): + context['blended'] = True + context['conference'] = seminar.conference + context['hours_presentiel'] = (seminar.duration.as_seconds() - seminar.conference.webclass_hours_complementary.as_seconds()) / 3600 + context['hours_elearning'] = seminar.conference.webclass_hours_complementary + + hours = seminar.duration.as_seconds() / 3600 + context['nb_parts'] = seminar.is_multipart and seminar.number_of_parts or 5 + context['nb_days'] = int(hours / 6) + context['nb_semi_days'] = (hours / 3 ) % 2 >= 1 and 1 or 0 + context['title'] = seminar.title + if seminar.level == "Spécialisation": + context['title'] = seminar.course.title + " - " + seminar.title + + elif testimonial.conference: + # Attestation de conférence seule (sans séminaire) + conference = testimonial.conference + context['conference'] = conference + revisions = SeminarRevision.objects.filter( + conference=conference, user=user, seminar=None).order_by('date') + + if revisions: + context['first_revision'] = revisions[0] + + testimonials = Testimonial.objects.filter( + conference=conference, user=user, seminar=None) + if testimonials: + context['testimonial'] = testimonials[0] + + context['hours_presentiel'] = conference.duration.as_seconds() / 3600 + context['hours_elearning'] = conference.webclass_hours_complementary if conference.webclass_hours_complementary else conference.duration + context['blended'] = conference.webclass and conference in user.auditor.get().conferences.all() + + hours = conference.duration.as_seconds() / 3600 + context['nb_parts'] = 5 + context['nb_days'] = int(hours / 6) + context['nb_semi_days'] = (hours / 3 ) % 2 >= 1 and 1 or 0 + context['title'] = conference.title + return context @@ -901,9 +930,14 @@ class TestimonialListView(ListView): testimonials = Testimonial.objects.filter(user=user) for testimonial in testimonials: if testimonial.seminar: + # Attestation de séminaire seminar = testimonial.seminar if seminar_validated(user, seminar) and (get_seminar_delta(user, seminar) >= 0 or testimonial.date_added <= REVISION_DATE_FILTER): t.append(testimonial) + elif testimonial.conference: + # Attestation de conférence (webconf sans séminaire associé) + # Pour les conférences, pas de validation nécessaire, juste vérifier que l'attestation existe + t.append(testimonial) return t def get_context_data(self, **kwargs): @@ -975,6 +1009,73 @@ class TestimonialPaybackView(TestimonialView): return super(TestimonialPaybackView, self).dispatch(*args, **kwargs) +# Views for Conference testimonials (without associated Seminar) +class ConferenceTestimonialView(PDFTemplateResponseMixin, DetailView): + + context_object_name = "conference" + model = Conference + template_name = 'teleforma/conference_testimonial.html' + pdf_template_name = template_name + pdf_filename = 'testimonial.pdf' + + def get_context_data(self, **kwargs): + context = super(ConferenceTestimonialView, self).get_context_data(**kwargs) + conference = context['conference'] + + testimonials = Testimonial.objects.filter( + conference=conference, user=self.request.user, seminar=None) + + if testimonials: + testimonial = testimonials[0] + context.update(get_testimonial_context(testimonial)) + + return context + + @method_decorator(login_required) + def dispatch(self, *args, **kwargs): + return super(ConferenceTestimonialView, self).dispatch(*args, **kwargs) + + +class ConferenceTestimonialDownloadView(ConferenceTestimonialView): + + pdf_filename = 'testimonial.pdf' + + def get_pdf_filename(self): + super(ConferenceTestimonialDownloadView, self).get_pdf_filename() + conference = self.get_object() + prefix = str(_('Testimonial')) + filename = '_'.join([prefix, conference.title.replace(',', ' '), + self.request.user.first_name, self.request.user.last_name, ]) + filename += '.pdf' + return filename + + +class ConferenceTestimonialPaybackView(ConferenceTestimonialView): + + template_name = 'teleforma/conference_testimonial_payback.html' + pdf_template_name = template_name + + def get_context_data(self, **kwargs): + context = super(ConferenceTestimonialPaybackView, + self).get_context_data(**kwargs) + conference = context['conference'] + context['price'] = conference.price + # recompute price if use has used a promo code + for cart in Cart.objects.filter(user=self.request.user, status=Cart.STATE_PAYMENT_ACCEPTED): + if cart.has_item(conference): + if cart.promo_code and conference in cart.promo_code.conferences.all(): + context['price'] = conference.price - \ + (conference.price * cart.promo_code.reduction / 100.0) + break + + context['site'] = Site.objects.get_current() + return context + + @method_decorator(login_required) + def dispatch(self, *args, **kwargs): + return super(ConferenceTestimonialPaybackView, self).dispatch(*args, **kwargs) + + class QuizQuestionView(SeminarAccessMixin, SeminarRevisionMixin, QuizTake): template_name = 'quiz/question.html' -- 2.39.5