From 4a3e09126867349e307f66b048ffd84058888b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Wed, 3 Aug 2016 15:53:35 +0200 Subject: [PATCH 01/35] =?UTF-8?q?Mise=20=C3=A0=20jour=20des=20acc=C3=A8s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - La vue d'édition du profil n'est plus accessible aux non-COF - Le calendrier dynamique étant réservé aux adhérents, le lien vers l'inscription est caché aux non-adhérents. --- gestioncof/templates/home.html | 2 +- gestioncof/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gestioncof/templates/home.html b/gestioncof/templates/home.html index bcec273c..7616664f 100644 --- a/gestioncof/templates/home.html +++ b/gestioncof/templates/home.html @@ -45,7 +45,6 @@ {% endfor %} {% endif %} - {% endif %}

Divers

@@ -57,6 +56,7 @@ {% if not user.profile.login_clipper %}
  • Changer mon mot de passe
  • {% endif %}
    + {% endif %} {% if user.profile.is_buro %}
    diff --git a/gestioncof/views.py b/gestioncof/views.py index 378cb44d..be0adb35 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -295,7 +295,7 @@ def survey_status(request, survey_id): "form": form}) -@login_required +@cof_required def profile(request): success = False if request.method == "POST": From 5a1d854bb1cf7b978242a888359543fdbedc27a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Wed, 3 Aug 2016 15:59:05 +0200 Subject: [PATCH 02/35] Petite correction de style --- gestioncof/templates/calendar_subscription.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gestioncof/templates/calendar_subscription.html b/gestioncof/templates/calendar_subscription.html index 52f6d492..5f0bc988 100644 --- a/gestioncof/templates/calendar_subscription.html +++ b/gestioncof/templates/calendar_subscription.html @@ -37,7 +37,7 @@ souscrire aux événements du COF et/ou aux spectacles BdA.
    {% csrf_token %} {{ form.as_p }} - +
    {% endblock %} From 45385be556fdc61514bc7d70fff07f4db319ffa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Wed, 3 Aug 2016 17:54:12 +0200 Subject: [PATCH 03/35] Meilleure gestion des liens sur la page d'accueil MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Le lien vers l'inscription à un tirage BdA disparaît après sa fermeture - Les liens “mes places” et “revente” n'apparaissent qu'après la fermeture du tirage. - Le lien vers les ratios des demandes est déplacé dans la partie “Gestion des tirages” pour servir d'archive au BdA. Fixes #55 --- gestioncof/templates/home.html | 16 +++++++++++----- gestioncof/views.py | 4 +++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/gestioncof/templates/home.html b/gestioncof/templates/home.html index 7616664f..9d32c40c 100644 --- a/gestioncof/templates/home.html +++ b/gestioncof/templates/home.html @@ -37,10 +37,13 @@ {% for tirage in open_tirages %} {% endfor %}
    @@ -49,7 +52,7 @@

    Divers

    {% endfor %} {% else %} diff --git a/gestioncof/views.py b/gestioncof/views.py index be0adb35..3b0a3104 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -14,6 +14,7 @@ from django.http import Http404, HttpResponse from django.contrib.auth.decorators import login_required from django.contrib.auth.views import login as django_login_view from django.contrib.auth.models import User +from django.utils import timezone import django.utils.six as six from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \ @@ -40,7 +41,8 @@ def home(request): Survey.objects.filter(survey_open=True, old=False).all(), "open_events": Event.objects.filter(registration_open=True, old=False).all(), - "open_tirages": Tirage.objects.filter(active=True).all()} + "open_tirages": Tirage.objects.filter(active=True).all(), + "now": timezone.now()} return render(request, "home.html", data) From 19456756e4cab8b663ade488b3ab8b38bc441e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 6 Aug 2016 15:34:01 +0200 Subject: [PATCH 04/35] =?UTF-8?q?Cache=20les=20vieux=20=C3=A9v=C3=A9nement?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gestioncof/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gestioncof/views.py b/gestioncof/views.py index be0adb35..ed35efa7 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -188,7 +188,7 @@ def update_event_form_comments(event, form, registration): @login_required def event(request, event_id): event = get_object_or_404(Event, id=event_id) - if not event.registration_open: + if (not event.registration_open) or event.old: raise Http404 success = False if request.method == "POST": From 770162463d3e7b1b4b361931b86f8b798953109e Mon Sep 17 00:00:00 2001 From: Qwann Date: Sun, 7 Aug 2016 19:47:14 +0200 Subject: [PATCH 05/35] =?UTF-8?q?retrait=20des=20fl=C3=A8ches=20pour=20les?= =?UTF-8?q?=20champs=20num=20en=20lecture=20seule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gestioncof/static/css/cof.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gestioncof/static/css/cof.css b/gestioncof/static/css/cof.css index 5db3c9bb..b8f6e7f8 100644 --- a/gestioncof/static/css/cof.css +++ b/gestioncof/static/css/cof.css @@ -825,6 +825,16 @@ input#search_autocomplete:focus { color: #343a4a; } +input[type=number][readonly] { + -moz-appearance:textfield; +} + +input[type=number][readonly]::-webkit-inner-spin-button, +input[type=number][readonly]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + .autocomplete { margin-bottom:5px; } From b60b9f4e17827fd4d79c6b35b1d247b0ea571e0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sun, 7 Aug 2016 18:47:59 +0200 Subject: [PATCH 06/35] Utilisation d'un formset pour l'inscription - Changements mineurs dans `AdminEventForm` - Ajout d'une base pour le formset : `BaseEventRegistrationFormset` - Adaptation des vues de l'inscription et suppression d'une vue inutile. --- gestioncof/forms.py | 32 +++++-- gestioncof/templates/registration_form.html | 9 +- gestioncof/views.py | 95 ++++++--------------- 3 files changed, 54 insertions(+), 82 deletions(-) diff --git a/gestioncof/forms.py b/gestioncof/forms.py index 15db25ce..7aac9e5f 100644 --- a/gestioncof/forms.py +++ b/gestioncof/forms.py @@ -8,6 +8,7 @@ from django import forms from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User from django.forms.widgets import RadioSelect, CheckboxSelectMultiple +from django.forms.formsets import BaseFormSet from django.db.models import Max from gestioncof.models import CofProfile, EventCommentValue, \ @@ -263,17 +264,15 @@ STATUS_CHOICES = (('no', 'Non'), class AdminEventForm(forms.Form): - status = forms.ChoiceField(label="Inscription", + status = forms.ChoiceField(label="Inscription", initial="no", choices=STATUS_CHOICES, widget=RadioSelect) def __init__(self, *args, **kwargs): - event = kwargs.pop("event") - self.event = event + self.event = kwargs.pop("event") registration = kwargs.pop("current_registration", None) - current_choices = \ - registration.options.all() if registration is not None\ - else [] - paid = kwargs.pop("paid", None) + current_choices, paid = \ + (registration.options.all(), registration.paid) \ + if registration is not None else ([], None) if paid is True: kwargs["initial"] = {"status": "paid"} elif paid is False: @@ -288,7 +287,7 @@ class AdminEventForm(forms.Form): else: choices[choice.event_option.id].append(choice.id) all_choices = choices - for option in event.options.all(): + for option in self.event.options.all(): choices = [(choice.id, choice.value) for choice in option.choices.all()] if option.multi_choices: @@ -310,7 +309,7 @@ class AdminEventForm(forms.Form): initial=initial) field.option_id = option.id self.fields["option_%d" % option.id] = field - for commentfield in event.commentfields.all(): + for commentfield in self.event.commentfields.all(): initial = commentfield.default if registration is not None: try: @@ -338,6 +337,21 @@ class AdminEventForm(forms.Form): yield (self.fields[name].comment_id, value) +class BaseEventRegistrationFormset(BaseFormSet): + def __init__(self, *args, **kwargs): + self.events = kwargs.pop('events') + self.current_registrations = kwargs.pop('current_registrations', None) + self.extra = len(self.events) + super(BaseEventRegistrationFormset, self).__init__(*args, **kwargs) + + def _construct_form(self, index, **kwargs): + kwargs['event'] = self.events[index] + if self.current_registrations is not None: + kwargs['current_registration'] = self.current_registrations[index] + return super(BaseEventRegistrationFormset, self)._construct_form( + index, **kwargs) + + class CalendarForm(forms.ModelForm): subscribe_to_events = forms.BooleanField( initial=True, diff --git a/gestioncof/templates/registration_form.html b/gestioncof/templates/registration_form.html index b9699647..1eb16393 100644 --- a/gestioncof/templates/registration_form.html +++ b/gestioncof/templates/registration_form.html @@ -12,16 +12,15 @@ {{ user_form | bootstrap }} {{ profile_form | bootstrap }} - {% if event_forms %}
    - {% for event_form in event_forms %} + {{ event_formset.management_form }} + {% for event_form in event_formset %}
    -

    Inscription {{ event_form.event.title }} :

    +

    Inscription {{ event_form.event.title }} :

    - {{ event_form | bootstrap }} + {{ event_form | bootstrap }}
    {% endfor %} - {% endif %} {% if login_clipper or member %} {% endif %} diff --git a/gestioncof/views.py b/gestioncof/views.py index ed35efa7..1b8019a2 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -14,6 +14,7 @@ from django.http import Http404, HttpResponse from django.contrib.auth.decorators import login_required from django.contrib.auth.views import login as django_login_view from django.contrib.auth.models import User +from django.forms.models import formset_factory import django.utils.six as six from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \ @@ -27,7 +28,8 @@ from gestioncof.models import CofProfile, Clipper from gestioncof.decorators import buro_required, cof_required from gestioncof.forms import UserProfileForm, EventStatusFilterForm, \ SurveyForm, SurveyStatusFilterForm, RegistrationUserForm, \ - RegistrationProfileForm, AdminEventForm, EventForm, CalendarForm + RegistrationProfileForm, AdminEventForm, EventForm, CalendarForm, \ + BaseEventRegistrationFormset from bda.models import Tirage, Spectacle @@ -313,49 +315,12 @@ def registration_set_ro_fields(user_form, profile_form): profile_form.fields['login_clipper'].widget.attrs['readonly'] = True -@buro_required -def registration_form(request, login_clipper=None, username=None): - member = None - if login_clipper: - clipper = get_object_or_404(Clipper, username=login_clipper) - try: # check if the given user is already registered - member = User.objects.filter(username=login_clipper).get() - username = member.username - login_clipper = None - except User.DoesNotExist: - # new user, but prefill - user_form = RegistrationUserForm() - profile_form = RegistrationProfileForm() - user_form.fields['username'].initial = login_clipper - user_form.fields['email'].initial = \ - login_clipper + "@clipper.ens.fr" - profile_form.fields['login_clipper'].initial = login_clipper - if clipper.fullname: - bits = clipper.fullname.split(" ") - user_form.fields['first_name'].initial = bits[0] - if len(bits) > 1: - user_form.fields['last_name'].initial = " ".join(bits[1:]) - registration_set_ro_fields(user_form, profile_form) - if username: - member = get_object_or_404(User, username=username) - (profile, _) = CofProfile.objects.get_or_create(user=member) - # already existing, prefill - user_form = RegistrationUserForm(instance=member) - profile_form = RegistrationProfileForm(instance=profile) - registration_set_ro_fields(user_form, profile_form) - elif not login_clipper: - # new user - user_form = RegistrationUserForm() - profile_form = RegistrationProfileForm() - return render(request, "registration_form.html", - {"user_form": user_form, "profile_form": profile_form, - "member": member, "login_clipper": login_clipper}) - - @buro_required def registration_form2(request, login_clipper=None, username=None): events = Event.objects.filter(old=False).all() member = None + EventFormset = formset_factory(AdminEventForm, + BaseEventRegistrationFormset) if login_clipper: clipper = get_object_or_404(Clipper, username=login_clipper) try: # check if the given user is already registered @@ -366,10 +331,10 @@ def registration_form2(request, login_clipper=None, username=None): # new user, but prefill user_form = RegistrationUserForm() profile_form = RegistrationProfileForm() - event_forms = [AdminEventForm(event=event) for event in events] + event_formset = EventFormset(events=events, prefix='events') user_form.fields['username'].initial = login_clipper - user_form.fields['email'].initial = \ - login_clipper + "@clipper.ens.fr" + user_form.fields['email'].initial = "%s@clipper.ens.fr" \ + % login_clipper profile_form.fields['login_clipper'].initial = login_clipper if clipper.fullname: bits = clipper.fullname.split(" ") @@ -384,27 +349,25 @@ def registration_form2(request, login_clipper=None, username=None): user_form = RegistrationUserForm(instance=member) profile_form = RegistrationProfileForm(instance=profile) registration_set_ro_fields(user_form, profile_form) - event_forms = [] + current_registrations = [] for event in events: try: - current_registration = EventRegistration.objects.get( - user=member, event=event) - form = AdminEventForm( - event=event, - current_registration=current_registration, - paid=current_registration.paid) + current_registrations.append( + EventRegistration.objects.get(user=member, event=event)) except EventRegistration.DoesNotExist: - form = AdminEventForm(event=event) - event_forms.append(form) + current_registrations.append(None) + event_formset = EventFormset( + events=events, prefix='events', + current_registrations=current_registrations) elif not login_clipper: # new user user_form = RegistrationUserForm() profile_form = RegistrationProfileForm() - event_forms = [AdminEventForm(event=event) for event in events] + event_formset = EventFormset(events=events, prefix='events') return render(request, "registration_form.html", {"user_form": user_form, "profile_form": profile_form, "member": member, "login_clipper": login_clipper, - "event_forms": event_forms}) + "event_formset": event_formset}) @buro_required @@ -417,12 +380,13 @@ def registration(request): user_form = RegistrationUserForm(request_dict) profile_form = RegistrationProfileForm(request_dict) events = Event.objects.filter(old=False).all() - event_forms = \ - [AdminEventForm(request_dict, event=event) for event in events] + EventFormset = formset_factory(AdminEventForm, + BaseEventRegistrationFormset) + event_formset = EventFormset(events=events, data=request_dict, + prefix='events') user_form.is_valid() profile_form.is_valid() - for event_form in event_forms: - event_form.is_valid() + event_formset.is_valid() member = None login_clipper = None if "user_exists" in request_dict and request_dict["user_exists"]: @@ -439,26 +403,21 @@ def registration(request): login_clipper = clipper.username except Clipper.DoesNotExist: pass - for form in event_forms: - if not form.is_valid(): - break - if form.cleaned_data['status'] == 'no': - continue - all_choices = get_event_form_choices(form.event, form) if user_form.is_valid() and profile_form.is_valid() \ - and not any([not form.is_valid() for form in event_forms]): + and event_formset.is_valid(): member = user_form.save() (profile, _) = CofProfile.objects.get_or_create(user=member) was_cof = profile.is_cof request_dict["num"] = profile.num profile_form = RegistrationProfileForm(request_dict, instance=profile) - profile_form.is_valid() profile_form.save() (profile, _) = CofProfile.objects.get_or_create(user=member) if profile.is_cof and not was_cof: send_custom_mail(member, "bienvenue") - for form in event_forms: + for form in event_formset: + if 'status' not in form.cleaned_data: + form.cleaned_data['status'] = 'no' if form.cleaned_data['status'] == 'no': try: current_registration = EventRegistration.objects.get( @@ -494,7 +453,7 @@ def registration(request): "profile_form": profile_form, "member": member, "login_clipper": login_clipper, - "event_forms": event_forms}) + "event_formset": event_formset}) else: return render(request, "registration.html") From 23ac3b722257e4379c7be1d5759698d23137e8e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 13 Aug 2016 02:56:42 +0200 Subject: [PATCH 07/35] Nettoyage --- gestioncof/views.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/gestioncof/views.py b/gestioncof/views.py index 263cad32..e632011a 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -320,30 +320,32 @@ def registration_set_ro_fields(user_form, profile_form): @buro_required def registration_form2(request, login_clipper=None, username=None): events = Event.objects.filter(old=False).all() - member = None EventFormset = formset_factory(AdminEventForm, BaseEventRegistrationFormset) + member = None if login_clipper: clipper = get_object_or_404(Clipper, username=login_clipper) try: # check if the given user is already registered - member = User.objects.filter(username=login_clipper).get() + member = User.objects.get(username=login_clipper) username = member.username login_clipper = None except User.DoesNotExist: # new user, but prefill - user_form = RegistrationUserForm() - profile_form = RegistrationProfileForm() - event_formset = EventFormset(events=events, prefix='events') - user_form.fields['username'].initial = login_clipper - user_form.fields['email'].initial = "%s@clipper.ens.fr" \ - % login_clipper - profile_form.fields['login_clipper'].initial = login_clipper + # user + user_form = RegistrationUserForm(initial={ + 'username': login_clipper, + 'email': "%s@clipper.ens.fr" % login_clipper}) if clipper.fullname: bits = clipper.fullname.split(" ") user_form.fields['first_name'].initial = bits[0] if len(bits) > 1: user_form.fields['last_name'].initial = " ".join(bits[1:]) + # profile + profile_form = RegistrationProfileForm(initial={ + 'login_clipper': login_clipper}) registration_set_ro_fields(user_form, profile_form) + # events + event_formset = EventFormset(events=events, prefix='events') if username: member = get_object_or_404(User, username=username) (profile, _) = CofProfile.objects.get_or_create(user=member) @@ -351,6 +353,7 @@ def registration_form2(request, login_clipper=None, username=None): user_form = RegistrationUserForm(instance=member) profile_form = RegistrationProfileForm(instance=profile) registration_set_ro_fields(user_form, profile_form) + # events current_registrations = [] for event in events: try: @@ -376,6 +379,7 @@ def registration_form2(request, login_clipper=None, username=None): def registration(request): if request.POST: request_dict = request.POST.copy() + # num ne peut pas être définit manuellement if "num" in request_dict: del request_dict["num"] success = False From f64d882dd6eaf8d28f3fb996ea2676e0d25365b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 13 Aug 2016 02:57:49 +0200 Subject: [PATCH 08/35] Fixes - Suppression d'un import inutile - Erreur d'encodage --- bda/views.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bda/views.py b/bda/views.py index ededccd7..776932e9 100644 --- a/bda/views.py +++ b/bda/views.py @@ -16,7 +16,6 @@ from django.core.mail import send_mail from django.utils import timezone from django.views.generic.list import ListView -from datetime import timedelta import time from gestioncof.decorators import cof_required, buro_required @@ -110,8 +109,7 @@ def places(request, tirage_id): def inscription(request, tirage_id): tirage = get_object_or_404(Tirage, id=tirage_id) if timezone.now() < tirage.ouverture: - error_desc = "Ouverture le %s" % ( - tirage.ouverture.strftime('%d %b %Y à %H:%M')) + error_desc = tirage.ouverture.strftime('Ouverture le %d %b %Y à %H:%M') return render(request, 'resume_inscription.html', {"error_title": "Le tirage n'est pas encore ouvert !", "error_description": error_desc}) From 12a4b8efa74d6c63a203e37019b6574e1f518372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sun, 14 Aug 2016 12:10:50 +0200 Subject: [PATCH 09/35] Petits changements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout d'un FIXME dans `gestioncof/forms.py` au niveua de la surcharge de la méthode privée `_construct_form` : ce trick ne sera plus nécessaire à partir de Django 1.9 - Utilisation correcte des `form.is_valid` dans `gestioncof.views.registration` --- gestioncof/views.py | 108 +++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/gestioncof/views.py b/gestioncof/views.py index e632011a..4ba41061 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -379,80 +379,84 @@ def registration_form2(request, login_clipper=None, username=None): def registration(request): if request.POST: request_dict = request.POST.copy() - # num ne peut pas être définit manuellement + member = None + login_clipper = None + success = False + events = Event.objects.filter(old=False).all() + # num ne peut pas être défini manuellement if "num" in request_dict: del request_dict["num"] - success = False + + # ----- + # Remplissage des formulaires + # ----- + user_form = RegistrationUserForm(request_dict) - profile_form = RegistrationProfileForm(request_dict) - events = Event.objects.filter(old=False).all() EventFormset = formset_factory(AdminEventForm, BaseEventRegistrationFormset) event_formset = EventFormset(events=events, data=request_dict, prefix='events') - user_form.is_valid() - profile_form.is_valid() - event_formset.is_valid() - member = None - login_clipper = None if "user_exists" in request_dict and request_dict["user_exists"]: username = request_dict["username"] try: - member = User.objects.filter(username=username).get() - (profile, _) = CofProfile.objects.get_or_create(user=member) + member = User.objects.get(username=username) user_form = RegistrationUserForm(request_dict, instance=member) - profile_form = RegistrationProfileForm(request_dict, - instance=profile) except User.DoesNotExist: try: - clipper = Clipper.objects.filter(username=username).get() + clipper = Clipper.objects.get(username=username) login_clipper = clipper.username except Clipper.DoesNotExist: pass - if user_form.is_valid() and profile_form.is_valid() \ - and event_formset.is_valid(): + + # ----- + # Validation des formulaires + # ----- + + if user_form.is_valid(): member = user_form.save() - (profile, _) = CofProfile.objects.get_or_create(user=member) + profile, _ = CofProfile.objects.get_or_create(user=member) was_cof = profile.is_cof request_dict["num"] = profile.num + # Maintenant on remplit le formulaire de profil profile_form = RegistrationProfileForm(request_dict, instance=profile) - profile_form.save() - (profile, _) = CofProfile.objects.get_or_create(user=member) - if profile.is_cof and not was_cof: - send_custom_mail(member, "bienvenue") - for form in event_formset: - if 'status' not in form.cleaned_data: - form.cleaned_data['status'] = 'no' - if form.cleaned_data['status'] == 'no': - try: - current_registration = EventRegistration.objects.get( + if profile_form.is_valid() and event_formset.is_valid(): + profile = profile_form.save() + if profile.is_cof and not was_cof: + send_custom_mail(member, "bienvenue") + for form in event_formset: + if 'status' not in form.cleaned_data: + form.cleaned_data['status'] = 'no' + if form.cleaned_data['status'] == 'no': + try: + current_registration = EventRegistration.objects \ + .get(user=member, event=form.event) + current_registration.delete() + except EventRegistration.DoesNotExist: + pass + continue + all_choices = get_event_form_choices(form.event, form) + (current_registration, created_reg) = \ + EventRegistration.objects.get_or_create( user=member, event=form.event) - current_registration.delete() - except EventRegistration.DoesNotExist: - pass - continue - all_choices = get_event_form_choices(form.event, form) - (current_registration, created_reg) = \ - EventRegistration.objects.get_or_create(user=member, - event=form.event) - update_event_form_comments(form.event, form, - current_registration) - current_registration.options = all_choices - current_registration.paid = \ - (form.cleaned_data['status'] == 'paid') - current_registration.save() - if form.event.title == "Mega 15" and created_reg: - field = EventCommentField.objects.get(event=form.event, - name="Commentaires") - try: - comments = EventCommentValue.objects.get( - commentfield=field, - registration=current_registration).content - except EventCommentValue.DoesNotExist: - comments = field.default - send_custom_mail(member, "mega", {"remarques": comments}) - success = True + update_event_form_comments(form.event, form, + current_registration) + current_registration.options = all_choices + current_registration.paid = \ + (form.cleaned_data['status'] == 'paid') + current_registration.save() + if form.event.title == "Mega 15" and created_reg: + field = EventCommentField.objects.get( + event=form.event, name="Commentaires") + try: + comments = EventCommentValue.objects.get( + commentfield=field, + registration=current_registration).content + except EventCommentValue.DoesNotExist: + comments = field.default + send_custom_mail(member, "mega", + {"remarques": comments}) + success = True return render(request, "registration_post.html", {"success": success, "user_form": user_form, From 5b0b60fadb7a15d642831540b9c9ab7e2232e0e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Wed, 17 Aug 2016 15:34:01 +0200 Subject: [PATCH 10/35] =?UTF-8?q?Meilleure=20inscription=20des=20ext=C3=A9?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lors de la création d'un compte exté via la vue `/registration` (i.e. compte non associé à un clipper), deux champs sont ajoutés au formulaire pour la création d'un mot de passe. Il est toujours possible de changer ce mot de passe via l'admin s'il est perdu par l'utilisateur. --- gestioncof/forms.py | 29 ++++++++++++++++++++++++++++- gestioncof/views.py | 25 ++++++++++++------------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/gestioncof/forms.py b/gestioncof/forms.py index 7aac9e5f..5fb3d0b3 100644 --- a/gestioncof/forms.py +++ b/gestioncof/forms.py @@ -8,7 +8,7 @@ from django import forms from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User from django.forms.widgets import RadioSelect, CheckboxSelectMultiple -from django.forms.formsets import BaseFormSet +from django.forms.formsets import BaseFormSet, formset_factory from django.db.models import Max from gestioncof.models import CofProfile, EventCommentValue, \ @@ -215,6 +215,32 @@ class RegistrationUserForm(forms.ModelForm): fields = ("username", "first_name", "last_name", "email") +class RegistrationPassUserForm(RegistrationUserForm): + """ + Formulaire pour changer le mot de passe d'un utilisateur. + """ + password1 = forms.CharField(label=_('Mot de passe'), + widget=forms.PasswordInput) + password2 = forms.CharField(label=_('Confirmation du mot de passe'), + widget=forms.PasswordInput) + + def clean_password2(self): + pass1 = self.cleaned_data['password1'] + pass2 = self.cleaned_data['password2'] + if pass1 and pass2: + if pass1 != pass2: + raise forms.ValidationError(_('Mots de passe non identiques.')) + return pass2 + + def save(self, commit=True, *args, **kwargs): + user = super(RegistrationPassUserForm, self).save(commit, *args, + **kwargs) + user.set_password(self.cleaned_data['password2']) + if commit: + user.save() + return user + + class RegistrationProfileForm(forms.ModelForm): def __init__(self, *args, **kw): super(RegistrationProfileForm, self).__init__(*args, **kw) @@ -350,6 +376,7 @@ class BaseEventRegistrationFormset(BaseFormSet): kwargs['current_registration'] = self.current_registrations[index] return super(BaseEventRegistrationFormset, self)._construct_form( index, **kwargs) +EventFormset = formset_factory(AdminEventForm, BaseEventRegistrationFormset) class CalendarForm(forms.ModelForm): diff --git a/gestioncof/views.py b/gestioncof/views.py index 4ba41061..5a665860 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -14,7 +14,6 @@ from django.http import Http404, HttpResponse from django.contrib.auth.decorators import login_required from django.contrib.auth.views import login as django_login_view from django.contrib.auth.models import User -from django.forms.models import formset_factory from django.utils import timezone import django.utils.six as six @@ -29,8 +28,8 @@ from gestioncof.models import CofProfile, Clipper from gestioncof.decorators import buro_required, cof_required from gestioncof.forms import UserProfileForm, EventStatusFilterForm, \ SurveyForm, SurveyStatusFilterForm, RegistrationUserForm, \ - RegistrationProfileForm, AdminEventForm, EventForm, CalendarForm, \ - BaseEventRegistrationFormset + RegistrationProfileForm, EventForm, CalendarForm, EventFormset, \ + RegistrationPassUserForm from bda.models import Tirage, Spectacle @@ -320,8 +319,6 @@ def registration_set_ro_fields(user_form, profile_form): @buro_required def registration_form2(request, login_clipper=None, username=None): events = Event.objects.filter(old=False).all() - EventFormset = formset_factory(AdminEventForm, - BaseEventRegistrationFormset) member = None if login_clipper: clipper = get_object_or_404(Clipper, username=login_clipper) @@ -366,7 +363,7 @@ def registration_form2(request, login_clipper=None, username=None): current_registrations=current_registrations) elif not login_clipper: # new user - user_form = RegistrationUserForm() + user_form = RegistrationPassUserForm() profile_form = RegistrationProfileForm() event_formset = EventFormset(events=events, prefix='events') return render(request, "registration_form.html", @@ -379,21 +376,23 @@ def registration_form2(request, login_clipper=None, username=None): def registration(request): if request.POST: request_dict = request.POST.copy() - member = None - login_clipper = None - success = False - events = Event.objects.filter(old=False).all() # num ne peut pas être défini manuellement if "num" in request_dict: del request_dict["num"] + member = None + login_clipper = None + success = False # ----- # Remplissage des formulaires # ----- - user_form = RegistrationUserForm(request_dict) - EventFormset = formset_factory(AdminEventForm, - BaseEventRegistrationFormset) + if 'password1' in request_dict or 'password2' in request_dict: + user_form = RegistrationPassUserForm(request_dict) + else: + user_form = RegistrationUserForm + profile_form = RegistrationProfileForm(request_dict) + events = Event.objects.filter(old=False).all() event_formset = EventFormset(events=events, data=request_dict, prefix='events') if "user_exists" in request_dict and request_dict["user_exists"]: From 26edffd78f0fc0057a1f2ece8b0d6910cd771c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Fri, 19 Aug 2016 21:25:04 +0200 Subject: [PATCH 11/35] Fix typos in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b304772c..a0dd3a55 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ Il ne vous reste plus qu'à initialiser les modèles de Django avec la commande Une base de donnée pré-remplie est disponible en lançant la commande : - python manage.py loaddata users bda gestion + python manage.py loaddata users root bda gestion Vous êtes prêts à développer ! Lancer GestioCOF en faisant @@ -171,6 +171,6 @@ Pour mettre à jour les modèles après une migration, il faut ensuite faire : ## Documentation utilisateur -Une brève documentation utilisateur pour se faliliariser plus vite avec l'outil +Une brève documentation utilisateur pour se familiariser plus vite avec l'outil est accessible sur le [wiki](https://git.eleves.ens.fr/cof-geek/gestioCOF/wikis/home). From fd6b2d68d3a5696aff35fff5d520bd404f198c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sun, 21 Aug 2016 15:18:00 +0200 Subject: [PATCH 12/35] Corrige l'ordre des champs du formulaire profil MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'ordre des champs n'était pas définit de façon correcte et donc n'était pas respecté. Fixes #54 --- gestioncof/forms.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/gestioncof/forms.py b/gestioncof/forms.py index 5fb3d0b3..3916da8d 100644 --- a/gestioncof/forms.py +++ b/gestioncof/forms.py @@ -184,14 +184,6 @@ class UserProfileForm(forms.ModelForm): super(UserProfileForm, self).__init__(*args, **kw) self.fields['first_name'].initial = self.instance.user.first_name self.fields['last_name'].initial = self.instance.user.last_name - self.fields.keyOrder = [ - 'first_name', - 'last_name', - 'phone', - 'mailing_cof', - 'mailing_bda', - 'mailing_bda_revente', - ] def save(self, *args, **kw): super(UserProfileForm, self).save(*args, **kw) @@ -201,8 +193,8 @@ class UserProfileForm(forms.ModelForm): class Meta: model = CofProfile - fields = ("phone", "mailing_cof", "mailing_bda", - "mailing_bda_revente", ) + fields = ["first_name", "last_name", "phone", "mailing_cof", + "mailing_bda", "mailing_bda_revente"] class RegistrationUserForm(forms.ModelForm): From 5d685a04d70964cc54407e9db34dc82b940cd913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sun, 21 Aug 2016 16:02:29 +0200 Subject: [PATCH 13/35] =?UTF-8?q?Emp=C3=AAche=20les=20conflits=20username-?= =?UTF-8?q?clipper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le formulaire de création d'utilisateur empêche de choisir un username de moins de 8 caractères. Fixes #57 --- gestioncof/forms.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gestioncof/forms.py b/gestioncof/forms.py index 3916da8d..064eef8e 100644 --- a/gestioncof/forms.py +++ b/gestioncof/forms.py @@ -10,6 +10,7 @@ from django.contrib.auth.models import User from django.forms.widgets import RadioSelect, CheckboxSelectMultiple from django.forms.formsets import BaseFormSet, formset_factory from django.db.models import Max +from django.core.validators import MinLengthValidator from gestioncof.models import CofProfile, EventCommentValue, \ CalendarSubscription @@ -201,6 +202,7 @@ class RegistrationUserForm(forms.ModelForm): def __init__(self, *args, **kw): super(RegistrationUserForm, self).__init__(*args, **kw) self.fields['username'].help_text = "" + self.fields['username'].validators = [MinLengthValidator(9)] class Meta: model = User From c07cf654fb6a62fc994847fdfebb56d876b3e138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Tue, 23 Aug 2016 17:40:24 +0200 Subject: [PATCH 14/35] Premier jet des clubs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On peut inscrire les utilisateurs aux différents clubs du COF. Le formulaire d'inscription est inclus dans la page “inscription d'un nouveau membre”. À réfléchir comment ajouter des infos supplémentaires : chèque des caution, commentaires, etc. --- gestioncof/forms.py | 13 +++++- gestioncof/migrations/0007_alter_club.py | 47 +++++++++++++++++++++ gestioncof/models.py | 9 ++-- gestioncof/templates/registration_form.html | 4 ++ gestioncof/views.py | 30 ++++++++++--- 5 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 gestioncof/migrations/0007_alter_club.py diff --git a/gestioncof/forms.py b/gestioncof/forms.py index 064eef8e..f6676c4f 100644 --- a/gestioncof/forms.py +++ b/gestioncof/forms.py @@ -13,7 +13,7 @@ from django.db.models import Max from django.core.validators import MinLengthValidator from gestioncof.models import CofProfile, EventCommentValue, \ - CalendarSubscription + CalendarSubscription, Club from gestioncof.widgets import TriStateCheckbox from gestioncof.shared import lock_table, unlock_table @@ -389,3 +389,14 @@ class CalendarForm(forms.ModelForm): model = CalendarSubscription fields = ['subscribe_to_events', 'subscribe_to_my_shows', 'other_shows'] + + +class ClubsForm(forms.Form): + """ + Formulaire d'inscription d'un membre à plusieurs clubs du COF. + """ + clubs = forms.ModelMultipleChoiceField( + label="Inscriptions aux clubs du COF", + queryset=Club.objects.all(), + widget=forms.CheckboxSelectMultiple, + required=False) diff --git a/gestioncof/migrations/0007_alter_club.py b/gestioncof/migrations/0007_alter_club.py new file mode 100644 index 00000000..324c59a6 --- /dev/null +++ b/gestioncof/migrations/0007_alter_club.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('gestioncof', '0006_add_calendar'), + ] + + operations = [ + migrations.AlterField( + model_name='club', + name='name', + field=models.CharField(unique=True, max_length=200, + verbose_name='Nom') + ), + migrations.AlterField( + model_name='club', + name='description', + field=models.TextField(verbose_name='Description', blank=True) + ), + migrations.AlterField( + model_name='club', + name='membres', + field=models.ManyToManyField(related_name='clubs', + to=settings.AUTH_USER_MODEL, + blank=True), + ), + migrations.AlterField( + model_name='club', + name='respos', + field=models.ManyToManyField(related_name='clubs_geres', + to=settings.AUTH_USER_MODEL, + blank=True), + ), + migrations.AlterField( + model_name='event', + name='start_date', + field=models.DateTimeField(null=True, + verbose_name='Date de d\xe9but', + blank=True), + ), + ] diff --git a/gestioncof/models.py b/gestioncof/models.py index 95837a3a..382a5750 100644 --- a/gestioncof/models.py +++ b/gestioncof/models.py @@ -86,10 +86,11 @@ post_save.connect(create_user_profile, sender=User) @python_2_unicode_compatible class Club(models.Model): - name = models.CharField("Nom", max_length=200) - description = models.TextField("Description") - respos = models.ManyToManyField(User, related_name="clubs_geres") - membres = models.ManyToManyField(User, related_name="clubs") + name = models.CharField("Nom", max_length=200, unique=True) + description = models.TextField("Description", blank=True) + respos = models.ManyToManyField(User, related_name="clubs_geres", + blank=True) + membres = models.ManyToManyField(User, related_name="clubs", blank=True) def __str__(self): return self.name diff --git a/gestioncof/templates/registration_form.html b/gestioncof/templates/registration_form.html index 1eb16393..8668152b 100644 --- a/gestioncof/templates/registration_form.html +++ b/gestioncof/templates/registration_form.html @@ -13,6 +13,10 @@ {{ user_form | bootstrap }} {{ profile_form | bootstrap }} +
    + + {{ clubs_form | bootstrap }} +
    {{ event_formset.management_form }} {% for event_form in event_formset %}
    diff --git a/gestioncof/views.py b/gestioncof/views.py index 5a665860..4e428791 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -29,7 +29,7 @@ from gestioncof.decorators import buro_required, cof_required from gestioncof.forms import UserProfileForm, EventStatusFilterForm, \ SurveyForm, SurveyStatusFilterForm, RegistrationUserForm, \ RegistrationProfileForm, EventForm, CalendarForm, EventFormset, \ - RegistrationPassUserForm + RegistrationPassUserForm, ClubsForm from bda.models import Tirage, Spectacle @@ -341,8 +341,9 @@ def registration_form2(request, login_clipper=None, username=None): profile_form = RegistrationProfileForm(initial={ 'login_clipper': login_clipper}) registration_set_ro_fields(user_form, profile_form) - # events + # events & clubs event_formset = EventFormset(events=events, prefix='events') + clubs_form = ClubsForm(initial={'clubs': member.clubs.all()}) if username: member = get_object_or_404(User, username=username) (profile, _) = CofProfile.objects.get_or_create(user=member) @@ -361,15 +362,20 @@ def registration_form2(request, login_clipper=None, username=None): event_formset = EventFormset( events=events, prefix='events', current_registrations=current_registrations) + # Clubs + clubs_form = ClubsForm(initial={'clubs': member.clubs.all()}) elif not login_clipper: # new user user_form = RegistrationPassUserForm() profile_form = RegistrationProfileForm() event_formset = EventFormset(events=events, prefix='events') + clubs_form = ClubsForm() return render(request, "registration_form.html", - {"user_form": user_form, "profile_form": profile_form, - "member": member, "login_clipper": login_clipper, - "event_formset": event_formset}) + {"member": member, "login_clipper": login_clipper, + "user_form": user_form, + "profile_form": profile_form, + "event_formset": event_formset, + "clubs_form": clubs_form}) @buro_required @@ -392,6 +398,7 @@ def registration(request): else: user_form = RegistrationUserForm profile_form = RegistrationProfileForm(request_dict) + clubs_form = ClubsForm(request_dict) events = Event.objects.filter(old=False).all() event_formset = EventFormset(events=events, data=request_dict, prefix='events') @@ -419,10 +426,13 @@ def registration(request): # Maintenant on remplit le formulaire de profil profile_form = RegistrationProfileForm(request_dict, instance=profile) - if profile_form.is_valid() and event_formset.is_valid(): + if (profile_form.is_valid() and event_formset.is_valid() + and clubs_form.is_valid()): + # Enregistrement du profil profile = profile_form.save() if profile.is_cof and not was_cof: send_custom_mail(member, "bienvenue") + # Enregistrement des inscriptions aux événements for form in event_formset: if 'status' not in form.cleaned_data: form.cleaned_data['status'] = 'no' @@ -455,6 +465,11 @@ def registration(request): comments = field.default send_custom_mail(member, "mega", {"remarques": comments}) + # Enregistrement des inscriptions aux clubs + member.clubs.clear() + for club in clubs_form.cleaned_data['clubs']: + club.membres.add(member) + club.save() success = True return render(request, "registration_post.html", {"success": success, @@ -462,7 +477,8 @@ def registration(request): "profile_form": profile_form, "member": member, "login_clipper": login_clipper, - "event_formset": event_formset}) + "event_formset": event_formset, + "clubs_form": clubs_form}) else: return render(request, "registration.html") From f25243b0824ae67f1178fea7ded76cc71b60dd64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Tue, 23 Aug 2016 18:57:59 +0200 Subject: [PATCH 15/35] Ajout d'une vue : membres d'un club MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Liste des membres inscrits à un club. - Vue accessible aux membres du burô et aux respos des clubs concernés. --- cof/urls.py | 5 ++++- gestioncof/templates/membres_clubs.html | 20 ++++++++++++++++++++ gestioncof/urls.py | 4 ++++ gestioncof/views.py | 16 ++++++++++++++-- 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 gestioncof/templates/membres_clubs.html diff --git a/cof/urls.py b/cof/urls.py index 5a4a49dc..750107fb 100644 --- a/cof/urls.py +++ b/cof/urls.py @@ -16,7 +16,8 @@ from django.contrib.auth import views as django_views from django_cas_ng import views as django_cas_views from gestioncof import views as gestioncof_views, csv_views from gestioncof.urls import export_patterns, petitcours_patterns, \ - surveys_patterns, events_patterns, calendar_patterns + surveys_patterns, events_patterns, calendar_patterns, \ + clubs_patterns from gestioncof.autocomplete import autocomplete @@ -38,6 +39,8 @@ urlpatterns = [ url(r'^event/', include(events_patterns)), # Calendrier url(r'^calendar/', include(calendar_patterns)), + # Clubs + url(r'^clubs/', include(clubs_patterns)), # Authentification url(r'^cof/denied$', TemplateView.as_view(template_name='cof-denied.html'), name="cof-denied"), diff --git a/gestioncof/templates/membres_clubs.html b/gestioncof/templates/membres_clubs.html new file mode 100644 index 00000000..d93e8cdb --- /dev/null +++ b/gestioncof/templates/membres_clubs.html @@ -0,0 +1,20 @@ +{% extends "base_title.html" %} + +{% block realcontent %} +

    {{ club }}

    + +{% if club.membres.exists %} +

    Liste des membres

    + +{% for member in club.membres.all %} + + + + +{% endfor %} + +{% else %} +Ce club ne contient actuellement aucun membre. +{% endif %} + +{% endblock %} diff --git a/gestioncof/urls.py b/gestioncof/urls.py index 89cd5aa8..367cfbe8 100644 --- a/gestioncof/urls.py +++ b/gestioncof/urls.py @@ -52,3 +52,7 @@ calendar_patterns = [ url(r'^(?P[a-z0-9-]+)/calendar.ics$', 'gestioncof.views.calendar_ics') ] + +clubs_patterns = [ + url(r'^membres/(?P\w+)', views.membres_club) +] diff --git a/gestioncof/views.py b/gestioncof/views.py index 4e428791..247d6cad 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -10,7 +10,7 @@ from datetime import timedelta from icalendar import Calendar, Event as Vevent from django.shortcuts import redirect, get_object_or_404, render -from django.http import Http404, HttpResponse +from django.http import Http404, HttpResponse, HttpResponseForbidden from django.contrib.auth.decorators import login_required from django.contrib.auth.views import login as django_login_view from django.contrib.auth.models import User @@ -24,7 +24,7 @@ from gestioncof.models import Event, EventRegistration, EventOption, \ from gestioncof.models import EventCommentField, EventCommentValue, \ CalendarSubscription from gestioncof.shared import send_custom_mail -from gestioncof.models import CofProfile, Clipper +from gestioncof.models import CofProfile, Clipper, Club from gestioncof.decorators import buro_required, cof_required from gestioncof.forms import UserProfileForm, EventStatusFilterForm, \ SurveyForm, SurveyStatusFilterForm, RegistrationUserForm, \ @@ -483,6 +483,18 @@ def registration(request): return render(request, "registration.html") +@login_required +def membres_club(request, name): + # Vérification des permissions : l'utilisateur doit être membre du burô + # ou respo du club. + user = request.user + club = get_object_or_404(Club, name=name) + if not request.user.profile.is_buro \ + and club not in user.clubs_geres.all(): + return HttpResponseForbidden('

    Permission denied

    ') + return render(request, 'membres_clubs.html', {'club': club}) + + @buro_required def export_members(request): response = HttpResponse(content_type='text/csv') From ccc1c791010bdd867e179215e3391f450f568386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Tue, 23 Aug 2016 20:10:25 +0200 Subject: [PATCH 16/35] Ajout de la liste des clubs --- gestioncof/templates/liste_clubs.html | 25 +++++++++++++++++++++++++ gestioncof/urls.py | 3 ++- gestioncof/views.py | 16 ++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 gestioncof/templates/liste_clubs.html diff --git a/gestioncof/templates/liste_clubs.html b/gestioncof/templates/liste_clubs.html new file mode 100644 index 00000000..0551817e --- /dev/null +++ b/gestioncof/templates/liste_clubs.html @@ -0,0 +1,25 @@ +{% extends "base_title.html" %} + +{% block page_size %}col-sm-8{% endblock %} + +{% block realcontent %} +

    Clubs enregistrés sur GestioCOF

    + +{% endblock %} diff --git a/gestioncof/urls.py b/gestioncof/urls.py index 367cfbe8..f0590b1e 100644 --- a/gestioncof/urls.py +++ b/gestioncof/urls.py @@ -54,5 +54,6 @@ calendar_patterns = [ ] clubs_patterns = [ - url(r'^membres/(?P\w+)', views.membres_club) + url(r'^membres/(?P\w+)', views.membres_club, name='membres-club'), + url(r'^liste', views.liste_clubs, name='liste-clubs') ] diff --git a/gestioncof/views.py b/gestioncof/views.py index 247d6cad..c74c1da6 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -483,6 +483,11 @@ def registration(request): return render(request, "registration.html") +# ----- +# Clubs +# ----- + + @login_required def membres_club(request, name): # Vérification des permissions : l'utilisateur doit être membre du burô @@ -495,6 +500,17 @@ def membres_club(request, name): return render(request, 'membres_clubs.html', {'club': club}) +@cof_required +def liste_clubs(request): + clubs = Club.objects + if request.user.profile.is_buro: + data = {'owned_clubs': clubs.all()} + else: + data = {'owned_clubs': request.user.clubs_geres, + 'other_clubs': clubs.exclude(respos=request.user)} + return render(request, 'liste_clubs.html', data) + + @buro_required def export_members(request): response = HttpResponse(content_type='text/csv') From 559ac5a39d23f97c8d048b802648c098b1491428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Tue, 23 Aug 2016 21:09:47 +0200 Subject: [PATCH 17/35] On peut changer de respos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tout se fait simplement depuis la vue `/clubs/membres/`. Il est déconseillé de passer par l'interface admin. --- gestioncof/templates/home.html | 1 + gestioncof/templates/liste_clubs.html | 4 +-- gestioncof/templates/membres_clubs.html | 35 ++++++++++++++++++++----- gestioncof/urls.py | 4 ++- gestioncof/views.py | 18 ++++++++++++- 5 files changed, 51 insertions(+), 11 deletions(-) diff --git a/gestioncof/templates/home.html b/gestioncof/templates/home.html index 9d32c40c..ccd72cce 100644 --- a/gestioncof/templates/home.html +++ b/gestioncof/templates/home.html @@ -70,6 +70,7 @@
  • Administration générale
  • Demandes de petits cours
  • Inscription d'un nouveau membre
  • +
  • Gestion des clubs
    • Évènements & Sondages

      diff --git a/gestioncof/templates/liste_clubs.html b/gestioncof/templates/liste_clubs.html index 0551817e..c248a7a6 100644 --- a/gestioncof/templates/liste_clubs.html +++ b/gestioncof/templates/liste_clubs.html @@ -8,7 +8,7 @@ {% for club in owned_clubs %}
    • - {{ club }} ({% for respo in club.respos.all %}{{ respo.get_full_name }}{% empty %}pas de respo{% endfor %}) + {{ club }} ({% for respo in club.respos.all %}{{ respo.get_full_name }}, {% empty %}pas de respo{% endfor %})
    • {% endfor %} @@ -16,7 +16,7 @@ {% for club in other_clubs %}
    • - {{ club }} ({% for respo in club.respos.all %}{{ respo.get_full_name }}{% empty %}pas de respo{% endfor %}) + {{ club }} ({% for respo in club.respos.all %}{{ respo.get_full_name }}, {% empty %}pas de respo{% endfor %})

    • {% endfor %} diff --git a/gestioncof/templates/membres_clubs.html b/gestioncof/templates/membres_clubs.html index d93e8cdb..8c932ed5 100644 --- a/gestioncof/templates/membres_clubs.html +++ b/gestioncof/templates/membres_clubs.html @@ -1,20 +1,41 @@ {% extends "base_title.html" %} + {% block realcontent %}

      {{ club }}

      + +{% if club.respos.exists %} +

      Respo{{ club.respos.all|pluralize }}

      +
    {{ member }}{{ member.email }}
    +{% for member in club.respos.all %} + + + + + +{% endfor %} +
    {{ member }}{{ member.email }}
    +{% else %} +

    Pas de respo

    +{% endif %} + + {% if club.membres.exists %}

    Liste des membres

    -{% for member in club.membres.all %} - - - - +{% for member in members_no_respo %} + + + + + {% endfor %} - +
    {{ member }}{{ member.email }}
    {{ member }}{{ member.email }}
    {% else %} -Ce club ne contient actuellement aucun membre. +Ce club ne comporte actuellement aucun membre. {% endif %} {% endblock %} diff --git a/gestioncof/urls.py b/gestioncof/urls.py index f0590b1e..ad108005 100644 --- a/gestioncof/urls.py +++ b/gestioncof/urls.py @@ -55,5 +55,7 @@ calendar_patterns = [ clubs_patterns = [ url(r'^membres/(?P\w+)', views.membres_club, name='membres-club'), - url(r'^liste', views.liste_clubs, name='liste-clubs') + url(r'^liste', views.liste_clubs, name='liste-clubs'), + url(r'^change_respo/(?P\w+)/(?P\d+)', + views.change_respo, name='change-respo'), ] diff --git a/gestioncof/views.py b/gestioncof/views.py index c74c1da6..b3b69902 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -497,7 +497,23 @@ def membres_club(request, name): if not request.user.profile.is_buro \ and club not in user.clubs_geres.all(): return HttpResponseForbidden('

    Permission denied

    ') - return render(request, 'membres_clubs.html', {'club': club}) + members_no_respo = club.membres.exclude(clubs_geres=club).all() + return render(request, 'membres_clubs.html', + {'club': club, + 'members_no_respo': members_no_respo}) + + +@buro_required +def change_respo(request, club_name, user_id): + club = get_object_or_404(Club, name=club_name) + user = get_object_or_404(User, id=user_id) + if user in club.respos.all(): + club.respos.remove(user) + elif user in club.membres.all(): + club.respos.add(user) + else: + raise Http404 + return redirect('membres-club', name=club_name) @cof_required From 14c90858871b4787912877a6b3f32b480fde1c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Tue, 23 Aug 2016 22:15:31 +0200 Subject: [PATCH 18/35] =?UTF-8?q?Emp=C3=AAche=20d'assigner=20un=20respo=20?= =?UTF-8?q?non=20membre=20du=20club?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dans l'interface admin --- gestioncof/admin.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/gestioncof/admin.py b/gestioncof/admin.py index 342317f3..7aedf089 100644 --- a/gestioncof/admin.py +++ b/gestioncof/admin.py @@ -4,6 +4,7 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals +from django import forms from django.contrib import admin from gestioncof.models import SurveyQuestionAnswer, SurveyQuestion, \ CofProfile, EventOption, EventOptionChoice, Event, Club, CustomMail, \ @@ -232,6 +233,25 @@ class PetitCoursDemandeAdmin(admin.ModelAdmin): class CustomMailAdmin(admin.ModelAdmin): search_fields = ('shortname', 'title') + +class ClubAdminForm(forms.ModelForm): + def clean(self): + cleaned_data = super(ClubAdminForm, self).clean() + respos = cleaned_data.get('respos') + members = cleaned_data.get('membres') + for respo in respos.all(): + if respo not in members: + raise forms.ValidationError( + "Erreur : le respo %s n'est pas membre du club." + % respo.get_full_name()) + return cleaned_data + + +class ClubAdmin(admin.ModelAdmin): + list_display = ['name'] + form = ClubAdminForm + + admin.site.register(Survey, SurveyAdmin) admin.site.register(SurveyQuestion, SurveyQuestionAdmin) admin.site.register(Event, EventAdmin) @@ -239,7 +259,7 @@ admin.site.register(EventOption, EventOptionAdmin) admin.site.unregister(User) admin.site.register(User, UserProfileAdmin) admin.site.register(CofProfile) -admin.site.register(Club) +admin.site.register(Club, ClubAdmin) admin.site.register(CustomMail) admin.site.register(PetitCoursSubject) admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin) From 87149d0d4e682582829783a1e96fff665edd303e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Tue, 23 Aug 2016 16:22:06 +0200 Subject: [PATCH 19/35] Premier jet Affiche la liste des descriptions des spectacles d'un tirage. Accessible sans authentification. --- bda/templates/descriptions.html | 33 +++++++++++++++++++++++++++++++++ bda/urls.py | 1 + bda/views.py | 6 ++++++ 3 files changed, 40 insertions(+) create mode 100644 bda/templates/descriptions.html diff --git a/bda/templates/descriptions.html b/bda/templates/descriptions.html new file mode 100644 index 00000000..a5512e0e --- /dev/null +++ b/bda/templates/descriptions.html @@ -0,0 +1,33 @@ +{% for show in shows %} + + + + + + + + + + + + + + + + + + + {% if slots_description != "" %} + + + + {% endif %} + +
    +

    {{ show.title }}

    +
    {{ show.location }}
    {{ show.date }}{{ show.slots }} place{{ show.slots|pluralize}} - show.price
    +

    {{ show.description }}

    +
    + {{ show.slots_description }} +
    +{% endfor %} diff --git a/bda/urls.py b/bda/urls.py index 8ec8f277..5d2c6d5f 100644 --- a/bda/urls.py +++ b/bda/urls.py @@ -32,4 +32,5 @@ urlpatterns = [ views.unpaid, name="bda-unpaid"), url(r'^mails-rappel/(?P\d+)$', views.send_rappel), + url(r'^descriptions/(?P\d+)$', views.descriptions_spectacles), ] diff --git a/bda/views.py b/bda/views.py index 776932e9..f6c1256a 100644 --- a/bda/views.py +++ b/bda/views.py @@ -364,3 +364,9 @@ def send_rappel(request, spectacle_id): else: ctxt['sent'] = False return render(request, "mails-rappel.html", ctxt) + + +def descriptions_spectacles(request, tirage_id): + tirage = get_object_or_404(Tirage, id=tirage_id) + shows = tirage.spectacle_set.all() + return render(request, 'descriptions.html', {'shows': shows}) From c5fa9d32f48b357655e9c1337b39cd32d3fdf98c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Wed, 24 Aug 2016 15:28:58 +0200 Subject: [PATCH 20/35] Erreur d'indentation --- bda/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bda/models.py b/bda/models.py index ba72416a..6504533f 100644 --- a/bda/models.py +++ b/bda/models.py @@ -29,7 +29,7 @@ class Tirage(models.Model): tokens = models.TextField("Graine(s) du tirage", blank=True) active = models.BooleanField("Tirage actif", default=False) enable_do_tirage = models.BooleanField("Le tirage peut être lancé", - default=False) + default=False) def date_no_seconds(self): return self.fermeture.strftime('%d %b %Y %H:%M') From ac0748d0a43592e0e966a5e1a3347ab0510c2c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Wed, 24 Aug 2016 15:38:04 +0200 Subject: [PATCH 21/35] Typo --- gestioncof/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gestioncof/views.py b/gestioncof/views.py index 5a665860..5b0cf7ec 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -390,7 +390,7 @@ def registration(request): if 'password1' in request_dict or 'password2' in request_dict: user_form = RegistrationPassUserForm(request_dict) else: - user_form = RegistrationUserForm + user_form = RegistrationUserForm(request_dict) profile_form = RegistrationProfileForm(request_dict) events = Event.objects.filter(old=False).all() event_formset = EventFormset(events=events, data=request_dict, From 9258f509ee0a45fdf9288f7ca3f1423fe10ab8a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Wed, 24 Aug 2016 16:03:21 +0200 Subject: [PATCH 22/35] Fix sur les noms d'utilisateurs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit À l'inscription on contraint les noms d'utilisateurs à dépasser 8 caractères seulement à la création de nouveaux utilisateurs pour garantir la rétrocompatibilité. --- gestioncof/forms.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gestioncof/forms.py b/gestioncof/forms.py index 064eef8e..07b7cd89 100644 --- a/gestioncof/forms.py +++ b/gestioncof/forms.py @@ -200,9 +200,11 @@ class UserProfileForm(forms.ModelForm): class RegistrationUserForm(forms.ModelForm): def __init__(self, *args, **kw): + new_user = kw.get('instance') is None super(RegistrationUserForm, self).__init__(*args, **kw) self.fields['username'].help_text = "" - self.fields['username'].validators = [MinLengthValidator(9)] + if new_user: + self.fields['username'].validators = [MinLengthValidator(9)] class Meta: model = User From da07f34b51adfc1db2b73a85f069f6c630ba312d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Wed, 24 Aug 2016 16:26:43 +0200 Subject: [PATCH 23/35] Correction du dernier commit --- gestioncof/forms.py | 6 +++--- gestioncof/views.py | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/gestioncof/forms.py b/gestioncof/forms.py index 07b7cd89..e7672c8f 100644 --- a/gestioncof/forms.py +++ b/gestioncof/forms.py @@ -200,11 +200,11 @@ class UserProfileForm(forms.ModelForm): class RegistrationUserForm(forms.ModelForm): def __init__(self, *args, **kw): - new_user = kw.get('instance') is None super(RegistrationUserForm, self).__init__(*args, **kw) self.fields['username'].help_text = "" - if new_user: - self.fields['username'].validators = [MinLengthValidator(9)] + + def force_long_username(self): + self.fields['username'].validators = [MinLengthValidator(9)] class Meta: model = User diff --git a/gestioncof/views.py b/gestioncof/views.py index 5b0cf7ec..a10f1867 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -364,6 +364,7 @@ def registration_form2(request, login_clipper=None, username=None): elif not login_clipper: # new user user_form = RegistrationPassUserForm() + user_form.force_long_username() profile_form = RegistrationProfileForm() event_formset = EventFormset(events=events, prefix='events') return render(request, "registration_form.html", @@ -405,7 +406,9 @@ def registration(request): clipper = Clipper.objects.get(username=username) login_clipper = clipper.username except Clipper.DoesNotExist: - pass + user_form.force_long_username() + else: + user_form.force_long_username() # ----- # Validation des formulaires From a2dceb300f55054b49333a659cb1ee9b8919b70e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Wed, 24 Aug 2016 23:57:55 +0200 Subject: [PATCH 24/35] Fix: vieux sondages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les sondages archivés (`survey.old = True`) mais toujours ouverts (`survey.suervy_open = True`) restaient accessibles. Ce n'est plus le cas --- gestioncof/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gestioncof/views.py b/gestioncof/views.py index a10f1867..b635ecab 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -94,7 +94,7 @@ def logout(request): @login_required def survey(request, survey_id): survey = get_object_or_404(Survey, id=survey_id) - if not survey.survey_open: + if not survey.survey_open or survey.old: raise Http404 success = False deleted = False From a2fb7143f8d0ba154438de4923dab80fc9df6367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Thu, 25 Aug 2016 00:19:42 +0200 Subject: [PATCH 25/35] Fix: calendrier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On autorise l'utilisateur à ne pas spécifier les spectacles supplémentaires à ajouter au calendrier. --- gestioncof/forms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gestioncof/forms.py b/gestioncof/forms.py index e7672c8f..62e47c0e 100644 --- a/gestioncof/forms.py +++ b/gestioncof/forms.py @@ -385,7 +385,8 @@ class CalendarForm(forms.ModelForm): other_shows = forms.ModelMultipleChoiceField( label="Spectacles supplémentaires.", queryset=Spectacle.objects.filter(tirage__active=True), - widget=forms.CheckboxSelectMultiple) + widget=forms.CheckboxSelectMultiple, + required=False) class Meta: model = CalendarSubscription From ab4e7ec084e5b3c246010e4bab2aaf01205e1519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Fri, 26 Aug 2016 05:28:04 +0200 Subject: [PATCH 26/35] Ajout d'infos dans les spectacles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajoute des informations supplémentaires au modèle `Spectacle`. - Supprime le champ inutilisé `priority`. - Utilise le champ inutilisé `slots_description`. - Adapte le template `descriptions.html` et la vue admin à ces changements. --- bda/admin.py | 10 ++- bda/fixtures/bda.json | 14 ---- bda/migrations/0007_extends_spectacle.py | 86 ++++++++++++++++++++++++ bda/models.py | 29 +++++++- bda/templates/descriptions.html | 33 ++++++++- 5 files changed, 152 insertions(+), 20 deletions(-) create mode 100644 bda/migrations/0007_extends_spectacle.py diff --git a/bda/admin.py b/bda/admin.py index eb8d3106..b23d79e0 100644 --- a/bda/admin.py +++ b/bda/admin.py @@ -9,7 +9,7 @@ from django.core.mail import send_mail from django.contrib import admin from django.db.models import Sum, Count from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\ - Attribution, Tirage + Attribution, Tirage, Quote, CategorieSpectacle from django import forms from datetime import timedelta @@ -182,7 +182,12 @@ class ChoixSpectacleAdmin(admin.ModelAdmin): 'spectacle__title') +class QuoteInline(admin.TabularInline): + model = Quote + + class SpectacleAdmin(admin.ModelAdmin): + inlines = [QuoteInline] model = Spectacle list_display = ("title", "date", "tirage", "location", "slots", "price", "listing") @@ -194,7 +199,7 @@ class SpectacleAdmin(admin.ModelAdmin): class TirageAdmin(admin.ModelAdmin): model = Tirage list_display = ("title", "ouverture", "fermeture", "active", - "enable_do_tirage") + "enable_do_tirage") readonly_fields = ("tokens", ) list_filter = ("active", ) search_fields = ("title", ) @@ -205,6 +210,7 @@ class SalleAdmin(admin.ModelAdmin): search_fields = ('name', 'address') +admin.site.register(CategorieSpectacle) admin.site.register(Spectacle, SpectacleAdmin) admin.site.register(Salle, SalleAdmin) admin.site.register(Participant, ParticipantAdmin) diff --git a/bda/fixtures/bda.json b/bda/fixtures/bda.json index d9bc1155..bb9fd73d 100644 --- a/bda/fixtures/bda.json +++ b/bda/fixtures/bda.json @@ -74,7 +74,6 @@ "description": "Jazz / Funk", "title": "Un super concert", "price": 10.0, - "priority": 1000, "rappel_sent": null, "location": 2, "date": "2016-09-30T18:00:00Z", @@ -91,7 +90,6 @@ "description": "Homemade", "title": "Une super pi\u00e8ce", "price": 10.0, - "priority": 1000, "rappel_sent": null, "location": 3, "date": "2016-09-29T14:00:00Z", @@ -108,7 +106,6 @@ "description": "Plein air, soleil, bonne musique", "title": "Concert pour la f\u00eate de la musique", "price": 5.0, - "priority": 1000, "rappel_sent": null, "location": 1, "date": "2016-09-21T15:00:00Z", @@ -125,7 +122,6 @@ "description": "Sous le regard s\u00e9v\u00e8re de Louis Pasteur", "title": "Op\u00e9ra sans d\u00e9cors", "price": 5.0, - "priority": 1000, "rappel_sent": null, "location": 4, "date": "2016-10-06T19:00:00Z", @@ -142,7 +138,6 @@ "description": "Buffet \u00e0 la fin", "title": "Concert Trouv\u00e8re", "price": 20.0, - "priority": 1000, "rappel_sent": null, "location": 5, "date": "2016-11-30T12:00:00Z", @@ -159,7 +154,6 @@ "description": "Vive les maths", "title": "Dessin \u00e0 la craie sur tableau noir", "price": 10.0, - "priority": 1000, "rappel_sent": null, "location": 6, "date": "2016-12-15T07:00:00Z", @@ -176,7 +170,6 @@ "description": "Une pi\u00e8ce \u00e0 un personnage", "title": "D\u00e9cors, d\u00e9montage en musique", "price": 0.0, - "priority": 1000, "rappel_sent": null, "location": 3, "date": "2016-12-26T07:00:00Z", @@ -193,7 +186,6 @@ "description": "Annulera, annulera pas\u00a0?", "title": "La Nuit", "price": 27.0, - "priority": 1000, "rappel_sent": null, "location": 1, "date": "2016-11-14T23:00:00Z", @@ -210,7 +202,6 @@ "description": "Le boum fait sa carte blanche", "title": "Turbomix", "price": 10.0, - "priority": 1000, "rappel_sent": null, "location": 2, "date": "2017-01-10T20:00:00Z", @@ -227,7 +218,6 @@ "description": "Unique repr\u00e9sentation", "title": "Carinettes et trombone", "price": 15.0, - "priority": 1000, "rappel_sent": null, "location": 5, "date": "2017-01-02T14:00:00Z", @@ -244,7 +234,6 @@ "description": "Suivi d'une jam session", "title": "Percussion sur rondins", "price": 5.0, - "priority": 1000, "rappel_sent": null, "location": 4, "date": "2017-01-13T14:00:00Z", @@ -261,7 +250,6 @@ "description": "\u00c9preuve sportive et artistique", "title": "Bassin aux ernests, nage libre", "price": 5.0, - "priority": 1000, "rappel_sent": null, "location": 1, "date": "2016-11-17T09:00:00Z", @@ -278,7 +266,6 @@ "description": "Sonore", "title": "Chant du barde", "price": 13.0, - "priority": 1000, "rappel_sent": null, "location": 2, "date": "2017-02-26T07:00:00Z", @@ -295,7 +282,6 @@ "description": "Cocorico", "title": "Chant du coq", "price": 4.0, - "priority": 1000, "rappel_sent": null, "location": 1, "date": "2016-12-17T04:00:00Z", diff --git a/bda/migrations/0007_extends_spectacle.py b/bda/migrations/0007_extends_spectacle.py new file mode 100644 index 00000000..2d6c6fdc --- /dev/null +++ b/bda/migrations/0007_extends_spectacle.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bda', '0006_add_tirage_switch'), + ] + + operations = [ + migrations.CreateModel( + name='CategorieSpectacle', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, + auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=300, verbose_name='Nom')), + ], + options={ + 'verbose_name': 'Cat\xe9gorie', + }, + ), + migrations.CreateModel( + name='Quote', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('text', models.TextField(verbose_name='Citation')), + ('author', models.CharField(max_length=200, verbose_name='Auteur')), + ], + ), + migrations.AlterModelOptions( + name='spectacle', + options={'ordering': ('date', 'title'), + 'verbose_name': 'Spectacle'}, + ), + migrations.RemoveField( + model_name='spectacle', + name='priority', + ), + migrations.AddField( + model_name='spectacle', + name='ext_link', + field=models.CharField( + max_length=500, + verbose_name='Lien vers le site du spectacle', + blank=True), + ), + migrations.AddField( + model_name='spectacle', + name='image', + field=models.ImageField(upload_to='imgs/shows/', null=True, + verbose_name='Image', blank=True), + ), + migrations.AlterField( + model_name='tirage', + name='enable_do_tirage', + field=models.BooleanField( + default=False, + verbose_name='Le tirage peut \xeatre lanc\xe9'), + ), + migrations.AlterField( + model_name='tirage', + name='tokens', + field=models.TextField(verbose_name='Graine(s) du tirage', + blank=True), + ), + migrations.AddField( + model_name='spectacle', + name='category', + field=models.ForeignKey(blank=True, to='bda.CategorieSpectacle', + null=True), + ), + migrations.AddField( + model_name='spectacle', + name='vips', + field=models.TextField(verbose_name='Personnalit\xe9s', + blank=True), + ), + migrations.AddField( + model_name='quote', + name='spectacle', + field=models.ForeignKey(to='bda.Spectacle'), + ), + ] diff --git a/bda/models.py b/bda/models.py index ba72416a..72c7fd3b 100644 --- a/bda/models.py +++ b/bda/models.py @@ -29,7 +29,7 @@ class Tirage(models.Model): tokens = models.TextField("Graine(s) du tirage", blank=True) active = models.BooleanField("Tirage actif", default=False) enable_do_tirage = models.BooleanField("Le tirage peut être lancé", - default=False) + default=False) def date_no_seconds(self): return self.fermeture.strftime('%d %b %Y %H:%M') @@ -47,16 +47,32 @@ class Salle(models.Model): return self.name +@python_2_unicode_compatible +class CategorieSpectacle(models.Model): + name = models.CharField('Nom', max_length=300) + + def __str__(self): + return self.name + + class Meta: + verbose_name = "Catégorie" + + @python_2_unicode_compatible class Spectacle(models.Model): title = models.CharField("Titre", max_length=300) + category = models.ForeignKey(CategorieSpectacle, blank=True, null=True) date = models.DateTimeField("Date & heure") location = models.ForeignKey(Salle) + vips = models.TextField('Personnalités', blank=True) description = models.TextField("Description", blank=True) slots_description = models.TextField("Description des places", blank=True) + image = models.ImageField('Image', blank=True, null=True, + upload_to='imgs/shows/') + ext_link = models.CharField('Lien vers le site du spectacle', blank=True, + max_length=500) price = models.FloatField("Prix d'une place") slots = models.IntegerField("Places") - priority = models.IntegerField("Priorité", default=1000) tirage = models.ForeignKey(Tirage) listing = models.BooleanField("Les places sont sur listing") rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True, @@ -64,7 +80,7 @@ class Spectacle(models.Model): class Meta: verbose_name = "Spectacle" - ordering = ("priority", "date", "title",) + ordering = ("date", "title",) def __repr__(self): return "[%s]" % self @@ -111,6 +127,13 @@ class Spectacle(models.Model): # On renvoie la liste des destinataires return members.values() + +class Quote(models.Model): + spectacle = models.ForeignKey(Spectacle) + text = models.TextField('Citation') + author = models.CharField('Auteur', max_length=200) + + PAYMENT_TYPES = ( ("cash", "Cash"), ("cb", "CB"), diff --git a/bda/templates/descriptions.html b/bda/templates/descriptions.html index a5512e0e..d95c2207 100644 --- a/bda/templates/descriptions.html +++ b/bda/templates/descriptions.html @@ -10,15 +10,25 @@ {{ show.location }} - + {% if show.category %}{{ show.category }}{% endif %} {{ show.date }} {{ show.slots }} place{{ show.slots|pluralize}} - show.price + + +

    {{ show.vips }}

    + +

    {{ show.description }}

    + {% for quote in show.quote_set.all %} +
    +

    “{{ quote.text }}”

    +
    {{ quote.author }}
    + {% endfor %} {% if slots_description != "" %} @@ -28,6 +38,27 @@ {% endif %} + {% if show.image %} + + +

    + {% if show.ext_link != "" %} + + + {% else %} + {{ show.title }} + {% endif %} +

    + + + {% elif show.ext_link != "" %} + + + Lien vers le site du spectacle + + + {% endif %} {% endfor %} From 3bca7787340a8206e3667afde7719d5d506d1dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Fri, 26 Aug 2016 06:06:45 +0200 Subject: [PATCH 27/35] Ajoute des filtres sur la page description. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les variables `GET` `location` et `category` permettent de filtrer sur les salles et catégories dans le résultats de `/bda/descriptions/` --- bda/migrations/0007_extends_spectacle.py | 9 ++++++--- bda/models.py | 2 +- bda/views.py | 15 +++++++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/bda/migrations/0007_extends_spectacle.py b/bda/migrations/0007_extends_spectacle.py index 2d6c6fdc..3fa6ccaa 100644 --- a/bda/migrations/0007_extends_spectacle.py +++ b/bda/migrations/0007_extends_spectacle.py @@ -16,7 +16,8 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(max_length=300, verbose_name='Nom')), + ('name', models.CharField(max_length=300, verbose_name='Nom', + unique=True)), ], options={ 'verbose_name': 'Cat\xe9gorie', @@ -25,9 +26,11 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Quote', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(verbose_name='ID', serialize=False, + auto_created=True, primary_key=True)), ('text', models.TextField(verbose_name='Citation')), - ('author', models.CharField(max_length=200, verbose_name='Auteur')), + ('author', models.CharField(max_length=200, + verbose_name='Auteur')), ], ), migrations.AlterModelOptions( diff --git a/bda/models.py b/bda/models.py index 72c7fd3b..8a7d0814 100644 --- a/bda/models.py +++ b/bda/models.py @@ -49,7 +49,7 @@ class Salle(models.Model): @python_2_unicode_compatible class CategorieSpectacle(models.Model): - name = models.CharField('Nom', max_length=300) + name = models.CharField('Nom', max_length=100, unique=True) def __str__(self): return self.name diff --git a/bda/views.py b/bda/views.py index f6c1256a..4ea0df32 100644 --- a/bda/views.py +++ b/bda/views.py @@ -10,6 +10,7 @@ from django.db import models from django.db.models import Count from django.core import serializers from django.forms.models import inlineformset_factory +from django.http import HttpResponseBadRequest import hashlib from django.core.mail import send_mail @@ -368,5 +369,15 @@ def send_rappel(request, spectacle_id): def descriptions_spectacles(request, tirage_id): tirage = get_object_or_404(Tirage, id=tirage_id) - shows = tirage.spectacle_set.all() - return render(request, 'descriptions.html', {'shows': shows}) + shows_qs = tirage.spectacle_set + category_name = request.GET.get('category', '') + location_id = request.GET.get('location', '') + if category_name: + shows_qs = shows_qs.filter(category__name=category_name) + if location_id: + try: + shows_qs = shows_qs.filter(location__id=int(location_id)) + except ValueError: + return HttpResponseBadRequest( + "La variable GET 'location' doit contenir un entier") + return render(request, 'descriptions.html', {'shows': shows_qs.all()}) From 2989a6e18646a43d5715e455f31e5adeaa901e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Fri, 26 Aug 2016 16:42:00 +0200 Subject: [PATCH 28/35] Add sites fixture --- README.md | 2 +- gestioncof/fixtures/sites.json | 10 ++++++++++ provisioning/prepare_django.sh | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 gestioncof/fixtures/sites.json diff --git a/README.md b/README.md index a0dd3a55..15f0ec0d 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ Il ne vous reste plus qu'à initialiser les modèles de Django avec la commande Une base de donnée pré-remplie est disponible en lançant la commande : - python manage.py loaddata users root bda gestion + python manage.py loaddata users root bda gestion sites Vous êtes prêts à développer ! Lancer GestioCOF en faisant diff --git a/gestioncof/fixtures/sites.json b/gestioncof/fixtures/sites.json new file mode 100644 index 00000000..a0d8c271 --- /dev/null +++ b/gestioncof/fixtures/sites.json @@ -0,0 +1,10 @@ +[ +{ + "fields": { + "domain": "localhost", + "name": "GestioCOF - dev - local" + }, + "model": "sites.site", + "pk": 1 +} +] diff --git a/provisioning/prepare_django.sh b/provisioning/prepare_django.sh index 5c661cc8..ef26235e 100644 --- a/provisioning/prepare_django.sh +++ b/provisioning/prepare_django.sh @@ -1,4 +1,4 @@ # Doit être lancé par bootstrap.sh python manage.py migrate -python manage.py loaddata users root bda gestion +python manage.py loaddata users root bda gestion sites From a5fa6950f50d0be01606c48607cffca8ff31ead4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 27 Aug 2016 15:08:36 +0200 Subject: [PATCH 29/35] Correction de permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La vue liste des spectacles utilisée par le burô n'était pas protégée derrière le décorateur `buro_required`. --- bda/urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bda/urls.py b/bda/urls.py index 8ec8f277..0866bf95 100644 --- a/bda/urls.py +++ b/bda/urls.py @@ -5,6 +5,7 @@ from __future__ import print_function from __future__ import unicode_literals from django.conf.urls import url +from gestioncof.decorators import buro_required from bda.views import SpectacleListView from bda import views @@ -23,7 +24,7 @@ urlpatterns = [ name='bda-etat-places'), url(r'^tirage/(?P\d+)$', views.tirage), url(r'^spectacles/(?P\d+)$', - SpectacleListView.as_view(), + buro_required(SpectacleListView.as_view()), name="bda-liste-spectacles"), url(r'^spectacles/(?P\d+)/(?P\d+)$', views.spectacle, From 1f8f94b93b9be14b54573cb11860486072e4836b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Mon, 29 Aug 2016 16:32:04 +0200 Subject: [PATCH 30/35] Cache les tirages pas encore ouverts au public MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seul le Burô peut voir les tirages actif pas encore ouverts. --- gestioncof/templates/home.html | 4 ++-- gestioncof/views.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/gestioncof/templates/home.html b/gestioncof/templates/home.html index 9d32c40c..f476a0dd 100644 --- a/gestioncof/templates/home.html +++ b/gestioncof/templates/home.html @@ -83,8 +83,8 @@

    Gestion tirages BDA

    - {% if open_tirages %} - {% for tirage in open_tirages %} + {% if active_tirages %} + {% for tirage in active_tirages %}