diff --git a/README.md b/README.md
index b304772c..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 bda gestion
+ python manage.py loaddata users root bda gestion sites
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).
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..b95c18de
--- /dev/null
+++ b/bda/migrations/0007_extends_spectacle.py
@@ -0,0 +1,89 @@
+# -*- 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=100, verbose_name='Nom',
+ unique=True)),
+ ],
+ 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..8a7d0814 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=100, unique=True)
+
+ 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/static/fonts/josefinsans.ttf b/bda/static/fonts/josefinsans.ttf
new file mode 100644
index 00000000..d234c43c
Binary files /dev/null and b/bda/static/fonts/josefinsans.ttf differ
diff --git a/bda/templates/descriptions.html b/bda/templates/descriptions.html
new file mode 100644
index 00000000..3ab514f2
--- /dev/null
+++ b/bda/templates/descriptions.html
@@ -0,0 +1,71 @@
+{% load staticfiles %}
+
+
+
+
+
+
+
+
+ {% for show in shows %}
+
+
+
+ {{ show.title }} |
+
+
+
+
+ {{ show.location }} | {{ show.category }} |
+
+
+ {{ show.date }} | {{ show.slots }} place{{ show.slots|pluralize}} {% if show.slots_description != "" %}({{ show.slots_description }}){% endif %}- {{ show.price }} |
+
+
+ {{ show.category }} |
+
+
+
+ {{ show.description }}
+ {% for quote in show.quote_set.all %}
+ «{{ quote.text }}»{% if show.quote.author %} - {{ quote.author }}{% endif %}
+ {% endfor %}
+ |
+
+ {% if show.image %}
+
+
|
+
+ {% endif %}
+
+
+ {% endfor %}
+
+
diff --git a/bda/templates/spectacle_list.html b/bda/templates/spectacle_list.html
index 0c3c7317..816461db 100644
--- a/bda/templates/spectacle_list.html
+++ b/bda/templates/spectacle_list.html
@@ -26,7 +26,7 @@
{% endfor %}
-
+
@@ -39,14 +39,15 @@
Exports
{% endblock %}
diff --git a/bda/urls.py b/bda/urls.py
index 8ec8f277..d0f8ec83 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,
@@ -32,4 +33,6 @@ urlpatterns = [
views.unpaid,
name="bda-unpaid"),
url(r'^mails-rappel/(?P\d+)$', views.send_rappel),
+ url(r'^descriptions/(?P\d+)$', views.descriptions_spectacles,
+ name='bda-descriptions'),
]
diff --git a/bda/views.py b/bda/views.py
index ededccd7..4ea0df32 100644
--- a/bda/views.py
+++ b/bda/views.py
@@ -10,13 +10,13 @@ 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
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 +110,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})
@@ -366,3 +365,19 @@ 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_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()})
diff --git a/cof/urls.py b/cof/urls.py
index 64af9eda..ca7ea247 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/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)
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/gestioncof/forms.py b/gestioncof/forms.py
index 15db25ce..8a64825f 100644
--- a/gestioncof/forms.py
+++ b/gestioncof/forms.py
@@ -8,10 +8,12 @@ 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, formset_factory
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
@@ -183,14 +185,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)
@@ -200,8 +194,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):
@@ -209,11 +203,40 @@ class RegistrationUserForm(forms.ModelForm):
super(RegistrationUserForm, self).__init__(*args, **kw)
self.fields['username'].help_text = ""
+ def force_long_username(self):
+ self.fields['username'].validators = [MinLengthValidator(9)]
+
class Meta:
model = User
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)
@@ -263,17 +286,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 +309,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 +331,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 +359,22 @@ 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)
+EventFormset = formset_factory(AdminEventForm, BaseEventRegistrationFormset)
+
+
class CalendarForm(forms.ModelForm):
subscribe_to_events = forms.BooleanField(
initial=True,
@@ -348,9 +385,21 @@ 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
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/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;
}
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.
{% endblock %}
diff --git a/gestioncof/templates/home.html b/gestioncof/templates/home.html
index bcec273c..0907adc6 100644
--- a/gestioncof/templates/home.html
+++ b/gestioncof/templates/home.html
@@ -37,26 +37,29 @@
{% for tirage in open_tirages %}
{% endfor %}
{% endif %}
- {% endif %}
Divers
+ {% endif %}
{% if user.profile.is_buro %}
Gestion tirages BDA
- {% if open_tirages %}
- {% for tirage in open_tirages %}
+ {% if active_tirages %}
+ {% for tirage in active_tirages %}
{% endfor %}
{% else %}
diff --git a/gestioncof/templates/liste_clubs.html b/gestioncof/templates/liste_clubs.html
new file mode 100644
index 00000000..c248a7a6
--- /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/templates/membres_clubs.html b/gestioncof/templates/membres_clubs.html
new file mode 100644
index 00000000..8c932ed5
--- /dev/null
+++ b/gestioncof/templates/membres_clubs.html
@@ -0,0 +1,41 @@
+{% extends "base_title.html" %}
+
+
+{% block realcontent %}
+
{{ club }}
+
+
+{% if club.respos.exists %}
+
Respo{{ club.respos.all|pluralize }}
+
+{% for member in club.respos.all %}
+
+ {{ member }} |
+ {{ member.email }} |
+ |
+
+{% endfor %}
+
+{% else %}
+
Pas de respo
+{% endif %}
+
+
+{% if club.membres.exists %}
+
Liste des membres
+
+{% for member in members_no_respo %}
+
+ {{ member }} |
+ {{ member.email }} |
+ |
+
+{% endfor %}
+
+{% else %}
+Ce club ne comporte actuellement aucun membre.
+{% endif %}
+
+{% endblock %}
diff --git a/gestioncof/templates/registration_form.html b/gestioncof/templates/registration_form.html
index b9699647..8668152b 100644
--- a/gestioncof/templates/registration_form.html
+++ b/gestioncof/templates/registration_form.html
@@ -12,16 +12,19 @@
{{ user_form | bootstrap }}
{{ profile_form | bootstrap }}
- {% if event_forms %}
- {% for event_form in event_forms %}
+
+
+ {{ clubs_form | bootstrap }}
+
+ {{ 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/urls.py b/gestioncof/urls.py
index 89cd5aa8..ad108005 100644
--- a/gestioncof/urls.py
+++ b/gestioncof/urls.py
@@ -52,3 +52,10 @@ calendar_patterns = [
url(r'^(?P
[a-z0-9-]+)/calendar.ics$',
'gestioncof.views.calendar_ics')
]
+
+clubs_patterns = [
+ url(r'^membres/(?P\w+)', views.membres_club, name='membres-club'),
+ 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 378cb44d..3148308a 100644
--- a/gestioncof/views.py
+++ b/gestioncof/views.py
@@ -10,10 +10,11 @@ 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
+from django.utils import timezone
import django.utils.six as six
from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \
@@ -23,11 +24,12 @@ 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, \
- RegistrationProfileForm, AdminEventForm, EventForm, CalendarForm
+ RegistrationProfileForm, EventForm, CalendarForm, EventFormset, \
+ RegistrationPassUserForm, ClubsForm
from bda.models import Tirage, Spectacle
@@ -40,7 +42,11 @@ 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()}
+ "active_tirages": Tirage.objects.filter(active=True).all(),
+ "open_tirages":
+ Tirage.objects.filter(active=True,
+ ouverture__lte=timezone.now()).all(),
+ "now": timezone.now()}
return render(request, "home.html", data)
@@ -91,7 +97,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
@@ -188,7 +194,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":
@@ -295,7 +301,7 @@ def survey_status(request, survey_id):
"form": form})
-@login_required
+@cof_required
def profile(request):
success = False
if request.method == "POST":
@@ -313,45 +319,6 @@ 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()
@@ -359,24 +326,27 @@ def registration_form2(request, login_clipper=None, username=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_forms = [AdminEventForm(event=event) for event in events]
- 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
+ # 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 & 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)
@@ -384,121 +354,185 @@ 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 = []
+ # events
+ 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)
+ # Clubs
+ clubs_form = ClubsForm(initial={'clubs': member.clubs.all()})
elif not login_clipper:
# new user
- user_form = RegistrationUserForm()
+ user_form = RegistrationPassUserForm()
+ user_form.force_long_username()
profile_form = RegistrationProfileForm()
- event_forms = [AdminEventForm(event=event) for event in events]
+ 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_forms": event_forms})
+ {"member": member, "login_clipper": login_clipper,
+ "user_form": user_form,
+ "profile_form": profile_form,
+ "event_formset": event_formset,
+ "clubs_form": clubs_form})
@buro_required
def registration(request):
if request.POST:
request_dict = request.POST.copy()
+ # num ne peut pas être défini manuellement
if "num" in request_dict:
del request_dict["num"]
- success = False
- 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]
- user_form.is_valid()
- profile_form.is_valid()
- for event_form in event_forms:
- event_form.is_valid()
member = None
login_clipper = None
+ success = False
+
+ # -----
+ # Remplissage des formulaires
+ # -----
+
+ if 'password1' in request_dict or 'password2' in request_dict:
+ user_form = RegistrationPassUserForm(request_dict)
+ else:
+ user_form = RegistrationUserForm(request_dict)
+ 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')
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
- 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]):
+ user_form.force_long_username()
+ else:
+ user_form.force_long_username()
+
+ # -----
+ # 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.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:
- if form.cleaned_data['status'] == 'no':
- try:
- current_registration = EventRegistration.objects.get(
+ 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'
+ 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})
+ # 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,
"user_form": user_form,
"profile_form": profile_form,
"member": member,
"login_clipper": login_clipper,
- "event_forms": event_forms})
+ "event_formset": event_formset,
+ "clubs_form": clubs_form})
else:
return render(request, "registration.html")
+# -----
+# Clubs
+# -----
+
+
+@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
')
+ 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
+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')
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