Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • mediatek/site-interludes
  • aeltheos/site-kwei
  • mediatek/site-kwei
3 results
Show changes
Showing
with 1310 additions and 44 deletions
......@@ -7,9 +7,14 @@ app_name = "accounts"
urlpatterns = [
path("login/", views.LoginView.as_view(), name="login"),
path("logout/", views.LogoutView.as_view(), name="logout"),
path("profile/", views.ProfileView.as_view(), name="profile"),
path("create/", views.CreateAccountView.as_view(), name="create"),
path("update/", views.UpdateAccountView.as_view(), name="update"),
path("change_password/", views.UpdatePasswordView.as_view(), name="change_password"),
path('activate/<uidb64>/<token>/', views.ActivateAccountView.as_view(), name='activate'),
path("password_reset/", views.ResetPasswordView.as_view(), name="password_reset"),
path(
"password_reset/<uidb64>/<token>/",
views.ResetPasswordConfirmView.as_view(),
name="password_reset_confirm"
),
]
from django.contrib import messages
from django.contrib.auth import login, logout
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import LoginView as DjangoLoginView
from django.contrib.auth import views as auth_views
from django.contrib.sites.shortcuts import get_current_site
from django.http import Http404
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.urls import reverse
from django.urls import reverse, reverse_lazy
from django.template.loader import render_to_string
from django.views.generic import FormView, RedirectView, TemplateView, UpdateView, View
from django.views.generic import FormView, RedirectView, UpdateView, View
from django.shortcuts import render, redirect
from accounts.forms import CreateAccountForm, UpdateAccountForm, UpdatePasswordForm
from accounts import forms
from accounts.models import EmailUser
from accounts.tokens import email_token_generator
from site_settings.models import SiteSettings
......@@ -28,9 +28,12 @@ def send_validation_email(request, user, subject, template):
user.email_user(subject, message)
class LoginView(DjangoLoginView):
class LoginView(auth_views.LoginView):
"""Vue pour se connecter"""
template_name = "login.html"
redirect_authenticated_user = "profile"
form_class = forms.LoginForm
class LogoutView(RedirectView):
"""Vue pour se deconnecter"""
......@@ -45,16 +48,16 @@ class LogoutView(RedirectView):
return super().get_redirect_url(*args, **kwargs)
class ProfileView(LoginRequiredMixin, TemplateView):
"""Vue des actions de gestion de son profil"""
template_name = "profile.html"
redirect_field_name = "next"
# ==============================
# Create Account
# ==============================
class CreateAccountView(View):
"""Vue pour la creation de compte"""
form_class = CreateAccountForm
form_class = forms.CreateAccountForm
template_name = 'create_account.html'
email_template = 'email/activation.html'
@staticmethod
def check_creation_allowed():
......@@ -83,7 +86,7 @@ class CreateAccountView(View):
user.email_confirmed = False
user.save()
send_validation_email(request, user, "Activer votre compte Interludes", "activation_email.html")
send_validation_email(request, user, "Activer votre compte Interludes", self.email_template)
messages.info(request, 'Un lien vous a été envoyé par mail. Utilisez le pour finaliser la création de compte.')
......@@ -93,7 +96,7 @@ class CreateAccountView(View):
class ActivateAccountView(RedirectView):
"""Vue d'activation de compte (lien envoyé par mail)"""
permanent = False
success_pattern_name = "accounts:profile"
success_pattern_name = "profile"
failure_pattern_name = "home"
def get_redirect_url(self, uidb64, token, *args, **kwargs):
......@@ -123,10 +126,16 @@ class ActivateAccountView(RedirectView):
return reverse(self.success_pattern_name)
# ==============================
# Update personal info
# ==============================
class UpdateAccountView(LoginRequiredMixin, UpdateView):
"""Vue pour la mise à jour des infos personnelles"""
template_name = "update.html"
form_class = UpdateAccountForm
form_class = forms.UpdateAccountForm
email_template = "email/change.html"
def get_object(self):
return self.request.user
......@@ -134,7 +143,7 @@ class UpdateAccountView(LoginRequiredMixin, UpdateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["update_form"] = context["form"]
context["password_form"] = UpdatePasswordForm(
context["password_form"] = forms.UpdatePasswordForm(
user=self.request.user
)
return context
......@@ -144,13 +153,13 @@ class UpdateAccountView(LoginRequiredMixin, UpdateView):
send_validation_email(
self.request, self.request.user,
"Valider le changement d'email de votre compte Interludes",
"change_email.html"
self.email_template
)
messages.info(self.request, 'Un lien vous a été envoyé par mail. Utilisez le pour valider la mise à jour.')
# return reverse("registration:email_confirmation_needed")
return reverse("accounts:profile")
return reverse("profile")
def form_valid(self, form):
messages.success(self.request, "Informations personnelles mises à jour")
......@@ -161,12 +170,12 @@ class UpdatePasswordView(LoginRequiredMixin, FormView):
""" Change a user's password """
template_name = "update.html"
form_class = UpdatePasswordForm
form_class = forms.UpdatePasswordForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["password_form"] = context["form"]
context["update_form"] = UpdateAccountForm(instance=self.request.user)
context["update_form"] = forms.UpdateAccountForm(instance=self.request.user)
return context
def get_form_kwargs(self):
......@@ -178,4 +187,32 @@ class UpdatePasswordView(LoginRequiredMixin, FormView):
form.apply()
messages.success(self.request, "Mot de passe mis à jour")
login(self.request, self.request.user)
return redirect("accounts:profile")
return redirect("profile")
# ==============================
# Reset password
# ==============================
class ResetPasswordView(auth_views.PasswordResetView):
"""Vue pour le gestion du mot de passe oublié"""
email_template_name = 'email/password_reset.html'
subject_template_name = 'email/password_reset.txt'
success_url = reverse_lazy('accounts:login')
template_name = 'password_reset.html'
form_class = forms.PasswordResetEmailForm
def form_valid(self, form):
messages.info(self.request, "Un email vous a été envoyé avec un lien de réinitialisation")
return super().form_valid(form)
class ResetPasswordConfirmView(auth_views.PasswordResetConfirmView):
"""Vue demandant de saisir un nouveau mot de passe"""
success_url = reverse_lazy('accounts:login')
template_name = "password_reset_confirm.html"
def form_valid(self, form):
messages.success(self.request, "Votre mot de passe a été enregistré")
return super().form_valid(form)
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class AdminPagesConfig(AppConfig):
name = 'admin_pages'
from django import forms
from django.conf import settings
from django.db.models import TextChoices
from shared.forms import FormRenderMixin
class Recipients(TextChoices):
ALL = ("a", "tous les utilisateurs")
REGISTERED = ("b", "tous les inscrits")
class SendEmailForm(forms.Form):
"""Formulaire pour un envoie d'email
à tous les utilisateurs/inscrits"""
dest = forms.ChoiceField(
choices=Recipients.choices, required=True,
label="Envoyer à", initial=Recipients.REGISTERED,
)
subject = forms.CharField(
max_length=100, required=True,
label="Sujet", initial=settings.USER_EMAIL_SUBJECT_PREFIX,
strip=True
)
text = forms.CharField(
label="Contenu", strip=True, widget=forms.Textarea
)
from django.db import models
# Create your models here.
{% extends "base.html" %}
{% load static %}
{% block nav_admin %}current{% endblock %}
{% block head %}
<script src="{% static 'js/vis-timeline.min.js' %}"></script>
{% endblock %}
{% block "content" %}
<h2>Page d'administration</h2>
<p><strong>Version {{ constants.WEBSITE_FULL_VERSION }}</strong></p>
<div class="flex wrap">
<a class="button" href="{% url 'admin_pages:participants.csv' %}"><i class="fa fa-download"></i> Participants</a>
<a class="button" href="{% url 'admin_pages:activities.csv' %}"><i class="fa fa-download"></i> Activités</a>
<a class="button" href="{% url 'admin_pages:slots.csv' %}"><i class="fa fa-download"></i> Crénaux</a>
<a class="button" href="{% url 'admin_pages:activity_choices.csv' %}"><i class="fa fa-download"></i> Choix d'activités</a>
</div>
<ul class="messagelist">
<li class="info">
Pour plus d'options d'accès et de modifications, aller à la <a href="{% url 'admin:index' %}">page d'administration de django</a>.
<br>N'y modifiez rien si vous n'êtes pas sûrs de ce que cela fait.
</li></ul>
<h2>Paramètres du site</h2>
<ul>
<li>La création de compte est {% if settings.registrations_open %}ouverte{% else %}fermée{% endif %}.</li>
<li>Les inscriptions sont {% if settings.inscriptions_open %}ouvertes{% else %}fermées{% endif %}. Dates informatives:
<ul>
<li>Ouverture : {{ settings.inscriptions_start|default:"non fixée" }}</li>
<li>Fermeture : {{ settings.inscriptions_end|default:"non fixée" }}</li>
</ul>
</li>
<li>Les emails des orgas sont {% if settings.show_host_emails %}affichés sur la page activité{% else %}masqués{% endif %}.</li>
<li>Le planning {% if settings.display_planning %}est affiché{% else %}n'est pas affiché{% endif %}.</li>
<li>La répartition des activités {% if settings.activities_allocated %}est effectuée et affichée{% else %}n'est pas faite/affichée{% endif %}.</li>
<li>{% if settings.global_message %}Un message global est affiché{% else %}Aucun message global{% endif %}.</li>
<li>Le lien du serveur discord {% if settings.discord_link %}est affiché{% else %}n'est pas affiché{% endif %}.</li>
<li>L'envoi d'email en masse est {% if settings.allow_mass_email %}activé{% else %}désactivé{% endif %}</li>
</ul>
<h2>Métriques</h2>
<div class="flex wrap lines">
<div class="stat">
<div class="qty">Participants</div>
<div class="nb_big">{{ metrics.participants }}</div>
</div>
<div class="stat">
<div class="qty">Ulm</div>
<div class="nb_small">{{ metrics.ulm }}</div>
</div>
<div class="stat">
<div class="qty">Lyon</div>
<div class="nb_small">{{ metrics.lyon }}</div>
</div>
<div class="stat">
<div class="qty">Rennes</div>
<div class="nb_small">{{ metrics.rennes }}</div>
</div>
<div class="stat">
<div class="qty">Paris-Saclay</div>
<div class="nb_small">{{ metrics.saclay }}</div>
</div>
<div class="stat">
<div class="qty">Non inscrits</div>
<div class="nb_small">{{ metrics.non_registered }}</div>
</div>
<div class="stat">
<div class="qty">Dormeurs</div>
<div class="nb_small">{{ metrics.sleeps }}</div>
</div>
<div class="stat">
<div class="qty">Payé⋅es</div>
<div class="nb_small">{{ metrics.paid }}</div>
</div>
</div>
<div class="flex wrap lines">
<div class="stat">
<div class="qty">Repas</div>
<div class="nb_big">{{ metrics.meals }}</div>
</div>
<div class="stat">
<div class="qty">Vendredi</div>
<div class="nb_small">{{ metrics.meal1 }}</div>
</div>
<div class="stat">
<div class="qty">S matin</div>
<div class="nb_small">{{ metrics.meal2 }}</div>
</div>
<div class="stat">
<div class="qty">S midi</div>
<div class="nb_small">{{ metrics.meal3 }}</div>
</div>
<div class="stat">
<div class="qty">S soir</div>
<div class="nb_small">{{ metrics.meal4 }}</div>
</div>
<div class="stat">
<div class="qty">D matin</div>
<div class="nb_small">{{ metrics.meal5 }}</div>
</div>
<div class="stat">
<div class="qty">D midi</div>
<div class="nb_small">{{ metrics.meal6 }}</div>
</div>
<div class="stat">
<div class="qty">D soir</div>
<div class="nb_small">{{ metrics.meal7 }}</div>
</div>
</div>
<div class="flex wrap lines">
<div class="stat">
<div class="qty">Activités</div>
<div class="nb_big">{{ metrics.activites }}</div>
</div>
<div class="stat">
<div class="qty">Affichées</div>
<div class="nb_small">{{ metrics.displayed }}</div>
</div>
<div class="stat">
<div class="qty">Inscription*</div>
<div class="nb_small">{{ metrics.act_ins }}</div>
</div>
<div class="stat">
<div class="qty">Mail orga</div>
<div class="nb_small">{{ metrics.communicate }}</div>
</div>
<div class="stat">
<div class="qty">Présentiel</div>
<div class="nb_small">{{ metrics.st_present }}</div>
</div>
<div class="stat">
<div class="qty">Distanciel</div>
<div class="nb_small">{{ metrics.st_distant }}</div>
</div>
<div class="stat">
<div class="qty">Les deux</div>
<div class="nb_small">{{ metrics.st_both }}</div>
</div>
</div>
<div class="flex wrap lines">
<div class="stat">
<div class="qty">Planning</div>
<div class="nb_big">{{ metrics.slots }}</div>
</div>
<div class="stat">
<div class="qty">Inscription*</div>
<div class="nb_small">{{ metrics.true_ins }}</div>
</div>
<div class="stat">
<div class="qty">Souhaits</div>
<div class="nb_small">{{ metrics.wish }}</div>
</div>
<div class="stat">
<div class="qty">Obtenus</div>
<div class="nb_small">{{ metrics.granted }}</div>
</div>
<div class="stat">
<div class="qty">Malformé</div>
<div class="nb_small">{{ metrics.malformed }}</div>
</div>
</div>
<p>*Une activité peut avoir plusieurs créneaux sur le planning. Les inscriptions se font par créneaux donc le nombre
d'inscriptions de la catégorie "Activités" est seulement informatif .
</p>
{% if metrics.malformed %}
<ul class="messagelist"><li class="error">
Des activités non ouvertes aux inscriptions apparaissent dans les listes de souhait.
</li></ul>
{% endif %}
<h2>Prévisualisation du planning</h2>
<ul class="messagelist">
{{ planning_validation|safe }}
</ul>
<p>Vous pouver uploader une version PDF dans le réglages (depuis django-admin)</p>
{% include "_planning.html" %}
<h2>Répartition des activités</h2>
<p>La répartition se fait depuis la <a href="{% url 'admin:index' %}">page d'administration de django</a>,
dans la rebrique "Choix d'activités" via la colonne "obtenue".</p>
<p>Une fois la répartition effectuée, vérifiez qu'elle passe les tests avant d'envoyer les mails</p>
{{ validations|safe }}
<script type="text/javascript">
{% if validation_errors %}
const errors = "!! Cette répartition ne passe PAS tous les tests !!\n\n";
{% else %}
const errors = "";
{% endif %}
{% if not settings.user_notified and settings.allow_mass_mail %}
function mail_inscrits() {
if (confirm(
`${errors}Cette action va envoyer {{ user_email_nb }} emails.\nÊtes-vous sur de vouloir continuer ?`
))
window.location = "{% url 'admin_pages:email_users' %}";
}
{% endif %}
{% if not settings.orga_notified and settings.allow_mass_mail %}
function mail_orgas() {
if (confirm(
`${errors}Cette action va envoyer {{ orga_email_nb }} emails.\nÊtes-vous sur de vouloir continuer ?`
))
window.location = "{% url 'admin_pages:email_orgas' %}";
}
{% endif %}
</script>
<p class="centered"><i class="fas fa-exclamation-triangle"></i> N'ENVOYER LES EMAILS QUE SI VOUS ÊTES SUR DE VOUS ! <i class="fas fa-exclamation-triangle"></i></p>
{% if not settings.allow_mass_mail %}
<p>L'envoi d'emails collectifs est désactivé dans les réglages. Activez le avant d'envoyer
et redésactivez le après.
</p>
{% endif %}
{% if settings.user_notified %}
<p>Un email communiquant la répartition aux utilisateurs a déjà été envoyé.
(Modifiez les paramètres s'il faut en renvoyer un)
</p>
{% else %}
<p>L'email aux inscrits enverra un email à tous les utilisateurs inscrits.
Il communiquera également le lien du discord si celui-ci est renseigné.</p>
{% endif %}
{% if settings.orga_notified %}
<p>Un email communiquant les inscrits aux organisateur à déjà été envoyé.
(Modifiez les paramètres s'il faut en renvoyer un)
</p>
{% else %}
<p>L'email aux orgas enverra un email uniquement aux orgas des activités qui ont demandé de communiquer la liste de participants.</p>
{% endif %}
<div class="flex wrap">
{% if settings.user_notified or not settings.allow_mass_mail %}
<button class="button disabled">Email aux inscrits</button>
{% else %}
<button class="button" onclick="mail_inscrits();">Email aux inscrits</button>
{% endif %}
{% if settings.orga_notified or not settings.allow_mass_mail %}
<button class="button disabled">Email aux orgas</button>
{% else %}
<button class="button" onclick="mail_orgas();">Email aux orgas</button>
{% endif %}
</div>
<h2>Mail aux utilisateurs</h2>
<p>Écrire un mail aux utilisateurs (tous ou seulement les inscrits)</p>
<p>Évitez de spammer. N'envoyez que si vraiment nécessaire.</p>
<p><a class="button{% if not settings.allow_mass_mail %} disabled{% endif %}" href="{% url 'admin_pages:email_new' %}">Écrire un nouveau mail</a></p>
{% endblock %}
{% autoescape off %}
Bonjour {{ activity.host_name }},
Voici la liste des participant·e·s inscrit·e·s à votre activité {{ activity }} :
{% for slot in slots %}{{ slot }}{% if slot.start %} (le {{ slot.start|date:"l à H:i" }}){% endif %} :
{% for participant in slot.participants %}
- {{ participant.participant }} {{ participant.participant.user.email }}{% empty %}
- Aucun participant inscrit.{% endfor %}
{% empty %}- Aucun créneau d'inscription.{% endfor %}
--
L'équipe Interludes
{% if settings.contact_email %}Pour nous contacter, envoyer un email à {{ settings.contact_email }}{% endif %}
{% endautoescape %}
{% autoescape off %}
Bonjour {{ user.first_name }} {{ user.last_name }},
{% if settings.discord_link %}
Vous pouvez rejoindre notre serveur discord avec le lien suivant : {{ settings.discord_link }}
{% endif %}
Les inscriptions aux Interludes sont fermées et la répartition des activités à été effectuée.
{% if requested_activities_nb %}
Vous avez obtenu {{ my_choices|length }} activité(s) (sur {{ requested_activities_nb }} souhaitée(s)).
{% for choice in my_choices %}
- {{ choice.slot }}{% if choice.slot.on_planning %} (le {{ choice.slot.start|date:"l à H:i" }}){% endif %}{% endfor %}{% if activities %}
Cette liste est également disponible sur la page "Mon compte" du site: {% url "profile" %}.
Votre adresse email sera communiquée aux orgas pour les activités nécessitant préparation avant l'événement.{% endif %}
{% else %}
Vous n'aviez demandé aucune activité.
{% endif %}
--
L'équipe Interludes
{% if settings.contact_email %}Pour nous contacter, envoyer un email à {{ settings.contact_email }}{% endif %}
{% endautoescape %}
{% extends "base.html" %}
{% load static %}
{% block "content" %}
<h2>Envoyer un email</h2>
<p>
Ce formulaire permet d'envoyer un mail à tous les comptes du site
(ou seulement aux comptes actuellements inscrits).
À utiliser avec modération.
</p>
<ul>
<li>Nombre de comptes : {{ accounts_nb }}</li>
<li>Nombre d'inscrits : {{ registered_nb }}</li>
</ul>
<form method="post" action="{% url 'admin_pages:email_new' %}">
{% csrf_token %}
<table>
<tr><td><strong>De :</strong></td><td>{{ from_email }}</td></tr>
<tr><td><strong>Envoyer à : <strong></td><td>{{ form.dest }}</td></tr>
<tr><td><strong>Sujet : <strong></td><td>{{ form.subject }}</td></tr>
</table>
{{ form.text }}
<br>
<div class="flex">
<input type="submit" value="Envoyer">
<a class="button" href="{% url 'admin_pages:index' %}">Annuler</a>
</div>
</form>
{% endblock %}
from django.test import TestCase
# Create your tests here.
from django.urls import path, include
from admin_pages import views
urlpatterns = [
path('', views.AdminView.as_view(), name="index"),
path('export/activities/', views.ExportActivities.as_view(), name="activities.csv"),
path('export/slots/', views.ExportSlots.as_view(), name="slots.csv"),
path('export/participants/', views.ExportParticipants.as_view(), name="participants.csv"),
path('export/activity_choices/', views.ExportActivityChoices.as_view(), name="activity_choices.csv"),
path('email/send_user_emails_0564946523/', views.SendUserEmail.as_view(), name="email_users"),
path('email/send_orga_emails_5682480453/', views.SendOrgaEmail.as_view(), name="email_orgas"),
path('email/new_email/', views.NewEmail.as_view(), name="email_new"),
]
from django.conf import settings
from django.contrib import messages
from django.core.mail import mail_admins, send_mass_mail
from django.db.models import Count
from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django.template.loader import render_to_string
from django.urls import reverse, reverse_lazy
from django.views.generic import FormView, RedirectView, TemplateView
from accounts.models import EmailUser
from home import models
from home.views import get_planning_context
from site_settings.models import Colors, SiteSettings
from shared.views import CSVWriteView, SuperuserRequiredMixin
from interludes import settings as site_settings
from admin_pages.forms import Recipients, SendEmailForm
# ==============================
# Main Admin views
# ==============================
class AdminView(SuperuserRequiredMixin, TemplateView):
template_name = "admin.html"
def get_metrics(self):
registered = models.ParticipantModel.objects.filter(
is_registered=True, user__is_active=True
)
acts = models.ActivityModel.objects.all()
slots_in = models.SlotModel.objects.all()
wishes = models.ActivityChoicesModel.objects.filter(
participant__is_registered=True, participant__user__is_active=True
)
class metrics:
participants = registered.count()
ulm = registered.filter(school=models.ParticipantModel.ENS.ENS_ULM).count()
lyon = registered.filter(school=models.ParticipantModel.ENS.ENS_LYON).count()
rennes = registered.filter(school=models.ParticipantModel.ENS.ENS_RENNES).count()
saclay = registered.filter(school=models.ParticipantModel.ENS.ENS_CACHAN).count()
non_registered = EmailUser.objects.filter(is_active=True).count() - participants
# mugs = registered.filter(mug=True).count()
sleeps = registered.filter(sleeps=True).count()
paid = registered.filter(paid=True).count()
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()
meal7 = registered.filter(meal_sunday_evening=True).count()
meals = meal1 + meal2 + meal3 + meal4 + meal5 + meal6
activites = acts.count()
displayed = acts.filter(display=True).count()
act_ins = acts.filter(display=True, must_subscribe=True).count()
communicate = acts.filter(communicate_participants=True).count()
st_present = acts.filter(display=True, status=models.ActivityModel.Status.PRESENT).count()
st_distant = acts.filter(display=True, status=models.ActivityModel.Status.DISTANT).count()
st_both = acts.filter(display=True, status=models.ActivityModel.Status.BOTH).count()
slots = slots_in.count()
true_ins = slots_in.filter(subscribing_open=True).count()
wish = wishes.count()
granted = wishes.filter(accepted=True).count()
malformed = models.ActivityChoicesModel.objects.filter(slot__subscribing_open=False).count()
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"""
slots = models.SlotModel.objects.filter(subscribing_open=True)
min_fails = ""
max_fails = ""
for slot in slots:
total = models.ActivityChoicesModel.objects.filter(
slot=slot, accepted=True, participant__is_registered=True,
participant__user__is_active=True
).aggregate(total=Count("id"))["total"]
max = slot.activity.max_participants
min = slot.activity.min_participants
if max != 0 and max < total:
max_fails += "<br> &bullet;&ensp;{}: {} inscrits (maximum {})".format(
slot, total, max
)
if min > total:
min_fails += "<br> &bullet;&ensp;{}: {} inscrits (minimum {})".format(
slot, 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"""
slots = models.SlotModel.objects.filter(subscribing_open=True)
conflicts = []
for i, slot_1 in enumerate(slots):
for slot_2 in slots[i+1:]:
if slot_1.conflicts(slot_2):
conflicts.append((slot_1, slot_2))
base_qs = models.ActivityChoicesModel.objects.filter(
accepted=True, participant__is_registered=True,
participant__user__is_active=True
)
errors = ""
for slot_1, slot_2 in conflicts:
participants_1 = {x.participant for x in base_qs.filter(slot=slot_1)}
participants_2 = {x.participant for x in base_qs.filter(slot=slot_2)}
intersection = participants_1.intersection(participants_2)
if intersection:
errors += '<br> &bullet;&ensp; {} participe à la fois à "{}" et à "{}"'.format(
", ".join(str(x) for x in intersection), slot_1, slot_2
)
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_slot_less(self):
"""verifie que toutes les activité demandant une liste de participant ont un créneaux"""
activities = models.ActivityModel.objects.filter(communicate_participants=True)
errors = ""
for activity in activities:
count = models.SlotModel.objects.filter(activity=activity).count()
if count == 0:
errors += "<br> &bullet;&ensp; {}".format(activity.title)
if errors:
return '<li class="error">Certaines activités demandant une liste de participants n\'ont pas de créneaux :{}<br>Leurs orgas vont recevoir un mail inutile.</li>'.format(
errors
)
return '<li class="success">Toutes les activités demandant une liste de participants ont au moins un créneau</li>'
def validate_multiple_similar_inscription(self):
"""verifie que personne n'est inscrit à la même activité plusieurs fois"""
slots = models.SlotModel.objects.filter(subscribing_open=True)
conflicts = []
for i, slot_1 in enumerate(slots):
for slot_2 in slots[i+1:]:
if slot_1.activity == slot_2.activity:
conflicts.append((slot_1, slot_2))
base_qs = models.ActivityChoicesModel.objects.filter(
accepted=True, participant__is_registered=True,
participant__user__is_active=True
)
errors = ""
for slot_1, slot_2 in conflicts:
participants_1 = {x.participant for x in base_qs.filter(slot=slot_1)}
participants_2 = {x.participant for x in base_qs.filter(slot=slot_2)}
intersection = participants_1.intersection(participants_2)
if intersection:
errors += '<br> &bullet;&ensp; {} inscrit aux créneaux "{}" et "{}" de l\'activité "{}"'.format(
", ".join(str(x) for x in intersection), slot_1, slot_2, slot_1.activity
)
if errors:
return '<li class="error">Des participants sont inscrits plusieurs fois à la même activité :{}</li>'.format(
errors
)
return '<li class="success">Aucun inscrit plusieurs fois à une même activité</li>'
def planning_validation(self):
"""Vérifie que toutes les activités ont le bon nombre de créneaux
dans le planning"""
errors = ""
activities = models.ActivityModel.objects.all()
for activity in activities:
nb_wanted = activity.desired_slot_nb
nb_got = activity.slots.count()
if nb_wanted != nb_got:
errors += '<br> &bullet;&ensp; "{}" souhaite {} crénaux mais en a {}.'.format(
activity.title, nb_wanted, nb_got
)
if errors:
return '<li class="error">Certaines activités ont trop/pas assez de crénaux :{}</li>'.format(
errors
)
return '<li class="success">Toutes les activités ont le bon nombre de crénaux</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 += self.validate_multiple_similar_inscription()
validations += self.validate_slot_less()
if settings.discord_link:
validations += '<li class="success">Le lien du discord est renseigné</li>'
else:
validations += '<li class="error">Le lien du discord n\'est pas renseigné</li>'
validations += '</ul>'
user_email_nb = models.ParticipantModel.objects.filter(
is_registered=True, user__is_active=True
).count()
orga_email_nb = models.ActivityModel.objects.filter(
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,
"planning_validation": self.planning_validation(),
}
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context["metrics"] = self.get_metrics()
context.update(get_planning_context())
context.update(self.validate_activity_allocation())
return context
# ==============================
# DB Export Views
# ==============================
class ExportActivities(SuperuserRequiredMixin, CSVWriteView):
filename = "activites_interludes"
model = models.ActivityModel
fields = [
# The key is "host_id" but listed as "host" in auto-found field names
# which leads to an error...
'id', 'display', 'title', 'act_type', 'game_type', 'description',
'desc_as_html', 'host_id', 'host_name', 'host_email', 'host_info', 'show_email',
'must_subscribe', 'communicate_participants', 'max_participants',
'min_participants', 'duration', 'desired_slot_nb',
'available_friday_evening', 'available_friday_night',
'available_saturday_morning', 'available_saturday_afternoon',
'available_saturday_evening', 'available_saturday_night',
'available_sunday_morning', 'available_sunday_afternoon',
'constraints', 'status', 'needs', 'comments'
]
class ExportSlots(SuperuserRequiredMixin, CSVWriteView):
filename = "créneaux_interludes"
headers = [
"Titre", "Début", "Salle",
"Ouverte aux inscriptions", "Affiché sur le planning", "Affiché sur l'activité",
"Couleur", "Durée", "Durée activité",
]
def get_rows(self):
slots = models.SlotModel.objects.all()
rows = []
for slot in slots:
rows.append([
str(slot), slot.start, slot.room,
slot.subscribing_open, slot.on_planning, slot.on_activity,
Colors(slot.color).name, slot.duration, slot.activity.duration,
])
return rows
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 midi", "Reaps D soir", "Payé⋅e", "Prix",
"Montant payé"
]
def get_rows(self):
profiles = models.ParticipantModel.objects.filter(
is_registered=True,user__is_active=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,
profile.meal_sunday_evening,
profile.paid,
profile.cost,
profile.amount_paid
])
return rows
class ExportActivityChoices(SuperuserRequiredMixin, CSVWriteView):
filename = "choix_activite_interludes"
model = models.ActivityChoicesModel
headers = ["id_participant", "nom_participant", "mail_participant", "priorité", "obtenu", "nom_créneau", "id_créneau"]
def get_rows(self):
activities = models.ActivityChoicesModel.objects.all()
rows = []
for act in activities:
if act.participant.is_registered and act.participant.user.is_active:
rows.append([
act.participant.id, str(act.participant), act.participant.user.email, act.priority,
act.accepted, str(act.slot), act.slot.id
])
return rows
# ==============================
# Send email views
# ==============================
class SendEmailBase(SuperuserRequiredMixin, RedirectView):
"""Classe abstraite pour l'envoie d'un groupe d'emails"""
pattern_name = "admin_pages:index"
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 = models.ParticipantModel.objects.filter(
is_registered=True, user__is_active=True
)
emails = []
settings = SiteSettings.load()
for participant in participants:
my_choices = models.ActivityChoicesModel.objects.filter(participant=participant)
message = render_to_string("email/user.html", {
"user": participant.user,
"settings": settings,
"requested_activities_nb": my_choices.count(),
"my_choices": my_choices.filter(accepted=True),
})
emails.append((
site_settings.USER_EMAIL_SUBJECT_PREFIX + "Vos activités", # 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"
"{}".format(nb_sent, site_settings.EMAIL_SIGNATURE)
)
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 = models.ActivityModel.objects.filter(communicate_participants=True)
emails = []
settings = SiteSettings.load()
for activity in activities:
slots = models.SlotModel.objects.filter(activity=activity)
message = render_to_string("email/orga.html", {
"activity": activity,
"settings": settings,
"slots": slots,
})
emails.append((
site_settings.USER_EMAIL_SUBJECT_PREFIX +
"Liste d'inscrits à votre activité {}".format(activity.title), # subject
message,
self.from_address, # From:
[activity.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"
"{}".format(nb_sent, site_settings.EMAIL_SIGNATURE)
)
messages.success(self.request, "{} mails envoyés aux orgas".format(nb_sent))
class NewEmail(SuperuserRequiredMixin, FormView):
"""Créer un nouveau mail"""
template_name = "send_email.html"
form_class = SendEmailForm
success_url = reverse_lazy("admin_pages:index")
from_address = None
def get_emails(self, selection):
"""return the list of destination emails"""
if selection == Recipients.ALL:
users = EmailUser.objects.filter(is_active=True)
return [u.email for u in users]
elif selection == Recipients.REGISTERED:
participants = models.ParticipantModel.objects.filter(
is_registered=True, user__is_active=True
)
return [p.user.email for p in participants]
else:
raise ValueError("Invalid selection specifier\n")
@staticmethod
def sending_allowed():
"""Checks if sending mass emails is allowed"""
settings = SiteSettings.load()
return settings.allow_mass_mail
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
if not self.sending_allowed():
messages.error(request, "L'envoi de mail de masse est désactivé dans les réglages")
else:
dest = form.cleaned_data["dest"]
subject = form.cleaned_data["subject"]
text = form.cleaned_data["text"]
emails = []
for to_addr in self.get_emails(dest):
emails.append([
subject,
text,
self.from_address,
[to_addr]
])
nb_sent = send_mass_mail(emails, fail_silently=False)
mail_admins(
"Email envoyé",
"Un email a été envoyé à {}.\n"
"Nombre total de mail envoyés: {}\n\n"
"Sujet : {}\n\n"
"{}\n\n"
"{}".format(
Recipients(dest).label, nb_sent, subject, text,
site_settings.EMAIL_SIGNATURE
)
)
messages.success(self.request, "{} mails envoyés".format(nb_sent))
return super().form_valid(form)
def get_context_data(self, *args, **kwargs):
"""ajoute l'email d'envoie aux données contextuelles"""
context = super().get_context_data(*args, **kwargs)
context["from_email"] = self.from_address if self.from_address else settings.DEFAULT_FROM_EMAIL
context["registered_nb"] = models.ParticipantModel.objects.filter(
is_registered = True, user__is_active=True
).count()
context["accounts_nb"] = EmailUser.objects.filter(is_active=True).count()
return context
def get(self, request, *args, **kwargs):
if self.sending_allowed():
return super().get(request, *args, **kwargs)
messages.error(request, "L'envoi de mail de masse est désactivé dans les réglages")
return HttpResponseRedirect(self.get_success_url())
from django.contrib import admin
from home.models import InterludesActivity, InterludesParticipant, ActivityList
from home import models
from shared.admin import ExportCsvMixin
# Titre de la vue (tag <h1>)
......@@ -9,27 +9,100 @@ admin.site.site_header = "Administration site interludes"
admin.site.site_title = "Admin Interludes"
@admin.register(InterludesActivity)
class InterludesActivityAdmin(ExportCsvMixin, admin.ModelAdmin):
@admin.register(models.ActivityModel)
class ActivityModelAdmin(ExportCsvMixin, admin.ModelAdmin):
"""option d'affichage des activités dans la vue django admin"""
list_display = ("title", "host_name", "display", "must_subscribe","on_planning")
list_filter = ("display", "must_subscribe", "on_planning")
filename = "export_activites.csv"
list_display = ("title", "host_name", "display", "must_subscribe",)
list_filter = ("display", "must_subscribe", "status",)
ordering = ("title", "host_name",)
list_editable = ("display",)
fields = (
"title", "display",
("host_name", "host_email"), "show_email",
"host_info",
"act_type", "game_type",
"description", "desc_as_html",
("min_participants", "max_participants"),
"must_subscribe",
"communicate_participants",
("duration", "desired_slot_nb"),
(
"available_friday_evening",
"available_friday_night",
"available_saturday_morning",
"available_saturday_afternoon",
"available_saturday_evening",
"available_saturday_night",
"available_sunday_morning",
"available_sunday_afternoon"
),
"constraints",
"status", "needs",
"comments",
)
list_per_page = 100
csv_export_fields = [
# The key is "host_id" but listed as "host" in auto-found field names
# which leads to an error...
'id', 'display', 'title', 'act_type', 'game_type', 'description',
'desc_as_html', 'host_id', 'host_name', 'host_email', 'show_email', 'host_info',
'must_subscribe', 'communicate_participants', 'max_participants',
'min_participants', 'duration', 'desired_slot_nb',
'available_friday_evening', 'available_friday_night',
'available_saturday_morning', 'available_saturday_afternoon',
'available_saturday_evening', 'available_saturday_night',
'available_sunday_morning', 'available_sunday_afternoon',
'constraints', 'status', 'needs', 'comments'
]
@admin.register(InterludesParticipant)
class InterludesParticipantAdmin(ExportCsvMixin, admin.ModelAdmin):
@admin.register(models.SlotModel)
class SlotModelAdmin(ExportCsvMixin, admin.ModelAdmin):
"""option d'affichage des créneaux dans la vue d'admin"""
filename = "export_slots.csv"
csv_export_fields = (
"activity_id", "title",
"start", "duration", "room",
"on_planning", "on_activity", "color",
)
list_display = ("__str__", "start", "room", "subscribing_open", "on_planning", "on_activity",)
list_filter = ("subscribing_open", "on_planning", "on_activity", "activity__display",)
list_editable = ("subscribing_open", "on_planning", "on_activity",)
ordering = ("activity", "title", "start",)
@admin.register(models.ParticipantModel)
class ParticipantModelAdmin(ExportCsvMixin, admin.ModelAdmin):
"""option d'affichage des participant dans la vue django admin"""
list_display = ("user", "school", "is_registered")
list_filter = ("school", "is_registered")
filename = "export_participants.csv"
fields = (
"user", "school", "is_registered",
("meal_friday_evening", "meal_saturday_morning", "meal_saturday_midday",
"meal_saturday_evening", "meal_sunday_morning", "meal_sunday_midday",
"meal_sunday_evening"),
"sleeps", "nb_murder", "paid", "amount_paid", "comment"
)
list_display = ("user", "school", "is_registered", "comment")
list_filter = (
"school", "is_registered", "sleeps",
"meal_friday_evening", "meal_saturday_morning", "meal_saturday_midday",
"meal_saturday_evening", "meal_sunday_morning", "meal_sunday_midday",
"meal_sunday_evening", "nb_murder", "paid"
)
ordering = ("user",)
list_per_page = 200
@admin.register(ActivityList)
@admin.register(models.ActivityChoicesModel)
class ActivityListAdmin(ExportCsvMixin, admin.ModelAdmin):
"""option d'affichage des choix d'activités dans la vue django admin"""
list_display = ("participant", "priority", "activity",)
list_filter = ("activity", "participant",)
ordering = ("participant", "priority",)
list_per_page = 200
filename = "export_choix_activite.csv"
list_display = ("slot", "participant", "priority", "accepted")
list_filter = (
"slot__activity", "participant__is_registered", "slot__activity__display",
"accepted", "slot__subscribing_open",
)
list_editable = ("accepted",)
ordering = ("slot", "priority", "participant",)
list_per_page = 400
from django import forms
from django.core.exceptions import ValidationError
from home.models import ActivityList, InterludesParticipant
from home import models
from shared.forms import FormRenderMixin
class InscriptionForm(FormRenderMixin, forms.ModelForm):
class Meta:
model = InterludesParticipant
model = models.ParticipantModel
fields = (
"school", "sleeps", "mug",
"school", "sleeps", # "mug",
"meal_friday_evening", "meal_saturday_morning", "meal_saturday_midday",
"meal_saturday_evening", "meal_sunday_morning", "meal_sunday_midday",
"meal_saturday_evening", "meal_sunday_morning", "meal_sunday_midday", "meal_sunday_evening",
"paid","nb_murder", "comment"
)
field_groups = [["school"], ["sleeps"], ["mug"], [
"meal_friday_evening", "meal_saturday_morning", "meal_saturday_midday",
"meal_saturday_evening", "meal_sunday_morning", "meal_sunday_midday",
]]
field_groups = [["school"], ["sleeps"], #["mug"],
[
"meal_friday_evening", "meal_saturday_morning", "meal_saturday_midday",
"meal_saturday_evening", "meal_sunday_morning", "meal_sunday_midday", "meal_sunday_evening"
],
["paid"],["nb_murder"], ["comment"]
]
def save(self, *args, commit=True, **kwargs):
participant = super().save(*args, commit=False, **kwargs)
......@@ -28,5 +33,79 @@ class InscriptionForm(FormRenderMixin, forms.ModelForm):
class ActivityForm(FormRenderMixin, forms.ModelForm):
class Meta:
model = ActivityList
fields = ("activity",)
model = models.ActivityChoicesModel
fields = ("slot",)
labels = {"slot":""}
def __init__(self, *args, **kwargs):
super(ActivityForm, self).__init__(*args, **kwargs)
slots = models.SlotModel.objects.filter(subscribing_open=True)
self.fields['slot'].queryset = slots
class BaseActivityFormSet(forms.BaseFormSet):
"""Form set that fails if duplicate activities"""
def clean(self):
"""Checks for duplicate activities"""
if any(self.errors):
# Don't bother validating the formset unless each form is valid on its own
return
activities = []
for form in self.forms:
if self.can_delete and self._should_delete_form(form):
continue
activity = form.cleaned_data.get('slot')
if activity is None:
continue
if activity in activities:
raise ValidationError("Vous ne pouvez pas sélectionner une même activtté plusieurs fois")
activities.append(activity)
class ActivitySubmissionForm(FormRenderMixin, forms.ModelForm):
class Meta:
model = models.ActivityModel
fields = (
"title", "act_type", "game_type", "description",
"host_name", "host_email", "host_info",
"must_subscribe", "communicate_participants",
"max_participants", "min_participants",
"duration", "desired_slot_nb",
"available_friday_evening",
"available_friday_night",
"available_saturday_morning",
"available_saturday_afternoon",
"available_saturday_evening",
"available_saturday_night",
"available_sunday_morning",
"available_sunday_afternoon",
"constraints",
#"status",
"needs",
"comments",
)
def clean(self):
cleaned_data = super().clean()
maxi = cleaned_data.get("max_participants")
mini = cleaned_data.get("min_participants")
if maxi != 0 and mini > maxi:
raise forms.ValidationError(
"Le nombre minimal de participants est supérieur au nombre maximal",
code="invalid_order"
)
return cleaned_data
def save(self, *args, commit=True, **kwargs):
"""Enregistre l'activité dans la base de données"""
activity = models.ActivityModel(
**self.cleaned_data,
)
if commit:
activity.save()
return activity
# Generated by Django 3.2.7 on 2021-10-05 18:45
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import home.models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='ActivityModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('display', models.BooleanField(default=False, help_text="Si vrai, s'affiche sur la page activités", verbose_name='afficher dans la liste')),
('show_email', models.BooleanField(default=True, help_text="Si l'affichage d'email global et cette case sont vrai, affiche l'email de l'orga", verbose_name="afficher l'email de l'orga")),
('title', models.CharField(max_length=200, verbose_name='Titre')),
('act_type', models.CharField(choices=[('1 partie', 'Une partie'), ('2+ parties', 'Quelques parties'), ('Tournoi', 'Tournoi'), ('freeplay', 'Freeplay'), ('other', 'Autre')], max_length=12, verbose_name="Type d'activité")),
('game_type', models.CharField(choices=[('jeu cartes', 'Jeu de cartes'), ('jeu plateau', 'Jeu de société'), ('table RPG', 'Jeu de rôle sur table'), ('large RPG', 'Jeu de rôle grandeur nature'), ('videogame', 'Jeu vidéo'), ('partygame', 'Party game'), ('puzzle', 'Puzzle ou analogue'), ('secret roles', 'Jeu à rôles secrets'), ('coop', 'Jeu coopératif'), ('other', 'Autre')], max_length=12, verbose_name='Type de jeu')),
('description', models.TextField(help_text='Texte ou html selon la valeur de "Description HTML".\n', max_length=10000, verbose_name='description')),
('desc_as_html', models.BooleanField(default=False, help_text='Assurer vous que le texte est bien formaté, cette option peut casser la page activités.', verbose_name='Description au format HTML')),
('host_name', models.CharField(blank=True, help_text='Peut-être laissé vide pour des simples activités sans orga', max_length=50, null=True, verbose_name="nom de l'organisateur")),
('host_email', models.EmailField(help_text='Utilisé pour communiquer la liste des participants si demandé', max_length=254, verbose_name="email de l'organisateur")),
('host_info', models.TextField(blank=True, max_length=1000, null=True, verbose_name='Autre orgas/contacts')),
('must_subscribe', models.BooleanField(default=False, help_text="Informatif, il faut utiliser les créneaux pour ajouter dans la liste d'inscription", verbose_name='sur inscription')),
('communicate_participants', models.BooleanField(verbose_name="communiquer la liste des participants à l'orga avant l'événement")),
('max_participants', models.PositiveIntegerField(default=0, help_text='0 pour illimité', verbose_name='Nombre maximum de participants')),
('min_participants', models.PositiveIntegerField(default=0, verbose_name='Nombre minimum de participants')),
('duration', models.DurationField(help_text='format hh:mm:ss', verbose_name='Durée')),
('desired_slot_nb', models.PositiveIntegerField(default=1, validators=[home.models.validate_nonzero], verbose_name='Nombre de créneaux souhaités')),
('available_friday_evening', models.CharField(choices=[('0', 'Idéal'), ('1', 'Acceptable'), ('2', 'Indisponible')], default='1', max_length=1, verbose_name='Crénau vendredi soir')),
('available_friday_night', models.CharField(choices=[('0', 'Idéal'), ('1', 'Acceptable'), ('2', 'Indisponible')], default='1', max_length=1, verbose_name='Crénau vendredi nuit')),
('available_saturday_morning', models.CharField(choices=[('0', 'Idéal'), ('1', 'Acceptable'), ('2', 'Indisponible')], default='1', max_length=1, verbose_name='Crénau samedi matin')),
('available_saturday_afternoon', models.CharField(choices=[('0', 'Idéal'), ('1', 'Acceptable'), ('2', 'Indisponible')], default='1', max_length=1, verbose_name='Crénau samedi après-midi')),
('available_saturday_evening', models.CharField(choices=[('0', 'Idéal'), ('1', 'Acceptable'), ('2', 'Indisponible')], default='1', max_length=1, verbose_name='Crénau samedi soir')),
('available_saturday_night', models.CharField(choices=[('0', 'Idéal'), ('1', 'Acceptable'), ('2', 'Indisponible')], default='1', max_length=1, verbose_name='Crénau samedi nuit')),
('available_sunday_morning', models.CharField(choices=[('0', 'Idéal'), ('1', 'Acceptable'), ('2', 'Indisponible')], default='1', max_length=1, verbose_name='Crénau dimanche matin')),
('available_sunday_afternoon', models.CharField(choices=[('0', 'Idéal'), ('1', 'Acceptable'), ('2', 'Indisponible')], default='1', max_length=1, verbose_name='Crénau dimanche après-midi')),
('constraints', models.TextField(blank=True, max_length=2000, null=True, verbose_name='Contraintes particulières')),
('status', models.CharField(choices=[('P', 'En présentiel uniquement'), ('D', 'En distanciel uniquement'), ('2', 'Les deux')], max_length=1, verbose_name='Présentiel/distanciel')),
('needs', models.TextField(blank=True, max_length=2000, null=True, verbose_name='Besoin particuliers')),
('comments', models.TextField(blank=True, max_length=2000, null=True, verbose_name='Commentaires')),
('host', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Organisateur')),
],
options={
'verbose_name': 'activité',
},
),
migrations.CreateModel(
name='SlotModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(default='{act_title}', help_text="Utilisez '{act_title}' pour insérer le titre de l'activité correspondante", max_length=200, verbose_name='Titre')),
('start', models.DateTimeField(verbose_name='début')),
('duration', models.DurationField(blank=True, help_text="Format 00:00:00. Laisser vide pour prendre la durée de l'activité correspondante", null=True, verbose_name='durée')),
('room', models.CharField(blank=True, max_length=100, null=True, verbose_name='salle')),
('on_planning', models.BooleanField(default=True, verbose_name='afficher sur le planning')),
('on_activity', models.BooleanField(default=True, verbose_name="afficher dans la description de l'activité")),
('subscribing_open', models.BooleanField(default=False, help_text="Si vrai, apparaît dans la liste du formulaire d'inscription", verbose_name='ouvert aux inscriptions')),
('color', models.CharField(choices=[('a', 'Rouge'), ('b', 'Orange'), ('c', 'Jaune'), ('d', 'Vert'), ('e', 'Bleu'), ('f', 'Bleu foncé'), ('g', 'Noir')], default='a', help_text='La légende des couleurs est modifiable dans les paramètres', max_length=1, verbose_name='Couleur')),
('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.activitymodel', verbose_name='Activité')),
],
options={
'verbose_name': 'créneau',
'verbose_name_plural': 'créneaux',
},
),
migrations.CreateModel(
name='ParticipantModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('school', models.CharField(choices=[('U', 'ENS Ulm'), ('L', 'ENS Lyon'), ('R', 'ENS Rennes'), ('C', 'ENS Paris Saclay')], max_length=1, verbose_name='ENS de rattachement')),
('is_registered', models.BooleanField(default=False, verbose_name='est inscrit')),
('meal_friday_evening', models.BooleanField(default=False, verbose_name='repas de vendredi soir')),
('meal_saturday_morning', models.BooleanField(default=False, verbose_name='repas de samedi matin')),
('meal_saturday_midday', models.BooleanField(default=False, verbose_name='repas de samedi midi')),
('meal_saturday_evening', models.BooleanField(default=False, verbose_name='repas de samedi soir')),
('meal_sunday_morning', models.BooleanField(default=False, verbose_name='repas de dimanche matin')),
('meal_sunday_midday', models.BooleanField(default=False, verbose_name='repas de dimanche soir')),
('sleeps', models.BooleanField(default=False, verbose_name='dormir sur place')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='Utilisateur', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'participant',
},
),
migrations.CreateModel(
name='ActivityChoicesModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('priority', models.PositiveIntegerField(verbose_name='priorité')),
('accepted', models.BooleanField(default=False, verbose_name='Obtenue')),
('participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.participantmodel', verbose_name='participant')),
('slot', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.slotmodel', verbose_name='créneau')),
],
options={
'verbose_name': "choix d'activités",
'verbose_name_plural': "choix d'activités",
'ordering': ('participant', 'priority'),
'unique_together': {('participant', 'slot'), ('priority', 'participant')},
},
),
]
# Generated by Django 3.2.16 on 2022-11-08 18:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='participantmodel',
name='comment',
field=models.TextField(blank=True, max_length=2000, null=True, verbose_name='Commentaire'),
),
migrations.AddField(
model_name='participantmodel',
name='nb_murder',
field=models.PositiveIntegerField(default=0, verbose_name='Nombre de murder réalisées'),
),
]
# Generated by Django 3.2.16 on 2022-11-15 09:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0002_auto_20221108_1943'),
]
operations = [
migrations.AlterField(
model_name='activitymodel',
name='status',
field=models.CharField(blank=True, choices=[('P', 'En présentiel uniquement'), ('D', 'En distanciel uniquement'), ('2', 'Les deux')], default='P', max_length=1, verbose_name='Présentiel/distanciel'),
),
]
# Generated by Django 3.2.16 on 2022-11-17 20:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0003_alter_activitymodel_status'),
]
operations = [
migrations.AddField(
model_name='participantmodel',
name='paid',
field=models.BooleanField(default=False, verbose_name='payé(e)'),
),
]