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
Commits on Source (65)
Showing
with 520 additions and 282 deletions
# Change Log
## Version 2.0.0-beta - Coming soon
- Added a form that allows admins to send emails to all users
- Added a form for users to submit activities
- Added a changeable caption for the planning
- Added fixes/improvement from 48h des jeux:
- bug fixed in activity submission form
- new validator that checks the number of slots for each activity in the planning
- fixed room display on activity page
- fixed planning info displayed on activity even when planning hidden
- added boolean field to show host email on activity
- added boolean field to separate showing slot on planning and next to activity
## Version 1.2.8 - 2021-05-06
- Added links to FAQ
......
......@@ -6,7 +6,8 @@ SECRET := interludes/secret.py
.PHONY: help
help: ## Show this help
@egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
@echo "make: list of useful targets :"
@egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
.PHONY: install
install: ## Install requirements
......@@ -37,8 +38,8 @@ start: install $(SECRET) migrate serve ## Install requirements, apply migrations
.PHONY: clean
clean: ## Remove migrations and delete database
find . -path "*/migrations/*.py" -not -name "__init__.py" -delete
find . -path "*/migrations/*.pyc" -delete
find . -path "*/migrations/*.py" -not -name "__init__.py" -not -path "*/venv/*" -delete
find . -path "*/migrations/*.pyc" -not -path "*/venv/*" -delete
rm $(DB)
.PHONY: test
......
......@@ -2,6 +2,8 @@
Ce répo contient le sites des interludes. Ce site est en ligne à [https://interludes.ens.fr](https://interludes.ens.fr).
Ce répo est une copie du répo initial sur le [git interne de l'ENS Ulm](https://git.eleves.ens.fr/dlesbre/site-interludes).
Ce répo est diffusé sous une [license MIT](https://choosealicense.com/licenses/mit/).
**Contenu:**
......@@ -17,7 +19,7 @@ Ce répo est diffusé sous une [license MIT](https://choosealicense.com/licenses
Pour installer toutes les dépendances et lancer le serveur :
git clone https://git.eleves.ens.fr/dlesbre/site-interludes.git &&
git clone https://gitlab.crans.org/mediatek/site-interludes.git &&
cd site-interlude &&
python3 -m venv venv &&
source venv/bin/activate &&
......@@ -88,12 +90,13 @@ Le site se gère depuis deux pages d'administration:
- Utilisateurs - contient tous les utilisateurs et leur permissions. Pour donner les droits d'administrateur à quelqu'un il faut lui donner le statut superutilisateur (accès à l'admin du site) ET le statut équipe (accès à l'admin django)
- Paramêtres - les réglages du site, ils permettent:
- ouvrir/fermer la création de compte, les inscriptions
- ouvrir fermer le formulaire de proposition d'activités
- afficher/cacher le planning
- renseigner l'email de contact, les dates de l'événement, les dates d'inscription
- ajouter un message global au dessus de toutes les pages
- bloquer/autoriser l'envoi d'email globaux
- Activités - liste des activités prévues. C'est ici que vous pouvez rajouter/modifier les activités qui s'affichent sur la page activité.
Pour le moment il n'y a pas de formulaire qui permette aux orga de proposer une activité sur le site (on était passé par un appel à projet externe et on avait rempli les activités nous-même)
Un formulaire permet aux utilisateurs de proposer des activités directement. Ils vous faudra les relire et les valider ensuite manuellement pour qu'elles soient affichées sur le site.
- Crénaux - place une activité sur le planning. Une activité peut avoir plusieurs crénaux si elle a lieu plusieurs fois. Noter que les inscriptions se font à des crénaux et non a des activités.
- Participant - liste des gens inscrits et des informations sur leur inscription (ENS, repas choisi...)
- Choix d'activité - Liste de (participant, priorité, activité) indiquant les voeux des participant. Une fois que vous avez fait l'attribution, cocher les case "Obtenues" pour indiquer qui a eu quelle activité.
......@@ -105,10 +108,11 @@ Le site se gère depuis deux pages d'administration:
- permet d'envoyer deux séries d'emails :
- une aux inscrits pour leur communiquer les activités qu'ils ont obtenus
- une aux orgas qui ont besoin de connaître la liste des participants à l'avance pour préparer leurs activités.
- permet l'écriture d'un mail à tous.
## En production
Le serveur a besoin d'être configuré pour HTTPS et d'être configuré pour livrer directement les fichiers situés des `/static/`.
Le serveur a besoin d'être configuré pour HTTPS et d'être configuré pour livrer directement les fichiers situés dans `/static/` et `/media/`.
1. Installer les dépendances `make install`
......@@ -126,7 +130,6 @@ Le serveur a besoin d'être configuré pour HTTPS et d'être configuré pour liv
A.K.A. la liste des trucs utiles que j'ai pas eu le temps d'ajouter
- Un formulaire pour proposer une activité directement sur le site
- Intégrer l'[algorithme de répartition](https://github.com/Imakoala/InterludesMatchings) dans le site au lieu de le faire tourner en externe à partir des export CSV et de remplir les résultats à la main
- Envoyer une concaténation de tous les emails aux admin (pour vérification, et pas juste en copie pour éviter le spam...)
- Générer la version PDF du planning automatiquement au lieu de la faire à base de captures d'écran
......@@ -135,5 +138,10 @@ A.K.A. la liste des trucs utiles que j'ai pas eu le temps d'ajouter
## Liens divers
- [Le site des interludes 2021](https://interludes.ens.fr)
- [Le répo initial](https://git.eleves.ens.fr/dlesbre/site-interludes) sur le gitlab de l'ENS Ulm
- [Le github de l'algorithme de répartition](https://github.com/Imakoala/InterludesMatchings)
- [Le wiki de Paris-Saclay](https://wiki.crans.org/VieBdl/InterLudes) qui recensent les visuels, sites webs et photos des interludes passées.
- [Le wiki de Paris-Saclay](https://wiki.crans.org/VieBdl/InterLudes) qui recense les visuels, sites webs et photos des interludes passées.
- [Le gitlab du site des 48h des jeux](https://git.eleves.ens.fr/dlesbre/48h-des-jeux) un événement très similaire intra-ENS Ulm, c'est fork de ce répo.
- [Le site des 48h des jeux](https://48hdesjeux.cof.ens.fr/)
- [Le site du club jeu d'Ulm](https://jeux.cof.ens.fr/)
- [le site des interludes 2023](https://interludes.crans.org/)
# Generated by Django 3.0.8 on 2021-03-21 17:30
# Generated by Django 3.2.7 on 2021-10-05 18:45
from django.db import migrations, models
import django.utils.timezone
......@@ -9,7 +9,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0011_update_proxy_permissions'),
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
......
{% extends 'base.html' %}
{% block "content" %}
<h2>Mot de passe oublié ?</h2>
<p>Saissisez votre adresse email ci-dessous pour recevoir un lien de réinitialisation du mot de passe.</p>
<h2>Mot de passe oublié ?</h2>
<p>Saissisez votre adresse email ci-dessous pour recevoir un lien de réinitialisation du mot de passe.</p>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<div class="flex">
<input type="submit" value="Valider">
<input type="submit" value="Valider">
<a class="button" href="{% url 'accounts:login' %}">Annuler</a>
</div>
</form>
</form>
{% endblock %}
......@@ -6,12 +6,12 @@
<h2>Saissisez un nouveau mot de passe</h2>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<div class="flex">
<input type="submit" value="Changer mon mot de passe">
<a class="button" href="{% url 'accounts:login' %}">Annuler</a>
</div>
{% csrf_token %}
{{ form.as_p }}
<div class="flex">
<input type="submit" value="Changer mon mot de passe">
<a class="button" href="{% url 'accounts:login' %}">Annuler</a>
</div>
</form>
{% else %}
......@@ -19,8 +19,8 @@
<h2>Lien invalide</h2>
<p>Le lien de réinitialisation est invalide, peut-être a-t-il déjà été utilisé.
Veuillez <a href="{% url 'accounts:password_reset' %}">demander un nouveau lien</a>.
Veuillez <a href="{% url 'accounts:password_reset' %}">demander un nouveau lien</a>.
</p>
{% endif %}
{% endblock %}
\ No newline at end of file
{% endblock %}
......@@ -8,13 +8,12 @@ from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
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 import forms
from accounts.models import EmailUser
from accounts.tokens import email_token_generator
from home.models import InterludesActivityChoices
from site_settings.models import SiteSettings
def send_validation_email(request, user, subject, template):
......
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
)
......@@ -31,10 +31,12 @@
<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>
......@@ -68,8 +70,12 @@
<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>
......@@ -99,8 +105,12 @@
<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>
......@@ -167,6 +177,10 @@
<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" %}
......@@ -237,4 +251,12 @@
<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 %}
{% 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 %}
......@@ -10,4 +10,5 @@ urlpatterns = [
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
from django.views.generic import RedirectView, TemplateView
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 SiteSettings
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
......@@ -21,19 +26,24 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
template_name = "admin.html"
def get_metrics(self):
registered = models.InterludesParticipant.objects.filter(is_registered = True)
acts = models.InterludesActivity.objects.all()
slots_in = models.InterludesSlot.objects.all()
wishes = models.InterludesActivityChoices.objects.filter(participant__is_registered=True)
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.InterludesParticipant.ENS.ENS_ULM).count()
lyon = registered.filter(school=models.InterludesParticipant.ENS.ENS_LYON).count()
rennes = registered.filter(school=models.InterludesParticipant.ENS.ENS_RENNES).count()
saclay = registered.filter(school=models.InterludesParticipant.ENS.ENS_CACHAN).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()
......@@ -41,33 +51,35 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
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.InterludesActivity.Status.PRESENT).count()
st_distant = acts.filter(display=True, status=models.InterludesActivity.Status.DISTANT).count()
st_both = acts.filter(display=True, status=models.InterludesActivity.Status.BOTH).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.InterludesActivityChoices.objects.filter(slot__subscribing_open=False).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.InterludesSlot.objects.filter(subscribing_open=True)
slots = models.SlotModel.objects.filter(subscribing_open=True)
min_fails = ""
max_fails = ""
for slot in slots:
total = models.InterludesActivityChoices.objects.filter(
slot=slot, accepted=True, participant__is_registered=True
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
......@@ -92,14 +104,15 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
def validate_activity_conflicts(self):
"""Vérifie que personne n'est inscrit à des activités simultanées"""
slots = models.InterludesSlot.objects.filter(subscribing_open=True)
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.InterludesActivityChoices.objects.filter(
accepted=True, participant__is_registered=True
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:
......@@ -119,10 +132,10 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
def validate_slot_less(self):
"""verifie que toutes les activité demandant une liste de participant ont un créneaux"""
activities = models.InterludesActivity.objects.filter(communicate_participants=True)
activities = models.ActivityModel.objects.filter(communicate_participants=True)
errors = ""
for activity in activities:
count = models.InterludesSlot.objects.filter(activity=activity).count()
count = models.SlotModel.objects.filter(activity=activity).count()
if count == 0:
errors += "<br> &bullet;&ensp; {}".format(activity.title)
if errors:
......@@ -133,14 +146,15 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
def validate_multiple_similar_inscription(self):
"""verifie que personne n'est inscrit à la même activité plusieurs fois"""
slots = models.InterludesSlot.objects.filter(subscribing_open=True)
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.InterludesActivityChoices.objects.filter(
accepted=True, participant__is_registered=True
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:
......@@ -158,6 +172,24 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
)
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">'
......@@ -185,8 +217,10 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
validations += '</ul>'
user_email_nb = models.InterludesParticipant.objects.filter(is_registered=True).count()
orga_email_nb = models.InterludesActivity.objects.filter(
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()
......@@ -194,7 +228,8 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
"validations": validations,
"user_email_nb": user_email_nb,
"orga_email_nb": orga_email_nb,
"validation_errors": '<li class="error">' in validations
"validation_errors": '<li class="error">' in validations,
"planning_validation": self.planning_validation(),
}
def get_context_data(self, *args, **kwargs):
......@@ -212,24 +247,37 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
class ExportActivities(SuperuserRequiredMixin, CSVWriteView):
filename = "activites_interludes"
model = models.InterludesActivity
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ée sur le planning",
"Ouverte aux inscriptions", "Affiché sur le planning", "Affiché sur l'activité",
"Couleur", "Durée", "Durée activité",
]
def get_rows(self):
slots = models.InterludesSlot.objects.all()
slots = models.SlotModel.objects.all()
rows = []
for slot in slots:
rows.append([
str(slot), slot.start, slot.room,
slot.subscribing_open, slot.on_planning,
models.InterludesSlot.Colors(slot.color).name, slot.duration, slot.activity.duration ,
slot.subscribing_open, slot.on_planning, slot.on_activity,
Colors(slot.color).name, slot.duration, slot.activity.duration,
])
return rows
......@@ -238,10 +286,13 @@ class ExportParticipants(SuperuserRequiredMixin, CSVWriteView):
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"
"Repas D matin", "Repas D midi", "Reaps D soir", "Payé⋅e", "Prix",
"Montant payé"
]
def get_rows(self):
profiles = models.InterludesParticipant.objects.filter(is_registered=True).all()
profiles = models.ParticipantModel.objects.filter(
is_registered=True,user__is_active=True
).all()
rows = []
for profile in profiles:
rows.append([
......@@ -258,19 +309,23 @@ class ExportParticipants(SuperuserRequiredMixin, CSVWriteView):
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.InterludesActivityChoices
model = models.ActivityChoicesModel
headers = ["id_participant", "nom_participant", "mail_participant", "priorité", "obtenu", "nom_créneau", "id_créneau"]
def get_rows(self):
activities = models.InterludesActivityChoices.objects.all()
activities = models.ActivityChoicesModel.objects.all()
rows = []
for act in activities:
if act.participant.is_registered:
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
......@@ -304,11 +359,13 @@ class SendUserEmail(SendEmailBase):
def get_emails(self):
"""genere les mails a envoyer"""
participants = models.InterludesParticipant.objects.filter(is_registered=True)
participants = models.ParticipantModel.objects.filter(
is_registered=True, user__is_active=True
)
emails = []
settings = SiteSettings.load()
for participant in participants:
my_choices = models.InterludesActivityChoices.objects.filter(participant=participant)
my_choices = models.ActivityChoicesModel.objects.filter(participant=participant)
message = render_to_string("email/user.html", {
"user": participant.user,
"settings": settings,
......@@ -316,7 +373,7 @@ class SendUserEmail(SendEmailBase):
"my_choices": my_choices.filter(accepted=True),
})
emails.append((
"Information interludes", # subject
site_settings.USER_EMAIL_SUBJECT_PREFIX + "Vos activités", # subject
message,
self.from_address, # From:
[participant.user.email], # To:
......@@ -337,7 +394,7 @@ class SendUserEmail(SendEmailBase):
"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)
"{}".format(nb_sent, site_settings.EMAIL_SIGNATURE)
)
messages.success(self.request, "{} mails envoyés aux utilisateurs".format(nb_sent))
......@@ -349,18 +406,19 @@ class SendOrgaEmail(SendEmailBase):
def get_emails(self):
"""genere les mails a envoyer"""
activities = models.InterludesActivity.objects.filter(communicate_participants=True)
activities = models.ActivityModel.objects.filter(communicate_participants=True)
emails = []
settings = SiteSettings.load()
for activity in activities:
slots = models.InterludesSlot.objects.filter(activity=activity)
slots = models.SlotModel.objects.filter(activity=activity)
message = render_to_string("email/orga.html", {
"activity": activity,
"settings": settings,
"slots": slots,
})
emails.append((
"[interludes] Liste d'inscrit à votre activité {}".format(activity.title), # subject
site_settings.USER_EMAIL_SUBJECT_PREFIX +
"Liste d'inscrits à votre activité {}".format(activity.title), # subject
message,
self.from_address, # From:
[activity.host_email] # To:
......@@ -381,6 +439,81 @@ class SendOrgaEmail(SendEmailBase):
"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)
"{}".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())
......@@ -9,8 +9,8 @@ admin.site.site_header = "Administration site interludes"
admin.site.site_title = "Admin Interludes"
@admin.register(models.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"""
filename = "export_activites.csv"
list_display = ("title", "host_name", "display", "must_subscribe",)
......@@ -18,44 +18,83 @@ class InterludesActivityAdmin(ExportCsvMixin, admin.ModelAdmin):
ordering = ("title", "host_name",)
list_editable = ("display",)
fields = (
"title",
("host_name", "host_email"),
"status", "act_type", "duration",
"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",
"description", "desc_as_html",
"display",
"notes",
("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(models.InterludesSlot)
class InterludesSlotAdmin(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"
list_display = ("__str__", "start", "room", "subscribing_open", "on_planning",)
list_filter = ("subscribing_open", "on_planning", "activity__display",)
list_editable = ("subscribing_open", "on_planning",)
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.InterludesParticipant)
class InterludesParticipantAdmin(ExportCsvMixin, admin.ModelAdmin):
@admin.register(models.ParticipantModel)
class ParticipantModelAdmin(ExportCsvMixin, admin.ModelAdmin):
"""option d'affichage des participant dans la vue django admin"""
filename = "export_participants.csv"
list_display = ("user", "school", "is_registered")
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(models.InterludesActivityChoices)
@admin.register(models.ActivityChoicesModel)
class ActivityListAdmin(ExportCsvMixin, admin.ModelAdmin):
"""option d'affichage des choix d'activités dans la vue django admin"""
filename = "export_choix_activite.csv"
......
......@@ -8,18 +8,20 @@ from shared.forms import FormRenderMixin
class InscriptionForm(FormRenderMixin, forms.ModelForm):
class Meta:
model = models.InterludesParticipant
model = models.ParticipantModel
fields = (
"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",
]
"meal_saturday_evening", "meal_sunday_morning", "meal_sunday_midday", "meal_sunday_evening"
],
["paid"],["nb_murder"], ["comment"]
]
def save(self, *args, commit=True, **kwargs):
......@@ -31,13 +33,13 @@ class InscriptionForm(FormRenderMixin, forms.ModelForm):
class ActivityForm(FormRenderMixin, forms.ModelForm):
class Meta:
model = models.InterludesActivityChoices
model = models.ActivityChoicesModel
fields = ("slot",)
labels = {"slot":""}
def __init__(self, *args, **kwargs):
super(ActivityForm, self).__init__(*args, **kwargs)
slots = models.InterludesSlot.objects.filter(subscribing_open=True)
slots = models.SlotModel.objects.filter(subscribing_open=True)
self.fields['slot'].queryset = slots
class BaseActivityFormSet(forms.BaseFormSet):
......@@ -57,3 +59,53 @@ class BaseActivityFormSet(forms.BaseFormSet):
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.0.8 on 2021-03-21 17:30
# 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):
......@@ -15,32 +16,64 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='InterludesActivity',
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')),
('status', models.CharField(choices=[('P', 'En présentiel uniquement'), ('D', 'En distanciel uniquement'), ('2', 'Les deux')], max_length=1, verbose_name='Présentiel/distanciel')),
('act_type', models.CharField(choices=[('Tournoi', 'Tournoi'), ('partie', 'Une partie'), ('parties', 'Quelques parties'), ('freeplay', 'Freeplay'), ('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')),
('duration', models.DurationField(help_text='format hh:mm:ss', verbose_name='Durée')),
('max_participants', models.PositiveIntegerField(help_text='0 pour illimité', verbose_name='Nombre maximum de participants')),
('min_participants', models.PositiveIntegerField(verbose_name='Nombre minimum de participants')),
('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")),
('display', models.BooleanField(default=False, verbose_name="afficher dans la liste d'activités")),
('must_subscribe', models.BooleanField(default=False, help_text="Une activité doit être affichée dans la liste également pour que l'on puisse si inscrire", verbose_name='sur inscription')),
('host_name', models.CharField(max_length=50, verbose_name="nom de l'organisateur")),
('host_email', models.EmailField(max_length=254, verbose_name="email de l'organisateur")),
('description', models.TextField(max_length=2000, verbose_name='description')),
('on_planning', models.BooleanField(default=False, help_text='Nécessite de salle et heure de début non vide', verbose_name='afficher sur le planning')),
('start', models.DateTimeField(blank=True, null=True, verbose_name='début')),
('room', models.CharField(blank=True, max_length=100, null=True, verbose_name='salle')),
('notes', models.TextField(blank=True, max_length=2000, verbose_name='Notes privées')),
('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='InterludesParticipant',
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')),
......@@ -59,19 +92,19 @@ class Migration(migrations.Migration):
},
),
migrations.CreateModel(
name='ActivityList',
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')),
('activity', models.ForeignKey(db_column='activité', on_delete=django.db.models.deletion.CASCADE, to='home.InterludesActivity')),
('participant', models.ForeignKey(db_column='participant', on_delete=django.db.models.deletion.CASCADE, to='home.InterludesParticipant')),
('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', 'activity'), ('priority', 'participant')},
'unique_together': {('participant', 'slot'), ('priority', 'participant')},
},
),
]
# Generated by Django 3.0.8 on 2021-03-29 14:46
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('home', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='interludesactivity',
name='canonical',
field=models.ForeignKey(blank=True, help_text="Si plusieurs copie d'une activité existe (pour plusieurs crénaux), et une seule est affichée, sélectionner là dans les copie pour réparer les liens du planning vers la description", null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.InterludesActivity', verbose_name='Représentant canonique'),
),
migrations.AddField(
model_name='interludesactivity',
name='desc_as_html',
field=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'),
),
migrations.AddField(
model_name='interludesactivity',
name='subscribing_open',
field=models.BooleanField(default=False, help_text="Si vrai, apparaît dans la liste du formulaire d'inscription", verbose_name='ouverte aux inscriptions'),
),
migrations.AlterField(
model_name='interludesactivity',
name='description',
field=models.TextField(help_text='Texte ou html selon la valeur de "Description HTML".\n', max_length=2000, verbose_name='description'),
),
migrations.AlterField(
model_name='interludesactivity',
name='display',
field=models.BooleanField(default=False, help_text="Si vrai, s'affiche sur la page activités", verbose_name='afficher dans la liste'),
),
migrations.AlterField(
model_name='interludesactivity',
name='must_subscribe',
field=models.BooleanField(default=False, help_text="Informatif, il faut utiliser 'ouverte aux inscriptions' pour ajouter dans la liste d'inscription", verbose_name='sur inscription'),
),
]
# 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.0.8 on 2021-04-28 12:46
# Generated by Django 3.2.16 on 2022-11-15 09:18
from django.db import migrations, models
import site_settings.models
class Migration(migrations.Migration):
dependencies = [
('site_settings', '0005_auto_20210425_1732'),
('home', '0002_auto_20221108_1943'),
]
operations = [
migrations.AlterField(
model_name='sitesettings',
name='planning_file',
field=models.FileField(blank=True, null=True, storage=site_settings.models.OverwriteStorage(), upload_to='', verbose_name='Version PDF du planning'),
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.0.8 on 2021-04-07 12:20
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('home', '0002_auto_20210329_1646'),
]
operations = [
migrations.CreateModel(
name='InterludesActivityChoices',
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.InterludesParticipant', verbose_name='participant')),
],
options={
'verbose_name': "choix d'activités",
'verbose_name_plural': "choix d'activités",
'ordering': ('participant', 'priority'),
},
),
migrations.CreateModel(
name='InterludesSlot',
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(blank=True, null=True, verbose_name='début')),
('room', models.CharField(blank=True, max_length=100, null=True, verbose_name='salle')),
('on_planning', models.BooleanField(default=False, help_text='Nécessite de salle et heure de début non vide', verbose_name='afficher sur le planning')),
('subscribing_open', models.BooleanField(default=False, help_text="Si vrai, apparaît dans la liste du formulaire d'inscription", verbose_name='ouvert aux inscriptions')),
],
options={
'verbose_name': 'créneau',
'verbose_name_plural': 'créneaux',
},
),
migrations.RemoveField(
model_name='interludesactivity',
name='canonical',
),
migrations.RemoveField(
model_name='interludesactivity',
name='on_planning',
),
migrations.RemoveField(
model_name='interludesactivity',
name='room',
),
migrations.RemoveField(
model_name='interludesactivity',
name='start',
),
migrations.RemoveField(
model_name='interludesactivity',
name='subscribing_open',
),
migrations.AlterField(
model_name='interludesactivity',
name='must_subscribe',
field=models.BooleanField(default=False, help_text="Informatif, il faut utiliser les créneaux pour ajouter dans la liste d'inscription", verbose_name='sur inscription'),
),
migrations.DeleteModel(
name='ActivityList',
),
migrations.AddField(
model_name='interludesslot',
name='activity',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.InterludesActivity', verbose_name='Activité'),
),
migrations.AddField(
model_name='interludesactivitychoices',
name='slot',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.InterludesSlot', verbose_name='créneau'),
),
migrations.AlterUniqueTogether(
name='interludesactivitychoices',
unique_together={('participant', 'slot'), ('priority', 'participant')},
),
]
# Generated by Django 3.0.8 on 2021-04-24 15:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0003_auto_20210407_1420'),
]
operations = [
migrations.AddField(
model_name='interludesslot',
name='color',
field=models.CharField(choices=[('a', 'Bleu foncé'), ('b', 'Rouge'), ('c', 'Jaune'), ('d', 'Bleu'), ('e', 'Vert'), ('f', 'Noir'), ('g', 'Orange')], default='a', max_length=1, verbose_name='Couleur'),
),
migrations.AlterField(
model_name='interludesactivity',
name='description',
field=models.TextField(help_text='Texte ou html selon la valeur de "Description HTML".\n', max_length=10000, verbose_name='description'),
),
migrations.AlterField(
model_name='interludesactivity',
name='host_email',
field=models.EmailField(help_text='Utilisé pour communiquer la liste des participants si demandé', max_length=254, verbose_name="email de l'organisateur"),
),
migrations.AlterField(
model_name='interludesactivity',
name='host_name',
field=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"),
),
]