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 948 additions and 106 deletions
{% extends "base.html" %}
{% load static %}
{% block nav_faq %}current{% endblock %}
{% block "content" %}
<h2>Dois-je être inscrit&middot;e pour participer&nbsp;?</h2>
<p>Les interludes ayant lieu à distance cette année, l'inscription est facultative.
Une inscription est seulement nécessaire pour certaines activités au nombre de places limité.
Sans inscription tu peux rejoindre notre
{% if settings.discord_link %}
<a href="{{ settings.discord_link }}">serveur discord</a>
{% else %}serveur discord{% endif %}
et y participer au jeux libres et à toutes les activités sans inscriptions.
</p>
<h2>Comment seront organisées les interludes à distance&nbsp;?</h2>
<p>Les interludes passeront principalement par un
{% if settings.discord_link %}<a href="{{ settings.discord_link }}">serveur discord</a>{% else %}serveur discord{% endif %}.
Nous y regrouperont
une liste de sites permettant de jouer en ligne et des canaux vocaux dédiés.
</p>
<p>Pour les <a href="{% url 'activites' %}">activités</a> proposées, leur description
vous indiquera s'il vous faudra autre chose qu'un ordinateur et une connexion internet pour participer.
</p>
<p>Le serveur contiendra des salons écrits et vocaux pour organiser des parties sur d'autres plateformes en lignes.</p>
<h2>Comment rejoindre le serveur discord&nbsp;?</h2>
{% if settings.discord_link %}
<p>Suivez <a href="{{ settings.discord_link }}">ce lien</a>.</p>
{% else %}
<p>Le lien vous sera communiqué par mail peu avant l'événement. Il apparaîtra également sur ce site.</p>
{% endif %}
<p>Vous pouvez utiliser Discord sur navigateur sans créer de compte (mais se connecter sans compte
pose parfois des problèmes). Sinon il existe une application mobile (Android et iOS) et un programme (Linux/Windows/Mac)
permettant d'y accèder via un compte.
</p>
<h2>Où jouer en ligne&nbsp;?</h2>
<p>Voici une liste (non-exhaustive) des plateformes de jeu en ligne&nbsp;:
<ul>
<li><a href="https://fr.boardgamearena.com/">Board Game Arena</a> (site gratuit nécessitant un compte)</li>
<li><a href="https://store.steampowered.com/app/286160/Tabletop_Simulator/">Tabletop Simulator</a> (jeu vendu 20€ sur steam)</li>
<li>Des sites spécialisés (gratuits)&nbsp;<ul>
<li>Pour Codenames : <a href="https://codenames.game/">codenames.game</a> ou <a href="https://www.horsepaste.com/">horsepaste.com</a>.</li>
<li>Pour Hanabi : <a href="https://hanab.live/">hanab.live</a> (nécessite un compte).</li>
<li>Pour Catan : <a href="https://colonist.io/">colonist.io</a>.</li>
<li>Pour Shadow-Hunter : <a href="http://shadowhunters.live/">shadowhunters.live</a>.</li>
<li>Pour Diplomacy : <a href="https://vdiplomacy.com/">vdiplomacy.com</a> (nécessite un compte).</li>
</ul></li>
<li>N'importe quel jeu vidéo multijoueur</li>
</ul>
Vous trouverez une liste plus fournie sur le discord interludes.
</p>
<h2>Comment proposer une activité&nbsp;?</h2>
{% if settings.activity_submission_open %}
<p>Vous pouver proposer une activité en remplissant <a href="{% url 'activity_submission' %}">ce formulaire</a>.</p>
<p>Il vous faudra être connecté et renseigner, le titre, une description, le nombre de places, la durée, les horaires possibles/idéaux, et tout autre besoin particulier...</p>
{% else %}
<p>L'appel a activités est fermé pour le moment.
Contactez nous en cas de besoin urgent ou d'idée trop géniale pour être délaissée.</p>
{% endif %}
<h2>Comment sont réparties les activités&nbsp;?</h2>
<p>
La répartition est faite par un algorithme puis vérifiée à la main.
Dans la mesure du possible, l'algorithme essaie d'attribue au moins une activité par personne. Par conséquent, si vous ne mettez qu'une seule activité, vous avez plus de chance de l'avoir.
</p>
<p>
Les activités qui n'ont pas de limite de place (et toutes les activités avec moins de demande que de places) ne comptent pas pour ce système, donc vous pouvez les mettre et vous ne serez pas pénalisés.
</p>
<p>
Vous ne pourrez pas avoir deux activités qui se déroulent en même temps. Si l'une vous est attribué l'autre souhait sera ignoré.
</p>
<p>On n'a pas trouvé le code des années précédentes, mais je suspecte fortement que ce soit un algo similaire en départageant les égalités aléatoirement plutôt qu'au shotgun.</p>
<p>Plus précisément : l'algorithme se base sur le problème hôpital-résident&nbsp;:</p>
<ol>
<li>Il commence par essayer d'attribuer une activité à chaque participant.es au mieux possible, en utilisant la librairie matching de python. Les égalités sont départagées aléatoirement. Plus un choix est haut dans votre liste de souhait, plus vous avez de chance de vous le voir attribuer. Si vous n'avez qu'un seul choix, vous avez plus de chance de vous le voir attribué. (les participant.es avec un seul choix sont automatique placé.es avant ceux qui en ont plusieurs)</li>
<li>Toutes les activités attribuées sont supprimées, ainsi que les voeux résolus des joueurs.</li>
<li>Tant qu'il reste des place dans des activités et des participant.es qui veulent y participer, on recommence à l'étape 1.</li>
</ol>
<p>Le code est sur <a href="https://github.com/Imakoala/InterludesMatchings">github</a>, il ne marche pas encore parfaitement, et on risque de devoir bidouiller à la main en plus pour résoudre tous les cas particuliers (conflits d'horaires, activité présente plusieurs fois...).
{% endblock %}
{% extends "base.html" %}
{% load static %}
{% block nav_faq %}current{% endblock %}
{% block "content" %}
<h2>Comment se rendre à l'ENS Ulm ?</h2>
<p>Plan transport + instructions</p>
<h2>Conditions sanitaires ?</h2>
<p>On ne sait pas trop...</p>
<h2>Comment s'incrire ?</h2>
{% if settings.inscriptions_open %}
<p> Les <a href="{% url 'inscription' %}">inscriptions</a> sont ouvertes. Vous pouvez vous inscrire aux repas et à l'hébergement.
{% if settings.activity_inscriptions_open %}
Vous pouvez aussi vous inscrire aux activités.
{% else %}
L'inscription aux activités aura lieu plupart.
{% endif %}
</p>
{% else %}
<p> Les inscriptions ne sont pas ouvertes. </p>
{% endif %}
<h2>Comment payer ?</h2>
De préférence par l'associations qui gèrent les Interludes dans votre ENS (Bureau des Loisirs, Bul...). Sinon par espèce, chèque, carte sur place.
<h2>Comment proposer une activité&nbsp;?</h2>
{% if settings.activity_submission_open %}
<p>Vous pouver proposer une activité en remplissant <a href="{% url 'activity_submission' %}">ce formulaire</a>.</p>
<p>Il vous faudra être connecté et renseigner, le titre, une description, le nombre de places, la durée, les horaires possibles/idéaux, et tout autre besoin particulier...</p>
{% else %}
<p>L'appel a activités est fermé pour le moment.
Contactez nous en cas de besoin urgent ou d'idée trop géniale pour être délaissée.</p>
{% endif %}
<h2>Quelles seront les conditions pour dormir/manger&nbsp;?</h2>
<p> Nous proposerons un logement indéterminé, pour celleux qui le souhaitent</p>
<p> Les repas seront préparés et servis sur place.</p>
<h2>Comment se rendre à l'événement&nbsp;?</h2>
<p>Les Interludes se déroule à l'ENS Paris-Saclay : 4 avenue des Sciences, 91190 Gif-sur-Yvette. L'accueil se fait par l'entrée principale.</p>
<div>
<p class="centered">
<iframe width="750" height="400"
frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="https://www.openstreetmap.org/export/embed.html?bbox=2.1628072857856755%2C48.71179934847555%2C2.1672758460044865%2C48.71333201299027&amp;layer=mapnik" style="border: 1px solid black"></iframe><br/>
<small><a href="https://www.openstreetmap.org/#map=19/48.71257/2.16504">View Larger Map</a></small>
</p>
</div>
<div id="public-transport-info">
<div id="transport-tcl-bus-1">
Bus 9
</div>
<div id="transport-tcl-bus-2">
Bus 91.06
</div>
<div id="transport-tcl-bus-3">
Bus 91.10
</div>
<span id="transport-tcl-stop">Arrêt : Moulon</span>
</div>
<h2>Conditions pour dormir/manger</h2>
<p>Une réponse</p>
<h2>Comment sont réparties les activités&nbsp;?</h2>
<p>
La répartition est faite par un algorithme puis vérifiée à la main.
Dans la mesure du possible, l'algorithme essaie d'attribue au moins une activité par personne. Par conséquent, si vous ne mettez qu'une seule activité, vous avez plus de chance de l'avoir.
</p>
<p>
Les activités qui n'ont pas de limite de place (et toutes les activités avec moins de demande que de places) ne comptent pas pour ce système, donc vous pouvez les mettre et vous ne serez pas pénalisés.
</p>
<p>
Vous ne pourrez pas avoir deux activités qui se déroulent en même temps. Si l'une vous est attribué l'autre souhait sera ignoré.
</p>
<p>On n'a pas trouvé le code des années précédentes, mais je suspecte fortement que ce soit un algo similaire en départageant les égalités aléatoirement plutôt qu'au shotgun.</p>
<p>Plus précisément : l'algorithme se base sur le problème hôpital-résident&nbsp;:</p>
<ol>
<li>Il commence par essayer d'attribuer une activité à chaque participant.es au mieux possible, en utilisant la librairie matching de python. Les égalités sont départagées aléatoirement. Plus un choix est haut dans votre liste de souhait, plus vous avez de chance de vous le voir attribuer. Si vous n'avez qu'un seul choix, vous avez plus de chance de vous le voir attribué. (les participant.es avec un seul choix sont automatique placé.es avant ceux qui en ont plusieurs)</li>
<li>Toutes les activités attribuées sont supprimées, ainsi que les voeux résolus des joueurs.</li>
<li>Tant qu'il reste des place dans des activités et des participant.es qui veulent y participer, on recommence à l'étape 1.</li>
</ol>
<p>Le code est sur <a href="https://github.com/Imakoala/InterludesMatchings">github</a>, il ne marche pas encore parfaitement, et on risque de devoir bidouiller à la main en plus pour résoudre tous les cas particuliers (conflits d'horaires, activité présente plusieurs fois...).
<h2>Proposer une activité ?</h2>
<p>Une réponse</p>
{% endblock %}
\ No newline at end of file
{% if settings.contact_email_reversed %}
<h2>J'ai encore une question, je fais quoi ?</h2>
<p> Hésite pas à nous passer un mail à <span class="antispam">{{ settings.contact_email_reversed }}</span> pour nous poser tes questions !</p>
{% endif %}
{% endblock %}
......@@ -3,17 +3,80 @@
{% block nav_home %}current{% endblock %}
{% block "content" %}
<h2>Présentation</h2>
{% if settings.discord_link %}
<ul class="messagelist"><li class="info">
Rejoins notre <a href="{{ settings.discord_link }}">serveur discord</a> pour participer à l'événement.
</li></ul>
{% endif %}
<p>
Les interludes, ou interENS ludiques, regroupent annuellement
les étudiants des quatres ENS de France autour d'activités ludiques.
les étudiants des quatre ENS de France autour d'activités ludiques.
Jeux de plateau, jeux de rôles, jeux vidéos, murders et autres sont
à l'honneur durant ce week-end de trois jours.
</p>
<p>
Cette année, c'est au tour de l'ENS Ulm d'organiser les interludes.
L'événement a habituellement lieu en février mais à cause du COVID,
il a été retardé et est prévu le week-end du vendredi 9 au dimanche 11
avril 2021.
Cette année, c'est au tour de l'{{ settings.hosting_school }} d'organiser les interludes.
Elles auront lieu
{% if settings.date_start %}{% if settings.date_end %}
le week-end du <strong>{{ settings.date_start.day }}-{{ settings.date_end }}</strong>.
{% else %}
le week-end du <strong>{{ settings.date_start }}</strong>.
{% endif %}
{% else %}
à une date non-fixée.
{% endif %}
</p>
<h2>Inscriptions</h2>
<h3>Inscription à l'événement</h3>
<p>
L'inscription à l'événement, aux repas, à l'hebergement et aux activités se fait dans <a href="{% url 'inscription'%}">l'onglet inscription</a>
</p>
</p>
{% if settings.inscriptions_start and settings.inscriptions_end %}
<p>Les inscriptions aux activités seront ouvertes
du <strong>{{ settings.inscriptions_start|date:"l d F Y à H:i" }}</strong>
au <strong>{{ settings.inscriptions_end|date:"l d F Y à H:i" }}</strong>.
</p>
{% elif settings.inscriptions_start %}
<p>Les inscriptions aux activités ouvrirons le <strong>{{ settings.inscriptions_start|date:"l d F Y à H:i" }}</strong>.</p>
{% elif settings.inscriptions_end %}
<p>Les inscriptions fermerons le <strong>{{ settings.inscriptions_end|date:"l d F Y à H:i" }}</strong>.</p>
{% endif %}
<h2>Tarifs</h2>
<p>
Les tarifs sont différenciés entre salarié·es et non-salarié·es.
<ul>
<li><strong>Participation à l'événement :</strong> 6€/4€</li>
<li><strong>Repas (du vendredi soir au dimanche midi) :</strong> 3€/2€ par repas</li>
<li><strong>Repas du dimanche soir (à emporter) :</strong> 2€</li>
<li><strong>Logement :</strong> gratuit (chez les Saclaysien⋅nes)</li>
</ul>
</p>
<h2>Menu des repas</h2>
<p>
Cette année, le menu sera entièrement végétarien. Il sera annoncé bientôt.
<ul>
<li><strong>Vendredi soir :</strong> Pâtes forestières & crumble aux pommes</li>
<li><strong>Samedi matin :</strong>Petit dej'</li>
<li><strong>Samedi midi :</strong>Chili sin carne (avec des pâtes) & crème dessert</li>
<li><strong>Samedi soir :</strong>Gratin patate poireau & charlotte</li>
<li><strong>Dimanche matin :</strong>Petit dej'</li>
<li><strong>Dimanche midi :</strong>Wraps & cookie</li>
<li><strong>Dimanche soir (à emporter):</strong>Wraps & fruit (ou cookie)</li>
</ul>
<h2>Liens divers</h2>
<ul>
<li>Le code source de ce site est sur <a href="https://gitlab.crans.org/mediatek/site-kwei">gitlab</a>.</li>
<li>Un historique des interludes avec leurs visuels, site webs et photos est sur le <a
href="https://wiki.crans.org/VieBdl/InterLudes">wiki Crans de Paris-Saclay</a>.</li>
</ul>
{% endblock %}
......@@ -3,9 +3,26 @@
{% block nav_inscription %}current{% endblock %}
{% block "content" %}
<h2>Inscriptions</h2>
<p>
Les inscriptions ne sont pas encores ouvertes.
Nous communiquerons pas mail via les BDE des différentes écoles pour leur ouverture.
</p>
<h2>Inscriptions à l'événement</h2>
<p> L'inscription est fermée </p>
<h2>Inscriptions aux activités</h2>
{% if settings.inscriptions_not_open_yet %}
<p>Les inscriptions aux activités ne sont pas encores ouvertes.</p>
<p>Leur ouverture est prévue le <strong>{{ settings.inscriptions_start|date:"l d F Y à H:i" }}</strong>.</p>
<p>Nous communiquerons par mail via les BDE des différentes écoles pour leur ouverture.</p>
{% elif settings.inscriptions_have_closed %}
<p>Les inscriptions aux activités sont fermées.</p>
<p>
Les inscriptions cette année sont facultatives,
tu peux quand même rejoindre le {% if settings.discord_link %}<a href="{{ settings.discord_link }}">serveur discord</a>{% else %}serveur discord{% endif %} et participer aux jeux libres et
aux activités qui ne nécessitent pas d'inscription.
</p>
{% if settings.contact_email %}
<p>Pour tout problème, contacter&nbsp;:<br><span class="antispam">{{ settings.contact_email_reversed }}</span></p>
{% endif %}
{% else %}
<p>Les inscriptions aux activités ne sont pas encores ouvertes ou ont été fermées.</p>
<p>Nous communiquerons par mail via les BDE des différentes écoles pour leur ouverture.</p>
{% endif %}
{% endblock %}
{% extends "base.html" %}
{% load static %}
{% block nav_inscription %}current{% endblock %}
{% block "content" %}
<h2>Inscriptions</h2>
{% if settings.discord_link %}
<ul class="messagelist">
<li class="info">Rejoignez aussi <a href="{{ settings.discord_link }}">le serveur discord</a>,
C'est là que s'organiseront la plupart des activités.</li>
</ul>
{% endif %}
<form id="main_form" method="post" action="{% url 'inscription' %}">
{% csrf_token %}
Cette année, l'événement étant en distanciel, il est gratuit.
<p>{{ form.school.label_tag }} {{ form.school }}</p>
<h3>Choix d'activités</h3>
<p>Saissisez les activités auquelles vous voulez vous inscrire, par ordre de préférence.
Vous trouverez une description des activités sur <a href="{% url 'activites' %}">cette page</a>.
</p>
<p>
Vous pouvez revenir modifier votre choix jusqu'à la fermeture des
inscriptions{% if settings.inscriptions_end %} (le {{ settings.inscriptions_end|date:"l d F Y à H:i" }}){% endif %}.
</p>
<p>Si vous vous inscrivez à une activité qui nécessite préparation, nous communiquerons
votre email aux orgas pour qu'iels puissent vous contacter.
</p>
{% if formset.non_form_errors %}
{{ formset.non_form_errors }}
{% endif %}
{{ formset.management_data }}
{{ formset.management_form }}
{% for form in formset %}
<div class="activity-form flex">
{{ form.as_p }}
<button class="button delete-activity" style="align-self: center; flex-grow: 0;">Supprimer</button>
</div>
{% endfor %}
<button class="button" id="add-activity">Ajouter une activité</button>
<div class="flex">
<input type="submit" value="Valider">
<a class="button" href="{% url 'profile' %}">Annuler</a>
</div>
</form>
<script>
const button_add_activity = document.querySelector("#add-activity");
const button_submit_form = document.querySelector('[type="submit"]');
const activity_form = document.getElementsByClassName("activity-form");
const main_form = document.querySelector("#main_form");
const total_forms = document.querySelector("#id_form-TOTAL_FORMS");
const form_regex = /form-(\d*)-/g;
var form_count = activity_form.length - 1;
function add_new_form(event) {
// adds a new activity form when clicking on the + button
event.preventDefault();
// clone the first form and replaces it's id
const new_form = activity_form[0].cloneNode(true);
form_count++;
new_form.innerHTML = new_form.innerHTML.replace(form_regex, `form-${form_count}-`);
// add it and increment form total
main_form.insertBefore(new_form, button_add_activity);
new_form.querySelector("select").value = "";
total_forms.setAttribute("value", `${form_count+1}`);
}
button_add_activity.addEventListener("click", add_new_form);
function delete_form(event) {
if (!event.target.classList.contains("delete-activity")) return;
event.preventDefault();
if (form_count == 0) {
// don't delete the first element
activity_form[0].querySelector("select").value = "";
return;
}
event.target.parentElement.remove();
form_count--;
total_forms.setAttribute("value", `${form_count+1}`);
// update form numbers
let count = 0;
for (const form of activity_form) {
// the replace changes the field value
// so we save and restore it
const select = form.querySelector("select");
const value = select.value;
form.innerHTML = form.innerHTML.replace(form_regex, `form-${count++}-`);
form.querySelector("select").value = value;
}
}
main_form.addEventListener("click", delete_form);
</script>
{% endblock %}
......@@ -4,21 +4,59 @@
{% block nav_inscription %}current{% endblock %}
{% block "content" %}
<h2>Inscriptions</h2>
<form id="main_form" method="post" action="{% url 'inscription' %}">
{% csrf_token %}
{{ form.as_p }}
{% csrf_token %}
<h3>Choix d'activités</h3>
<h2>Inscription à l'événement</h2>
<p>L'inscription à l'événement,à l'hébergement
et aux repas se fait sur cette page.</p>
<h3>École</h3>
<p>{{ form.school.label_tag }} {{ form.school }}</p>
<h3>Repas</h3>
<p>Vous pouvez vous inscrire aux repas ci-dessous :</p>
<ul>
<li>Vendredi soir {{ form.meal_friday_evening }}</li>
<li>Petit dej' du samedi {{ form.meal_saturday_morning }}</li>
<li>Samedi midi {{ form.meal_saturday_midday }}</li>
<li>Samedi soir {{ form.meal_saturday_evening }}</li>
<li>Dimanche matin {{ form.meal_sunday_morning }}</li>
<li>Dimanche midi {{ form.meal_sunday_midday }}</li>
<li>Dimanche soir (à emporter) {{ form.meal_sunday_evening }}</li>
</ul>
<p>Les menus sont disponibles sur la <a href="{% url 'home' %}">page d'accueil</a>
<h3>Hébergement</h3>
<p>Pour l'hébergement, nous proposons le logement chez les Saclaysien⋅nes (comme aux InterQ). Si vous souhaitez un hébergement, cochez la case suivante : {{ form.sleeps }} </p>
<p>L'idée est de proposer aux normalien⋅nes de Saclay de loger celleux des autres écoles. Si vous avez besoin d'un hébergement vous pouvez compléter <a href="https://framaforms.org/logement-interludes-2023-extes-1668450149">ce questionnaire</a>. Si vous voulez loger quelqu'un <a href="https://framaforms.org/logement-interludes-2023-cote-hebergement-1668438253">c'est ici</a>. Merci à ceux qui veulent bien accueillir !</p>
<p>Saissisez les activités auquelles vous voulez vous inscrire, par ordre de préférence.</p>
<h3>Tarif</h3>
<p>Le tarif est différent selon si vous percevez un salaire ou non. Êtes-vous payé⋅e : {{ form.paid }}</p>
<h2>Inscription aux activités</h2>
{% if formset.non_form_errors %}
{{ formset.non_form_errors }}
{% endif %}
{{ formset.management_data }}
{{ formset.management_form }}
{% if settings.activity_inscriptions_open %}
<p>Vous pouvez vous inscrire à certaines activités sur cette page. La
plupart des activités ne demandent pas d'inscription et seront en libre
accès durant tout l'événement, mais certaines demandent une inscription
à l'avance.</p>
<h3>Choix d'activités</h3>
<p>Saissisez les activités auquelles vous voulez vous inscrire, <strong>par ordre de préférence</strong>.
Vous trouverez une description des activités sur <a href="{% url 'activites' %}">cette page</a>.</p>
<p>Si vous vous inscrivez à une activité qui nécessite préparation, nous communiquerons
votre email aux orgas pour qu'iels puissent vous contacter.</p>
{% for form in formset %}
<div class="activity-form flex">
{{ form.as_p }}
......@@ -26,10 +64,33 @@
</div>
{% endfor %}
<button class="button" id="add-activity">Ajouter une activité</button>
{% else %}
<p>Vous pourrez vous inscrire à certaines activités sur cette page. La
plupart des activités ne demandent pas d'inscription et seront en libre
accès durant tout l'événement, mais certaines demandent une inscription
à l'avance.{% if settings.inscriptions_end %} Les inscriptions aux activités ouvrent le {{ settings.inscriptions_start|date:"l d F Y à H:i" }}){% endif %}. </p>
{% endif %}
<h3>Expérience</h3>
<p>Pour vous attribuer au mieux les activités, nous aimerions connaître votre exprérieurnce en murder (jeux de rôles grandeurs nature). Si vous avez perdu le compte, 42 conviendra très bien ;) </p>
Nombre de murder déjà jouées : {{ form.nb_murder }}
<h2>Commentaire</h2>
<p>Pour préciser toutes les informations qui vous semblent utiles comme les allergies alimentaires.</p>
{{ form.comment }}
<p>
Vous pouvez revenir modifier vos choix jusqu'à la fermeture des
inscriptions{% if settings.inscriptions_end %} (le {{ settings.inscriptions_end|date:"l d F Y à H:i" }}){% endif %}.
</p>
<div class="flex">
<input type="submit" value="Valider">
<a class="button" href="{% url 'accounts:profile' %}">Annuler</a>
<a class="button" href="{% url 'profile' %}">Annuler</a>
</div>
</form>
......
......@@ -4,7 +4,10 @@
{% block "content" %}
<h2>Inscriptions</h2>
<p>Vous devez être connecté pour pouvoir vous inscrire à l'événement.</p>
<p>Aller à la page de <a href="{% url 'accounts:login' %}">connexion</a> pour vous connectez
<p>Vous devez être connecté pour pouvoir vous inscrire</p>
<p>Aller à la page de <a href="{% url 'accounts:login' %}">connexion</a> pour vous connecter
ou à celle de <a href="{% url 'accounts:create' %}">création de compte</a> si vous n'avez pas de compte.</p>
{% if settings.inscriptions_end %}
<p>Les inscriptions seront ouvertes jusqu'au {{ settings.inscriptions_end|date:"l d F Y à H:i" }})</p>
{% endif %}
{% endblock %}
{% extends "base.html" %}
{% block nav_profile %}current{% endblock %}
{% block "content" %}
<h2>Mon compte</h2>
<p>Connecté en tant que {{ user.first_name }} {{ user.last_name }} ({{ user.email }})</p>
{% if user.is_superuser %}
<ul class="messagelist">
<li class="info">
Vous avez les droits d'administrateurs.
<br>Aller à la <a href="{% url 'admin_pages:index' %}">page d'administration du site</a>
{% if user.is_staff %}
<br>Aller à la <a href="{% url 'admin:index' %}">page d'administration de django</a>
(N'y modifier rien sy vous n'êtes pas sûrs de vous)
{% endif %}
</li>
</ul>{% endif %}
{% if user.profile.is_registered %}
<strong>Mon inscription&nbsp;:</strong>
{% if settings.inscriptions_open and settings.inscriptions_end %}
modifiable jusqu'au {{ settings.inscriptions_end|date:"l d F Y à H:i" }}
{% endif %}
<ul>
<li>Tarif personnalisé : {{ user.profile.cost }}€</li>
<li>{% if user.profile.sleeps %}Inscrit pour dormir sur place{% else %}Non inscrit pour dormir sur place{% endif %}</li>
{% comment "pas de tasse" %}<li>{% if user.profile.mug %}Commandse une tasse{% else %}Ne commande pas de tasse{% endif %}</li>{% endcomment%}
{% if user.profile.nb_meals != 0 %}
<li>Inscrit à {{ user.profile.nb_meals }} repas :</li>
<ul>
{% if user.profile.meal_friday_evening == True %}<li>Vendredi soir</li>{% endif %}
{% if user.profile.meal_saturday_morning == True %}<li>Petit dej' du samedi</li>{% endif %}
{% if user.profile.meal_saturday_midday == True %}<li>Samedi midi</li>{% endif %}
{% if user.profile.meal_saturday_evening == True %}<li>Samedi soir</li>{% endif %}
{% if user.profile.meal_sunday_morning == True %}<li>Petit dej' du dimanche</li>{% endif %}
{% if user.profile.meal_sunday_midday == True %}<li>Dimanche midi</li>{% endif %}
{% if user.profile.meal_sunday_evening == True %}<li>Dimanche soir (à emporter)</li>{% endif %}
</ul>
{% else %}
<li>Inscrit à aucun repas</li>
{% endif %}
{% if settings.activities_allocated %}
{% if my_choices %}
<li>Inscrit à {{ my_choices|length }} activités&nbsp;:
<ul>
{% for choice in my_choices %}
<li><a href="{% url 'activites' %}#{{ choice.slot.activity.slug }}">{{ choice.slot }}</a>
{% if choice.slot.on_planning %}
(le {{ choice.slot.start|date:"l à H:i" }}<!-- en {{ choice.slot.room }} -->)
{% endif %}
</li>
{% endfor %}
</ul>
</li>
{% else %}
<li>Inscrit à aucune activité</li>
{% endif %}
{% else %}
{% if my_choices %}
<li>{{ my_choices|length }} activités souhaitées&nbsp;:
<ol>
{% for choice in my_choices %}
<li><a href="{% url 'activites' %}#{{ choice.slot.activity.slug }}">{{ choice.slot }}</a>
{% if choice.slot.on_planning %}
(le {{ choice.slot.start|date:"l à H:i" }}<!-- en {{ choice.slot.room }} -->)
{% endif %}
</li>
{% endfor %}
</ol>
</li>
{% else %}
<li>Aucune activité souhaitée</li>
{% endif %}
{% endif %}
</ul>
{% else %}
<strong>Vous n'avez pas encore renseigné vos choix d'activités.</strong>
{% if not settings.inscriptions_open %}
{% if settings.inscriptions_not_open_yet %}
<p>Les inscriptions aux activités ne sont pas encores ouvertes. Elles ouvrirons le <strong>{{ settings.inscriptions_start|date:"l d F Y à H:i" }}</strong>.</p>
{% elif settings.inscriptions_have_closed %}
<p>Les inscriptions aux activités sont fermées.</p>
{% else %}
<p>Les inscriptions aux activités ne sont pas encores ouvertes ou ont été fermées.</p>
{% endif %}
{% elif settings.inscriptions_end %}
<p>les inscriptions aux activités sont ouvertes jusqu'au {{ settings.inscriptions_end|date:"l d F Y à H:i" }}).</p>
{% endif %}
<br><br>
{% endif %}
<div class="flex wrap">
{% if user.profile.is_registered %}
{% if settings.inscriptions_open %}
<a class="button" href="{% url 'desinscription' %}">Me désinscrire</a>
<a class="button" href="{% url 'inscription' %}">Modifier mon inscription</a>
{% else %}
<script type="text/javascript">
function clicked() {
if (confirm(
'Les inscriptions étant fermées, vous ne pourrez pas vous réinscrire.\nVoulez vous vraiment vous désinscrire?'
))
window.location = "{% url 'desinscription' %}";
}
</script>
<button class="button" onclick="clicked();">Me désinscrire</button>
{% endif %}
{% elif settings.inscriptions_open %}
<a class="button" href="{% url 'inscription' %}">S'inscrire</a>
{% endif %}
<a class="button" href="{% url 'accounts:update' %}">Modifier mes informations</a>
<a class="button" href="{% url 'accounts:logout' %}">Déconnexion</a>
</div>
{% endblock %}
from django.conf import settings
from django.conf.urls.static import static
from django.contrib.sitemaps.views import sitemap
from django.views.generic import RedirectView
from django.urls import path, include
......@@ -7,14 +9,21 @@ from home import views
sitemaps = {"static_pages": views.StaticViewSitemap}
urlpatterns = [
path('', views.HomeView.as_view(), {"template": "home.html"}, name = 'home'),
path('', views.HomeView.as_view(), name = 'home'),
path('inscription/', views.RegisterView.as_view(), name = 'inscription'),
path('activites/', views.ActivityView.as_view(), {"template":"activites.html"}, name = 'activites'),
path('faq/', views.FAQView.as_view(), {"template":"faq.html"}, name = 'FAQ'),
path('desinscription/', views.UnregisterView.as_view(), name="desinscription"),
path('activites/', views.ActivityView.as_view(), name = 'activites'),
path('activites/nouvelle/', views.ActivitySubmissionView.as_view(), name = 'activity_submission'),
path('faq/', views.FAQView.as_view(), name = 'FAQ'),
path("profil/", views.ProfileView.as_view(), name="profile"),
path('favicon.ico', RedirectView.as_view(url='/static/imgs/favicon.ico')),
path(
'sitemap.xml', sitemap, {'sitemaps': sitemaps},
name='django.contrib.sitemaps.views.sitemap'
),
path('accounts/', include("accounts.urls")),
path('admin_pages/', include(('admin_pages.urls', 'admin_pages'), namespace="admin_pages")),
path('comptes/', include("accounts.urls")),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
from datetime import timedelta
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.sitemaps import Sitemap
from django.forms import formset_factory
from django.shortcuts import redirect, render
from django.urls import reverse
from django.views.generic import UpdateView, TemplateView, View
from django.urls import reverse, reverse_lazy
from django.views.generic import FormView, RedirectView, TemplateView, View
from home.models import ActivityList, InterludesActivity
from home.forms import ActivityForm, BaseActivityFormSet, InscriptionForm
from home import models
from home.forms import ActivityForm, ActivitySubmissionForm, BaseActivityFormSet, InscriptionForm
from site_settings.models import SiteSettings
# ==============================
# Site static pages
# ==============================
class HomeView(TemplateView):
"""Vue pour la page d'acceuil"""
template_name = "home.html"
def get_planning_context():
"""Returns the context dict needed to display the planning"""
settings = SiteSettings.load()
context = dict()
context['planning'] = models.SlotModel.objects.filter(on_planning=True).order_by("title")
if settings.date_start is not None:
context['friday'] = settings.date_start.day
context['saturday'] = (settings.date_start + timedelta(days=1)).day
context['sunday'] = (settings.date_start + timedelta(days=2)).day
else:
context['friday'] = 1
context['saturday'] = 2
context['sunday'] = 3
return context
class ActivityView(TemplateView):
"""Vue pour la liste des activités"""
......@@ -23,7 +44,8 @@ class ActivityView(TemplateView):
def get_context_data(self, **kwargs):
"""ajoute la liste des activités au contexte"""
context = super(ActivityView, self).get_context_data(**kwargs)
context['activities'] = InterludesActivity.objects.filter(display=True).order_by("title")
context['activities'] = models.ActivityModel.objects.filter(display=True).order_by("title")
context.update(get_planning_context())
return context
......@@ -32,6 +54,33 @@ class FAQView(TemplateView):
template_name = "faq.html"
# ==============================
# Profile and registration
# ==============================
class ProfileView(LoginRequiredMixin, TemplateView):
"""Vue des actions de gestion de son profil"""
template_name = "profile.html"
redirect_field_name = "next"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
settings = SiteSettings.load()
if settings.activities_allocated:
my_choices = models.ActivityChoicesModel.objects.filter(
participant=self.request.user.profile,
accepted=True
)
else:
my_choices = models.ActivityChoicesModel.objects.filter(
participant=self.request.user.profile
)
context["my_choices"] = my_choices
return context
class RegisterClosed(TemplateView):
"""Vue pour quand les inscriptions ne sont pas ouvertes"""
template_name = "inscription/closed.html"
......@@ -46,45 +95,58 @@ class RegisterUpdateView(LoginRequiredMixin, TemplateView):
template_name = "inscription/form.html"
form_class = InscriptionForm
formset_class = formset_factory(form=ActivityForm, extra=3, formset=BaseActivityFormSet)
success_url = reverse_lazy("profile")
@staticmethod
def get_activities(participant):
activities = ActivityList.objects.filter(participant=participant).order_by("priority")
return [{"activity": act.activity} for act in activities]
def get_slots(participant):
activities = models.ActivityChoicesModel.objects.filter(participant=participant).order_by("priority")
return [{"slot": act.slot} for act in activities]
@staticmethod
def set_activities(participant, formset):
# delete old activites
ActivityList.objects.filter(participant=participant).delete()
models.ActivityChoicesModel.objects.filter(participant=participant).delete()
priority = 0
for form in formset:
data = form.cleaned_data
if data:
activity = data["activity"]
ActivityList(priority=priority, participant=participant, activity=activity).save()
slot = data["slot"]
models.ActivityChoicesModel(
priority=priority, participant=participant, slot=slot
).save()
priority += 1
def get(self, request, *args, **kwargs):
participant = request.user.profile
activities = self.get_activities(participant)
slots = self.get_slots(participant)
form = self.form_class(instance=participant)
formset = self.formset_class(initial=activities)
formset = self.formset_class(initial=slots)
context = {"form": form, "formset": formset}
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
settings = SiteSettings.load()
form = self.form_class(request.POST, instance=request.user.profile)
formset = self.formset_class(request.POST)
if not (form.is_valid() and formset.is_valid()):
context = {"form": form, "formset": formset}
return render(request, self.template_name, context)
if settings.activity_inscriptions_open : # meal + sleep + activities open
formset = self.formset_class(request.POST)
if not (form.is_valid() and formset.is_valid()):
context = {"form": form, "formset": formset}
return render(request, self.template_name, context)
self.set_activities(request.user.profile, formset)
else: # only meal and sleep open
if not form.is_valid():
participant = request.user.profile
slots = self.get_slots(participant)
formset = self.formset_class(initial=slots)
context = {"form": form, "formset": formset}
return render(request, self.template_name, context)
form.save()
self.set_activities(request.user.profile, formset)
messages.success(request, "Votre inscription a bien été enregistrée")
return redirect("accounts:profile", permanent=False)
return redirect(self.success_url, permanent=False)
class RegisterView(View):
"""Vue pour l'inscription
......@@ -98,6 +160,73 @@ class RegisterView(View):
return RegisterUpdateView.as_view()(request)
class UnregisterView(LoginRequiredMixin, RedirectView):
pattern_name = "profile"
def get_redirect_url(self, *args, **kwargs):
participant = self.request.user.profile
participant.is_registered = False
participant.save()
messages.success(self.request, "Vous avez été désinscrit")
return reverse(self.pattern_name)
# ==============================
# Activity Submission Form
# ==============================
class ActivitySubmissionView(LoginRequiredMixin, FormView):
"""Vue pour proposer une activité"""
template_name = "activity_submission.html"
form_class = ActivitySubmissionForm
success_url = reverse_lazy("profile")
@staticmethod
def submission_check():
"""Vérifie si le formulaire est ouvert ou non"""
settings = SiteSettings.load()
return settings.activity_submission_open
def get_initial(self):
init = super().get_initial()
user = self.request.user
init.update({
"host_name": "{} {}".format(user.first_name, user.last_name),
"host_email": user.email,
})
return init
def not_open(self, request):
"""Appelé quand le formulaire est désactivé"""
messages.error(request, "La soumission d'activité est desactivée")
return redirect(self.success_url, permanent=False)
def get(self, request, *args, **kwargs):
if not self.submission_check():
return self.not_open(request)
return super().get(self, request, *args, **kwargs)
def post(self, request, *args, **kwargs):
if not self.submission_check():
return self.not_open(request)
form = self.form_class(request.POST)
if not form.is_valid():
context = self.get_context_data()
context["form"] = form
return render(request, self.template_name, context)
form.save()
messages.success(request, "Votre activité a bien été enregistrée. Elle sera affichée sur le site après relecture par les admins.")
return redirect(self.success_url, permanent=False)
# ==============================
# Sitemap
# ==============================
class StaticViewSitemap(Sitemap):
"""Vue générant la sitemap.xml du site"""
changefreq = 'monthly'
......
from django.contrib.auth import get_user_model
from cas_server.auth import AuthUser, DjangoAuthUser
class InterLudesAuthUser(DjangoAuthUser): # pragma: no cover
"""
Overrides DjangoAuthUser constructor
"""
def __init__(self, username):
User = get_user_model()
try:
self.user = User.objects.get(email=username)
except User.DoesNotExist:
pass
super(DjangoAuthUser, self).__init__(username)
\ No newline at end of file
# Secrets that must be changed in production
SECRET_KEY = "i*4$=*fa(644(*!9m2)0-*&sows2uz$b^brb(=)elfn3+y6#1n"
ADMINS = [("superuser", "superuser@admin.fr"),]
DB_NAME = "db.sqlite3"
SERVER_EMAIL = "root@localhost"
DEFAULT_FROM_EMAIL = "webmaster@localhost"
EMAIL_HOST = "localhost"
EMAIL_PORT = 587
EMAIL_HOST_USER = None
EMAIL_HOST_PASSWORD = None
......@@ -19,16 +19,60 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'tx$xi%n!8cghirp377zb)gd24g#=&w*ik(bx2h(i8ji0_&9_5l'
# SECURITY WARNING: don't run with debug turned on in production!
try:
from . import secret
except ImportError:
raise ImportError(
"The interludes/secret.py file is missing.\n"
"Run 'make secret' to generate a secret."
)
def import_secret(name):
"""
Shorthand for importing a value from the secret module and raising an
informative exception if a secret is missing.
"""
try:
return getattr(secret, name)
except AttributeError:
raise RuntimeError("Secret missing: {}".format(name))
SECRET_KEY = import_secret("SECRET_KEY")
DB_NAME = import_secret("DB_NAME")
ADMINS = import_secret("ADMINS")
SERVER_EMAIL = import_secret("SERVER_EMAIL")
DEFAULT_FROM_EMAIL = import_secret("DEFAULT_FROM_EMAIL")
EMAIL_HOST = import_secret("EMAIL_HOST")
EMAIL_PORT = import_secret("EMAIL_PORT")
EMAIL_HOST_USER = import_secret("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = import_secret("EMAIL_HOST_PASSWORD")
EMAIL_USE_SSL = True
# FIXME - set to False in production
DEBUG = True
ADMINS = [("respos", "respointerludes2021@ens.psl.eu"),]
# FIXME - set hosts in production
ALLOWED_HOSTS = []
if DEBUG:
# This will display emails in Console.
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
else:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
SECURE_SSL_REDIRECT = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_SECONDS = 3600
SECURE_HSTS_PRELOAD = True
SECURE_REFERRER_POLICY = "same-origin"
# Application definition
......@@ -42,9 +86,10 @@ INSTALLED_APPS = [
'django.contrib.sitemaps',
'home.apps.HomeConfig',
'admin_pages.apps.AdminPagesConfig',
'accounts.apps.AccountsConfig',
'site_settings.apps.SiteSettingsConfig',
'shared.apps.SharedConfig'
'shared.apps.SharedConfig',
]
MIDDLEWARE = [
......@@ -78,6 +123,8 @@ TEMPLATES = [
WSGI_APPLICATION = 'interludes.wsgi.application'
# Auto primary key type
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
......@@ -85,7 +132,7 @@ WSGI_APPLICATION = 'interludes.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'NAME': os.path.join(BASE_DIR, DB_NAME),
}
}
......@@ -97,21 +144,13 @@ AUTHENTICATION_BACKENDS = (
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_USER_MODEL = 'accounts.EmailUser'
AUTH_PROFILE_MODULE = 'home.InterludesParticipant'
AUTH_PROFILE_MODULE = 'home.ParticipantModel'
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
{ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', },
{ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', },
{ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', },
{ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', },
]
# Session time in seconds
......@@ -137,9 +176,17 @@ USE_TZ = True
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
LOGIN_URL = "accounts:login"
LOGIN_REDIRECT_URL = "accounts:profile"
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
LOGIN_URL = 'accounts:login'
LOGIN_REDIRECT_URL = 'profile'
# Prefix to mails to admins
EMAIL_SUBJECT_PREFIX = '[DJANGO WEBLUDES] '
# Signature to mails to admins
EMAIL_SIGNATURE = '-- Site Interludes (mail généré automatiquement)'
# This will display emails in Console.
# FIXME: remove in production
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# Prefix to mails to users
USER_EMAIL_SUBJECT_PREFIX = "[interludes] "
......@@ -16,7 +16,10 @@ Including another URLconf
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('home.urls')),
]
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + [
path('admin/', admin.site.urls),
path('', include('home.urls')),
]
Django~=3.0.8
\ No newline at end of file
Django~=3.2.7
import csv
from shared.views import CSVWriteView
from django.http import HttpResponse
class CSVWriteViewForAdmin(CSVWriteView):
def get_values(self):
return self.queryset.values()
class ExportCsvMixin:
"""
class abstraite pour permettre l'export CSV rapide d'un modele
utiliser csv_export_exclude pour exclure des colonnes du fichier généré
"""
def export_as_csv(self, request, queryset):
"""renvoie un fichier CSV contenant l'information du queryset"""
meta = self.model._meta
field_names = [field.name for field in meta.fields]
if self.csv_export_exclude:
field_names = [field for field in field_names if not field in self.csv_export_exclude]
filename = None
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
writer = csv.writer(response)
csv_export_exclude = []
csv_export_fields = None
writer.writerow(field_names)
for obj in queryset:
writer.writerow([getattr(obj, field) for field in field_names])
def get_filename(self):
if self.filename:
return self.filename
return str(self.model._meta)
return response
def export_as_csv(self, request, queryset):
"""renvoie un fichier CSV contenant l'information du queryset"""
view = CSVWriteViewForAdmin(
request=request, queryset=queryset, model=self.model,
filename=self.get_filename(), exclude_fields=self.csv_export_exclude,
fields=self.csv_export_fields,
)
return view.get(request)
export_as_csv.short_description = "Exporter au format CSV"
actions = ["export_as_csv"]
csv_export_exclude = None
from django.db import models
from django.contrib.auth.base_user import BaseUserManager
# Create your models here.
def normalize_email(email: str) -> str:
"""Normalizes an email address"""
return BaseUserManager.normalize_email(email)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.