Merge branch 'master' into k-fet

This commit is contained in:
Martin Pépin 2016-08-31 23:52:31 +02:00
commit 3d25d1ab77
25 changed files with 650 additions and 199 deletions

View file

@ -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).

View file

@ -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)

View file

@ -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",

View file

@ -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'),
),
]

View file

@ -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"),

Binary file not shown.

View file

@ -0,0 +1,71 @@
{% load staticfiles %}
<!doctype html>
<html>
<head>
<style>
@font-face {
font-family: josefinsans;
src: url({% static "fonts/josefinsans.ttf" %});
}
*::-moz-selection {
background: #B0B0B0;
}
*::selection {
background: #B0B0B0;
}
.descTable{
width: auto;
margin: 0 auto 1em;
border-collapse: collapse;
border-spacing: 0;
font-size: 14px;
line-height: 2;
max-width: 100%;
background-color: transparent;
font-family: 'josefinsans', 'Arial';
font-weight: 700;
color: #5a5a5a;
}
</style>
<meta charset="utf8" />
</head>
<body>
{% for show in shows %}
<table class="descTable">
<thead>
<tr>
<th colspan="2"><p style="text-align:center;font-size:22px;">{{ show.title }}</p></th>
</tr>
</thead>
<tbody>
<tr>
<td><p style="text-align: left;">{{ show.location }}</p></td><td class="column-2"><p style="text-align: right;">{{ show.category }}</p></td>
</tr>
<tr>
<td><p style="text-align: left;">{{ show.date }}</p></td><td class="column-2"><p style="text-align: right;">{{ show.slots }} place{{ show.slots|pluralize}} {% if show.slots_description != "" %}({{ show.slots_description }}){% endif %}- {{ show.price }}</p></td>
</tr>
<tr>
<td colspan="2"><p style="text-align: justify;">{{ show.category }}</p></td>
</tr>
<tr>
<td colspan="2">
<p style="text-align: justify;">{{ show.description }}</p>
{% for quote in show.quote_set.all %}
<p style="text-align:center; font-style: italic;">«{{ quote.text }}»{% if show.quote.author %} - {{ quote.author }}{% endif %}</p>
{% endfor %}
</td>
</tr>
{% if show.image %}
<tr>
<td colspan="2"><p style="text-align:center;"><a href="{{ show.ext_link }}"><img style="display: inline;" src="{{ MEDIA_URL }}{{ show.image }}" alt="{{ show.title }}" width="200px"></a></p></td>
</tr>
{% endif %}
</tbody>
</table>
{% endfor %}
</body>
</html>

View file

@ -39,14 +39,15 @@
</script>
<script>
jQuery(document).ready(function($) {
$(".clickable-row").click(function() {
window.document.location = $(this).data("href");
});
$(".clickable-row").click(function() {
window.document.location = $(this).data("href");
});
});
</script>
<h3> Exports </h3>
<ul>
<li><a href="{% url 'bda-unpaid' tirage_id %}">Mailing list impayés</a>
<li><a href="{% url 'bda-descriptions' tirage_id %}">Lien vers les descriptions des spectacles, à utiliser dans une page wordpress</a>
</ul>
{% endblock %}

View file

@ -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<tirage_id>\d+)$', views.tirage),
url(r'^spectacles/(?P<tirage_id>\d+)$',
SpectacleListView.as_view(),
buro_required(SpectacleListView.as_view()),
name="bda-liste-spectacles"),
url(r'^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$',
views.spectacle,
@ -32,4 +33,6 @@ urlpatterns = [
views.unpaid,
name="bda-unpaid"),
url(r'^mails-rappel/(?P<spectacle_id>\d+)$', views.send_rappel),
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
name='bda-descriptions'),
]

View file

@ -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()})

View file

@ -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"),

View file

@ -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)

View file

@ -0,0 +1,10 @@
[
{
"fields": {
"domain": "localhost",
"name": "GestioCOF - dev - local"
},
"model": "sites.site",
"pk": 1
}
]

View file

@ -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)

View file

@ -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),
),
]

View file

@ -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

View file

@ -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;
}

View file

@ -37,7 +37,7 @@ souscrire aux événements du COF et/ou aux spectacles BdA.
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Enregistrer" />
<input type="submit" value="Enregistrer" class="btn btn-primary" />
</form>
{% endblock %}

View file

@ -37,26 +37,29 @@
{% for tirage in open_tirages %}
<ul>
<h4>{{ tirage.title }}</h4>
<li><a href="{% url "bda-tirage-inscription" tirage.id %}">Inscription</a></li>
<li><a href="{% url "bda-etat-places" tirage.id %}">État des demandes</a>
<li><a href="{% url "bda-places-attribuees" tirage.id %}">Mes places</a></li>
<li><a href="{% url "bda-revente" tirage.id %}">Revendre une place</a></li>
{% if tirage.fermeture > now %}
<li><a href="{% url "bda-tirage-inscription" tirage.id %}">Inscription</a></li>
<li><a href="{% url "bda-etat-places" tirage.id %}">État des demandes</a></li>
{% else %}
<li><a href="{% url "bda-places-attribuees" tirage.id %}">Mes places</a></li>
<li><a href="{% url "bda-revente" tirage.id %}">Revendre une place</a></li>
{% endif %}
</ul>
{% endfor %}
</div>
{% endif %}
{% endif %}
<h3 class="block-title">Divers<span class="pull-right glyphicon glyphicon-question-sign"></span></h3>
<div class="hm-block">
<ul>
<li><a href="{% url "gestioncof.views.calendar" %}">Calendrier dynamique</a></li>
<li><a href="{% url "gestioncof.views.calendar" %}">Calendrier dynamique</a></li>
{% if user.profile.is_cof %}<li><a href="{% url "petits-cours-inscription" %}">Inscription pour donner des petits cours</a></li>{% endif %}
<li><a href="{% url "gestioncof.views.profile" %}">Éditer mon profil</a></li>
{% if not user.profile.login_clipper %}<li><a href="{% url "django.contrib.auth.views.password_change" %}">Changer mon mot de passe</a></li>{% endif %}
</ul>
</div>
{% endif %}
</div>
{% if user.profile.is_buro %}
<div class="col-sm-6 buro-user-hm">
@ -67,6 +70,7 @@
<li><a href="{% url "admin:index" %}">Administration générale</a></li>
<li><a href="{% url "petits-cours-demandes-list" %}">Demandes de petits cours</a></li>
<li><a href="{% url "gestioncof.views.registration" %}">Inscription d'un nouveau membre</a></li>
<li><a href="{% url "liste-clubs" %}">Gestion des clubs</a></li>
</ul>
<ul>
<h4>Évènements & Sondages</h4>
@ -80,12 +84,15 @@
</div>
<h3 class="block-title">Gestion tirages BDA<span class="pull-right glyphicon glyphicon-list"></span></h3>
<div class="hm-block">
{% if open_tirages %}
{% for tirage in open_tirages %}
{% if active_tirages %}
{% for tirage in active_tirages %}
<ul>
<h4>{{ tirage.title }}</h4>
<li><a href="{% url "bda-liste-spectacles" tirage.id %}">Spectacles</a></li>
<li><a href="{% url "admin:bda_participant_changelist" %}?tirage__id__exact={{ tirage.id }}">Participants</a></li>
{% if tirage.fermeture < now %}
<li><a href="{% url "bda-etat-places" tirage.id %}">Ratios</a></li>
{% endif %}
</ul>
{% endfor %}
{% else %}

View file

@ -0,0 +1,25 @@
{% extends "base_title.html" %}
{% block page_size %}col-sm-8{% endblock %}
{% block realcontent %}
<h2>Clubs enregistrés sur GestioCOF</h2>
<ul>
{% for club in owned_clubs %}
<li>
<a href="{% url "membres-club" club.name %}">
{{ club }} ({% for respo in club.respos.all %}{{ respo.get_full_name }}, {% empty %}pas de respo{% endfor %})
</a>
</li>
{% endfor %}
{% if other_clubs %}
{% for club in other_clubs %}
<li>
<p>
{{ club }} ({% for respo in club.respos.all %}{{ respo.get_full_name }}, {% empty %}pas de respo{% endfor %})
</p>
</li>
{% endfor %}
{% endif %}
</ul>
{% endblock %}

View file

@ -0,0 +1,41 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2>{{ club }}</h2>
{% if club.respos.exists %}
<h3>Respo{{ club.respos.all|pluralize }}</h3>
<table class="table table-striped">
{% for member in club.respos.all %}
<tr>
<td>{{ member }}</td>
<td>{{ member.email }}</td>
<td><a class="glyphicon glyphicon-arrow-down"
href="{% url "change-respo" club.name member.id %}"></a></td>
</tr>
{% endfor %}
</table>
{% else %}
<h3>Pas de respo</h3>
{% endif %}
{% if club.membres.exists %}
<h3>Liste des membres</h3>
<table class="table table-striped">
{% for member in members_no_respo %}
<tr>
<td>{{ member }}</td>
<td>{{ member.email }}</td>
<td><a class="glyphicon glyphicon-arrow-up"
href="{% url "change-respo" club.name member.id %}"></a></td>
</tr>
{% endfor %}
</table>
{% else %}
Ce club ne comporte actuellement aucun membre.
{% endif %}
{% endblock %}

View file

@ -12,16 +12,19 @@
<table>
{{ user_form | bootstrap }}
{{ profile_form | bootstrap }}
{% if event_forms %}
</table>
{% for event_form in event_forms %}
<hr />
<table>
{{ clubs_form | bootstrap }}
</table>
{{ event_formset.management_form }}
{% for event_form in event_formset %}
<hr />
<h3>Inscription {{ event_form.event.title }} :</h2>
<h3>Inscription {{ event_form.event.title }}&nbsp;:</h3>
<table>
{{ event_form | bootstrap }}
{{ event_form | bootstrap }}
</table>
{% endfor %}
{% endif %}
{% if login_clipper or member %}
<input type="hidden" name="user_exists" value="1" />
{% endif %}

View file

@ -52,3 +52,10 @@ calendar_patterns = [
url(r'^(?P<token>[a-z0-9-]+)/calendar.ics$',
'gestioncof.views.calendar_ics')
]
clubs_patterns = [
url(r'^membres/(?P<name>\w+)', views.membres_club, name='membres-club'),
url(r'^liste', views.liste_clubs, name='liste-clubs'),
url(r'^change_respo/(?P<club_name>\w+)/(?P<user_id>\d+)',
views.change_respo, name='change-respo'),
]

View file

@ -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('<h1>Permission denied</h1>')
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')

View file

@ -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