Forked from
aeltheos / site-kwei
170 commits behind the upstream repository.
-
Dorian Lesbre authored5fb78447
views.py 15.53 KiB
from datetime import timedelta
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.sitemaps import Sitemap
from django.core.mail import mail_admins, send_mass_mail
from django.db.models import Count
from django.forms import formset_factory
from django.shortcuts import redirect, render
from django.template.loader import render_to_string
from django.urls import reverse
from django.views.generic import RedirectView, UpdateView, TemplateView, View
from accounts.models import EmailUser
from home.models import ActivityList, InterludesActivity, InterludesParticipant
from home.forms import ActivityForm, BaseActivityFormSet, InscriptionForm
from site_settings.models import SiteSettings
from shared.views import CSVWriteView, SuperuserRequiredMixin
# ==============================
# Site static pages
# ==============================
class HomeView(TemplateView):
"""Vue pour la page d'acceuil"""
template_name = "home.html"
class ActivityView(TemplateView):
"""Vue pour la liste des activités"""
template_name = "activites.html"
def get_context_data(self, **kwargs):
"""ajoute la liste des activités au contexte"""
context = super(ActivityView, self).get_context_data(**kwargs)
settings = SiteSettings.load()
context['activities'] = InterludesActivity.objects.filter(display=True).order_by("title")
context['planning'] = InterludesActivity.objects.filter(on_planning=True).order_by("title")
if settings.date_start is not None:
context['friday'] = settings.date_start.day
context['saturday'] = (settings.date_start + timedelta(days=1)).day
context['sunday'] = (settings.date_start + timedelta(days=2)).day
else:
context['friday'] = 1
context['saturday'] = 2
context['sunday'] = 3
return context
class FAQView(TemplateView):
"""Vue pour la FAQ"""
template_name = "faq.html"
# ==============================
# Registration
# ==============================
class RegisterClosed(TemplateView):
"""Vue pour quand les inscriptions ne sont pas ouvertes"""
template_name = "inscription/closed.html"
class RegisterSignIn(TemplateView):
"""Vue affichée quand les inscriptions sont ouverte mais
l'utilisateur n'est pas connecté"""
template_name = "inscription/signin.html"
class RegisterUpdateView(LoginRequiredMixin, TemplateView):
"""Vue pour s'inscrire et modifier son inscription"""
template_name = "inscription/form.html"
form_class = InscriptionForm
formset_class = formset_factory(form=ActivityForm, extra=3, formset=BaseActivityFormSet)
@staticmethod
def get_activities(participant):
activities = ActivityList.objects.filter(participant=participant).order_by("priority")
return [{"activity": act.activity} for act in activities]
@staticmethod
def set_activities(participant, formset):
# delete old activites
ActivityList.objects.filter(participant=participant).delete()
priority = 0
for form in formset:
data = form.cleaned_data
if data:
activity = data["activity"]
ActivityList(priority=priority, participant=participant, activity=activity).save()
priority += 1
def get(self, request, *args, **kwargs):
participant = request.user.profile
activities = self.get_activities(participant)
form = self.form_class(instance=participant)
formset = self.formset_class(initial=activities)
context = {"form": form, "formset": formset}
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST, instance=request.user.profile)
formset = self.formset_class(request.POST)
if not (form.is_valid() and formset.is_valid()):
context = {"form": form, "formset": formset}
return render(request, self.template_name, context)
form.save()
self.set_activities(request.user.profile, formset)
messages.success(request, "Votre inscription a bien été enregistrée")
return redirect("accounts:profile", permanent=False)
class RegisterView(View):
"""Vue pour l'inscription
repartie sur les vue RegisterClosed, RegisterSignIn et RegisterUpdateView"""
def dispatch(self, request, *args, **kwargs):
settings = SiteSettings.load()
if not settings.inscriptions_open:
return RegisterClosed.as_view()(request)
if not request.user.is_authenticated:
return RegisterSignIn.as_view()(request)
return RegisterUpdateView.as_view()(request)
class UnregisterView(LoginRequiredMixin, RedirectView):
pattern_name = "accounts:profile"
def get_redirect_url(self, *args, **kwargs):
participant = self.request.user.profile
participant.is_registered = False
participant.save()
messages.success(self.request, "Vous avez été désinscrit")
return reverse(self.pattern_name)
# ==============================
# Admin views
# ==============================
class AdminView(SuperuserRequiredMixin, TemplateView):
template_name = "admin.html"
def get_metrics(self):
registered = InterludesParticipant.objects.filter(is_registered = True)
acts = InterludesActivity.objects.all()
wishes = ActivityList.objects.filter(participant__is_registered=True)
class metrics:
participants = registered.count()
ulm = registered.filter(school="U").count()
lyon = registered.filter(school="L").count()
rennes = registered.filter(school="R").count()
saclay = registered.filter(school="P").count()
non_registered = EmailUser.objects.filter(is_active=True).count() - participants
meal1 = registered.filter(meal_friday_evening=True).count()
meal2 = registered.filter(meal_saturday_morning=True).count()
meal3 = registered.filter(meal_saturday_midday=True).count()
meal4 = registered.filter(meal_saturday_evening=True).count()
meal5 = registered.filter(meal_sunday_morning=True).count()
meal6 = registered.filter(meal_sunday_midday=True).count()
meals = meal1 + meal2 + meal3 + meal4 + meal5 + meal6
# mugs = registered.filter(mug=True).count()
sleeps = registered.filter(sleeps=True).count()
activites = acts.count()
act_ins = acts.filter(must_subscribe=True).count()
wish = wishes.count()
granted = wishes.filter(accepted=True).count()
st_present = acts.filter(status=InterludesActivity.Status.PRESENT).count()
st_distant = acts.filter(status=InterludesActivity.Status.DISTANT).count()
st_both = acts.filter(status=InterludesActivity.Status.BOTH).count()
# validation de la repartition des activités
accepted = wishes.filter(accepted=True)
# order_by is useless but required
counts = accepted.values("activity").annotate(total=Count("id")).order_by("activity")
return metrics
def validate_activity_participant_nb(self):
""" Vérifie que le nombre de participant inscrit
à chaque activité est compris entre le min et le max"""
activities = InterludesActivity.objects.filter(subscribing_open=True)
min_fails = ""
max_fails = ""
for act in activities:
total = ActivityList.objects.filter(
activity=act, accepted=True, participant__is_registered=True
).aggregate(total=Count("id"))["total"]
max = act.max_participants
min = act.min_participants
if max != 0 and max < total:
max_fails += "<br> • {}: {} inscrits (maximum {})".format(
act.title, total, max
)
if min > total:
min_fails += "<br> • {}: {} inscrits (minimum {})".format(
act.title, total, min
)
message = ""
if min_fails:
message += '<li class="error">Activités en sous-effectif : {}</li>'.format(min_fails)
else:
message += '<li class="success">Aucune activité en sous-effectif</li>'
if max_fails:
message += '<li class="error">Activités en sur-effectif : {}</li>'.format(max_fails)
else:
message += '<li class="success">Aucune activité en sur-effectif</li>'
return message
def validate_activity_conflicts(self):
"""Vérifie que personne n'est inscrit à des activités simultanées"""
activities = InterludesActivity.objects.filter(subscribing_open=True)
conflicts = []
for i, act1 in enumerate(activities):
for act2 in activities[i+1:]:
if act1.conflicts(act2):
conflicts.append((act1, act2))
base_qs = ActivityList.objects.filter(accepted=True, participant__is_registered=True)
errors = ""
for act1, act2 in conflicts:
participants1 = base_qs.filter(activity=act1).values("participant")
participants2 = base_qs.filter(activity=act1).values("participant")
intersection = participants1 & participants2
if intersection:
print(intersection)
errors += '<br> •  {} participe(nt) à la fois à "{}" et à "{}"'.format(
",".join(str(InterludesParticipant.objects.get(id=x["participant"])) for x in intersection),
act1.title, act2.title
)
if errors:
return '<li class="error">Des participants ont plusieurs activités au même moment :{}</li>'.format(
errors
)
return '<li class="success">Aucun inscrit à plusieurs activités simultanées</li>'
def validate_activity_allocation(self):
settings = SiteSettings.load()
validations = '<ul class="messagelist">'
# validate global settings
if not settings.inscriptions_open:
validations += '<li class="success">Les inscriptions sont fermées</li>'
else:
validations += '<li class="error">Les inscriptions sont encores ouvertes</li>'
if settings.activities_allocated:
validations += '<li class="success">La répartition est marquée comme effectuée</li>'
else:
validations += '<li class="error">La répartition n\'est pas marquée comme effectuée</li>'
# longer validations
validations += self.validate_activity_participant_nb()
validations += self.validate_activity_conflicts()
validations += '</ul>'
user_email_nb = InterludesParticipant.objects.filter(is_registered=True).count()
orga_email_nb = InterludesActivity.objects.filter(
subscribing_open=True, communicate_participants=True
).count()
return {
"validations": validations,
"user_email_nb": user_email_nb,
"orga_email_nb": orga_email_nb,
"validation_errors": '<li class="error">' in validations
}
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context["metrics"] = self.get_metrics()
context.update(self.validate_activity_allocation())
return context
class ExportActivities(SuperuserRequiredMixin, CSVWriteView):
filename = "activites_interludes"
model = InterludesActivity
class ExportParticipants(SuperuserRequiredMixin, CSVWriteView):
filename = "participants_interludes"
headers = [
"id", "mail", "prénom", "nom", "ENS", "Dors sur place", #"Tasse",
"Repas vendredi", "Repas S matin", "Repas S midi", "Repas S soir",
"Repas D matin", "Repas D soir"
]
def get_rows(self):
profiles = InterludesParticipant.objects.filter(is_registered=True).all()
rows = []
for profile in profiles:
rows.append([
profile.user.id,
profile.user.email,
profile.user.first_name,
profile.user.last_name,
profile.school,
profile.sleeps,
# profile.mug,
profile.meal_friday_evening,
profile.meal_saturday_morning,
profile.meal_saturday_midday,
profile.meal_saturday_evening,
profile.meal_sunday_morning,
profile.meal_sunday_midday,
])
return rows
class ExportActivityChoices(SuperuserRequiredMixin, CSVWriteView):
filename = "choix_activite_interludes"
model = ActivityList
headers = ["id_participant", "nom_participant", "priorité", "obtenu", "nom_activité", "id_activité"]
def get_rows(self):
activities = ActivityList.objects.all()
rows = []
for act in activities:
if act.participant.is_registered:
rows.append([
act.participant.id, str(act.participant), act.priority,
act.accepted, str(act.activity), act.activity.id
])
return rows
class SendEmailBase(SuperuserRequiredMixin, RedirectView):
"""Classe abstraite pour l'envoie d'un groupe d'emails"""
pattern_name = "site_admin"
from_address = None
def send_emails(self):
raise NotImplementedError("{}.send_emails isn't implemented".format(self.__class__.__name__))
def get_redirect_url(self, *args, **kwargs):
settings = SiteSettings.load()
if settings.allow_mass_mail:
self.send_emails()
else:
messages.error(self.request, "L'envoi de mail de masse est désactivé dans les réglages")
return reverse(self.pattern_name)
class SendUserEmail(SendEmailBase):
"""Envoie aux utilisateurs leur repartition d'activité"""
def get_emails(self):
"""genere les mails a envoyer"""
participants = InterludesParticipant.objects.filter(is_registered=True)
emails = []
settings = SiteSettings.load()
for participant in participants:
my_activities = ActivityList.objects.filter(participant=participant)
message = render_to_string("email/user.html", {
"user": participant.user,
"settings": settings,
"requested_activities_nb": my_activities.count(),
"activities": my_activities.filter(accepted=True),
})
emails.append((
"Vos activités interludes", # subject
message,
self.from_address, # From:
[participant.user.email], # To:
))
return emails
def send_emails(self):
settings = SiteSettings.load()
if settings.user_notified:
messages.error(self.request, "Les participants ont déjà reçu un mail annonçant la répartition. Modifiez les réglages pour en envoyer un autre")
return
settings.user_notified = True
settings.save()
emails = self.get_emails()
nb_sent = send_mass_mail(emails, fail_silently=False)
mail_admins(
"Emails de répartition envoyés aux participants",
"Les participants ont reçu un mail leur communiquant la répartition des activités\n"
"Nombre total de mail envoyés: {}\n\n"
"-- Site Interludes (mail généré automatiquement".format(nb_sent)
)
messages.success(self.request, "{} mails envoyés aux utilisateurs".format(nb_sent))
class SendOrgaEmail(SendEmailBase):
"""
Envoie aux organisateur leur communiquant les nom/mail des inscrits
à leurs activités
"""
def get_emails(self):
"""genere les mails a envoyer"""
activities = InterludesActivity.objects.filter(
subscribing_open=True, communicate_participants=True
)
emails = []
settings = SiteSettings.load()
for act in activities:
participants = ActivityList.objects.filter(activity=act, accepted=True)
message = render_to_string("email/orga.html", {
"activity": act,
"settings": settings,
"participants": participants,
})
emails.append((
"Vos activités interludes", # subject
message,
self.from_address, # From:
[act.host_email] # To:
))
return emails
def send_emails(self):
settings = SiteSettings.load()
if settings.orga_notified:
messages.error(self.request, "Les orgas ont déjà reçu un mail avec leur listes d'inscrits. Modifiez les réglages pour en envoyer un autre")
return
settings.orga_notified = True
settings.save()
emails = self.get_emails()
nb_sent = send_mass_mail(emails, fail_silently=False)
mail_admins(
"Listes d'inscrits envoyés aux orgas",
"Les mails communiquant aux organisateurs leur listes d'inscrit ont été envoyés\n"
"Nombre total de mail envoyés: {}\n\n"
"-- Site Interludes (mail généré automatiquement".format(nb_sent)
)
messages.success(self.request, "{} mails envoyés aux orgas".format(nb_sent))
# ==============================
# Sitemap
# ==============================
class StaticViewSitemap(Sitemap):
"""Vue générant la sitemap.xml du site"""
changefreq = 'monthly'
def items(self):
"""list of pages to appear in sitemap"""
return ["home", "inscription", "activites", "FAQ"]
def location(self, item):
"""real url of an item"""
return reverse(item)
def priority(self, obj):
"""priority to appear in sitemap"""
# Priorize home page over the rest in search results
if obj == "home" or obj == "":
return 0.8
else:
return None # defaults to 0.5 when unset