Major update
This commit is contained in:
parent
8e1bf7b705
commit
2479b0a24d
0
bda/__init__.py
Normal file
0
bda/__init__.py
Normal file
14
bda/admin.py
Normal file
14
bda/admin.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
# coding: utf-8
|
||||
|
||||
from django.contrib import admin
|
||||
from bda.models import Spectacle, Participant, ChoixSpectacle
|
||||
|
||||
class ChoixSpectacleInline(admin.TabularInline):
|
||||
model = ChoixSpectacle
|
||||
sortable_field_name = "priority"
|
||||
|
||||
class ParticipantAdmin(admin.ModelAdmin):
|
||||
inlines = [ChoixSpectacleInline]
|
||||
|
||||
admin.site.register(Spectacle)
|
||||
admin.site.register(Participant, ParticipantAdmin)
|
92
bda/algorithm.py
Normal file
92
bda/algorithm.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
# coding: utf-8
|
||||
|
||||
from django.conf import settings
|
||||
import random
|
||||
|
||||
class Algorithm(object):
|
||||
|
||||
shows = None
|
||||
ranks = None
|
||||
origranks = None
|
||||
double = None
|
||||
|
||||
def __init__(self, shows, members):
|
||||
"""Initialisation :
|
||||
- on aggrège toutes les demandes pour chaque spectacle dans
|
||||
show.requests
|
||||
- on crée des tables de demandes pour chaque personne, afin de
|
||||
pouvoir modifier les rankings"""
|
||||
self.shows = []
|
||||
showdict = {}
|
||||
for show in shows:
|
||||
showdict[show] = show
|
||||
show.requests = []
|
||||
self.shows.append(show)
|
||||
self.ranks = {}
|
||||
self.origranks = {}
|
||||
self.double = {}
|
||||
for member in members:
|
||||
ranks = {}
|
||||
double = {}
|
||||
for i in range(1, settings.NUM_CHOICES + 1):
|
||||
choice = getattr(member, "choice%d" % i)
|
||||
if not choice:
|
||||
continue
|
||||
# Noter les doubles demandes
|
||||
if choice in double:
|
||||
double[choice] = True
|
||||
else:
|
||||
showdict[choice].requests.append(member)
|
||||
ranks[choice] = i
|
||||
double[choice] = False
|
||||
self.ranks[member] = ranks
|
||||
self.double[member] = double
|
||||
self.origranks[member] = dict(ranks)
|
||||
|
||||
def IncrementRanks(self, member, currank, increment = 1):
|
||||
for show in self.ranks[member]:
|
||||
if self.ranks[member][show] > currank:
|
||||
self.ranks[member][show] -= increment
|
||||
|
||||
def appendResult(self, l, member, show):
|
||||
l.append((member,
|
||||
self.ranks[member][show],
|
||||
self.origranks[member][show],
|
||||
self.double[member][show]))
|
||||
|
||||
def __call__(self, seed):
|
||||
random.seed(seed)
|
||||
results = []
|
||||
for show in self.shows:
|
||||
# On regroupe tous les gens ayant le même rang
|
||||
groups = {}
|
||||
for i in range(1, settings.NUM_CHOICES + 1):
|
||||
groups[i] = []
|
||||
for member in show.requests:
|
||||
groups[self.ranks[member][show]].append(member)
|
||||
# On passe à l'attribution
|
||||
winners = []
|
||||
losers = []
|
||||
for i in range(1, settings.NUM_CHOICES + 1):
|
||||
group = list(groups[i])
|
||||
random.shuffle(group)
|
||||
for member in group:
|
||||
if self.double[member][show]: # double
|
||||
if len(winners) + 1 < show.slots:
|
||||
self.appendResult(winners, member, show)
|
||||
self.appendResult(winners, member, show)
|
||||
elif not member.autoquit and len(winners) < show.slots:
|
||||
self.appendResult(winners, member, show)
|
||||
self.appendResult(losers, member, show)
|
||||
else:
|
||||
self.appendResult(losers, member, show)
|
||||
self.appendResult(losers, member, show)
|
||||
self.IncrementRanks(member, i, 2)
|
||||
else: # simple
|
||||
if len(winners) < show.slots:
|
||||
self.appendResult(winners, member, show)
|
||||
else:
|
||||
self.appendResult(losers, member, show)
|
||||
self.IncrementRanks(member, i)
|
||||
results.append((show,winners,losers))
|
||||
return results
|
44
bda/models.py
Normal file
44
bda/models.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# coding: utf-8
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db.models.signals import post_save
|
||||
|
||||
class Spectacle (models.Model):
|
||||
title = models.CharField ("Titre", max_length = 300)
|
||||
date = models.DateTimeField ("Date & heure")
|
||||
location = models.CharField ("Lieu", max_length = 300,
|
||||
blank = True, null = True)
|
||||
description = models.TextField ("Description", blank = True)
|
||||
slots_description = models.TextField ("Description des places", blank = True)
|
||||
slots = models.IntegerField ("Places")
|
||||
priority = models.IntegerField ("Priorité", default = 1000)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Spectacle"
|
||||
ordering = ("priority", "date","title",)
|
||||
|
||||
def __repr__ (self):
|
||||
return u"[%s]" % self.__unicode__()
|
||||
|
||||
def __unicode__ (self):
|
||||
return u"%s - %s @ %s" % (self.title, self.date, self.location)
|
||||
|
||||
class Participant (models.Model):
|
||||
user = models.ForeignKey(User, unique = True)
|
||||
choices = models.ManyToManyField(Spectacle, through = "ChoixSpectacle")
|
||||
|
||||
def __unicode__ (self):
|
||||
return u"%s" % (self.user)
|
||||
|
||||
class ChoixSpectacle (models.Model):
|
||||
participant = models.ForeignKey(Participant)
|
||||
spectacle = models.ForeignKey(Spectacle, related_name = "participants")
|
||||
priority = models.PositiveIntegerField("Priorité")
|
||||
double = models.BooleanField("Deux places<sup>1</sup>")
|
||||
autoquit = models.BooleanField("Abandon<sup>2</sup>")
|
||||
class Meta:
|
||||
ordering = ("priority",)
|
||||
#unique_together = (("participant", "spectacle",),)
|
||||
verbose_name = "voeu"
|
16
bda/tests.py
Normal file
16
bda/tests.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
This file demonstrates writing tests using the unittest module. These will pass
|
||||
when you run "manage.py test".
|
||||
|
||||
Replace this with more appropriate tests for your application.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
class SimpleTest(TestCase):
|
||||
def test_basic_addition(self):
|
||||
"""
|
||||
Tests that 1 + 1 always equals 2.
|
||||
"""
|
||||
self.assertEqual(1 + 1, 2)
|
40
bda/views.py
Normal file
40
bda/views.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
# coding: utf-8
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django import forms
|
||||
from django.forms.models import inlineformset_factory, BaseInlineFormSet
|
||||
|
||||
from gestioncof.shared import render_page
|
||||
from bda.models import Spectacle, Participant, ChoixSpectacle
|
||||
|
||||
class BaseBdaFormSet(BaseInlineFormSet):
|
||||
def clean(self):
|
||||
"""Checks that no two articles have the same title."""
|
||||
if any(self.errors):
|
||||
# Don't bother validating the formset unless each form is valid on its own
|
||||
return
|
||||
spectacles = []
|
||||
for i in range(0, self.total_form_count()):
|
||||
form = self.forms[i]
|
||||
if not form.cleaned_data:
|
||||
continue
|
||||
spectacle = form.cleaned_data['spectacle']
|
||||
delete = form.cleaned_data['DELETE']
|
||||
if not delete and spectacle in spectacles:
|
||||
raise forms.ValidationError("Vous ne pouvez pas vous inscrire deux fois pour le même spectacle.")
|
||||
spectacles.append(spectacle)
|
||||
|
||||
@login_required
|
||||
def inscription(request):
|
||||
BdaFormSet = inlineformset_factory(Participant, ChoixSpectacle, fields = ("spectacle","double","autoquit","priority",), formset = BaseBdaFormSet)
|
||||
participant, created = Participant.objects.get_or_create(user = request.user)
|
||||
success = False
|
||||
if request.method == "POST":
|
||||
formset = BdaFormSet(request.POST, instance = participant)
|
||||
if formset.is_valid():
|
||||
formset.save()
|
||||
success = True
|
||||
formset = BdaFormSet(instance = participant)
|
||||
else:
|
||||
formset = BdaFormSet(instance = participant)
|
||||
return render_page(request, {"formset": formset, "success": success}, "inscription-bda.html")
|
|
@ -3,6 +3,9 @@
|
|||
from django.contrib import admin
|
||||
from gestioncof.models import Survey, SurveyQuestion, SurveyQuestionAnswer
|
||||
from gestioncof.models import Event, EventOption, EventOptionChoice
|
||||
from gestioncof.models import CofProfile
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
@ -41,12 +44,11 @@ class SurveyAdmin(admin.ModelAdmin):
|
|||
SurveyQuestionInline,
|
||||
]
|
||||
|
||||
@add_link_field()
|
||||
class EventOptionChoiceInline(admin.StackedInline):
|
||||
class EventOptionChoiceInline(admin.TabularInline):
|
||||
model = EventOptionChoice
|
||||
|
||||
@add_link_field()
|
||||
class EventOptionInline(admin.StackedInline):
|
||||
@add_link_field(desc_text = lambda x: "Choix", link_text = lambda x: "Éditer les choix")
|
||||
class EventOptionInline(admin.TabularInline):
|
||||
model = EventOption
|
||||
|
||||
class EventOptionAdmin(admin.ModelAdmin):
|
||||
|
@ -59,7 +61,40 @@ class EventAdmin(admin.ModelAdmin):
|
|||
EventOptionInline,
|
||||
]
|
||||
|
||||
class CofProfileInline(admin.StackedInline):
|
||||
model = CofProfile
|
||||
inline_classes = ("collapse open",)
|
||||
|
||||
class UserProfileAdmin(UserAdmin):
|
||||
def login_clipper(self, obj):
|
||||
try:
|
||||
return obj.get_profile().login_clipper
|
||||
except UserProfile.DoesNotExist:
|
||||
return ""
|
||||
def is_buro(self, obj):
|
||||
try:
|
||||
return obj.get_profile().is_buro
|
||||
except UserProfile.DoesNotExist:
|
||||
return False
|
||||
is_buro.short_description = 'Membre du Buro'
|
||||
is_buro.boolean = True
|
||||
def is_cof(self, obj):
|
||||
try:
|
||||
return obj.get_profile().is_cof
|
||||
except UserProfile.DoesNotExist:
|
||||
return False
|
||||
is_cof.short_description = 'Membre du COF'
|
||||
is_cof.boolean = True
|
||||
list_display = UserAdmin.list_display + ('login_clipper','is_cof','is_buro',)
|
||||
list_filter = UserAdmin.list_filter + ('profile__is_cof', 'profile__is_buro')
|
||||
inlines = [
|
||||
CofProfileInline,
|
||||
]
|
||||
|
||||
admin.site.register(Survey, SurveyAdmin)
|
||||
admin.site.register(SurveyQuestion, SurveyQuestionAdmin)
|
||||
admin.site.register(Event, EventAdmin)
|
||||
admin.site.register(EventOption, EventOptionAdmin)
|
||||
admin.site.unregister(User)
|
||||
admin.site.register(User, UserProfileAdmin)
|
||||
admin.site.register(CofProfile)
|
||||
|
|
21
gestioncof/decorators.py
Normal file
21
gestioncof/decorators.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from django_cas.decorators import user_passes_test
|
||||
|
||||
def is_cof(user):
|
||||
try:
|
||||
profile = user.get_profile()
|
||||
return profile.is_cof
|
||||
except:
|
||||
return False
|
||||
|
||||
def cof_required(login_url = None):
|
||||
return user_passes_test(lambda u: is_cof(u), login_url=login_url)
|
||||
|
||||
def is_buro(user):
|
||||
try:
|
||||
profile = user.get_profile()
|
||||
return profile.is_buro
|
||||
except:
|
||||
return False
|
||||
|
||||
def buro_required(login_url = None):
|
||||
return user_passes_test(lambda u: is_buro(u), login_url=login_url)
|
|
@ -26,9 +26,11 @@ def choices_length (choices):
|
|||
return reduce (lambda m, choice: max (m, len (choice[0])), choices, 0)
|
||||
|
||||
class CofProfile(models.Model):
|
||||
user = models.OneToOneField(User)
|
||||
login_clipper = models.CharField("Login clipper", max_length = 8)
|
||||
user = models.OneToOneField(User, related_name = "profile")
|
||||
login_clipper = models.CharField("Login clipper", max_length = 8, blank = True)
|
||||
is_cof = models.BooleanField("Membre du COF", default = False)
|
||||
num = models.IntegerField ("Numéro d'adhérent", blank = True, default = 0),
|
||||
phone = models.CharField("Téléphone", max_length = 20, blank = True)
|
||||
occupation = models.CharField (_(u"Occupation"),
|
||||
default = "1A",
|
||||
choices = OCCUPATION_CHOICES,
|
||||
|
@ -37,20 +39,27 @@ class CofProfile(models.Model):
|
|||
default = "normalien",
|
||||
choices = TYPE_COTIZ_CHOICES,
|
||||
max_length = choices_length (TYPE_COTIZ_CHOICES))
|
||||
mailing_cof = models.BooleanField("Recevoir les mails COF", default = True)
|
||||
mailing_bda_revente = models.BooleanField("Recevoir les mails de revente de places BDA", default = True)
|
||||
mailing_cof = models.BooleanField("Recevoir les mails COF", default = False)
|
||||
mailing_bda_revente = models.BooleanField("Recevoir les mails de revente de places BDA", default = False)
|
||||
is_buro = models.BooleanField("Membre du Burô", default = False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Profil COF"
|
||||
verbose_name_plural = "Profils COF"
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.user.username)
|
||||
|
||||
def create_user_profile(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
CofProfile.objects.create(user = instance)
|
||||
CofProfile.objects.get_or_create(user = instance)
|
||||
post_save.connect(create_user_profile, sender = User)
|
||||
|
||||
class Event(models.Model):
|
||||
title = models.CharField("Titre", max_length = 200)
|
||||
location = models.CharField("Lieu", max_length = 200)
|
||||
start_date = models.DateField("Date de début", blank = True)
|
||||
end_date = models.DateField("Date de fin", blank = True)
|
||||
start_date = models.DateField("Date de début", blank = True, null = True)
|
||||
end_date = models.DateField("Date de fin", blank = True, null = True)
|
||||
description = models.TextField("Description", blank = True)
|
||||
registration_open = models.BooleanField("Inscriptions ouvertes", default = True)
|
||||
|
||||
|
@ -61,8 +70,9 @@ class Event(models.Model):
|
|||
return unicode(self.title)
|
||||
|
||||
class EventOption(models.Model):
|
||||
event = models.ForeignKey(Event)
|
||||
event = models.ForeignKey(Event, related_name = "options")
|
||||
name = models.CharField("Option", max_length = 200)
|
||||
multi_choices = models.BooleanField("Choix multiples", default = False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Option"
|
||||
|
@ -71,7 +81,7 @@ class EventOption(models.Model):
|
|||
return unicode(self.name)
|
||||
|
||||
class EventOptionChoice(models.Model):
|
||||
event_option = models.ForeignKey(EventOption)
|
||||
event_option = models.ForeignKey(EventOption, related_name = "choices")
|
||||
value = models.CharField("Valeur", max_length = 200)
|
||||
|
||||
class Meta:
|
||||
|
@ -88,6 +98,7 @@ class EventRegistration(models.Model):
|
|||
|
||||
class Meta:
|
||||
verbose_name = "Inscription"
|
||||
unique_together = ("user", "event")
|
||||
|
||||
class Survey(models.Model):
|
||||
title = models.CharField("Titre", max_length = 200)
|
||||
|
@ -124,7 +135,8 @@ class SurveyQuestionAnswer(models.Model):
|
|||
class SurveyAnswer(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
survey = models.ForeignKey(Survey)
|
||||
answers = models.ManyToManyField(SurveyQuestionAnswer)
|
||||
answers = models.ManyToManyField(SurveyQuestionAnswer, related_name = "selected_by")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Réponses"
|
||||
unique_together = ("user", "survey")
|
||||
|
|
|
@ -1,18 +1,35 @@
|
|||
from django.contrib.sites.models import Site
|
||||
from django.conf import settings
|
||||
from django_cas.backends import CASBackend
|
||||
from django.template import RequestContext, loader
|
||||
from django.http import HttpResponse
|
||||
|
||||
from gestioncof.models import CofProfile
|
||||
|
||||
def render_page (request, data, template):
|
||||
template = loader.get_template (template)
|
||||
context = RequestContext (request, data)
|
||||
return HttpResponse (template.render (context))
|
||||
|
||||
class COFCASBackend(CASBackend):
|
||||
def authenticate(self, ticket, service):
|
||||
"""Authenticates CAS ticket and retrieves user data"""
|
||||
user = super(COFCASBackend, self).authenticate(ticket, service)
|
||||
profile = user.get_profile()
|
||||
try:
|
||||
profile = user.get_profile()
|
||||
except CofProfile.DoesNotExist:
|
||||
profile, created = CofProfile.objects.get_or_create(user = user)
|
||||
profile.save()
|
||||
if not profile.login_clipper:
|
||||
profile.login_clipper = user.username
|
||||
profile.save()
|
||||
if not user.email:
|
||||
user.email = settings.CAS_EMAIL_FORMAT % profile.login_clipper
|
||||
user.save()
|
||||
if profile.is_buro and not user.is_superuser:
|
||||
user.is_superuser = True
|
||||
user.is_staff = True
|
||||
user.save()
|
||||
return user
|
||||
|
||||
def context_processor (request):
|
||||
|
|
0
gestioncof/templatetags/__init__.py
Normal file
0
gestioncof/templatetags/__init__.py
Normal file
12
gestioncof/templatetags/utils.py
Normal file
12
gestioncof/templatetags/utils.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
def key(d, key_name):
|
||||
try:
|
||||
value = d[key_name]
|
||||
except KeyError:
|
||||
from django.conf import settings
|
||||
value = settings.TEMPLATE_STRING_IF_INVALID
|
||||
return value
|
||||
key = register.filter('key', key)
|
|
@ -1,18 +1,18 @@
|
|||
# coding: utf-8
|
||||
from django.shortcuts import redirect, get_object_or_404
|
||||
from django.template import RequestContext, loader
|
||||
from django.http import HttpResponse, HttpResponseRedirect, Http404
|
||||
from django.http import Http404
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django import forms
|
||||
from django.forms.widgets import RadioSelect, CheckboxSelectMultiple
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from gestioncof.models import Survey, SurveyQuestion, SurveyQuestionAnswer, SurveyAnswer
|
||||
from gestioncof.models import Event, EventOption, EventOptionChoice, EventRegistration
|
||||
|
||||
def render_page (request, data, template):
|
||||
template = loader.get_template (template)
|
||||
context = RequestContext (request, data)
|
||||
return HttpResponse (template.render (context))
|
||||
from gestioncof.models import CofProfile
|
||||
from gestioncof.shared import render_page
|
||||
from gestioncof.decorators import buro_required, cof_required
|
||||
from gestioncof.widgets import TriStateCheckbox
|
||||
|
||||
@login_required
|
||||
def home(request):
|
||||
|
@ -27,7 +27,11 @@ def login(request):
|
|||
|
||||
@login_required
|
||||
def logout(request):
|
||||
if request.user.get_profile().login_clipper:
|
||||
try:
|
||||
profile = request.user.get_profile()
|
||||
except CofProfile.DoesNotExist:
|
||||
profile, created = CofProfile.objects.get_or_create(user = request.user)
|
||||
if profile.login_clipper:
|
||||
return redirect("django_cas.views.logout")
|
||||
else:
|
||||
return redirect("django.contrib.auth.views.logout")
|
||||
|
@ -74,35 +78,294 @@ def survey(request, survey_id):
|
|||
if not survey.survey_open:
|
||||
raise Http404
|
||||
success = False
|
||||
deleted = False
|
||||
if request.method == "POST":
|
||||
form = SurveyForm(request.POST, survey = survey)
|
||||
if form.is_valid():
|
||||
all_answers = []
|
||||
for question_id, answers_ids in form.answers():
|
||||
question = get_object_or_404(SurveyQuestion, id = question_id,
|
||||
survey = survey)
|
||||
if type(answers_ids) != list:
|
||||
answers_ids = [answers_ids]
|
||||
if not question.multi_answers and len(answers_ids) > 1:
|
||||
raise Http404
|
||||
for answer_id in answers_ids:
|
||||
answer_id = int(answer_id)
|
||||
answer = SurveyQuestionAnswer.objects.get(
|
||||
id = answer_id,
|
||||
survey_question = question)
|
||||
all_answers.append(answer)
|
||||
if request.POST.get('delete'):
|
||||
try:
|
||||
current_answer = SurveyAnswer.objects.get(user = request.user, survey = survey)
|
||||
current_answer.delete()
|
||||
current_answer = None
|
||||
except SurveyAnswer.DoesNotExist:
|
||||
current_answer = SurveyAnswer(user = request.user, survey = survey)
|
||||
current_answer.save()
|
||||
current_answer.answers = all_answers
|
||||
current_answer.save()
|
||||
current_answer = None
|
||||
form = SurveyForm(survey = survey)
|
||||
success = True
|
||||
deleted = True
|
||||
else:
|
||||
if form.is_valid():
|
||||
all_answers = []
|
||||
for question_id, answers_ids in form.answers():
|
||||
question = get_object_or_404(SurveyQuestion, id = question_id,
|
||||
survey = survey)
|
||||
if type(answers_ids) != list:
|
||||
answers_ids = [answers_ids]
|
||||
if not question.multi_answers and len(answers_ids) > 1:
|
||||
raise Http404
|
||||
for answer_id in answers_ids:
|
||||
if not answer_id:
|
||||
continue
|
||||
answer_id = int(answer_id)
|
||||
answer = SurveyQuestionAnswer.objects.get(
|
||||
id = answer_id,
|
||||
survey_question = question)
|
||||
all_answers.append(answer)
|
||||
try:
|
||||
current_answer = SurveyAnswer.objects.get(user = request.user, survey = survey)
|
||||
except SurveyAnswer.DoesNotExist:
|
||||
current_answer = SurveyAnswer(user = request.user, survey = survey)
|
||||
current_answer.save()
|
||||
current_answer.answers = all_answers
|
||||
current_answer.save()
|
||||
success = True
|
||||
else:
|
||||
try:
|
||||
current_answer = SurveyAnswer.objects.get(user = request.user, survey = survey)
|
||||
form = SurveyForm(survey = survey, current_answers = current_answer.answers)
|
||||
except SurveyAnswer.DoesNotExist:
|
||||
current_answer = None
|
||||
form = SurveyForm(survey = survey)
|
||||
return render_page(request, {"survey": survey, "form": form, "success": success}, "survey.html")
|
||||
return render_page(request, {"survey": survey, "form": form, "success": success, "deleted": deleted, "current_answer": current_answer}, "survey.html")
|
||||
|
||||
class EventForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
event = kwargs.pop("event")
|
||||
current_choices = kwargs.pop("current_choices", None)
|
||||
super(EventForm, self).__init__(*args, **kwargs)
|
||||
choices = {}
|
||||
if current_choices:
|
||||
for choice in current_choices.all():
|
||||
if choice.event_option.id not in choices:
|
||||
choices[choice.event_option.id] = [choice.id]
|
||||
else:
|
||||
choices[choice.event_option.id].append(choice.id)
|
||||
all_choices = choices
|
||||
for option in event.options.all():
|
||||
choices = [(choice.id, choice.value) for choice in option.choices.all()]
|
||||
if option.multi_choices:
|
||||
initial = [] if option.id not in all_choices else all_choices[option.id]
|
||||
field = forms.MultipleChoiceField(label = option.name,
|
||||
choices = choices,
|
||||
widget = CheckboxSelectMultiple,
|
||||
required = False,
|
||||
initial = initial)
|
||||
else:
|
||||
initial = None if option.id not in all_choices else all_choices[option.id][0]
|
||||
field = forms.ChoiceField(label = option.name,
|
||||
choices = choices,
|
||||
widget = RadioSelect,
|
||||
required = False,
|
||||
initial = initial)
|
||||
field.option_id = option.id
|
||||
self.fields["option_%d" % option.id] = field
|
||||
|
||||
def choices(self):
|
||||
for name, value in self.cleaned_data.items():
|
||||
if name.startswith('option_'):
|
||||
yield (self.fields[name].option_id, value)
|
||||
|
||||
@login_required
|
||||
def event(request, event_id):
|
||||
event = get_object_or_404(Event, id = event_id)
|
||||
if not event.registration_open:
|
||||
raise Http404
|
||||
success = False
|
||||
if request.method == "POST":
|
||||
form = EventForm(request.POST, event = event)
|
||||
if form.is_valid():
|
||||
all_choices = []
|
||||
for option_id, choices_ids in form.choices():
|
||||
option = get_object_or_404(EventOption, id = option_id,
|
||||
event = event)
|
||||
if type(choices_ids) != list:
|
||||
choices_ids = [choices_ids]
|
||||
if not option.multi_choices and len(choices_ids) > 1:
|
||||
raise Http404
|
||||
for choice_id in choices_ids:
|
||||
if not choice_id:
|
||||
continue
|
||||
choice_id = int(choice_id)
|
||||
choice = EventOptionChoice.objects.get(
|
||||
id = choice_id,
|
||||
event_option = option)
|
||||
all_choices.append(choice)
|
||||
try:
|
||||
current_registration = EventRegistration.objects.get(user = request.user, event = event)
|
||||
except EventRegistration.DoesNotExist:
|
||||
current_registration = EventRegistration(user = request.user, event = event)
|
||||
current_registration.save()
|
||||
current_registration.options = all_choices
|
||||
current_registration.save()
|
||||
success = True
|
||||
else:
|
||||
try:
|
||||
current_registration = EventRegistration.objects.get(user = request.user, event = event)
|
||||
form = EventForm(event = event, current_choices = current_registration.options)
|
||||
except EventRegistration.DoesNotExist:
|
||||
form = EventForm(event = event)
|
||||
return render_page(request, {"event": event, "form": form, "success": success}, "event.html")
|
||||
|
||||
@buro_required()
|
||||
def event_status(request, event_id):
|
||||
event = get_object_or_404(Event, id = event_id)
|
||||
registrants = EventRegistration.objects.filter(event = event).all()
|
||||
return render_page(request, {"event": event, "registrants": registrants}, "event_status.html")
|
||||
|
||||
class SurveyStatusFilterForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
survey = kwargs.pop("survey")
|
||||
super(SurveyStatusFilterForm, self).__init__(*args, **kwargs)
|
||||
answers = {}
|
||||
for question in survey.questions.all():
|
||||
for answer in question.answers.all():
|
||||
name = "question_%d_answer_%d" % (question.id, answer.id)
|
||||
if self.is_bound and self.data.get(self.add_prefix(name), None):
|
||||
initial = self.data.get(self.add_prefix(name), None)
|
||||
else:
|
||||
initial = "none"
|
||||
field = forms.ChoiceField(label = "%s : %s" % (question.question, answer.answer),
|
||||
choices = [("yes", "yes"),("no","no"),("none","none")],
|
||||
widget = TriStateCheckbox,
|
||||
required = False,
|
||||
initial = initial)
|
||||
field.question_id = question.id
|
||||
field.answer_id = answer.id
|
||||
self.fields[name] = field
|
||||
|
||||
def filters(self):
|
||||
for name, value in self.cleaned_data.items():
|
||||
if name.startswith('question_'):
|
||||
yield (self.fields[name].question_id, self.fields[name].answer_id, value)
|
||||
|
||||
class EventStatusFilterForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
event = kwargs.pop("event")
|
||||
super(EventStatusFilterForm, self).__init__(*args, **kwargs)
|
||||
choices = {}
|
||||
for option in event.options.all():
|
||||
for choice in option.choices.all():
|
||||
name = "option_%d_choice_%d" % (option.id, choice.id)
|
||||
if self.is_bound and self.data.get(self.add_prefix(name), None):
|
||||
initial = self.data.get(self.add_prefix(name), None)
|
||||
else:
|
||||
initial = "none"
|
||||
field = forms.ChoiceField(label = "%s : %s" % (option.name, choice.value),
|
||||
choices = [("yes", "yes"),("no","no"),("none","none")],
|
||||
widget = TriStateCheckbox,
|
||||
required = False,
|
||||
initial = initial)
|
||||
field.option_id = option.id
|
||||
field.choice_id = choice.id
|
||||
self.fields[name] = field
|
||||
|
||||
def filters(self):
|
||||
for name, value in self.cleaned_data.items():
|
||||
if name.startswith('option_'):
|
||||
yield (self.fields[name].option_id, self.fields[name].choice_id, value)
|
||||
|
||||
def clean_post_for_status(initial):
|
||||
d = dict(initial)
|
||||
for k, v in d.items():
|
||||
if k.startswith("id_"):
|
||||
del d[k]
|
||||
if type(v) == list and len(v) >= 1:
|
||||
d[k[3:]] = v[0]
|
||||
else:
|
||||
d[k[3:]] = v
|
||||
return d
|
||||
|
||||
@buro_required()
|
||||
def event_status(request, event_id):
|
||||
event = get_object_or_404(Event, id = event_id)
|
||||
registrations_query = EventRegistration.objects.filter(event = event)
|
||||
post_data = clean_post_for_status(request.POST)
|
||||
form = EventStatusFilterForm(post_data or None, event = event)
|
||||
if form.is_valid():
|
||||
for option_id, choice_id, value in form.filters():
|
||||
choice = get_object_or_404(EventOptionChoice, id = choice_id, event_option__id = option_id)
|
||||
if value == "none":
|
||||
continue
|
||||
if value == "yes":
|
||||
registrations_query = registrations_query.filter(options__id__exact = choice.id)
|
||||
elif value == "no":
|
||||
registrations_query = registrations_query.exclude(options__id__exact = choice.id)
|
||||
user_choices = registrations_query.prefetch_related("user").all()
|
||||
options = EventOption.objects.filter(event = event).all()
|
||||
choices_count = {}
|
||||
for option in options:
|
||||
for choice in option.choices.all():
|
||||
choices_count[choice.id] = 0
|
||||
for user_choice in user_choices:
|
||||
for choice in user_choice.options.all():
|
||||
choices_count[choice.id] += 1
|
||||
return render_page(request, {"event": event, "user_choices": user_choices, "options": options, "choices_count": choices_count, "form": form}, "event_status.html")
|
||||
|
||||
@buro_required()
|
||||
def survey_status(request, survey_id):
|
||||
survey = get_object_or_404(Survey, id = survey_id)
|
||||
answers_query = SurveyAnswer.objects.filter(survey = survey)
|
||||
post_data = clean_post_for_status(request.POST)
|
||||
form = SurveyStatusFilterForm(post_data or None, survey = survey)
|
||||
if form.is_valid():
|
||||
for question_id, answer_id, value in form.filters():
|
||||
answer = get_object_or_404(SurveyQuestionAnswer, id = answer_id, survey_question__id = question_id)
|
||||
if value == "none":
|
||||
continue
|
||||
if value == "yes":
|
||||
answers_query = answers_query.filter(answers__id__exact = answer.id)
|
||||
elif value == "no":
|
||||
answers_query = answers_query.exclude(answers__id__exact = answer.id)
|
||||
user_answers = answers_query.prefetch_related("user").all()
|
||||
questions = SurveyQuestion.objects.filter(survey = survey).all()
|
||||
answers_count = {}
|
||||
for question in questions:
|
||||
for answer in question.answers.all():
|
||||
answers_count[answer.id] = 0
|
||||
for user_answer in user_answers:
|
||||
for answer in user_answer.answers.all():
|
||||
answers_count[answer.id] += 1
|
||||
return render_page(request, {"survey": survey, "user_answers": user_answers, "questions": questions, "answers_count": answers_count, "form": form}, "survey_status.html")
|
||||
|
||||
class UserProfileForm(forms.ModelForm):
|
||||
first_name = forms.CharField(label=_(u'Prénom'), max_length=30)
|
||||
last_name = forms.CharField(label=_(u'Nom'), max_length=30)
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
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_revente',
|
||||
]
|
||||
|
||||
def save(self, *args, **kw):
|
||||
super(UserProfileForm, self).save(*args, **kw)
|
||||
self.instance.user.first_name = self.cleaned_data.get('first_name')
|
||||
self.instance.user.last_name = self.cleaned_data.get('last_name')
|
||||
self.instance.user.save()
|
||||
|
||||
class Meta:
|
||||
model = CofProfile
|
||||
fields = ("phone", "mailing_cof", "mailing_bda_revente",)
|
||||
|
||||
@login_required
|
||||
def profile(request):
|
||||
success = False
|
||||
if request.method == "POST":
|
||||
form = UserProfileForm(request.POST, instance = request.user.get_profile())
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
success = True
|
||||
else:
|
||||
form = UserProfileForm(instance = request.user.get_profile())
|
||||
return render_page(request, {"form": form, "success": success}, "profile.html")
|
||||
|
||||
@login_required
|
||||
def registration(request):
|
||||
data = {"surveys": Survey.objects.filter(survey_open = True).all(),
|
||||
"events": Event.objects.filter(registration_open = True).all()}
|
||||
return render_page(request, data, "registration.html")
|
||||
|
|
18
gestioncof/widgets.py
Normal file
18
gestioncof/widgets.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from django.forms.widgets import Widget
|
||||
from django.forms.util import flatatt
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
class TriStateCheckbox(Widget):
|
||||
|
||||
def __init__(self, attrs=None, choices=()):
|
||||
super(TriStateCheckbox, self).__init__(attrs)
|
||||
# choices can be any iterable, but we may need to render this widget
|
||||
# multiple times. Thus, collapse it into a list so it can be consumed
|
||||
# more than once.
|
||||
self.choices = list(choices)
|
||||
|
||||
def render(self, name, value, attrs=None, choices=()):
|
||||
if value is None: value = 'none'
|
||||
final_attrs = self.build_attrs(attrs, value=value)
|
||||
output = [u"<span class=\"tristate\"%s></span>" % flatatt(final_attrs)]
|
||||
return mark_safe('\n'.join(output))
|
234
media/cof.css
234
media/cof.css
|
@ -12,23 +12,136 @@ html,body {
|
|||
clear: both;
|
||||
}
|
||||
|
||||
#cof a:link, #cof a:active, #cof a:visited {
|
||||
a:link, a:active, a:visited {
|
||||
color: #111;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#cof a:hover {
|
||||
a:hover {
|
||||
color: #444;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#cof form {
|
||||
display: block;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
form#profile table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
#cof fieldset {
|
||||
table#bda_formset {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bda-field-spectacle {
|
||||
width: 65%;
|
||||
}
|
||||
|
||||
.bda-field-spectacle select {
|
||||
width: 94%;
|
||||
margin: 0 3%;
|
||||
}
|
||||
|
||||
.bda-field-double, .bda-field-autoquit, .bda-field-priority {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bda-field-double {
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.bda-field-autoquit {
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.tools-cell {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.tools {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.tools a.icon.drag-handler, .tools a.icon.delete-handler {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.tools a.icon.drag-handler {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
form#bda_form p {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
table#bda_formset {
|
||||
border-spacing: 0px 5px;
|
||||
}
|
||||
|
||||
tbody.bda_formset_content {
|
||||
}
|
||||
|
||||
tr.dynamic-form td {
|
||||
border-width: 1px 1px 1px 0px;
|
||||
border-style: solid;
|
||||
border-color: #888;
|
||||
background: #EEE;
|
||||
}
|
||||
|
||||
tr.dynamic-form td:first-child {
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
tr.dynamic-form.predelete td {
|
||||
background-color: #FFECEC;
|
||||
border-width: 1px 1px 1px 0px;
|
||||
border-style: solid;
|
||||
border-color: #CCC;
|
||||
}
|
||||
|
||||
tr.dynamic-form.predelete td:first-child {
|
||||
border-top-left-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
|
||||
tr.dynamic-form.predelete td:last-child {
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
|
||||
|
||||
.ui-sortable-helper {
|
||||
}
|
||||
|
||||
tr.ui-sortable-placeholder td, .placeholder-cell {
|
||||
background: transparent;
|
||||
border-width: 1px 1px 1px 0px;
|
||||
border-style: solid;
|
||||
border-color: #CCC;
|
||||
}
|
||||
|
||||
tr.ui-sortable-placeholder td:first-child {
|
||||
border-top-left-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
|
||||
tr.ui-sortable-placeholder td:last-child {
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
|
||||
form {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
form#profile table td, form#profile table th {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
form#profile table th {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
@ -37,16 +150,16 @@ html,body {
|
|||
width: auto;
|
||||
}
|
||||
|
||||
#cof fieldset legend {
|
||||
fieldset legend {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#cof #main-login-container {
|
||||
#main-login-container {
|
||||
width: 500px;
|
||||
margin: 7em auto;
|
||||
}
|
||||
|
||||
#cof #main-login {
|
||||
#main-login {
|
||||
width: 500px;
|
||||
border: 15px solid #333;
|
||||
-webkit-border-radius: 20px;
|
||||
|
@ -54,13 +167,14 @@ html,body {
|
|||
border-radius: 20px;
|
||||
}
|
||||
|
||||
#cof #main-container {
|
||||
#main-container {
|
||||
max-width: 90%;
|
||||
width: 800px;
|
||||
margin: 7em auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#cof #main {
|
||||
width: 800px;
|
||||
#main {
|
||||
border: 15px solid #333;
|
||||
-webkit-border-radius: 20px;
|
||||
-moz-border-radius: 20px;
|
||||
|
@ -69,61 +183,72 @@ html,body {
|
|||
box-shadow: 0 0 100px #AAA inset;
|
||||
}
|
||||
|
||||
#cof #main #main-content {
|
||||
#main #main-content {
|
||||
font-size: 1.25em;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#cof #main #main-content ul {
|
||||
#main #main-content ul {
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
#cof #main h1 {
|
||||
#main h1 {
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
#cof #main h1 a {
|
||||
#main h1 a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#cof #main h2 {
|
||||
#main h2 {
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#cof #main h3 {
|
||||
#main h3 {
|
||||
font-size: 1.3em;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#cof #main p {
|
||||
#main h4 {
|
||||
margin-top: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#main p {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#cof #main form li {
|
||||
#main form li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#cof .success {
|
||||
.success {
|
||||
font-weight: bold;
|
||||
color: #00B000;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#cof #main form ul.errorlist li {
|
||||
#main form ul.errorlist li {
|
||||
font-weight: bold;
|
||||
color: #B00000;
|
||||
background-color: transparent;
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
#cof #main-login.login_block {
|
||||
form#bda_form ul.errorlist li {
|
||||
padding-left: 0px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#main-login.login_block {
|
||||
padding: 2em;
|
||||
box-shadow: 0 0 100px #AAA inset;
|
||||
}
|
||||
|
||||
#cof a#login_clipper, #cof a#login_outsider {
|
||||
a#login_clipper, a#login_outsider {
|
||||
float: left;
|
||||
display: block;
|
||||
width: 250px;
|
||||
|
@ -137,41 +262,41 @@ html,body {
|
|||
color: #FFF;
|
||||
}
|
||||
|
||||
#cof a#login_clipper {
|
||||
a#login_clipper {
|
||||
background-color: #123E96;
|
||||
box-shadow: 0 0 100px #040C78 inset;
|
||||
}
|
||||
|
||||
#cof a#login_clipper:hover {
|
||||
a#login_clipper:hover {
|
||||
background-color: #164BB6;
|
||||
}
|
||||
|
||||
#cof a#login_outsider {
|
||||
a#login_outsider {
|
||||
background-color: #961221;
|
||||
box-shadow: 0 0 100px #780411 inset;
|
||||
}
|
||||
|
||||
#cof a#login_outsider:hover {
|
||||
a#login_outsider:hover {
|
||||
background-color: #B31729;
|
||||
}
|
||||
|
||||
#cof #main-login label {
|
||||
#main-login label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#cof #main-login label span.accesskey {
|
||||
#main-login label span.accesskey {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#cof #main-login input {
|
||||
#main-login input {
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
#cof #main-login .btn-row {
|
||||
#main-login .btn-row {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#cof .btn-submit {
|
||||
.btn-submit, .btn-addmore {
|
||||
float: none;
|
||||
clear: none;
|
||||
display: inline;
|
||||
|
@ -186,7 +311,7 @@ html,body {
|
|||
background: linear-gradient(center top, #EEE, #CCC);
|
||||
}
|
||||
|
||||
#cof #main-login .btn-reset {
|
||||
#main-login .btn-reset {
|
||||
float: none;
|
||||
clear: none;
|
||||
margin-left: 5px;
|
||||
|
@ -200,7 +325,7 @@ html,body {
|
|||
|
||||
/* RESET --------------------------------- */
|
||||
/* reset some properties for elements since defaults are not crossbrowser - http: //meyerweb.com/eric/thoughts/2007/05/01/reset-reloaded/ */
|
||||
html,body,div,span,h1,h2,h3,p,a,img,ul,li,fieldset,form,label,legend {
|
||||
html,body,div,span,h1,h2,h3,h4,p,a,img,ul,li,fieldset,form,label,legend {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
|
@ -245,7 +370,7 @@ tt {
|
|||
}
|
||||
|
||||
/* FORMS --------------------------------- */
|
||||
input {
|
||||
#main-login input {
|
||||
border-width: 1px;
|
||||
font-family: Verdana,sans-serif;
|
||||
font-size: 1.1em;
|
||||
|
@ -254,7 +379,7 @@ input {
|
|||
min-height: 1.5em;
|
||||
}
|
||||
|
||||
input[type="text"], input[type=password] {
|
||||
#main-login input[type="text"], #main-login input[type=password] {
|
||||
border: 2px solid #888;
|
||||
-webkit-border-radius: 8px;
|
||||
-moz-border-radius: 8px;
|
||||
|
@ -263,6 +388,15 @@ input[type="text"], input[type=password] {
|
|||
min-height: 2em;
|
||||
}
|
||||
|
||||
input[type="text"], input[type=password] {
|
||||
border: 1px solid #888;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 3px #AAA inset;
|
||||
padding: 0px 2px 0px 2px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
height: 1px;
|
||||
|
@ -275,24 +409,6 @@ hr {
|
|||
background: linear-gradient(left, hsla(0,0%,70%,0) 0%, hsla(0,0%,70%,.75) 50%, hsla(0,0%,70%,0) 100%);
|
||||
}
|
||||
|
||||
.fm-v div.row {
|
||||
margin: 0;
|
||||
padding: .5em 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fm-v div.row label {
|
||||
float: left;
|
||||
width: 100%;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.fm-v div.row input.btn-submit {
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fm-v div.row.fl-controls-left {
|
||||
width: 50%;
|
||||
float: left;
|
||||
.tristate:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
BIN
media/droidserif.woff
Normal file
BIN
media/droidserif.woff
Normal file
Binary file not shown.
BIN
media/images/no.png
Normal file
BIN
media/images/no.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 686 B |
BIN
media/images/none.png
Normal file
BIN
media/images/none.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 576 B |
BIN
media/images/yes.png
Normal file
BIN
media/images/yes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 610 B |
|
@ -95,7 +95,7 @@ TEMPLATE_LOADERS = (
|
|||
)
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
"django.core.context_processors.auth",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.core.context_processors.debug",
|
||||
"django.core.context_processors.i18n",
|
||||
"django.core.context_processors.media",
|
||||
|
@ -116,6 +116,7 @@ ROOT_URLCONF = 'urls'
|
|||
|
||||
TEMPLATE_DIRS = (
|
||||
"/home/gestion/www/templates/gestioncof",
|
||||
"/home/gestion/www/templates/bda",
|
||||
)
|
||||
|
||||
LOGIN_URL = "/gestion/login"
|
||||
|
@ -142,6 +143,9 @@ INSTALLED_APPS = (
|
|||
'django.contrib.admin',
|
||||
'django.contrib.admindocs',
|
||||
'gestioncof',
|
||||
'bda',
|
||||
'pads',
|
||||
'rezo',
|
||||
)
|
||||
|
||||
# A sample logging configuration. The only tangible logging
|
||||
|
|
115
templates/bda/inscription-bda.html
Normal file
115
templates/bda/inscription-bda.html
Normal file
|
@ -0,0 +1,115 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link href="{{ STATIC_URL }}grappelli/jquery/ui/css/custom-theme/jquery-ui-1.8.custom.css" rel="stylesheet" type="text/css" media="screen" title="no title" charset="utf-8" />
|
||||
<script src="{{ STATIC_URL }}grappelli/jquery/jquery-1.6.2.min.js" type="text/javascript"></script>
|
||||
<script src="{{ STATIC_URL }}grappelli/jquery/ui/js/jquery-ui-1.8.15.custom.min.js" type="text/javascript"></script>
|
||||
<link href="{{ STATIC_URL }}grappelli/css/tools.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{{ STATIC_URL }}grappelli/css/jquery-ui-grappelli-extensions.css" rel="stylesheet" type="text/css" />
|
||||
{% endblock %}
|
||||
|
||||
{% block realcontent %}
|
||||
<script type="text/javascript">
|
||||
var django = {
|
||||
"jQuery": jQuery.noConflict(true)
|
||||
};
|
||||
|
||||
(function($) {
|
||||
cloneMore = function(selector, type) {
|
||||
var newElement = $(selector).clone(true);
|
||||
var total = $('#id_' + type + '-TOTAL_FORMS').val();
|
||||
newElement.find(':input').each(function() {
|
||||
var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
|
||||
var id = 'id_' + name;
|
||||
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
|
||||
});
|
||||
newElement.find('label').each(function() {
|
||||
var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
|
||||
$(this).attr('for', newFor);
|
||||
});
|
||||
total++;
|
||||
$('#id_' + type + '-TOTAL_FORMS').val(total);
|
||||
$(selector).after(newElement);
|
||||
}
|
||||
deleteButtonHandler = function(elem) {
|
||||
elem.bind("click", function() {
|
||||
var deleteInput = $(this).prev(),
|
||||
form = $(this).parents(".dynamic-form").first();
|
||||
// callback
|
||||
// toggle options.predeleteCssClass and toggle checkbox
|
||||
if (form.hasClass("has_original")) {
|
||||
form.toggleClass("predelete");
|
||||
if (deleteInput.attr("checked")) {
|
||||
deleteInput.attr("checked", false);
|
||||
} else {
|
||||
deleteInput.attr("checked", true);
|
||||
}
|
||||
}
|
||||
// callback
|
||||
});
|
||||
};
|
||||
$(document).ready(function($) {
|
||||
deleteButtonHandler($("table#bda_formset tbody.bda_formset_content").find("a.delete-handler"));
|
||||
$("table#bda_formset tbody.bda_formset_content").sortable({
|
||||
handle: "a.drag-handler",
|
||||
items: "tr",
|
||||
axis: "y",
|
||||
appendTo: 'body',
|
||||
forceHelperSize: true,
|
||||
placeholder: 'ui-sortable-placeholder',
|
||||
forcePlaceholderSize: true,
|
||||
containment: 'form#bda_form',
|
||||
tolerance: 'pointer',
|
||||
start: function(evt, ui) {
|
||||
var template = "",
|
||||
len = ui.item.children("td").length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
template += "<td style='height:" + (ui.item.outerHeight() + 12 ) + "px' class='placeholder-cell'> </td>"
|
||||
}
|
||||
template += "";
|
||||
ui.placeholder.html(template);
|
||||
},
|
||||
stop: function(evt, ui) {
|
||||
// Toggle div.table twice to remove webkits border-spacing bug
|
||||
$("table#bda_formset").toggle().toggle();
|
||||
},
|
||||
});
|
||||
$("#bda_form").bind("submit", function(){
|
||||
var sortable_field_name = "priority";
|
||||
var i = 1;
|
||||
$(".bda_formset_content").find("tr").each(function(){
|
||||
var fields = $(this).find("td :input[value]"),
|
||||
select = $(this).find("td select");
|
||||
if (select.val() && fields.serialize()) {
|
||||
$(this).find("input[name$='"+sortable_field_name+"']").val(i);
|
||||
i++;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
})(django.jQuery);
|
||||
</script>
|
||||
|
||||
<h2>Inscription au tirage au sort du BDA</h2>
|
||||
{% if success %}
|
||||
<p class="success">Votre inscription a été mise à jour avec succès !</p>
|
||||
{% endif %}
|
||||
<form id="bda_form" method="post" action="{% url bda.views.inscription %}">
|
||||
{% csrf_token %}
|
||||
{% include "inscription-formset.html" %}
|
||||
<input type="button" class="btn-addmore" value="Ajouter un autre vœu" id="add_more">
|
||||
<script>
|
||||
django.jQuery('#add_more').click(function() {
|
||||
cloneMore('tbody.bda_formset_content tr:last-child', 'choixspectacle_set');
|
||||
});
|
||||
</script>
|
||||
<input type="submit" class="btn-submit" value="Enregistrer" />
|
||||
<hr />
|
||||
<p class="footnotes">
|
||||
<sup>1</sup>: demander deux places pour ce spectable<br />
|
||||
<sup>2</sup>: abandonner une place si impossible d'en obtenir une seconde pour ce spectacle (si vous avez coché l'option <tt>Deux places</tt> pour ce spectacle)<br />
|
||||
<sup>3</sup>: cette liste de vœu est ordonnée (du plus important au moins important), pour ajuster la priorité vous pouvez déplacer chaque vœu<br />
|
||||
</p>
|
||||
</form>
|
||||
{% endblock %}
|
40
templates/bda/inscription-formset.html
Normal file
40
templates/bda/inscription-formset.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
{{ formset.non_form_errors.as_ul }}
|
||||
<table id="bda_formset" class="form">
|
||||
{{ formset.management_form }}
|
||||
{% for form in formset.forms %}
|
||||
{% if forloop.first %}
|
||||
<thead><tr>
|
||||
{% for field in form.visible_fields %}
|
||||
{% if field.name != "DELETE" and field.name != "priority" %}
|
||||
<th class="bda-field-{{ field.name }}">{{ field.label|safe|capfirst }}</th>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<th><sup>3</sup></th>
|
||||
</tr></thead>
|
||||
<tbody class="bda_formset_content">
|
||||
{% endif %}
|
||||
<tr class="{% cycle row1,row2 %} dynamic-form {% if form.instance.pk %}has_original{% endif %}">
|
||||
{% for field in form.visible_fields %}
|
||||
{% if field.name != "DELETE" and field.name != "priority" %}
|
||||
<td class="bda-field-{{ field.name }}">
|
||||
{% if forloop.first %}
|
||||
{{ form.non_field_errors }}
|
||||
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
|
||||
{% endif %}
|
||||
{{ field.errors.as_ul }}
|
||||
{{ field }}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<td class="tools-cell"><div class="tools">
|
||||
<a href="javascript://" class="icon drag-handler" title="Déplacer"></a>
|
||||
<input type="checkbox" name="{{ form.DELETE.html_name }}" style="display: none;" />
|
||||
<input type="hidden" name="{{ form.priority.html_name }}" style="{{ form.priority.value }}" />
|
||||
<a href="javascript://" class="icon delete-handler" title="Supprimer"></a>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
|
@ -4,6 +4,7 @@
|
|||
<title>{{ site.name }}</title>
|
||||
<link type="text/css" rel="stylesheet" href="{{ MEDIA_URL }}/cof.css" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
<body id="cof">
|
||||
{% block content %}{% endblock %}
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
<div id="header">
|
||||
<h1>{% block title %}<a href="{% url gestioncof.views.home %}">{{ site.name }}</a>{% endblock %}</h1>
|
||||
<hr />
|
||||
<div id="main-content">
|
||||
{% block realcontent %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="main-content">
|
||||
{% block realcontent %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
16
templates/gestioncof/event.html
Normal file
16
templates/gestioncof/event.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Événement: {{ event.title }}</h2>
|
||||
{% if success %}
|
||||
<p class="success">Votre inscription a bien été enregistrée ! Vous pouvez cependant la modifier jusqu'à la fin des inscriptions.</p>
|
||||
{% endif %}
|
||||
{% if event.details %}
|
||||
<p>{{ event.details }}</p>
|
||||
{% endif %}
|
||||
<form method="post" action="{% url gestioncof.views.event event.id %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" class="btn-submit" value="Enregistrer" />
|
||||
</form>
|
||||
{% endblock %}
|
41
templates/gestioncof/event_status.html
Normal file
41
templates/gestioncof/event_status.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load utils %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Événement: {{ event.title }}{% if user.is_staff %} – <a href="{% url admin:gestioncof_event_change event.id %}">Administration</a>{% endif %}</h2>
|
||||
{% if event.details %}
|
||||
<p>{{ event.details }}</p>
|
||||
{% endif %}
|
||||
{% include "tristate_js.html" %}
|
||||
<h3>Filtres</h3>
|
||||
<form method="post" action="{% url gestioncof.views.event_status event.id %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" class="btn-submit" value="Filtrer" />
|
||||
</form>
|
||||
<h3>Résultats globaux</h3>
|
||||
{% for option in options %}
|
||||
<h4>{{ option.value }}</h4>
|
||||
<ul>
|
||||
{% for choice in option.choices.all %}
|
||||
<li>{{ choice.value }} : {{ choices_count|key:choice.id }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
<h3>Réponses individuelles</h3>
|
||||
<ul>
|
||||
{% for user_choice in user_choices %}{% with user_choice.user as auser %}
|
||||
{% if user_choice.options.all %}
|
||||
<li>
|
||||
{% if auser.first_name and auser.last_name %}{{ auser.first_name }} {{ auser.last_name }}
|
||||
{% else %}<tt>{{ auser.username }}</tt>{% endif %} :
|
||||
<ul>
|
||||
{% for choice in user_choice.options.all %}
|
||||
<li>{{ choice.event_option.name }} : {{ choice.value }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
|
@ -18,11 +18,25 @@
|
|||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if user.get_profile.is_buro %}
|
||||
<h3>Administration</h3>
|
||||
<ul>
|
||||
{% for event in events %}
|
||||
<li><a href="{% url gestioncof.views.event_status event.id %}">Événement : {{ event.title }}</a></li>
|
||||
{% endfor %}
|
||||
{% for survey in surveys %}
|
||||
<li><a href="{% url gestioncof.views.survey_status survey.id %}">Sondage : {{ survey.title }}</a></li>
|
||||
{% endfor %}
|
||||
<li><a href="{% url gestioncof.views.registration %}">Inscription d'un nouveau membre</a></li>
|
||||
<li><a href="{% url admin:index %}">Administration</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
<h3>Divers</h3>
|
||||
<ul>
|
||||
{% if user.is_buro or user.is_staff %}
|
||||
<li><a href="{% url admin:index %}">Administration</a></li>
|
||||
{% endif %}
|
||||
{% if user.get_profile.login_clipper == "seguin" or user.get_profile.login_clipper == "cmoreau" %}
|
||||
<li><a href="{% url bda.views.inscription %}">Inscription au tirage au sort du BDA</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url gestioncof.views.profile %}">Éditer mon profil</a></li>
|
||||
<li><a href="{% url gestioncof.views.logout %}">Se déconnecter</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
|
15
templates/gestioncof/profile.html
Normal file
15
templates/gestioncof/profile.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Modifier mon profil</h2>
|
||||
{% if success %}
|
||||
<p class="success">Votre profil a été mis à jour avec succès !</p>
|
||||
{% endif %}
|
||||
<form id="profile" method="post" action="{% url gestioncof.views.profile %}">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
<input type="submit" class="btn-submit" value="Enregistrer" />
|
||||
</form>
|
||||
{% endblock %}
|
15
templates/gestioncof/registration.html
Normal file
15
templates/gestioncof/registration.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Inscription d'un nouveau membre</h2>
|
||||
{% if success %}
|
||||
<p class="success">{{ member.first_name }} {{ member.last_name }} a été inscrit avec succès en tant que membre n°{{ member.get_profile.num }}!</p>
|
||||
{% endif %}
|
||||
<form id="profile" method="post" action="{% url gestioncof.views.registration %}">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
<input type="submit" class="btn-submit" value="Enregistrer l'inscription" />
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -3,8 +3,12 @@
|
|||
{% block realcontent %}
|
||||
<h2>Sondage: {{ survey.title }}</h2>
|
||||
{% if success %}
|
||||
{% if deleted %}
|
||||
<p class="success">Votre réponse a bien été supprimée !</p>
|
||||
{% else %}
|
||||
<p class="success">Votre réponse a bien été enregistrée ! Vous pouvez cependant la modifier jusqu'à la fin du sondage.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if survey.details %}
|
||||
<p>{{ survey.details }}</p>
|
||||
{% endif %}
|
||||
|
@ -12,5 +16,8 @@
|
|||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" class="btn-submit" value="Enregistrer" />
|
||||
{% if current_answer %}
|
||||
<input type="submit" name="delete" class="btn-submit" value="Supprimer ma réponse" />
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
41
templates/gestioncof/survey_status.html
Normal file
41
templates/gestioncof/survey_status.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load utils %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Sondage: {{ survey.title }}{% if user.is_staff %} – <a href="{% url admin:gestioncof_survey_change survey.id %}">Administration</a>{% endif %}</h2>
|
||||
{% if survey.details %}
|
||||
<p>{{ survey.details }}</p>
|
||||
{% endif %}
|
||||
<h3>Filtres</h3>
|
||||
{% include "tristate_js.html" %}
|
||||
<form method="post" action="{% url gestioncof.views.survey_status survey.id %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" class="btn-submit" value="Filtrer" />
|
||||
</form>
|
||||
<h3>Résultats globaux</h3>
|
||||
{% for question in questions %}
|
||||
<h4>{{ question.question }}</h4>
|
||||
<ul>
|
||||
{% for answer in question.answers.all %}
|
||||
<li>{{ answer.answer }} : {{ answers_count|key:answer.id }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
<h3>Réponses individuelles</h3>
|
||||
<ul>
|
||||
{% for user_answer in user_answers %}{% with user_answer.user as auser %}
|
||||
{% if user_answer.answers.all %}
|
||||
<li>
|
||||
{% if auser.first_name and auser.last_name %}{{ auser.first_name }} {{ auser.last_name }}
|
||||
{% else %}<tt>{{ auser.username }}</tt>{% endif %} :
|
||||
<ul>
|
||||
{% for answer in user_answer.answers.all %}
|
||||
<li>{{ answer.survey_question.question }} : {{ answer.answer }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
69
templates/gestioncof/tristate_js.html
Normal file
69
templates/gestioncof/tristate_js.html
Normal file
|
@ -0,0 +1,69 @@
|
|||
<script type="text/javascript">
|
||||
var supernifty_tristate = function() {
|
||||
var
|
||||
YES = { image: "{{ MEDIA_URL }}/images/yes.png", state: "yes" },
|
||||
NO = { image: "{{ MEDIA_URL }}/images/no.png", state: "no" },
|
||||
NONE = { image: "{{ MEDIA_URL }}/images/none.png", state: "none" };
|
||||
|
||||
function tristate_elements() {
|
||||
if ( document.getElementsByClassName != undefined ) {
|
||||
return document.getElementsByClassName( "tristate" );
|
||||
}
|
||||
else {
|
||||
var
|
||||
all = document.getElementsByTagName('*'),
|
||||
alllength = all.length,
|
||||
result = [], i;
|
||||
for ( i = 0; i < alllength; i++ ) {
|
||||
if ( all[i].className == 'tristate' ) {
|
||||
result.push( all[i] );
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
var list = tristate_elements(),
|
||||
i,
|
||||
html;
|
||||
for ( i = 0; i < list.length; i++ ) {
|
||||
var state = NONE;
|
||||
var value = list[i].getAttribute("value");
|
||||
if ( value == 'yes' )
|
||||
state = YES;
|
||||
else if ( value == 'no' )
|
||||
state = NO;
|
||||
html = "<img id=\"" + list[i].id + "_img\" src=\"" + state.image + "\" onclick=\"supernifty_tristate.update('" + list[i].id + "')\"/><input type=\"hidden\" id=\"" + list[i].id + "_frm\" name=\"" + list[i].id + "\" value=\"" + state.state + "\"/>";
|
||||
list[i].innerHTML = html;
|
||||
}
|
||||
},
|
||||
|
||||
update: function(id) {
|
||||
var state = document.getElementById( id + "_frm" ).value, next;
|
||||
// yes -> no -> none -> yes
|
||||
if ( state == 'yes' ) {
|
||||
next = NO;
|
||||
}
|
||||
else if ( state == 'no' ) {
|
||||
next = NONE;
|
||||
}
|
||||
else { // assume none
|
||||
next = YES;
|
||||
}
|
||||
document.getElementById( id + "_img" ).src = next.image;
|
||||
document.getElementById( id + "_frm" ).value = next.state;
|
||||
}
|
||||
}
|
||||
}();
|
||||
|
||||
// onload handler
|
||||
var existing_onload = window.onload;
|
||||
window.onload = function() {
|
||||
if ( existing_onload != undefined ) {
|
||||
existing_onload();
|
||||
}
|
||||
supernifty_tristate.init();
|
||||
}
|
||||
</script>
|
6
urls.py
6
urls.py
|
@ -10,7 +10,13 @@ urlpatterns = patterns('',
|
|||
url(r'^outsider/logout$', 'django.contrib.auth.views.logout', {'next_page': '/gestion/'}),
|
||||
url(r'^login$', 'gestioncof.views.login'),
|
||||
url(r'^logout$', 'gestioncof.views.logout'),
|
||||
url(r'^profile$', 'gestioncof.views.profile'),
|
||||
url(r'^registration$', 'gestioncof.views.registration'),
|
||||
url(r'^bda/inscription$', 'bda.views.inscription'),
|
||||
url(r'^survey/(?P<survey_id>\d+)$', 'gestioncof.views.survey'),
|
||||
url(r'^event/(?P<event_id>\d+)$', 'gestioncof.views.event'),
|
||||
url(r'^survey/(?P<survey_id>\d+)/status$', 'gestioncof.views.survey_status'),
|
||||
url(r'^event/(?P<event_id>\d+)/status$', 'gestioncof.views.event_status'),
|
||||
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^grappelli/', include('grappelli.urls')),
|
||||
|
|
Loading…
Reference in a new issue