mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-04-24 00:12:40 +00:00
Compare commits
2 Commits
ca0601fb24
...
97eea3b11a
Author | SHA1 | Date | |
---|---|---|---|
|
97eea3b11a | ||
|
702c8d8c9e |
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.db.models.signals import post_save, pre_save
|
from django.db.models.signals import post_save, pre_save
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class ParticipationConfig(AppConfig):
|
class ParticipationConfig(AppConfig):
|
||||||
@ -10,6 +11,7 @@ class ParticipationConfig(AppConfig):
|
|||||||
The participation app contains the data about the teams, solutions, ...
|
The participation app contains the data about the teams, solutions, ...
|
||||||
"""
|
"""
|
||||||
name = 'participation'
|
name = 'participation'
|
||||||
|
verbose_name = _("participations")
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from participation import signals
|
from participation import signals
|
||||||
|
@ -3,13 +3,12 @@
|
|||||||
|
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
import math
|
import math
|
||||||
import os
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Index
|
from django.db.models import Index, Q
|
||||||
from django.template.defaultfilters import slugify
|
from django.template.defaultfilters import slugify
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils import timezone, translation
|
from django.utils import timezone, translation
|
||||||
@ -211,7 +210,7 @@ class Team(models.Model):
|
|||||||
"""
|
"""
|
||||||
:return: The mailing list to contact the team members.
|
:return: The mailing list to contact the team members.
|
||||||
"""
|
"""
|
||||||
return f"equipe-{slugify(self.trigram)}@{os.getenv('SYMPA_HOST', 'localhost')}"
|
return f"equipe-{slugify(self.trigram)}@{settings.SYMPA_HOST}"
|
||||||
|
|
||||||
def create_mailing_list(self):
|
def create_mailing_list(self):
|
||||||
"""
|
"""
|
||||||
@ -392,21 +391,21 @@ class Tournament(models.Model):
|
|||||||
"""
|
"""
|
||||||
:return: The mailing list to contact the team members.
|
:return: The mailing list to contact the team members.
|
||||||
"""
|
"""
|
||||||
return f"equipes-{slugify(self.name)}@{os.getenv('SYMPA_HOST', 'localhost')}"
|
return f"equipes-{slugify(self.name)}@{settings.SYMPA_HOST}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def organizers_email(self):
|
def organizers_email(self):
|
||||||
"""
|
"""
|
||||||
:return: The mailing list to contact the team members.
|
:return: The mailing list to contact the team members.
|
||||||
"""
|
"""
|
||||||
return f"organisateurs-{slugify(self.name)}@{os.getenv('SYMPA_HOST', 'localhost')}"
|
return f"organisateurs-{slugify(self.name)}@{settings.SYMPA_HOST}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def jurys_email(self):
|
def jurys_email(self):
|
||||||
"""
|
"""
|
||||||
:return: The mailing list to contact the team members.
|
:return: The mailing list to contact the team members.
|
||||||
"""
|
"""
|
||||||
return f"jurys-{slugify(self.name)}@{os.getenv('SYMPA_HOST', 'localhost')}"
|
return f"jurys-{slugify(self.name)}@{settings.SYMPA_HOST}"
|
||||||
|
|
||||||
def create_mailing_lists(self):
|
def create_mailing_lists(self):
|
||||||
"""
|
"""
|
||||||
@ -847,6 +846,8 @@ class Participation(models.Model):
|
|||||||
return _("Participation of the team {name} ({trigram})").format(name=self.team.name, trigram=self.team.trigram)
|
return _("Participation of the team {name} ({trigram})").format(name=self.team.name, trigram=self.team.trigram)
|
||||||
|
|
||||||
def important_informations(self):
|
def important_informations(self):
|
||||||
|
from survey.models import Survey
|
||||||
|
|
||||||
informations = []
|
informations = []
|
||||||
|
|
||||||
missing_payments = Payment.objects.filter(registrations__in=self.team.participants.all(), valid=False)
|
missing_payments = Payment.objects.filter(registrations__in=self.team.participants.all(), valid=False)
|
||||||
@ -865,6 +866,19 @@ class Participation(models.Model):
|
|||||||
'content': content,
|
'content': content,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if self.valid:
|
||||||
|
for survey in Survey.objects.filter(Q(tournament__isnull=True) | Q(tournament=self.tournament), Q(invite_team=True),
|
||||||
|
~Q(completed_teams=self.team)).all():
|
||||||
|
text = _("Please answer to the survey \"{name}\". You can go to the survey on <a href=\"{survey_link}\">that link</a>, "
|
||||||
|
"using the token code you received by mail.")
|
||||||
|
content = format_lazy(text, name=survey.name, survey_link=f"{settings.LIMESURVEY_URL}/index.php/{survey.survey_id}")
|
||||||
|
informations.append({
|
||||||
|
'title': _("Required answer to survey"),
|
||||||
|
'type': "warning",
|
||||||
|
'priority': 12,
|
||||||
|
'content': content
|
||||||
|
})
|
||||||
|
|
||||||
if self.tournament:
|
if self.tournament:
|
||||||
informations.extend(self.informations_for_tournament(self.tournament))
|
informations.extend(self.informations_for_tournament(self.tournament))
|
||||||
if self.final:
|
if self.final:
|
||||||
|
@ -234,7 +234,7 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
|
|||||||
mail_plain = render_to_string("participation/mails/request_validation.txt", mail_context)
|
mail_plain = render_to_string("participation/mails/request_validation.txt", mail_context)
|
||||||
mail_html = render_to_string("participation/mails/request_validation.html", mail_context)
|
mail_html = render_to_string("participation/mails/request_validation.html", mail_context)
|
||||||
send_mail(f"[{settings.APP_NAME}] {_('Team validation')}", mail_plain, settings.DEFAULT_FROM_EMAIL,
|
send_mail(f"[{settings.APP_NAME}] {_('Team validation')}", mail_plain, settings.DEFAULT_FROM_EMAIL,
|
||||||
[self.object.participation.tournament.organizers_email], html_message=mail_html)
|
[self.object.participation.tournament.organizers_email], html_message=mail_html)
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
@ -270,7 +270,7 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
|
|||||||
mail_plain = render_to_string("participation/mails/team_validated.txt", mail_context_plain)
|
mail_plain = render_to_string("participation/mails/team_validated.txt", mail_context_plain)
|
||||||
mail_html = render_to_string("participation/mails/team_validated.html", mail_context_html)
|
mail_html = render_to_string("participation/mails/team_validated.html", mail_context_html)
|
||||||
registration.user.email_user(f"[{settings.APP_NAME}] {_('Team validated')}", mail_plain,
|
registration.user.email_user(f"[{settings.APP_NAME}] {_('Team validated')}", mail_plain,
|
||||||
html_message=mail_html)
|
html_message=mail_html)
|
||||||
elif "invalidate" in self.request.POST:
|
elif "invalidate" in self.request.POST:
|
||||||
self.object.participation.valid = None
|
self.object.participation.valid = None
|
||||||
self.object.participation.save()
|
self.object.participation.save()
|
||||||
@ -280,7 +280,7 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
|
|||||||
mail_plain = render_to_string("participation/mails/team_not_validated.txt", mail_context_plain)
|
mail_plain = render_to_string("participation/mails/team_not_validated.txt", mail_context_plain)
|
||||||
mail_html = render_to_string("participation/mails/team_not_validated.html", mail_context_html)
|
mail_html = render_to_string("participation/mails/team_not_validated.html", mail_context_html)
|
||||||
send_mail(f"[{settings.APP_NAME}] {_('Team not validated')}", mail_plain,
|
send_mail(f"[{settings.APP_NAME}] {_('Team not validated')}", mail_plain,
|
||||||
None, [self.object.email], html_message=mail_html)
|
None, [self.object.email], html_message=mail_html)
|
||||||
else:
|
else:
|
||||||
form.add_error(None, _("You must specify if you validate the registration or not."))
|
form.add_error(None, _("You must specify if you validate the registration or not."))
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.db.models.signals import post_save, pre_save
|
from django.db.models.signals import post_save, pre_save
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class RegistrationConfig(AppConfig):
|
class RegistrationConfig(AppConfig):
|
||||||
@ -10,6 +11,7 @@ class RegistrationConfig(AppConfig):
|
|||||||
Registration app contains the detail about users only.
|
Registration app contains the detail about users only.
|
||||||
"""
|
"""
|
||||||
name = 'registration'
|
name = 'registration'
|
||||||
|
verbose_name = _("registrations")
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from registration import signals
|
from registration import signals
|
||||||
|
@ -8,6 +8,7 @@ from django.contrib.sites.models import Site
|
|||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils import timezone, translation
|
from django.utils import timezone, translation
|
||||||
@ -260,6 +261,8 @@ class ParticipantRegistration(Registration):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def registration_informations(self):
|
def registration_informations(self):
|
||||||
|
from survey.models import Survey
|
||||||
|
|
||||||
informations = []
|
informations = []
|
||||||
if not self.team:
|
if not self.team:
|
||||||
text = _("You are not in a team. You can <a href=\"{create_url}\">create one</a> "
|
text = _("You are not in a team. You can <a href=\"{create_url}\">create one</a> "
|
||||||
@ -300,6 +303,20 @@ class ParticipantRegistration(Registration):
|
|||||||
'content': content,
|
'content': content,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if self.team.participation.valid:
|
||||||
|
for survey in Survey.objects.filter(Q(tournament__isnull=True) | Q(tournament=self.team.participation.tournament),
|
||||||
|
Q(invite_team=False), Q(invite_coaches=True) | Q(invite_coaches=self.is_coach),
|
||||||
|
~Q(completed_registrations=self)):
|
||||||
|
text = _("Please answer to the survey \"{name}\". You can go to the survey on <a href=\"{survey_link}\">that link</a>, "
|
||||||
|
"using the token code you received by mail.")
|
||||||
|
content = format_lazy(text, name=survey.name, survey_link=f"{settings.LIMESURVEY_URL}/index.php/{survey.survey_id}")
|
||||||
|
informations.append({
|
||||||
|
'title': _("Required answer to survey"),
|
||||||
|
'type': "warning",
|
||||||
|
'priority': 12,
|
||||||
|
'content': content
|
||||||
|
})
|
||||||
|
|
||||||
informations.extend(self.team.important_informations())
|
informations.extend(self.team.important_informations())
|
||||||
|
|
||||||
return informations
|
return informations
|
||||||
@ -315,19 +332,19 @@ class ParticipantRegistration(Registration):
|
|||||||
tournament = Tournament.final_tournament()
|
tournament = Tournament.final_tournament()
|
||||||
payment = self.payments.filter(final=True).first() if self.is_student else None
|
payment = self.payments.filter(final=True).first() if self.is_student else None
|
||||||
message = loader.render_to_string('registration/mails/final_selection.txt',
|
message = loader.render_to_string('registration/mails/final_selection.txt',
|
||||||
{
|
{
|
||||||
'user': self.user,
|
'user': self.user,
|
||||||
'domain': site.domain,
|
'domain': site.domain,
|
||||||
'tournament': tournament,
|
'tournament': tournament,
|
||||||
'payment': payment,
|
'payment': payment,
|
||||||
})
|
})
|
||||||
html = loader.render_to_string('registration/mails/final_selection.html',
|
html = loader.render_to_string('registration/mails/final_selection.html',
|
||||||
{
|
{
|
||||||
'user': self.user,
|
'user': self.user,
|
||||||
'domain': site.domain,
|
'domain': site.domain,
|
||||||
'tournament': tournament,
|
'tournament': tournament,
|
||||||
'payment': payment,
|
'payment': payment,
|
||||||
})
|
})
|
||||||
self.user.email_user(subject, message, html_message=html)
|
self.user.email_user(subject, message, html_message=html)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -807,9 +824,9 @@ class Payment(models.Model):
|
|||||||
site = Site.objects.first()
|
site = Site.objects.first()
|
||||||
for registration in self.registrations.all():
|
for registration in self.registrations.all():
|
||||||
message = loader.render_to_string('registration/mails/payment_reminder.txt',
|
message = loader.render_to_string('registration/mails/payment_reminder.txt',
|
||||||
dict(registration=registration, payment=self, domain=site.domain))
|
dict(registration=registration, payment=self, domain=site.domain))
|
||||||
html = loader.render_to_string('registration/mails/payment_reminder.html',
|
html = loader.render_to_string('registration/mails/payment_reminder.html',
|
||||||
dict(registration=registration, payment=self, domain=site.domain))
|
dict(registration=registration, payment=self, domain=site.domain))
|
||||||
registration.user.email_user(subject, message, html_message=html)
|
registration.user.email_user(subject, message, html_message=html)
|
||||||
|
|
||||||
def send_helloasso_payment_confirmation_mail(self):
|
def send_helloasso_payment_confirmation_mail(self):
|
||||||
@ -818,18 +835,18 @@ class Payment(models.Model):
|
|||||||
site = Site.objects.first()
|
site = Site.objects.first()
|
||||||
for registration in self.registrations.all():
|
for registration in self.registrations.all():
|
||||||
message = loader.render_to_string('registration/mails/payment_confirmation.txt',
|
message = loader.render_to_string('registration/mails/payment_confirmation.txt',
|
||||||
dict(registration=registration, payment=self, domain=site.domain))
|
dict(registration=registration, payment=self, domain=site.domain))
|
||||||
html = loader.render_to_string('registration/mails/payment_confirmation.html',
|
html = loader.render_to_string('registration/mails/payment_confirmation.html',
|
||||||
dict(registration=registration, payment=self, domain=site.domain))
|
dict(registration=registration, payment=self, domain=site.domain))
|
||||||
registration.user.email_user(subject, message, html_message=html)
|
registration.user.email_user(subject, message, html_message=html)
|
||||||
|
|
||||||
payer = self.get_checkout_intent()['order']['payer']
|
payer = self.get_checkout_intent()['order']['payer']
|
||||||
payer_name = f"{payer['firstName']} {payer['lastName']}"
|
payer_name = f"{payer['firstName']} {payer['lastName']}"
|
||||||
if not self.registrations.filter(user__email=payer['email']).exists():
|
if not self.registrations.filter(user__email=payer['email']).exists():
|
||||||
message = loader.render_to_string('registration/mails/payment_confirmation.txt',
|
message = loader.render_to_string('registration/mails/payment_confirmation.txt',
|
||||||
dict(registration=payer_name, payment=self, domain=site.domain))
|
dict(registration=payer_name, payment=self, domain=site.domain))
|
||||||
html = loader.render_to_string('registration/mails/payment_confirmation.html',
|
html = loader.render_to_string('registration/mails/payment_confirmation.html',
|
||||||
dict(registration=payer_name, payment=self, domain=site.domain))
|
dict(registration=payer_name, payment=self, domain=site.domain))
|
||||||
send_mail(subject, message, None, [payer['email']], html_message=html)
|
send_mail(subject, message, None, [payer['email']], html_message=html)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
|
@ -145,9 +145,9 @@ class AddOrganizerView(VolunteerMixin, CreateView):
|
|||||||
password=password,
|
password=password,
|
||||||
domain=site.domain))
|
domain=site.domain))
|
||||||
html = render_to_string('registration/mails/add_organizer.html', dict(user=registration.user,
|
html = render_to_string('registration/mails/add_organizer.html', dict(user=registration.user,
|
||||||
inviter=self.request.user,
|
inviter=self.request.user,
|
||||||
password=password,
|
password=password,
|
||||||
domain=site.domain))
|
domain=site.domain))
|
||||||
registration.user.email_user(subject, message, html_message=html)
|
registration.user.email_user(subject, message, html_message=html)
|
||||||
|
|
||||||
if registration.is_admin:
|
if registration.is_admin:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
channels[daphne]~=4.1.0
|
channels[daphne]~=4.1.0
|
||||||
channels-redis~=4.2.0
|
channels-redis~=4.2.0
|
||||||
|
citric~=1.4.0
|
||||||
crispy-bootstrap5~=2024.10
|
crispy-bootstrap5~=2024.10
|
||||||
Django>=5.1.2,<6.0
|
Django>=5.1.2,<6.0
|
||||||
django-crispy-forms~=2.3
|
django-crispy-forms~=2.3
|
||||||
|
0
survey/__init__.py
Normal file
0
survey/__init__.py
Normal file
13
survey/admin.py
Normal file
13
survey/admin.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Copyright (C) 2025 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import Survey
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Survey)
|
||||||
|
class SurveyAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('survey_id', 'name', 'invite_team', 'invite_coaches', 'tournament',)
|
||||||
|
list_filter = ('invite_team', 'invite_coaches', 'tournament',)
|
||||||
|
search_fields = ('name',)
|
11
survey/apps.py
Normal file
11
survey/apps.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Copyright (C) 2025 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "survey"
|
||||||
|
verbose_name = _("surveys")
|
28
survey/forms.py
Normal file
28
survey/forms.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from django import forms
|
||||||
|
|
||||||
|
from .models import Survey
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyForm(forms.ModelForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
if 'survey_id' in self.initial:
|
||||||
|
self.fields['survey_id'].disabled = True
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Survey
|
||||||
|
exclude = ('completed_registrations', 'completed_teams',)
|
||||||
|
widgets = {
|
||||||
|
'completed_registrations': forms.SelectMultiple(attrs={
|
||||||
|
'class': 'selectpicker',
|
||||||
|
'data-live-search': 'true',
|
||||||
|
'data-live-search-normalize': 'true',
|
||||||
|
'data-width': 'fit',
|
||||||
|
}),
|
||||||
|
'completed_teams': forms.SelectMultiple(attrs={
|
||||||
|
'class': 'selectpicker',
|
||||||
|
'data-live-search': 'true',
|
||||||
|
'data-live-search-normalize': 'true',
|
||||||
|
'data-width': 'fit',
|
||||||
|
}),
|
||||||
|
}
|
13
survey/management/commands/fetch_survey_completion_data.py
Normal file
13
survey/management/commands/fetch_survey_completion_data.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Copyright (C) 2025 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
|
||||||
|
from ...models import Survey
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
for survey in Survey.objects.all():
|
||||||
|
survey.fetch_completion_data()
|
83
survey/migrations/0001_initial.py
Normal file
83
survey/migrations/0001_initial.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# Generated by Django 5.1.5 on 2025-03-19 21:12
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
(
|
||||||
|
"participation",
|
||||||
|
"0023_tournament_unified_registration",
|
||||||
|
),
|
||||||
|
("registration", "0014_participantregistration_country"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Survey",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"survey_id",
|
||||||
|
models.IntegerField(
|
||||||
|
help_text="The numeric identifier of the Limesurvey.",
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="survey identifier",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=255, verbose_name="display name")),
|
||||||
|
(
|
||||||
|
"invite_team",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="When this field is checked, teams will get only one survey invitation instead of one per person.",
|
||||||
|
verbose_name="invite whole team",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"invite_coaches",
|
||||||
|
models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
help_text="When this field is checked, coaches will also be invited in the survey. No effect when the whole team is invited.",
|
||||||
|
verbose_name="invite coaches",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"completed_registrations",
|
||||||
|
models.ManyToManyField(
|
||||||
|
related_name="completed_surveys",
|
||||||
|
to="registration.participantregistration",
|
||||||
|
verbose_name="participants that completed the survey",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"completed_teams",
|
||||||
|
models.ManyToManyField(
|
||||||
|
related_name="completed_surveys",
|
||||||
|
to="participation.team",
|
||||||
|
verbose_name="teams that completed the survey",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tournament",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
help_text="When this field is filled, the survey participants will be restricted to this tournament members.",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="participation.tournament",
|
||||||
|
verbose_name="tournament restriction",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "survey",
|
||||||
|
"verbose_name_plural": "surveys",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,53 @@
|
|||||||
|
# Generated by Django 5.1.5 on 2025-03-19 22:51
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
(
|
||||||
|
"participation",
|
||||||
|
"0023_tournament_unified_registration",
|
||||||
|
),
|
||||||
|
("registration", "0014_participantregistration_country"),
|
||||||
|
("survey", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="survey",
|
||||||
|
name="completed_registrations",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="completed_surveys",
|
||||||
|
to="registration.participantregistration",
|
||||||
|
verbose_name="participants that completed the survey",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="survey",
|
||||||
|
name="completed_teams",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="completed_surveys",
|
||||||
|
to="participation.team",
|
||||||
|
verbose_name="teams that completed the survey",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="survey",
|
||||||
|
name="tournament",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
help_text="When this field is filled, the survey participants will be restricted to this tournament members.",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="surveys",
|
||||||
|
to="participation.tournament",
|
||||||
|
verbose_name="tournament restriction",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
0
survey/migrations/__init__.py
Normal file
0
survey/migrations/__init__.py
Normal file
137
survey/models.py
Normal file
137
survey/models.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
# Copyright (C) 2025 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from citric import Client
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from participation.models import Team, Tournament
|
||||||
|
from registration.models import ParticipantRegistration, StudentRegistration
|
||||||
|
|
||||||
|
|
||||||
|
class Survey(models.Model):
|
||||||
|
"""
|
||||||
|
Ce modèle représente un sondage LimeSurvey afin de faciliter l'import des
|
||||||
|
participant⋅es au sondage et d'effectuer le suivi.
|
||||||
|
"""
|
||||||
|
survey_id = models.IntegerField(
|
||||||
|
primary_key=True,
|
||||||
|
verbose_name=_("survey identifier"),
|
||||||
|
help_text=_("The numeric identifier of the Limesurvey."),
|
||||||
|
)
|
||||||
|
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("display name"),
|
||||||
|
)
|
||||||
|
|
||||||
|
invite_team = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("invite whole team"),
|
||||||
|
help_text=_("When this field is checked, teams will get only one survey invitation instead of one per person."),
|
||||||
|
)
|
||||||
|
|
||||||
|
invite_coaches = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
verbose_name=_("invite coaches"),
|
||||||
|
help_text=_("When this field is checked, coaches will also be invited in the survey. No effect when the whole team is invited."),
|
||||||
|
)
|
||||||
|
|
||||||
|
tournament = models.ForeignKey(
|
||||||
|
Tournament,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name="surveys",
|
||||||
|
verbose_name=_("tournament restriction"),
|
||||||
|
help_text=_("When this field is filled, the survey participants will be restricted to this tournament members."),
|
||||||
|
)
|
||||||
|
|
||||||
|
completed_registrations = models.ManyToManyField(
|
||||||
|
ParticipantRegistration,
|
||||||
|
blank=True,
|
||||||
|
related_name="completed_surveys",
|
||||||
|
verbose_name=_("participants that completed the survey"),
|
||||||
|
)
|
||||||
|
|
||||||
|
completed_teams = models.ManyToManyField(
|
||||||
|
Team,
|
||||||
|
blank=True,
|
||||||
|
related_name="completed_surveys",
|
||||||
|
verbose_name=_("teams that completed the survey"),
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def participants(self):
|
||||||
|
if self.invite_team:
|
||||||
|
teams = Team.objects.filter(participation__valid=True)
|
||||||
|
if self.tournament:
|
||||||
|
teams = teams.filter(participation__tournament=self.tournament)
|
||||||
|
return teams.all()
|
||||||
|
else:
|
||||||
|
if self.invite_coaches:
|
||||||
|
registrations = ParticipantRegistration.objects.filter(team__participation__valid=True)
|
||||||
|
else:
|
||||||
|
registrations = StudentRegistration.objects.filter(team__participation__valid=True)
|
||||||
|
if self.tournament:
|
||||||
|
registrations = registrations.filter(team__participation__tournament=self.tournament)
|
||||||
|
return registrations.all()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def completed(self):
|
||||||
|
if self.invite_team:
|
||||||
|
return self.completed_teams
|
||||||
|
else:
|
||||||
|
return self.completed_registrations
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse_lazy("survey:survey_detail", args=(self.survey_id,))
|
||||||
|
|
||||||
|
def generate_participants_data(self):
|
||||||
|
participants_data = []
|
||||||
|
if self.invite_team:
|
||||||
|
for team in self.participants:
|
||||||
|
participant_data = {"firstname": team.name, "lastname": f"(équipe {team.trigram})", "email": team.email}
|
||||||
|
participants_data.append(participant_data)
|
||||||
|
else:
|
||||||
|
for reg in self.participants:
|
||||||
|
participant_data = {"firstname": reg.user.first_name, "lastname": reg.user.last_name, "email": reg.user.email}
|
||||||
|
participants_data.append(participant_data)
|
||||||
|
return participants_data
|
||||||
|
|
||||||
|
def invite_all(self):
|
||||||
|
participants_data = self.generate_participants_data()
|
||||||
|
with Client(f"{settings.LIMESURVEY_URL}/index.php/admin/remotecontrol", settings.LIMESURVEY_USER, settings.LIMESURVEY_PASSWORD) as client:
|
||||||
|
try:
|
||||||
|
current_participants = client.list_participants(self.survey_id, limit=10000)
|
||||||
|
except:
|
||||||
|
current_participants = []
|
||||||
|
current_participants_email = set(participant['participant_info']['email'] for participant in current_participants)
|
||||||
|
participants_data = [participant_data for participant_data in participants_data if participant_data['email'] not in current_participants_email]
|
||||||
|
try:
|
||||||
|
client.activate_tokens(self.survey_id)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
new_participants = client.add_participants(self.survey_id, participant_data=participants_data)
|
||||||
|
if new_participants:
|
||||||
|
client.invite_participants(self.survey_id, token_ids=[participant['tid'] for participant in new_participants])
|
||||||
|
return new_participants
|
||||||
|
|
||||||
|
def fetch_completion_data(self):
|
||||||
|
with Client(f"{settings.LIMESURVEY_URL}/index.php/admin/remotecontrol", settings.LIMESURVEY_USER, settings.LIMESURVEY_PASSWORD) as client:
|
||||||
|
participants = client.list_participants(self.survey_id, limit=10000, attributes=['completed'])
|
||||||
|
if self.invite_team:
|
||||||
|
team_names = [participant['participant_info']['firstname'] for participant in participants if participant['completed'] != 'N']
|
||||||
|
self.completed_teams.set(list(Team.objects.filter(name__in=team_names).values_list('id', flat=True)))
|
||||||
|
else:
|
||||||
|
mails = [participant['participant_info']['email'] for participant in participants if participant['completed'] != 'N']
|
||||||
|
self.completed_registrations.set(list(ParticipantRegistration.objects.filter(user__email__in=mails).values_list('id', flat=True)))
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("survey")
|
||||||
|
verbose_name_plural = _("surveys")
|
31
survey/tables.py
Normal file
31
survey/tables.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Copyright (C) 2025 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
import django_tables2 as tables
|
||||||
|
|
||||||
|
from .models import Survey
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyTable(tables.Table):
|
||||||
|
survey_id = tables.LinkColumn(
|
||||||
|
'survey:survey_detail',
|
||||||
|
args=[tables.A('survey_id')],
|
||||||
|
verbose_name=lambda: _("survey identifier").capitalize(),
|
||||||
|
)
|
||||||
|
|
||||||
|
nb_completed = tables.Column(
|
||||||
|
verbose_name=_("completed").capitalize,
|
||||||
|
accessor='survey_id'
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_nb_completed(self, record):
|
||||||
|
return f"{record.completed.count()}/{record.participants.count()}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
attrs = {
|
||||||
|
'class': 'table table-condensed table-striped',
|
||||||
|
}
|
||||||
|
model = Survey
|
||||||
|
fields = ('survey_id', 'name', 'invite_team', 'invite_coaches', 'tournament', 'nb_completed',)
|
||||||
|
order_by = ('survey_id',)
|
84
survey/templates/survey/survey_detail.html
Normal file
84
survey/templates/survey/survey_detail.html
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% load crispy_forms_filters %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="card bg-body shadow">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h4>
|
||||||
|
{% trans "survey"|capfirst %} {{ survey.survey_id }}
|
||||||
|
<a href="{{ TFJM.LIMESURVEY_URL }}/index.php/{{ survey.survey_id }}" target="_blank"><i class="fas fa-arrow-up-right-from-square"></i></a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-sm-6 text-sm-end">{% trans "Name:" %}</dt>
|
||||||
|
<dd class="col-sm-6">{{ survey.name }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-6 text-sm-end">{% trans "One answer per team:" %}</dt>
|
||||||
|
<dd class="col-sm-6">{{ survey.invite_team|yesno }}</dd>
|
||||||
|
|
||||||
|
{% if not survey.invite_team %}
|
||||||
|
<dt class="col-sm-6 text-sm-end">{% trans "Coaches can answer the survey:" %}</dt>
|
||||||
|
<dd class="col-sm-6">{{ survey.invite_coaches|yesno }}</dd>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if survey.tournament %}
|
||||||
|
<dt class="col-sm-6 text-sm-end">{% trans "Tournament restriction:" %}</dt>
|
||||||
|
<dd class="col-sm-6">{{ survey.tournament }}</dd>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<dt class="col-sm-6 text-sm-end">{% trans "Completion rate:" %}</dt>
|
||||||
|
<dd class="col-sm-6">
|
||||||
|
{{ survey.completed.count }}/{{ survey.participants.count }}
|
||||||
|
<a href="{% url "survey:survey_refresh_completed" pk=survey.pk %}"><i class="fas fa-arrow-rotate-right" alt="refresh"></i></a>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-center">
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#updateSurveyModal">{% trans "Update" %}</button>
|
||||||
|
<a class="btn btn-secondary" href="{% url "survey:survey_invite" pk=survey.pk %}">{% trans "Send invites" %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<table class="table table-condensed table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "participant"|capfirst %}</th>
|
||||||
|
<th>{% trans "completed"|capfirst %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for participant in survey.participants %}
|
||||||
|
<tr class="{% if participant in survey.completed.all %}table-success{% else %}table-danger{% endif %}">
|
||||||
|
{% if survey.invite_team %}
|
||||||
|
<td>{% trans "Team" %} {{ participant.name }} ({{ participant.trigram }})</td>
|
||||||
|
{% else %}
|
||||||
|
<td>{{ participant.user.first_name }} {{ participant.user.last_name }} ({% trans "team" %} {{ participant.team.trigram }})</td>
|
||||||
|
{% endif %}
|
||||||
|
{% if participant in survey.completed.all %}
|
||||||
|
<td>{% trans "Yes" %}</td>
|
||||||
|
{% else %}
|
||||||
|
<td>{% trans "No" %}</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% trans "Update survey" as modal_title %}
|
||||||
|
{% trans "Update" as modal_button %}
|
||||||
|
{% url "survey:survey_update" pk=survey.pk as modal_action %}
|
||||||
|
{% include "base_modal.html" with modal_id="updateSurvey" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initModal("updateSurvey", "{% url "survey:survey_update" pk=survey.pk %}")
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
17
survey/templates/survey/survey_form.html
Normal file
17
survey/templates/survey/survey_form.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% extends request.content_only|yesno:"empty.html,base.html" %}
|
||||||
|
|
||||||
|
{% load crispy_forms_filters i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
<div id="form-content">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
|
</div>
|
||||||
|
{% if object.pk %}
|
||||||
|
<button class="btn btn-primary" type="submit">{% trans "Update" %}</button>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-success" type="submit">{% trans "Create" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
14
survey/templates/survey/survey_list.html
Normal file
14
survey/templates/survey/survey_list.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load django_tables2 i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-grid">
|
||||||
|
<a href="{% url "survey:survey_create" %}" class="btn gap-0 btn-success">
|
||||||
|
<i class="fas fa-square-poll-horizontal"></i> {% trans "Add survey" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
{% render_table table %}
|
||||||
|
{% endblock %}
|
3
survey/tests.py
Normal file
3
survey/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
18
survey/urls.py
Normal file
18
survey/urls.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Copyright (C) 2025 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from .views import SurveyCreateView, SurveyDetailView, SurveyInviteView, \
|
||||||
|
SurveyListView, SurveyRefreshCompletedView, SurveyUpdateView
|
||||||
|
|
||||||
|
app_name = "survey"
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", SurveyListView.as_view(), name="survey_list"),
|
||||||
|
path("create/", SurveyCreateView.as_view(), name="survey_create"),
|
||||||
|
path("<int:pk>/", SurveyDetailView.as_view(), name="survey_detail"),
|
||||||
|
path("<int:pk>/invite/", SurveyInviteView.as_view(), name="survey_invite"),
|
||||||
|
path("<int:pk>/refresh/", SurveyRefreshCompletedView.as_view(), name="survey_refresh_completed"),
|
||||||
|
path("<int:pk>/update/", SurveyUpdateView.as_view(), name="survey_update"),
|
||||||
|
]
|
56
survey/views.py
Normal file
56
survey/views.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Copyright (C) 2025 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views.generic import CreateView, DetailView, UpdateView
|
||||||
|
from django_tables2 import SingleTableView
|
||||||
|
|
||||||
|
from tfjm.views import AdminMixin
|
||||||
|
from .forms import SurveyForm
|
||||||
|
from .models import Survey
|
||||||
|
from .tables import SurveyTable
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyListView(AdminMixin, SingleTableView):
|
||||||
|
model = Survey
|
||||||
|
table_class = SurveyTable
|
||||||
|
template_name = "survey/survey_list.html"
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyCreateView(AdminMixin, CreateView):
|
||||||
|
model = Survey
|
||||||
|
form_class = SurveyForm
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyDetailView(AdminMixin, DetailView):
|
||||||
|
model = Survey
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyInviteView(AdminMixin, DetailView):
|
||||||
|
model = Survey
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
survey = self.get_object()
|
||||||
|
new_participants = survey.invite_all()
|
||||||
|
if new_participants:
|
||||||
|
messages.success(request, _("Invites sent!"))
|
||||||
|
else:
|
||||||
|
messages.warning(request, _("All invites were already sent."))
|
||||||
|
return redirect("survey:survey_detail", survey.pk)
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyRefreshCompletedView(AdminMixin, DetailView):
|
||||||
|
model = Survey
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
survey = self.get_object()
|
||||||
|
survey.fetch_completion_data()
|
||||||
|
messages.success(request, _("Completion data refreshed!"))
|
||||||
|
return redirect("survey:survey_detail", survey.pk)
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyUpdateView(AdminMixin, UpdateView):
|
||||||
|
model = Survey
|
||||||
|
form_class = SurveyForm
|
@ -19,5 +19,8 @@
|
|||||||
# Update Google Drive notifications daily
|
# Update Google Drive notifications daily
|
||||||
0 0 * * * cd /code && python manage.py renew_gdrive_notifications -v 0
|
0 0 * * * cd /code && python manage.py renew_gdrive_notifications -v 0
|
||||||
|
|
||||||
|
# Fetch LimeSurvey completion data
|
||||||
|
*/15 * * 03-06 * cd /code && python manage.py fetch_survey_completion_data -v 0
|
||||||
|
|
||||||
# Clean temporary files
|
# Clean temporary files
|
||||||
30 * * * * rm -rf /tmp/*
|
30 * * * * rm -rf /tmp/*
|
||||||
|
@ -13,6 +13,7 @@ def tfjm_context(request):
|
|||||||
'HAS_OBSERVER': settings.HAS_OBSERVER,
|
'HAS_OBSERVER': settings.HAS_OBSERVER,
|
||||||
'HAS_FINAL': settings.HAS_FINAL,
|
'HAS_FINAL': settings.HAS_FINAL,
|
||||||
'HOME_PAGE_LINK': settings.HOME_PAGE_LINK,
|
'HOME_PAGE_LINK': settings.HOME_PAGE_LINK,
|
||||||
|
'LIMESURVEY_URL': settings.LIMESURVEY_URL,
|
||||||
'LOGO_PATH': "tfjm/img/" + settings.LOGO_FILE,
|
'LOGO_PATH': "tfjm/img/" + settings.LOGO_FILE,
|
||||||
'NB_ROUNDS': settings.NB_ROUNDS,
|
'NB_ROUNDS': settings.NB_ROUNDS,
|
||||||
'ML_MANAGEMENT': settings.ML_MANAGEMENT,
|
'ML_MANAGEMENT': settings.ML_MANAGEMENT,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Copyright (C) 2020 by Animath
|
# Copyright (C) 2020 by Animath
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import os
|
from django.conf import settings
|
||||||
|
|
||||||
_client = None
|
_client = None
|
||||||
|
|
||||||
@ -9,10 +9,10 @@ _client = None
|
|||||||
def get_sympa_client():
|
def get_sympa_client():
|
||||||
global _client
|
global _client
|
||||||
if _client is None:
|
if _client is None:
|
||||||
if os.getenv("SYMPA_PASSWORD", None): # pragma: no cover
|
if settings.SYMPA_PASSWORD is not None: # pragma: no cover
|
||||||
from sympasoap import Client
|
from sympasoap import Client
|
||||||
_client = Client("https://" + os.getenv("SYMPA_URL"))
|
_client = Client("https://" + settings.SYMPA_URL)
|
||||||
_client.login(os.getenv("SYMPA_EMAIL"), os.getenv("SYMPA_PASSWORD"))
|
_client.login(settings.SYMPA_EMAIL, settings.SYMPA_PASSWORD)
|
||||||
else:
|
else:
|
||||||
_client = FakeSympaSoapClient()
|
_client = FakeSympaSoapClient()
|
||||||
return _client
|
return _client
|
||||||
|
@ -74,6 +74,7 @@ INSTALLED_APPS = [
|
|||||||
'draw',
|
'draw',
|
||||||
'registration',
|
'registration',
|
||||||
'participation',
|
'participation',
|
||||||
|
'survey',
|
||||||
]
|
]
|
||||||
|
|
||||||
if "test" not in sys.argv: # pragma: no cover
|
if "test" not in sys.argv: # pragma: no cover
|
||||||
@ -300,6 +301,12 @@ CHANNEL_LAYERS = {
|
|||||||
PHONENUMBER_DB_FORMAT = 'NATIONAL'
|
PHONENUMBER_DB_FORMAT = 'NATIONAL'
|
||||||
PHONENUMBER_DEFAULT_REGION = 'FR'
|
PHONENUMBER_DEFAULT_REGION = 'FR'
|
||||||
|
|
||||||
|
# Sympa configuration
|
||||||
|
SYMPA_HOST = os.getenv("SYMPA_HOST", "localhost")
|
||||||
|
SYMPA_URL = os.getenv("SYMPA_URL", "localhost")
|
||||||
|
SYMPA_EMAIL = os.getenv("SYMPA_EMAIL", "contact@localhost")
|
||||||
|
SYMPA_PASSWORD = os.getenv("SYMPA_PASSWORD", None)
|
||||||
|
|
||||||
# Hello Asso API creds
|
# Hello Asso API creds
|
||||||
HELLOASSO_CLIENT_ID = os.getenv('HELLOASSO_CLIENT_ID', 'CHANGE_ME_IN_ENV_SETTINGS')
|
HELLOASSO_CLIENT_ID = os.getenv('HELLOASSO_CLIENT_ID', 'CHANGE_ME_IN_ENV_SETTINGS')
|
||||||
HELLOASSO_CLIENT_SECRET = os.getenv('HELLOASSO_CLIENT_SECRET', 'CHANGE_ME_IN_ENV_SETTINGS')
|
HELLOASSO_CLIENT_SECRET = os.getenv('HELLOASSO_CLIENT_SECRET', 'CHANGE_ME_IN_ENV_SETTINGS')
|
||||||
@ -322,6 +329,10 @@ GOOGLE_SERVICE_CLIENT = {
|
|||||||
# The ID of the Google Drive folder where to store the notation sheets
|
# The ID of the Google Drive folder where to store the notation sheets
|
||||||
NOTES_DRIVE_FOLDER_ID = os.getenv("NOTES_DRIVE_FOLDER_ID", "CHANGE_ME_IN_ENV_SETTINGS")
|
NOTES_DRIVE_FOLDER_ID = os.getenv("NOTES_DRIVE_FOLDER_ID", "CHANGE_ME_IN_ENV_SETTINGS")
|
||||||
|
|
||||||
|
LIMESURVEY_URL = os.getenv("LIMESURVEY_URL", "https://survey.example.com")
|
||||||
|
LIMESURVEY_USER = os.getenv("LIMESURVEY_USER", "CHANGE_ME_IN_ENV_SETTINGS")
|
||||||
|
LIMESURVEY_PASSWORD = os.getenv("LIMESURVEY_PASSWORD", "CHANGE_ME_IN_ENV_SETTINGS")
|
||||||
|
|
||||||
# Custom parameters
|
# Custom parameters
|
||||||
FORBIDDEN_TRIGRAMS = [
|
FORBIDDEN_TRIGRAMS = [
|
||||||
"BIT",
|
"BIT",
|
||||||
|
@ -74,6 +74,9 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.registration.is_admin %}
|
{% if user.registration.is_admin %}
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a class="nav-link" href="{% url "survey:survey_list" %}"><i class="fas fa-square-poll-horizontal"></i> {% trans "surveys"|capfirst %}</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
<a class="nav-link" href="{% url "admin:index" %}"><i class="fas fa-cog"></i> {% trans "Administration" %}</a>
|
<a class="nav-link" href="{% url "admin:index" %}"><i class="fas fa-cog"></i> {% trans "Administration" %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -44,6 +44,7 @@ urlpatterns = [
|
|||||||
path('draw/', include('draw.urls')),
|
path('draw/', include('draw.urls')),
|
||||||
path('participation/', include('participation.urls')),
|
path('participation/', include('participation.urls')),
|
||||||
path('registration/', include('registration.urls')),
|
path('registration/', include('registration.urls')),
|
||||||
|
path('survey/', include('survey.urls')),
|
||||||
|
|
||||||
path('media/authorization/photo/<str:filename>/', PhotoAuthorizationView.as_view(),
|
path('media/authorization/photo/<str:filename>/', PhotoAuthorizationView.as_view(),
|
||||||
name='photo_authorization'),
|
name='photo_authorization'),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user