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']
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']
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:
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
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)
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'])
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()
--- /dev/null
+# 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'),
+ ),
+ ]
--- /dev/null
+# 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'),
+ ),
+ ]
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)
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):
# 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'),
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",
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
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'
#footer {
height: 30px;
}
-
-
-
/*a:link, a:visited {
color: #BB0000;
text-decoration:none;
--- /dev/null
+<!DOCTYPE html>
+{% load i18n %}
+{% load teleforma_tags %}
+
+{% get_current_language as LANGUAGE_CODE %}
+{% get_available_languages as LANGUAGES %}
+<html xmlns="http://www.w3.org/1999/xhtml" lang="{{ LANGUAGE_CODE }}" xml:lang="{{ LANGUAGE_CODE }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
+
+<head>
+<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
+
+<title>{%block head_title %}{% description %} - {% trans "Testimonial" %}{% endblock %}</title>
+
+{% block stylesheets %}
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}teleforma/css/teleforma_pdf.css" />
+{% endblock %}
+
+{% block extra_stylesheets %}{% endblock %}
+
+</head>
+
+<body>
+{% block layout %}
+<div id="layout">
+
+<div id="header">
+ <span style="color: yellow; font-weight: bold;">></span>Attestation de formation
+</div>
+
+<div id="content">
+
+ <table class="table1">
+ <tr><td class="bold">{% trans "Last name" %} : </td><td>{{ testimonial.user.last_name|upper }}</td></tr>
+ <tr><td class="bold">{% trans "First name" %} : </td><td>{{ testimonial.user.first_name|upper }}</td></tr>
+ <tr><td class="bold">{% trans "Address" %} : </td><td>{{ testimonial.user.auditor.all.0.address }} {{ testimonial.user.auditor.all.0.postal_code }} {{ testimonial.user.auditor.all.0.city }}</td></tr>
+ <tr><td class="bold">Durée de la formation : </td><td>{{ conference.duration|hours }} {% trans "hours" %}</td></tr>
+ <tr><td class="bold">{% trans "Object" %} : </td><td>{{ conference.title }}</td></tr>
+ <tr><td class="bold">Type de formation : </td><td>Présentiel / Webconférence</td></tr>
+ <tr><td class="bold">Date de la formation : </td><td>{{ first_revision.date|date:'j F Y' }}</td></tr>
+ </table>
+
+ <table class="table2">
+ <tr><td>
+ {{ conference.department.address|safe }}
+ </td>
+ <td>
+ <img src="https://{{ site_url }}{{ conference.department.signature.url }}" title="Pro-Barreau signature" alt="Pro-Barreau signature" style="width:300px;" />
+ </td></tr>
+ </table>
+
+</div>
+
+{% block footer %}
+<div id="footer">
+ Copyright © {% current_year %} {{ conference.department }}
+ </div>
+{% endblock %}
+
+</div>
+{% endblock layout %}
+
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+{% load i18n %}
+{% load teleforma_tags %}
+
+{% get_current_language as LANGUAGE_CODE %}
+{% get_available_languages as LANGUAGES %}
+<html xmlns="http://www.w3.org/1999/xhtml" lang="{{ LANGUAGE_CODE }}" xml:lang="{{ LANGUAGE_CODE }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
+
+<head>
+<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
+
+<title>{%block head_title %}{% description %} - {% trans "Testimonial" %}{% endblock %}</title>
+
+{% block stylesheets %}
+<link rel="stylesheet" type="text/css" href="/static/teleforma/css/teleforma_pdf.css" />
+{% endblock %}
+
+<style>
+
+@page :first {
+ background: url(https://{{ site.domain }}/static/teleforma/images/attestation_fifpl_v4.png) no-repeat center;
+ background-size: cover;
+ margin: 0;
+}
+
+ #user {
+ position: absolute;
+ top: 380px;
+ left: 200px;
+ }
+
+ #conference {
+ position: absolute;
+ top: 420px;
+ left: 60px;
+ }
+
+ #date_start_d {
+ position: absolute;
+ top: 460px;
+ left: 395px;
+ }
+
+ #date_start_m {
+ position: absolute;
+ top: 460px;
+ left: 430px;
+ }
+ #date_start_y {
+ position: absolute;
+ top: 460px;
+ left: 475px;
+ }
+
+ #date_end_d {
+ position: absolute;
+ top: 460px;
+ left: 595px;
+ }
+
+ #date_end_m {
+ position: absolute;
+ top: 460px;
+ left: 630px;
+ }
+ #date_end_y {
+ position: absolute;
+ top: 460px;
+ left: 675px;
+ }
+
+ #conference-semi-days {
+ position: absolute;
+ top: 530px;
+ left: 172px;
+ }
+ #conference-days {
+ position: absolute;
+ top: 530px;
+ left: 366px;
+ }
+ #conference-presentiel-hours {
+ position: absolute;
+ top: 530px;
+ left: 550px;
+ }
+ #conference-elearning-hours {
+ position: absolute;
+ top: 676px;
+ left: 172px;
+ }
+
+
+ #nb-parts {
+ position: absolute;
+ top: 676px;
+ left: 366px;
+ }
+
+
+
+ #price-ht {
+ position: absolute;
+ top: 798px;
+ left: 265px;
+ }
+
+ #price-ttc {
+ position: absolute;
+ top: 798px;
+ left: 430px;
+ }
+
+ #date-validated {
+ position: absolute;
+ top: 676px;
+ left: 550px;
+ }
+
+ #date-now {
+ position: absolute;
+ top: 920px;
+ left: 94px;
+ }
+
+
+</style>
+
+{% block extra_stylesheets %}{% endblock %}
+
+</head>
+
+<body id="conference_testimonial_payback">
+{% block layout %}
+<div id="layout">
+<div id="content" style="font-size=1.3em;">
+
+
+<div id="user">
+ {{ testimonial.user.first_name }} {{ testimonial.user.last_name }}
+</div>
+
+
+<div id="conference">
+ {{ title }}
+</div>
+
+
+
+
+<div id="date_start_d" class="date">
+ {{ first_revision.date|date:'j' }}
+</div>
+<div id="date_start_m">
+ {{ first_revision.date|date:'m' }}
+</div>
+<div id="date_start_y">
+ {{ first_revision.date|date:'Y' }}
+</div>
+
+<div id="date_end_d">
+ {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'j' }}{% else %}{{ testimonial.date_added|date:'j' }}{% endif %}
+</div>
+<div id="date_end_m">
+ {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'m' }}{% else %}{{ testimonial.date_added|date:'m' }}{% endif %}
+</div>
+<div id="date_end_y">
+ {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'Y' }}{% else %}{{ testimonial.date_added|date:'Y' }}{% endif %}
+</div>
+
+
+{% if blended %}
+<div id="conference-semi-days">
+ {{ nb_semi_days }}
+</div>
+<div id="conference-days">
+ {{ nb_days }}
+</div>
+<div id="conference-presentiel-hours">
+ {{ hours_presentiel }}
+</div>
+{% endif %}
+
+<div id="conference-elearning-hours">
+ {{ hours_elearning|hours }}
+</div>
+
+<div id="nb-parts">
+ {{ nb_parts }}
+</div>
+
+<div id="price-ht">
+ {{ price|floatformat:2 }}
+</div>
+
+<div id="price-ttc">
+ {{ price|floatformat:2 }}
+</div>
+
+
+<div id="date-now">
+ {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'j F Y' }}{% else %}{{ testimonial.date_added|date:'j F Y' }}{% endif %}
+</div>
+
+<div id="date-validated">
+ {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'j F Y' }}{% else %}{{ testimonial.date_added|date:'j F Y' }}{% endif %}
+</div>
+
+</div>
+</div>
+{% endblock layout %}
+
+</body>
+</html>
{% for testimonial in object_list %}
<div class="course_content">
<div class="course_subtitle">
- <h3><img src="{{ STATIC_URL }}/teleforma/images/item_title.png" width="10px" alt="" /> {% if testimonial.seminar %}{{ testimonial.seminar.title }}{% else %}{{ testimonial.title }}{% endif %}</h3>
+ <h3><img src="{{ STATIC_URL }}/teleforma/images/item_title.png" width="10px" alt="" /> {% if testimonial.seminar %}{{ testimonial.seminar.title }}{% elif testimonial.conference %}{{ testimonial.conference.title }}{% else %}{{ testimonial.title }}{% endif %}</h3>
</div>
<table class="listing" width="100%">
<tbody id="spacing">
+ {% if testimonial.seminar %}
<tr>
<td><a href="{% url 'teleforma-seminar-testimonial' testimonial.seminar.id %}?format=pdf" target="_blank"><img src="{{ STATIC_URL }}/teleforma/images/application-msword.png" style="vertical-align:middle" alt="" /> {% trans "Training testimonial" %}</a></td>
<td>{% trans "validated on" %} {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'j F Y' }}{% else %}{{ testimonial.date_added|date:'j F Y' }}{% endif %}</td>
<td><a href="{% url 'teleforma-seminar-testimonial-payback' testimonial.seminar.id %}?format=pdf" target="_blank"><img src="{{ STATIC_URL }}teleforma/images/download.png" style="vertical-align:middle" alt="" title="{% trans "Download" %}" /></a></td>
</tr>
{% endif %}
+ {% elif testimonial.conference %}
+ <tr>
+ <td><a href="{% url 'teleforma-conference-testimonial' testimonial.conference.id %}?format=pdf" target="_blank"><img src="{{ STATIC_URL }}/teleforma/images/application-msword.png" style="vertical-align:middle" alt="" /> {% trans "Training testimonial" %}</a></td>
+ <td>{% trans "validated on" %} {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'j F Y' }}{% else %}{{ testimonial.date_added|date:'j F Y' }}{% endif %}</td>
+ <td><a href="{% url 'teleforma-conference-testimonial-download' testimonial.conference.id %}?format=pdf"><img src="{{ STATIC_URL }}teleforma/images/download.png" style="vertical-align:middle" alt="" title="{% trans "Download" %}" /></a></td>
+ </tr>
+
+ {% if show_fifpl %}<tr>
+ <td><a href="{% url 'teleforma-conference-testimonial-payback' testimonial.conference.id %}?format=pdf" target="_blank"><img src="{{ STATIC_URL }}/teleforma/images/application-msword.png" style="vertical-align:middle" alt="" /> {% trans "Payback testimonial" %} ({% trans "only to have support of the training costs" %})</td>
+ <td>{% trans "validated on" %} {% if testimonial.date_modified %}{{ testimonial.date_modified|date:'j F Y' }}{% else %}{{ testimonial.date_added|date:'j F Y' }}{% endif %}</td>
+ <td><a href="{% url 'teleforma-conference-testimonial-payback' testimonial.conference.id %}?format=pdf" target="_blank"><img src="{{ STATIC_URL }}teleforma/images/download.png" style="vertical-align:middle" alt="" title="{% trans "Download" %}" /></a></td>
+ </tr>
+ {% endif %}
+ {% endif %}
</tbody>
</table>
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
url(r'^desk/seminars/(?P<pk>.*)/testimonial-payback/$', TestimonialPaybackView.as_view(),
name="teleforma-seminar-testimonial-payback"),
+ # Conference Testimonial (for conferences without seminars)
+ url(r'^desk/conferences/(?P<pk>.*)/testimonial/$', ConferenceTestimonialView.as_view(),
+ name="teleforma-conference-testimonial"),
+ url(r'^desk/conferences/(?P<pk>.*)/testimonial/download/$', ConferenceTestimonialDownloadView.as_view(),
+ name="teleforma-conference-testimonial-download"),
+ url(r'^desk/conferences/(?P<pk>.*)/testimonial-payback/$', ConferenceTestimonialPaybackView.as_view(),
+ name="teleforma-conference-testimonial-payback"),
+
# Postman
url(r'^messages/', include('postman.urls')),
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
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):
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'