Commit gore : premier push vers git.eleves

This commit is contained in:
Guillaume Seguin 2016-05-21 23:57:36 +02:00
parent e2e3bcd2b8
commit 392be324f6
2961 changed files with 295287 additions and 20 deletions

3
.gitignore vendored
View file

@ -1,3 +1,6 @@
*.pyc
*.swp
*.swo
cof/settings.py
settings.py
*~

12
apache/wsgi.py Normal file
View file

@ -0,0 +1,12 @@
"""
WSGI config for myproject project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cof.settings")
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

View file

@ -0,0 +1,9 @@
import autocomplete_light
from bda.models import Participant, Spectacle
autocomplete_light.register(Participant, search_fields=('user__username','user__first_name','user__last_name'),
autocomplete_js_attributes={'placeholder': 'participant...'})
autocomplete_light.register(Spectacle, search_fields=('title',),
autocomplete_js_attributes={'placeholder': 'spectacle...'})

View file

@ -12,7 +12,7 @@ import hashlib
from django.core.mail import send_mail
from datetime import datetime
from datetime import datetime, timedelta
import time
from gestioncof.decorators import cof_required, buro_required
@ -54,7 +54,7 @@ def etat_places(request):
spectacles_dict[spectacle["spectacle"]].ratio = spectacles_dict[spectacle["spectacle"]].total/float(spectacles_dict[spectacle["spectacle"]].slots)
total += spectacle["total"]
for spectacle in spectacles2:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
spectacles_dict[spectacle["spectacle"]].total += 2*spectacle["total"]
spectacles_dict[spectacle["spectacle"]].ratio = spectacles_dict[spectacle["spectacle"]].total/float(spectacles_dict[spectacle["spectacle"]].slots)
total += spectacle["total"]
return render(request, "etat-places.html", {"spectacles": spectacles, "total": total})
@ -94,9 +94,30 @@ def places(request):
"total": total,
"warning": warning})
@cof_required
def places_ics(request):
participant, created = Participant.objects.get_or_create(user = request.user)
places = participant.attribution_set.order_by("spectacle__date", "spectacle").all()
filtered_places = []
places_dict = {}
spectacles = []
for place in places:
if place.spectacle in spectacles:
places_dict[place.spectacle].double = True
else:
place.double = False
place.spectacle.dtend = place.spectacle.date + timedelta(seconds=7200)
places_dict[place.spectacle] = place
spectacles.append(place.spectacle)
filtered_places.append(place)
date = place.spectacle.date.date()
return render(request, "resume_places.ics",
{"participant": participant,
"places": filtered_places}, content_type="text/calendar")
@cof_required
def inscription(request):
if datetime.now() > datetime(2014, 10, 5, 12, 00) and request.user.username != "seguin":
if datetime.now() > datetime(2015, 10, 4, 12, 00) and request.user.username != "seguin":
participant, created = Participant.objects.get_or_create(user = request.user)
choices = participant.choixspectacle_set.order_by("priority").all()
return render(request, "resume_inscription.html", {"error_title": "C'est fini !", "error_description": u"Tirage au sort dans la journée !", "choices": choices})
@ -176,7 +197,7 @@ def do_tirage(request):
member.total += show.price
members2 = members2.items()
data["members2"] = sorted(members2, key = lambda m: m[0].user.last_name)
if False and request.user.username in ["seguin", "harazi","fromherz"]:
if False and request.user.username in ["seguin", "harazi","fromherz", "ccadiou"]:
Attribution.objects.all().delete()
for (show, members, _) in results:
for (member, _, _, _) in members:
@ -259,3 +280,11 @@ def spectacle(request, spectacle_id):
@buro_required
def unpaid(request):
return render(request, "bda-unpaid.html", {"unpaid": Participant.objects.filter(paid = False).all()})
@buro_required
def liste_spectacles_ics(request):
spectacles = Spectacle.objects.order_by("date").all()
for spectacle in spectacles:
spectacle.dtend = spectacle.date + timedelta(seconds=7200)
return render(request, "liste_spectacles.ics",
{"spectacles": spectacles}, content_type="text/calendar")

0
bda2/__init__.py Normal file
View file

177
bda2/admin.py Normal file
View file

@ -0,0 +1,177 @@
# coding: utf-8
from django.core.mail import send_mail
from django.contrib.contenttypes.models import ContentType
from django.contrib import admin
from django.db.models import Sum, Count
from bda2.models import Spectacle, Salle, Participant, ChoixSpectacle, Attribution
class ChoixSpectacleInline(admin.TabularInline):
model = ChoixSpectacle
sortable_field_name = "priority"
class AttributionInline(admin.TabularInline):
model = Attribution
class ParticipantAdmin(admin.ModelAdmin):
#inlines = [ChoixSpectacleInline]
inlines = [AttributionInline]
def queryset(self, request):
return Participant.objects.annotate(nb_places = Count('attributions'),
total = Sum('attributions__price'))
def nb_places(self, obj):
return obj.nb_places
nb_places.admin_order_field = "nb_places"
nb_places.short_description = "Nombre de places"
def total(self, obj):
tot = obj.total
if tot: return u"%.02f" % tot
else: return u"0 €"
total.admin_order_field = "total"
total.short_description = "Total à payer"
list_display = ("user", "nb_places", "total", "paid", "paymenttype")
list_filter = ("paid",)
search_fields = ('user__username', 'user__first_name', 'user__last_name')
actions = ['send_attribs',]
actions_on_bottom = True
list_per_page = 400
def send_choices(self, request, queryset):
for member in queryset.all():
choices = member.choixspectacle_set.order_by('priority').all()
if len(choices) == 0:
continue
mail = u"""Cher(e) %s,
Voici tes choix de spectacles tels que notre système les a enregistrés :\n\n""" % member.user.get_full_name()
next_rank = 1
member_shows = {}
for choice in choices:
if choice.spectacle in member_shows: continue
else: member_shows[choice.spectacle] = True
extra = ""
if choice.double:
extra += u" ; deux places"
if choice.autoquit:
extra += u" ; désistement automatique"
mail += u"- Choix %d : %s%s\n" % (next_rank, choice.spectacle, extra)
next_rank += 1
mail += u"""\nSi cette liste est incorrecte, merci de nous contacter au plus vite (avant samedi 6 octobre 18h).
Artistiquement,
Le BdA"""
send_mail ("Choix de spectacles (BdA du COF)", mail,
"bda@ens.fr", [member.user.email],
fail_silently = True)
count = len(queryset.all())
if count == 1:
message_bit = u"1 membre a"
plural = ""
else:
message_bit = u"%d membres ont" % count
plural = "s"
self.message_user(request, u"%s été informé%s avec succès." % (message_bit, plural))
send_choices.short_description = u"Envoyer les choix par mail"
def send_attribs(self, request, queryset):
for member in queryset.all():
attribs = member.attributions.all()
if len(attribs) == 0:
mail = u"""Cher-e %s,
Tu t'es inscrit-e pour le tirage au sort du BdA. Malheureusement, tu n'as
obtenu aucune place.
Nous sommes conscients que le nombre de places proposées lors de ce tirage
au sort est très restreint, mais nous sommes limités par les quotas que
nous imposent les théâtres.
Nous proposons cependant de nombreuses offres hors-tirage tout au long de
l'année, et nous t'invitons à nous contacter si l'une d'entre elles t'intéresse !
--
Le (nouveau) Bureau des Arts
(Thomas, Caroline, Antonin, Cécile, Hugo)
"""
name = member.user.get_full_name()
mail = mail % name
else:
mail = u"""Cher-e %s,
Tu t'es inscrit-e pour le tirage au sort du BdA. Tu as été sélectionné-e
pour les spectacles suivants :
%s
Nous sommes conscients que le nombre de places proposées lors de ce tirage
au sort est très restreint, mais nous sommes limités par les quotas que
nous imposent les théâtres.
*Paiement*
L'intégralité de ces places de spectacles est à régler à partir du lundi
18 janvier et AVANT le vendredi 22 janvier, au bureau du COF pendant les
heures de permanences (du lundi au vendredi entre 12h et 14h, et entre 18h
et 20h). Des facilités de paiement sont bien évidemment possibles : nous
pouvons ne pas encaisser le chèque immédiatement, ou bien découper votre
paiement en deux fois. Il est possible de payer par carte, chèque ou en espèces.
*Mode de retrait des places*
Lors du paiement, nous vous donnerons les places physiques que nous possédons, et vous indiquerons
quelles places sont sur listing, à retirer le soir même de la représentation. Nous vous enverrons un mail de rappel quelques jours avant les spectacles.
Nous vous rappelons que l'obtention de places du BdA vous engage à
respecter les règles de fonctionnement :
http://www.cof.ens.fr/bda/?page_id=1370
Le système de revente des places via les mails BdA-revente est accessible
directement sur votre compte GestioCOF.
En vous souhaitant de belles représentations,
--
Le (nouveau) Bureau des Arts
(Thomas, Caroline, Antonin, Cécile, Hugo)
"""
attribs_text = ""
name = member.user.get_full_name()
for attrib in attribs:
attribs_text += u"- 1 place pour %s\n" % attrib
mail = mail % (name, attribs_text)
send_mail ("[BdA] Résultats du tirage au sort", mail,
"bda@ens.fr", [member.user.email],
fail_silently = True)
count = len(queryset.all())
if count == 1:
message_bit = u"1 membre a"
plural = ""
else:
message_bit = u"%d membres ont" % count
plural = "s"
self.message_user(request, u"%s été informé%s avec succès." % (message_bit, plural))
send_attribs.short_description = u"Envoyer les résultats par mail"
class AttributionAdmin(admin.ModelAdmin):
def paid(self, obj):
return obj.participant.paid
paid.short_description = 'A payé'
paid.boolean = True
list_display = ("id", "spectacle", "participant", "given", "paid")
search_fields = ('spectacle__title', 'participant__user__username', 'participant__user__first_name', 'participant__user__last_name')
import autocomplete_light
class ChoixSpectacleAdmin(admin.ModelAdmin):
form = autocomplete_light.modelform_factory(ChoixSpectacle)
list_display = ("participant", "spectacle", "priority", "double", "autoquit")
list_filter = ("double", "autoquit")
search_fields = ('participant__user__username', 'participant__user__first_name', 'participant__user__last_name')
class SpectacleAdmin(admin.ModelAdmin):
model = Spectacle
list_display = ("title", "date", "location", "slots", "price")
list_filter = ("location",)
search_fields = ("title", "location__name")
admin.site.register(Spectacle, SpectacleAdmin)
admin.site.register(Salle)
admin.site.register(Participant, ParticipantAdmin)
admin.site.register(Attribution, AttributionAdmin)
admin.site.register(ChoixSpectacle, ChoixSpectacleAdmin)

104
bda2/algorithm.py Normal file
View file

@ -0,0 +1,104 @@
# coding: utf-8
from django.conf import settings
from django.db.models import Max
import random
class Algorithm(object):
shows = None
ranks = None
origranks = None
double = None
def __init__(self, shows, members, choices):
"""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.max_group = 2 * choices.aggregate(Max('priority'))['priority__max']
self.shows = []
showdict = {}
for show in shows:
show.nrequests = 0
showdict[show] = show
show.requests = []
self.shows.append(show)
self.ranks = {}
self.origranks = {}
self.choices = {}
next_rank = {}
member_shows = {}
for member in members:
self.ranks[member] = {}
self.choices[member] = {}
next_rank[member] = 1
member_shows[member] = {}
for choice in choices:
member = choice.participant
if choice.spectacle in member_shows[member]: continue
else: member_shows[member][choice.spectacle] = True
showdict[choice.spectacle].requests.append(member)
showdict[choice.spectacle].nrequests += 2 if choice.double else 1
self.ranks[member][choice.spectacle] = next_rank[member]
next_rank[member] += 2 if choice.double else 1
self.choices[member][choice.spectacle] = choice
for member in members:
self.origranks[member] = dict(self.ranks[member])
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.choices[member][show].double))
"""
Pour les 2 Walkyries: c'est Sandefer
Pour les 4 places pour l'Oratorio c'est Maxence Arutkin
"""
def __call__(self, seed):
random.seed(seed)
results = []
shows = sorted(self.shows, key = lambda x: float(x.nrequests) / x.slots, reverse = True)
for show in shows:
# On regroupe tous les gens ayant le même rang
groups = dict([(i, []) for i in range(1, self.max_group + 1)])
for member in show.requests:
if self.ranks[member][show] == 0:
raise RuntimeError, (member, show.title)
groups[self.ranks[member][show]].append(member)
# On passe à l'attribution
winners = []
losers = []
for i in range(1, self.max_group + 1):
group = list(groups[i])
random.shuffle(group)
for member in group:
if self.choices[member][show].double: # double
if len(winners) + 1 < show.slots:
self.appendResult(winners, member, show)
self.appendResult(winners, member, show)
elif not self.choices[member][show].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

View file

@ -0,0 +1,9 @@
import autocomplete_light
from bda.models import Participant, Spectacle
autocomplete_light.register(Participant, search_fields=('user__username','user__first_name','user__last_name'),
autocomplete_js_attributes={'placeholder': 'participant...'})
autocomplete_light.register(Spectacle, search_fields=('title',),
autocomplete_js_attributes={'placeholder': 'spectacle...'})

421
bda2/difftobda Normal file
View file

@ -0,0 +1,421 @@
Only in .: .admin.py.swp
Binary files ../bda/__init__.pyc and ./__init__.pyc differ
diff -p -u -r ../bda/admin.py ./admin.py
--- ../bda/admin.py 2013-12-17 10:19:56.000000000 +0100
+++ ./admin.py 2012-12-08 22:31:46.000000000 +0100
@@ -4,8 +4,8 @@ from django.core.mail import send_mail
from django.contrib.contenttypes.models import ContentType
from django.contrib import admin
-from django.db.models import Sum, Count
-from bda.models import Spectacle, Salle, Participant, ChoixSpectacle, Attribution
+from django.db.models import Sum
+from bda2.models import Spectacle, Salle, Participant, ChoixSpectacle, Attribution
class ChoixSpectacleInline(admin.TabularInline):
model = ChoixSpectacle
@@ -17,18 +17,13 @@ class AttributionInline(admin.TabularInl
class ParticipantAdmin(admin.ModelAdmin):
#inlines = [ChoixSpectacleInline]
inlines = [AttributionInline]
- def queryset(self, request):
- return Participant.objects.annotate(nb_places = Count('attributions'),
- total = Sum('attributions__price'))
def nb_places(self, obj):
- return obj.nb_places
- nb_places.admin_order_field = "nb_places"
+ return len(obj.attribution_set.all())
nb_places.short_description = "Nombre de places"
def total(self, obj):
- tot = obj.total
+ tot = obj.attributions.aggregate(total = Sum('price'))['total']
if tot: return u"%.02f €" % tot
else: return u"0 €"
- total.admin_order_field = "total"
total.short_description = "Total à payer"
list_display = ("user", "nb_places", "total", "paid", "paymenttype")
list_filter = ("paid",)
@@ -61,7 +56,7 @@ Voici tes choix de spectacles tels que n
Artistiquement,
Le BdA"""
send_mail ("Choix de spectacles (BdA du COF)", mail,
- "bda@ens.fr", [member.user.email],
+ "bda@clipper.ens.fr", [member.user.email],
fail_silently = True)
count = len(queryset.all())
if count == 1:
@@ -86,48 +81,41 @@ pour les spectacles suivants :
%s
*Paiement*
-L'intégralité de ces places de spectacles est à régler à partir du jeudi
-10 octobre et AVANT le mercredi 23 octobre, au bureau du COF pendant les
-heures de permanences (du lundi au vendredi entre 12h et 14h, et entre 18h
-et 20h). Des facilités de paiement sont bien évidemment possibles : nous
-pouvons ne pas encaisser le chèque immédiatement, ou bien découper votre
-paiement en deux fois.
+Ces spectacles sont à régler avant le lundi 17 décembre, pendant les
+heures de permanences du COF (tous les jours de la semaine entre 12h et
+14h, et entre 18h et 20h). Des facilités de paiement sont bien évidemment
+possibles (encaissement échelonné des chèques).
*Mode de retrait des places*
-Au moment du paiement, une enveloppe vous sera remise, contenant les
-places pour l'Opéra de Paris, pour les premiers spectacles de la Comédie
-française, certains spectacles du Châtelet et du Théâtre de la Ville.
-
-Pour les concerts Radio France, le Théâtre des Champs-Élysées, le théâtre
-du Rond-Point, le théâtre de la Colline, le théâtre de l'Athénée, l'IRCAM,
-la Cité de la musique et le 104, le Studio-Théâtre de la Comédie
-française, les places seront nominatives et à retirer au théâtre le soir
-de la représentation au moins une demi-heure avant le début du spectacle.
-
-Pour le théâtre de l'Odéon, la salle Richelieu le théâtre du Vieux
-colombier de la Comédie française, certains spectacles du théâtre de la
-Ville et du théâtre de Châtelet ainsi que pour le théâtre de Chaillot, les
-places seront distribuées environ une semaine avant la représentation (un
-mail vous en avertira).
-
-Nous vous rappelons que l'obtention de places du BdA vous engage à
-respecter les règles de fonctionnement :
-http://www.cof.ens.fr/bda/?page_id=1370
-Le système de revente des places via les mails BdA-revente sera très
-prochainement disponible, directement sur votre compte GestioCOF.
+Pour l'Opéra de Paris, le théâtre de la Colline et le théâtre du Châtelet,
+les places sont à retirer au COF le jour du paiement.
+
+Pour les concerts Radio France, le théâtre des Champs-Élysées, l'IRCAM
+et le 104, les places seront nominatives et à retirer à la salle le soir de
+la représentation au moins une demi-heure avant le début du spectacle.
+
+Pour le théâtre de l'Odéon, la Comédie Française, le théâtre de la Ville,
+le théâtre de Chaillot, les places seront distribuées dans vos
+casiers environ une semaine avant la représentation (un mail vous en
+avertira).
+
+Dans tous les cas, n'hésitez pas à nous contacter si vous avez un doute !
+
+Nous profitons aussi de ce message pour vous annoncer qu'un festival de
+courts métrages aura lieu le 21 décembre dans l'école.
+Plus d'informations : http://www.cof.ens.fr/bda/courts.
+
+Culturellement vôtre,
-En vous souhaitant de très beaux spectacles tout au long de l'année,
--
-Le Bureau des Arts
-(Chloé, Emilie, Jaime, Maxime, Olivier)
-"""
+Le BdA"""
attribs_text = ""
name = member.user.get_full_name()
for attrib in attribs:
attribs_text += u"- 1 place pour %s\n" % attrib
mail = mail % (name, attribs_text)
- send_mail ("Résultats du tirage au sort", mail,
- "bda@ens.fr", [member.user.email],
+ send_mail ("Places de spectacle (BdA du COF)", mail,
+ "bda@clipper.ens.fr", [member.user.email],
fail_silently = True)
count = len(queryset.all())
if count == 1:
@@ -154,13 +142,7 @@ class ChoixSpectacleAdmin(admin.ModelAdm
list_filter = ("double", "autoquit")
search_fields = ('participant__user__username', 'participant__user__first_name', 'participant__user__last_name')
-class SpectacleAdmin(admin.ModelAdmin):
- model = Spectacle
- list_display = ("title", "date", "location", "slots", "price")
- list_filter = ("location",)
- search_fields = ("title", "location__name")
-
-admin.site.register(Spectacle, SpectacleAdmin)
+admin.site.register(Spectacle)
admin.site.register(Salle)
admin.site.register(Participant, ParticipantAdmin)
admin.site.register(Attribution, AttributionAdmin)
diff -p -u -r ../bda/algorithm.py ./algorithm.py
--- ../bda/algorithm.py 2013-10-07 14:08:44.000000000 +0200
+++ ./algorithm.py 2012-10-31 23:01:48.000000000 +0100
@@ -29,24 +29,25 @@ class Algorithm(object):
self.ranks = {}
self.origranks = {}
self.choices = {}
- next_rank = {}
- member_shows = {}
for member in members:
- self.ranks[member] = {}
- self.choices[member] = {}
- next_rank[member] = 1
- member_shows[member] = {}
- for choice in choices:
- member = choice.participant
- if choice.spectacle in member_shows[member]: continue
- else: member_shows[member][choice.spectacle] = True
- showdict[choice.spectacle].requests.append(member)
- showdict[choice.spectacle].nrequests += 2 if choice.double else 1
- self.ranks[member][choice.spectacle] = next_rank[member]
- next_rank[member] += 2 if choice.double else 1
- self.choices[member][choice.spectacle] = choice
- for member in members:
- self.origranks[member] = dict(self.ranks[member])
+ ranks = {}
+ member_choices = {}
+ member_shows = {}
+ #next_priority = 1
+ next_rank = 1
+ for choice in member.choixspectacle_set.order_by('priority').all():
+ if choice.spectacle in member_shows: continue
+ else: member_shows[choice.spectacle] = True
+ #assert choice.priority == next_priority
+ #next_priority += 1
+ showdict[choice.spectacle].requests.append(member)
+ showdict[choice.spectacle].nrequests += 2 if choice.double else 1
+ ranks[choice.spectacle] = next_rank
+ next_rank += 2 if choice.double else 1
+ member_choices[choice.spectacle] = choice
+ self.ranks[member] = ranks
+ self.choices[member] = member_choices
+ self.origranks[member] = dict(ranks)
def IncrementRanks(self, member, currank, increment = 1):
for show in self.ranks[member]:
Only in ../bda: algorithm.pyc
Only in .: difftobda
diff -p -u -r ../bda/models.py ./models.py
--- ../bda/models.py 2013-10-10 10:44:43.000000000 +0200
+++ ./models.py 2012-10-31 23:12:56.000000000 +0100
@@ -1,7 +1,5 @@
# coding: utf-8
-import calendar
-
from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
@@ -19,7 +17,7 @@ class Spectacle (models.Model):
date = models.DateTimeField ("Date & heure")
location = models.ForeignKey(Salle)
description = models.TextField ("Description", blank = True)
- slots_description = models.TextField ("Description des places", blank = True)
+ #slots_description = models.TextField ("Description des places", blank = True)
price = models.FloatField("Prix d'une place", blank = True)
slots = models.IntegerField ("Places")
priority = models.IntegerField ("Priorité", default = 1000)
@@ -31,14 +29,11 @@ class Spectacle (models.Model):
def __repr__ (self):
return u"[%s]" % self.__unicode__()
- def timestamp(self):
- return "%d" % calendar.timegm(self.date.utctimetuple())
-
def date_no_seconds(self):
return self.date.strftime('%d %b %Y %H:%M')
def __unicode__ (self):
- return u"%s - %s, %s, %.02f€" % (self.title, self.date_no_seconds(), self.location, self.price)
+ return u"%s - %s @ %s, %.02f€" % (self.title, self.date_no_seconds(), self.location, self.price)
PAYMENT_TYPES = (
("cash",u"Cash"),
@@ -48,7 +43,7 @@ PAYMENT_TYPES = (
)
class Participant (models.Model):
- user = models.ForeignKey(User, unique = True)
+ user = models.ForeignKey(User, unique = True, related_name = "participants2")
choices = models.ManyToManyField(Spectacle, through = "ChoixSpectacle", related_name = "chosen_by")
attributions = models.ManyToManyField(Spectacle, through = "Attribution", related_name = "attributed_to")
paid = models.BooleanField (u"A payé", default = False)
Binary files ../bda/models.pyc and ./models.pyc differ
diff -p -u -r ../bda/views.py ./views.py
--- ../bda/views.py 2014-01-04 21:37:15.000000000 +0100
+++ ./views.py 2013-02-12 22:03:38.000000000 +0100
@@ -1,23 +1,19 @@
# coding: utf-8
-from django.contrib.auth.models import User
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.db import models
from django.http import Http404
from django import forms
from django.forms.models import inlineformset_factory, BaseInlineFormSet
-from django.core import serializers
-import hashlib
from django.core.mail import send_mail
-from datetime import datetime
import time
from gestioncof.decorators import cof_required, buro_required
-from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution
-from bda.algorithm import Algorithm
+from bda2.models import Spectacle, Participant, ChoixSpectacle, Attribution
+from bda2.algorithm import Algorithm
class BaseBdaFormSet(BaseInlineFormSet):
def clean(self):
@@ -37,7 +33,7 @@ class BaseBdaFormSet(BaseInlineFormSet):
raise forms.ValidationError("Vous ne pouvez pas vous inscrire deux fois pour le même spectacle.")
spectacles.append(spectacle)
-@cof_required
+@buro_required
def etat_places(request):
spectacles1 = ChoixSpectacle.objects.all().values('spectacle','spectacle__title').annotate(total = models.Count('spectacle'))
spectacles2 = ChoixSpectacle.objects.filter(double = True).all().values('spectacle','spectacle__title').annotate(total = models.Count('spectacle'))
@@ -46,93 +42,47 @@ def etat_places(request):
total = 0
for spectacle in spectacles:
spectacle.total = 0
- spectacle.ratio = -1.0
spectacles_dict[spectacle.id] = spectacle
for spectacle in spectacles1:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
- spectacles_dict[spectacle["spectacle"]].ratio = spectacles_dict[spectacle["spectacle"]].total/float(spectacles_dict[spectacle["spectacle"]].slots)
total += spectacle["total"]
for spectacle in spectacles2:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
- spectacles_dict[spectacle["spectacle"]].ratio = spectacles_dict[spectacle["spectacle"]].total/float(spectacles_dict[spectacle["spectacle"]].slots)
total += spectacle["total"]
return render(request, "etat-places.html", {"spectacles": spectacles, "total": total})
-def _hash_queryset(queryset):
- data = serializers.serialize("json", queryset)
- hasher = hashlib.sha256()
- hasher.update(data)
- return hasher.hexdigest()
-
-@cof_required
-def places(request):
- participant, created = Participant.objects.get_or_create(user = request.user)
- places = participant.attribution_set.order_by("spectacle__date", "spectacle").all()
- total = sum([place.spectacle.price for place in places])
- filtered_places = []
- places_dict = {}
- spectacles = []
- dates = []
- warning = False
- for place in places:
- if place.spectacle in spectacles:
- places_dict[place.spectacle].double = True
- else:
- place.double = False
- places_dict[place.spectacle] = place
- spectacles.append(place.spectacle)
- filtered_places.append(place)
- date = place.spectacle.date.date()
- if date in dates:
- warning = True
- else:
- dates.append(date)
- return render(request, "resume_places.html",
- {"participant": participant,
- "places": filtered_places,
- "total": total,
- "warning": warning})
-
@cof_required
def inscription(request):
- if datetime.now() > datetime(2013, 10, 6, 23, 59):
- participant, created = Participant.objects.get_or_create(user = request.user)
- choices = participant.choixspectacle_set.order_by("priority").all()
- return render(request, "resume_inscription.html", {"error_title": "C'est fini !", "error_description": u"Tirage au sort le 7 octobre !", "choices": choices})
+ if time.time() > 1354921200:
+ return render(request, "error.html", {"error_title": "C'est fini !", "error_description": u"Tirage au sort le 6 octobre dans la soirée "})
BdaFormSet = inlineformset_factory(Participant, ChoixSpectacle, fields = ("spectacle","double","autoquit","priority",), formset = BaseBdaFormSet)
participant, created = Participant.objects.get_or_create(user = request.user)
success = False
- stateerror = False
if request.method == "POST":
- dbstate = _hash_queryset(participant.choixspectacle_set.all())
- if "dbstate" in request.POST and dbstate != request.POST["dbstate"]:
- stateerror = True
+ formset = BdaFormSet(request.POST, instance = participant)
+ if formset.is_valid():
+ #ChoixSpectacle.objects.filter(participant = participant).delete()
+ formset.save()
+ success = True
formset = BdaFormSet(instance = participant)
- else:
- formset = BdaFormSet(request.POST, instance = participant)
- if formset.is_valid():
- #ChoixSpectacle.objects.filter(participant = participant).delete()
- formset.save()
- success = True
- formset = BdaFormSet(instance = participant)
else:
formset = BdaFormSet(instance = participant)
- dbstate = _hash_queryset(participant.choixspectacle_set.all())
total_price = 0
for choice in participant.choixspectacle_set.all():
total_price += choice.spectacle.price
if choice.double: total_price += choice.spectacle.price
- return render(request, "inscription-bda.html", {"formset": formset, "success": success, "total_price": total_price, "dbstate": dbstate, "stateerror": stateerror})
+ return render(request, "inscription-bda.html", {"formset": formset, "success": success, "total_price": total_price})
+
+Spectacle.deficit = lambda x: (x.slots-x.nrequests)*x.price
def do_tirage(request):
form = TokenForm(request.POST)
if not form.is_valid():
return tirage(request)
- start = time.time()
data = {}
- shows = Spectacle.objects.select_related().all()
+ shows = Spectacle.objects.all()
members = Participant.objects.all()
- choices = ChoixSpectacle.objects.order_by('participant', 'priority').select_related().all()
+ choices = ChoixSpectacle.objects.all()
algo = Algorithm(shows, members, choices)
results = algo(form.cleaned_data["token"])
total_slots = 0
@@ -149,8 +99,8 @@ def do_tirage(request):
total_sold = 0
total_deficit = 0
opera_deficit = 0
- for (show, members, _) in results:
- deficit = (show.slots - len(members)) * show.price
+ for show in shows:
+ deficit = show.deficit()
total_sold += show.slots * show.price
if deficit >= 0:
if u"Opéra" in show.location.name:
@@ -159,23 +109,18 @@ def do_tirage(request):
data["total_sold"] = total_sold - total_deficit
data["total_deficit"] = total_deficit
data["opera_deficit"] = opera_deficit
- data["duration"] = time.time() - start
if request.user.is_authenticated():
members2 = {}
- members_uniq = {} # Participant objects are not shared accross spectacle results,
- # So assign a single object for each Participant id
for (show, members, _) in results:
for (member, _, _, _) in members:
- if member.id not in members_uniq:
- members_uniq[member.id] = member
+ if member not in members2:
members2[member] = []
member.total = 0
- member = members_uniq[member.id]
members2[member].append(show)
member.total += show.price
members2 = members2.items()
data["members2"] = sorted(members2, key = lambda m: m[0].user.last_name)
- if False and request.user.username in ["seguin", "harazi"]:
+ if False and request.user.username == "seguin":
Attribution.objects.all().delete()
for (show, members, _) in results:
for (member, _, _, _) in members:
@@ -220,7 +165,7 @@ Je souhaite revendre %s pour %s le %s (%
Contactez moi par email si vous êtes intéressés !
%s (%s)""" % (places, spectacle.title, spectacle.date_no_seconds(), spectacle.location, spectacle.price, request.user.get_full_name(), request.user.email)
- send_mail("%s" % spectacle, mail,
+ send_mail("Revente de place: %s" % spectacle, mail,
request.user.email, ["bda-revente@lists.ens.fr"],
fail_silently = True)
return render(request, "bda-success.html", {"show": spectacle, "places": places})
Only in .: views.py~

78
bda2/models.py Normal file
View file

@ -0,0 +1,78 @@
# coding: utf-8
import calendar
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 Salle (models.Model):
name = models.CharField ("Nom", max_length = 300)
address = models.TextField ("Adresse")
def __unicode__ (self):
return self.name
class Spectacle (models.Model):
title = models.CharField ("Titre", max_length = 300)
date = models.DateTimeField ("Date & heure")
location = models.ForeignKey(Salle)
description = models.TextField ("Description", blank = True)
slots_description = models.TextField ("Description des places", blank = True)
price = models.FloatField("Prix d'une place", 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 timestamp(self):
return "%d" % calendar.timegm(self.date.utctimetuple())
def date_no_seconds(self):
return self.date.strftime('%d %b %Y %H:%M')
def __unicode__ (self):
return u"%s - %s, %s, %.02f" % (self.title, self.date_no_seconds(), self.location, self.price)
PAYMENT_TYPES = (
("cash",u"Cash"),
("cb","CB"),
("cheque",u"Chèque"),
("autre",u"Autre"),
)
class Participant (models.Model):
user = models.ForeignKey(User, unique = True, related_name = "participants2")
choices = models.ManyToManyField(Spectacle, through = "ChoixSpectacle", related_name = "chosen_by")
attributions = models.ManyToManyField(Spectacle, through = "Attribution", related_name = "attributed_to")
paid = models.BooleanField (u"A payé", default = False)
paymenttype = models.CharField(u"Moyen de paiement", max_length = 6, choices = PAYMENT_TYPES, blank = True)
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>",default=False)
autoquit = models.BooleanField("Abandon<sup>2</sup>",default=False)
class Meta:
ordering = ("priority",)
unique_together = (("participant", "spectacle",),)
verbose_name = "voeu"
verbose_name_plural = "voeux"
class Attribution (models.Model):
participant = models.ForeignKey(Participant)
spectacle = models.ForeignKey(Spectacle, related_name = "attribues")
given = models.BooleanField(u"Donnée", default = False)
def __unicode__ (self):
return u"%s -- %s" % (self.participant, self.spectacle)

16
bda2/tests.py Normal file
View 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)

248
bda2/views.py Normal file
View file

@ -0,0 +1,248 @@
# coding: utf-8
from django.contrib.auth.models import User
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.db import models
from django.http import Http404
from django import forms
from django.forms.models import inlineformset_factory, BaseInlineFormSet
from django.core import serializers
import hashlib
from django.core.mail import send_mail
from datetime import datetime
import time
from gestioncof.decorators import cof_required, buro_required
from bda2.models import Spectacle, Participant, ChoixSpectacle, Attribution
from bda2.algorithm import Algorithm
class BaseBdaFormSet(BaseInlineFormSet):
def clean(self):
"""Checks that no two articles have the same title."""
super(BaseBdaFormSet, self).clean()
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)
@cof_required
def etat_places(request):
spectacles1 = ChoixSpectacle.objects.all().values('spectacle','spectacle__title').annotate(total = models.Count('spectacle'))
spectacles2 = ChoixSpectacle.objects.filter(double = True).all().values('spectacle','spectacle__title').annotate(total = models.Count('spectacle'))
spectacles = Spectacle.objects.all()
spectacles_dict = {}
total = 0
for spectacle in spectacles:
spectacle.total = 0
spectacle.ratio = 0.0
spectacles_dict[spectacle.id] = spectacle
for spectacle in spectacles1:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
spectacles_dict[spectacle["spectacle"]].ratio = spectacles_dict[spectacle["spectacle"]].total/float(spectacles_dict[spectacle["spectacle"]].slots)
total += spectacle["total"]
for spectacle in spectacles2:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
spectacles_dict[spectacle["spectacle"]].ratio = spectacles_dict[spectacle["spectacle"]].total/float(spectacles_dict[spectacle["spectacle"]].slots)
total += spectacle["total"]
return render(request, "etat-places.html", {"spectacles": spectacles, "total": total})
def _hash_queryset(queryset):
data = serializers.serialize("json", queryset)
hasher = hashlib.sha256()
hasher.update(data)
return hasher.hexdigest()
@cof_required
def places(request):
participant, created = Participant.objects.get_or_create(user = request.user)
places = participant.attribution_set.order_by("spectacle__date", "spectacle").all()
total = sum([place.spectacle.price for place in places])
filtered_places = []
places_dict = {}
spectacles = []
dates = []
warning = False
for place in places:
if place.spectacle in spectacles:
places_dict[place.spectacle].double = True
else:
place.double = False
places_dict[place.spectacle] = place
spectacles.append(place.spectacle)
filtered_places.append(place)
date = place.spectacle.date.date()
if date in dates:
warning = True
else:
dates.append(date)
return render(request, "resume_places.html",
{"participant": participant,
"places": filtered_places,
"total": total,
"warning": warning})
@cof_required
def inscription(request):
if datetime.now() > datetime(2016, 1, 17, 12, 00):
participant, created = Participant.objects.get_or_create(user = request.user)
choices = participant.choixspectacle_set.order_by("priority").all()
return render(request, "resume_inscription.html", {"error_title": "C'est fini !", "error_description": u"Tirage au sort le 17 janvier !", "choices": choices})
BdaFormSet = inlineformset_factory(Participant, ChoixSpectacle, fields = ("spectacle","double","autoquit","priority",), formset = BaseBdaFormSet)
participant, created = Participant.objects.get_or_create(user = request.user)
success = False
stateerror = False
if request.method == "POST":
dbstate = _hash_queryset(participant.choixspectacle_set.all())
if "dbstate" in request.POST and dbstate != request.POST["dbstate"]:
stateerror = True
formset = BdaFormSet(instance = participant)
else:
formset = BdaFormSet(request.POST, instance = participant)
if formset.is_valid():
#ChoixSpectacle.objects.filter(participant = participant).delete()
formset.save()
success = True
formset = BdaFormSet(instance = participant)
else:
formset = BdaFormSet(instance = participant)
dbstate = _hash_queryset(participant.choixspectacle_set.all())
total_price = 0
for choice in participant.choixspectacle_set.all():
total_price += choice.spectacle.price
if choice.double: total_price += choice.spectacle.price
return render(request, "inscription-bda2.html", {"formset": formset, "success": success, "total_price": total_price, "dbstate": dbstate, "stateerror": stateerror})
def do_tirage(request):
form = TokenForm(request.POST)
if not form.is_valid():
return tirage(request)
start = time.time()
data = {}
shows = Spectacle.objects.select_related().all()
members = Participant.objects.all()
choices = ChoixSpectacle.objects.order_by('participant', 'priority').select_related().all()
algo = Algorithm(shows, members, choices)
results = algo(form.cleaned_data["token"])
total_slots = 0
total_losers = 0
for (_, members, losers) in results:
total_slots += len(members)
total_losers += len(losers)
data["total_slots"] = total_slots
data["total_losers"] = total_losers
data["shows"] = shows
data["token"] = form.cleaned_data["token"]
data["members"] = members
data["results"] = results
total_sold = 0
total_deficit = 0
opera_deficit = 0
for (show, members, _) in results:
deficit = (show.slots - len(members)) * show.price
total_sold += show.slots * show.price
if deficit >= 0:
if u"Opéra" in show.location.name:
opera_deficit += deficit
total_deficit += deficit
data["total_sold"] = total_sold - total_deficit
data["total_deficit"] = total_deficit
data["opera_deficit"] = opera_deficit
data["duration"] = time.time() - start
if request.user.is_authenticated():
members2 = {}
members_uniq = {} # Participant objects are not shared accross spectacle results,
# So assign a single object for each Participant id
for (show, members, _) in results:
for (member, _, _, _) in members:
if member.id not in members_uniq:
members_uniq[member.id] = member
members2[member] = []
member.total = 0
member = members_uniq[member.id]
members2[member].append(show)
member.total += show.price
members2 = members2.items()
data["members2"] = sorted(members2, key = lambda m: m[0].user.last_name)
if False and request.user.username in ["seguin", "harazi", "fromherz", "mpepin"]:
Attribution.objects.all().delete()
for (show, members, _) in results:
for (member, _, _, _) in members:
attrib = Attribution(spectacle = show, participant = member)
attrib.save()
return render(request, "bda-attrib-extra.html", data)
else:
return render(request, "bda-attrib.html", data)
class TokenForm(forms.Form):
token = forms.CharField(widget = forms.widgets.Textarea())
@login_required
def tirage(request):
if request.POST:
form = TokenForm(request.POST)
if form.is_valid():
return do_tirage(request)
else:
form = TokenForm()
return render(request, "bda-token.html", {"form": form})
class SpectacleModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return u"%s le %s (%s) à %.02f" % (obj.title, obj.date_no_seconds(), obj.location, obj.price)
class ResellForm(forms.Form):
count = forms.ChoiceField(choices = (("1","1"),("2","2"),))
spectacle = SpectacleModelChoiceField(queryset = Spectacle.objects.none())
def __init__(self, participant, *args, **kwargs):
super(ResellForm, self).__init__(*args, **kwargs)
self.fields['spectacle'].queryset = participant.attributions.all().distinct()
def do_resell(request, form):
spectacle = form.cleaned_data["spectacle"]
count = form.cleaned_data["count"]
places = "2 places" if count == "2" else "une place"
mail = u"""Bonjour,
Je souhaite revendre %s pour %s le %s (%s) à %.02f.
Contactez moi par email si vous êtes intéressés !
%s (%s)""" % (places, spectacle.title, spectacle.date_no_seconds(), spectacle.location, spectacle.price, request.user.get_full_name(), request.user.email)
send_mail("%s" % spectacle, mail,
request.user.email, ["bda-revente@lists.ens.fr"],
fail_silently = True)
return render(request, "bda-success.html", {"show": spectacle, "places": places})
@login_required
def revente(request):
participant, created = Participant.objects.get_or_create(user = request.user)
if not participant.paid:
return render(request, "bda-notpaid.html", {})
if request.POST:
form = ResellForm(participant, request.POST)
if form.is_valid():
return do_resell(request, form)
else:
form = ResellForm(participant)
return render(request, "bda-revente.html", {"form": form})
@buro_required
def spectacle(request, spectacle_id):
spectacle = get_object_or_404(Spectacle, id = spectacle_id)
return render(request, "bda-emails.html", {"spectacle": spectacle})
@buro_required
def unpaid(request):
return render(request, "bda-unpaid.html", {"unpaid": Participant.objects.filter(paid = False).all()})

0
bda3/__init__.py Normal file
View file

180
bda3/admin.py Normal file
View file

@ -0,0 +1,180 @@
# coding: utf-8
from django.core.mail import send_mail
from django.contrib.contenttypes.models import ContentType
from django.contrib import admin
from django.db.models import Sum, Count
from bda3.models import Spectacle, Salle, Participant, ChoixSpectacle, Attribution
class ChoixSpectacleInline(admin.TabularInline):
model = ChoixSpectacle
sortable_field_name = "priority"
class AttributionInline(admin.TabularInline):
model = Attribution
class ParticipantAdmin(admin.ModelAdmin):
#inlines = [ChoixSpectacleInline]
inlines = [AttributionInline]
def queryset(self, request):
return Participant.objects.annotate(nb_places = Count('attributions'),
total = Sum('attributions__price'))
def nb_places(self, obj):
return obj.nb_places
nb_places.admin_order_field = "nb_places"
nb_places.short_description = "Nombre de places"
def total(self, obj):
tot = obj.total
if tot: return u"%.02f" % tot
else: return u"0 €"
total.admin_order_field = "total"
total.short_description = "Total à payer"
list_display = ("user", "nb_places", "total", "paid", "paymenttype")
list_filter = ("paid",)
search_fields = ('user__username', 'user__first_name', 'user__last_name')
actions = ['send_attribs',]
actions_on_bottom = True
list_per_page = 400
def send_choices(self, request, queryset):
for member in queryset.all():
choices = member.choixspectacle_set.order_by('priority').all()
if len(choices) == 0:
continue
mail = u"""Cher(e) %s,
Voici tes choix de spectacles tels que notre système les a enregistrés :\n\n""" % member.user.get_full_name()
next_rank = 1
member_shows = {}
for choice in choices:
if choice.spectacle in member_shows: continue
else: member_shows[choice.spectacle] = True
extra = ""
if choice.double:
extra += u" ; deux places"
if choice.autoquit:
extra += u" ; désistement automatique"
mail += u"- Choix %d : %s%s\n" % (next_rank, choice.spectacle, extra)
next_rank += 1
mail += u"""\nSi cette liste est incorrecte, merci de nous contacter au plus vite (avant samedi 6 octobre 18h).
Artistiquement,
Le BdA"""
send_mail ("Choix de spectacles (BdA du COF)", mail,
"bda@ens.fr", [member.user.email],
fail_silently = True)
count = len(queryset.all())
if count == 1:
message_bit = u"1 membre a"
plural = ""
else:
message_bit = u"%d membres ont" % count
plural = "s"
self.message_user(request, u"%s été informé%s avec succès." % (message_bit, plural))
send_choices.short_description = u"Envoyer les choix par mail"
def send_attribs(self, request, queryset):
for member in queryset.all():
attribs = member.attributions.all()
if len(attribs) == 0:
mail = u"""Cher-e %s,
Tu t'es inscrit-e pour le troisième tirage au sort du BdA. Nous avons le regret
de t'annoncer que tu n'as pas obtenu de place.
Nous sommes bien conscients que le nombre de places mises en jeu était
très restreint, mais les premier et deuxième tirages au sort comprenaient
déjà un nombre exceptionnel de places, et nous dépendons des limites
fixées par les théâtres partenaires pour l'obtention de places à tarif réduit.
Le système de revente des places via les mails BdA-revente reste disponible,
alors surveille tes mails.
En vous souhaitant une bonne fin de journée,
--
Le Bureau des Arts
"""
name = member.user.get_full_name()
mail = mail % name
else:
mail = u"""Cher-e %s,
Tu t'es inscrit-e pour le troisième tirage au sort du BdA. Tu as été
sélectionné-e pour les spectacles suivants :
%s
Nous sommes bien conscients que le nombre de places mises en jeu était
très restreint, mais les premier et deuxième tirages au sort comprenaient
déjà un nombre exceptionnel de places, et nous dépendons des limites fixées
par les théâtres partenaires pour l'obtention de places à tarif réduit.
*Paiement*
L'intégralité de ces places de spectacles est à régler à partir du lundi
11 avril et AVANT le 16 avril, au bureau du COF pendant les
heures de permanences (du lundi au vendredi entre 12h et 14h, et entre 18h
et 20h). Si vous n'êtes pas disponible cette semaine, prévenez-nous par mail.
Attention : au-delà de cette date, les places pourront être remises en jeu.
*Mode de retrait des places*
Au moment du paiement, les places pour les spectacles de la Comédie-Française
et pour la Philarmonie vous seront remises.
Nous vous rappelons que l'obtention de places du BdA vous engage à
respecter les règles de fonctionnement :
http://www.cof.ens.fr/bda/?page_id=1370
Le système de revente des places via les mails BdA-revente reste disponible,
directement sur votre compte GestioCOF.
En vous souhaitant de très beaux spectacles,
--
Le Bureau des Arts
"""
attribs_text = ""
name = member.user.get_full_name()
for attrib in attribs:
attribs_text += u"- 1 place pour %s\n" % attrib
mail = mail % (name, attribs_text)
send_mail ("[BdA] Résultats du tirage au sort", mail,
"bda@ens.fr", [member.user.email],
fail_silently = True)
count = len(queryset.all())
if count == 1:
message_bit = u"1 membre a"
plural = ""
else:
message_bit = u"%d membres ont" % count
plural = "s"
self.message_user(request, u"%s été informé%s avec succès." % (message_bit, plural))
send_attribs.short_description = u"Envoyer les résultats par mail"
class AttributionAdmin(admin.ModelAdmin):
def paid(self, obj):
return obj.participant.paid
paid.short_description = 'A payé'
paid.boolean = True
list_display = ("id", "spectacle", "participant", "given", "paid")
search_fields = ('spectacle__title', 'participant__user__username', 'participant__user__first_name', 'participant__user__last_name')
import autocomplete_light
class ChoixSpectacleAdmin(admin.ModelAdmin):
form = autocomplete_light.modelform_factory(ChoixSpectacle)
list_display = ("participant", "spectacle", "priority", "double", "autoquit")
list_filter = ("double", "autoquit")
search_fields = ('participant__user__username', 'participant__user__first_name', 'participant__user__last_name')
class SpectacleAdmin(admin.ModelAdmin):
model = Spectacle
list_display = ("title", "date", "location", "slots", "price")
list_filter = ("location",)
search_fields = ("title", "location__name")
admin.site.register(Spectacle, SpectacleAdmin)
admin.site.register(Salle)
admin.site.register(Participant, ParticipantAdmin)
admin.site.register(Attribution, AttributionAdmin)
admin.site.register(ChoixSpectacle, ChoixSpectacleAdmin)

104
bda3/algorithm.py Normal file
View file

@ -0,0 +1,104 @@
# coding: utf-8
from django.conf import settings
from django.db.models import Max
import random
class Algorithm(object):
shows = None
ranks = None
origranks = None
double = None
def __init__(self, shows, members, choices):
"""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.max_group = 2 * choices.aggregate(Max('priority'))['priority__max']
self.shows = []
showdict = {}
for show in shows:
show.nrequests = 0
showdict[show] = show
show.requests = []
self.shows.append(show)
self.ranks = {}
self.origranks = {}
self.choices = {}
next_rank = {}
member_shows = {}
for member in members:
self.ranks[member] = {}
self.choices[member] = {}
next_rank[member] = 1
member_shows[member] = {}
for choice in choices:
member = choice.participant
if choice.spectacle in member_shows[member]: continue
else: member_shows[member][choice.spectacle] = True
showdict[choice.spectacle].requests.append(member)
showdict[choice.spectacle].nrequests += 2 if choice.double else 1
self.ranks[member][choice.spectacle] = next_rank[member]
next_rank[member] += 2 if choice.double else 1
self.choices[member][choice.spectacle] = choice
for member in members:
self.origranks[member] = dict(self.ranks[member])
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.choices[member][show].double))
"""
Pour les 2 Walkyries: c'est Sandefer
Pour les 4 places pour l'Oratorio c'est Maxence Arutkin
"""
def __call__(self, seed):
random.seed(seed)
results = []
shows = sorted(self.shows, key = lambda x: float(x.nrequests) / x.slots, reverse = True)
for show in shows:
# On regroupe tous les gens ayant le même rang
groups = dict([(i, []) for i in range(1, self.max_group + 1)])
for member in show.requests:
if self.ranks[member][show] == 0:
raise RuntimeError, (member, show.title)
groups[self.ranks[member][show]].append(member)
# On passe à l'attribution
winners = []
losers = []
for i in range(1, self.max_group + 1):
group = list(groups[i])
random.shuffle(group)
for member in group:
if self.choices[member][show].double: # double
if len(winners) + 1 < show.slots:
self.appendResult(winners, member, show)
self.appendResult(winners, member, show)
elif not self.choices[member][show].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

View file

@ -0,0 +1,9 @@
import autocomplete_light
from bda.models import Participant, Spectacle
autocomplete_light.register(Participant, search_fields=('user__username','user__first_name','user__last_name'),
autocomplete_js_attributes={'placeholder': 'participant...'})
autocomplete_light.register(Spectacle, search_fields=('title',),
autocomplete_js_attributes={'placeholder': 'spectacle...'})

421
bda3/difftobda Normal file
View file

@ -0,0 +1,421 @@
Only in .: .admin.py.swp
Binary files ../bda/__init__.pyc and ./__init__.pyc differ
diff -p -u -r ../bda/admin.py ./admin.py
--- ../bda/admin.py 2013-12-17 10:19:56.000000000 +0100
+++ ./admin.py 2012-12-08 22:31:46.000000000 +0100
@@ -4,8 +4,8 @@ from django.core.mail import send_mail
from django.contrib.contenttypes.models import ContentType
from django.contrib import admin
-from django.db.models import Sum, Count
-from bda.models import Spectacle, Salle, Participant, ChoixSpectacle, Attribution
+from django.db.models import Sum
+from bda3.models import Spectacle, Salle, Participant, ChoixSpectacle, Attribution
class ChoixSpectacleInline(admin.TabularInline):
model = ChoixSpectacle
@@ -17,18 +17,13 @@ class AttributionInline(admin.TabularInl
class ParticipantAdmin(admin.ModelAdmin):
#inlines = [ChoixSpectacleInline]
inlines = [AttributionInline]
- def queryset(self, request):
- return Participant.objects.annotate(nb_places = Count('attributions'),
- total = Sum('attributions__price'))
def nb_places(self, obj):
- return obj.nb_places
- nb_places.admin_order_field = "nb_places"
+ return len(obj.attribution_set.all())
nb_places.short_description = "Nombre de places"
def total(self, obj):
- tot = obj.total
+ tot = obj.attributions.aggregate(total = Sum('price'))['total']
if tot: return u"%.02f €" % tot
else: return u"0 €"
- total.admin_order_field = "total"
total.short_description = "Total à payer"
list_display = ("user", "nb_places", "total", "paid", "paymenttype")
list_filter = ("paid",)
@@ -61,7 +56,7 @@ Voici tes choix de spectacles tels que n
Artistiquement,
Le BdA"""
send_mail ("Choix de spectacles (BdA du COF)", mail,
- "bda@ens.fr", [member.user.email],
+ "bda@clipper.ens.fr", [member.user.email],
fail_silently = True)
count = len(queryset.all())
if count == 1:
@@ -86,48 +81,41 @@ pour les spectacles suivants :
%s
*Paiement*
-L'intégralité de ces places de spectacles est à régler à partir du jeudi
-10 octobre et AVANT le mercredi 23 octobre, au bureau du COF pendant les
-heures de permanences (du lundi au vendredi entre 12h et 14h, et entre 18h
-et 20h). Des facilités de paiement sont bien évidemment possibles : nous
-pouvons ne pas encaisser le chèque immédiatement, ou bien découper votre
-paiement en deux fois.
+Ces spectacles sont à régler avant le lundi 17 décembre, pendant les
+heures de permanences du COF (tous les jours de la semaine entre 12h et
+14h, et entre 18h et 20h). Des facilités de paiement sont bien évidemment
+possibles (encaissement échelonné des chèques).
*Mode de retrait des places*
-Au moment du paiement, une enveloppe vous sera remise, contenant les
-places pour l'Opéra de Paris, pour les premiers spectacles de la Comédie
-française, certains spectacles du Châtelet et du Théâtre de la Ville.
-
-Pour les concerts Radio France, le Théâtre des Champs-Élysées, le théâtre
-du Rond-Point, le théâtre de la Colline, le théâtre de l'Athénée, l'IRCAM,
-la Cité de la musique et le 104, le Studio-Théâtre de la Comédie
-française, les places seront nominatives et à retirer au théâtre le soir
-de la représentation au moins une demi-heure avant le début du spectacle.
-
-Pour le théâtre de l'Odéon, la salle Richelieu le théâtre du Vieux
-colombier de la Comédie française, certains spectacles du théâtre de la
-Ville et du théâtre de Châtelet ainsi que pour le théâtre de Chaillot, les
-places seront distribuées environ une semaine avant la représentation (un
-mail vous en avertira).
-
-Nous vous rappelons que l'obtention de places du BdA vous engage à
-respecter les règles de fonctionnement :
-http://www.cof.ens.fr/bda/?page_id=1370
-Le système de revente des places via les mails BdA-revente sera très
-prochainement disponible, directement sur votre compte GestioCOF.
+Pour l'Opéra de Paris, le théâtre de la Colline et le théâtre du Châtelet,
+les places sont à retirer au COF le jour du paiement.
+
+Pour les concerts Radio France, le théâtre des Champs-Élysées, l'IRCAM
+et le 104, les places seront nominatives et à retirer à la salle le soir de
+la représentation au moins une demi-heure avant le début du spectacle.
+
+Pour le théâtre de l'Odéon, la Comédie Française, le théâtre de la Ville,
+le théâtre de Chaillot, les places seront distribuées dans vos
+casiers environ une semaine avant la représentation (un mail vous en
+avertira).
+
+Dans tous les cas, n'hésitez pas à nous contacter si vous avez un doute !
+
+Nous profitons aussi de ce message pour vous annoncer qu'un festival de
+courts métrages aura lieu le 21 décembre dans l'école.
+Plus d'informations : http://www.cof.ens.fr/bda/courts.
+
+Culturellement vôtre,
-En vous souhaitant de très beaux spectacles tout au long de l'année,
--
-Le Bureau des Arts
-(Chloé, Emilie, Jaime, Maxime, Olivier)
-"""
+Le BdA"""
attribs_text = ""
name = member.user.get_full_name()
for attrib in attribs:
attribs_text += u"- 1 place pour %s\n" % attrib
mail = mail % (name, attribs_text)
- send_mail ("Résultats du tirage au sort", mail,
- "bda@ens.fr", [member.user.email],
+ send_mail ("Places de spectacle (BdA du COF)", mail,
+ "bda@clipper.ens.fr", [member.user.email],
fail_silently = True)
count = len(queryset.all())
if count == 1:
@@ -154,13 +142,7 @@ class ChoixSpectacleAdmin(admin.ModelAdm
list_filter = ("double", "autoquit")
search_fields = ('participant__user__username', 'participant__user__first_name', 'participant__user__last_name')
-class SpectacleAdmin(admin.ModelAdmin):
- model = Spectacle
- list_display = ("title", "date", "location", "slots", "price")
- list_filter = ("location",)
- search_fields = ("title", "location__name")
-
-admin.site.register(Spectacle, SpectacleAdmin)
+admin.site.register(Spectacle)
admin.site.register(Salle)
admin.site.register(Participant, ParticipantAdmin)
admin.site.register(Attribution, AttributionAdmin)
diff -p -u -r ../bda/algorithm.py ./algorithm.py
--- ../bda/algorithm.py 2013-10-07 14:08:44.000000000 +0200
+++ ./algorithm.py 2012-10-31 23:01:48.000000000 +0100
@@ -29,24 +29,25 @@ class Algorithm(object):
self.ranks = {}
self.origranks = {}
self.choices = {}
- next_rank = {}
- member_shows = {}
for member in members:
- self.ranks[member] = {}
- self.choices[member] = {}
- next_rank[member] = 1
- member_shows[member] = {}
- for choice in choices:
- member = choice.participant
- if choice.spectacle in member_shows[member]: continue
- else: member_shows[member][choice.spectacle] = True
- showdict[choice.spectacle].requests.append(member)
- showdict[choice.spectacle].nrequests += 2 if choice.double else 1
- self.ranks[member][choice.spectacle] = next_rank[member]
- next_rank[member] += 2 if choice.double else 1
- self.choices[member][choice.spectacle] = choice
- for member in members:
- self.origranks[member] = dict(self.ranks[member])
+ ranks = {}
+ member_choices = {}
+ member_shows = {}
+ #next_priority = 1
+ next_rank = 1
+ for choice in member.choixspectacle_set.order_by('priority').all():
+ if choice.spectacle in member_shows: continue
+ else: member_shows[choice.spectacle] = True
+ #assert choice.priority == next_priority
+ #next_priority += 1
+ showdict[choice.spectacle].requests.append(member)
+ showdict[choice.spectacle].nrequests += 2 if choice.double else 1
+ ranks[choice.spectacle] = next_rank
+ next_rank += 2 if choice.double else 1
+ member_choices[choice.spectacle] = choice
+ self.ranks[member] = ranks
+ self.choices[member] = member_choices
+ self.origranks[member] = dict(ranks)
def IncrementRanks(self, member, currank, increment = 1):
for show in self.ranks[member]:
Only in ../bda: algorithm.pyc
Only in .: difftobda
diff -p -u -r ../bda/models.py ./models.py
--- ../bda/models.py 2013-10-10 10:44:43.000000000 +0200
+++ ./models.py 2012-10-31 23:12:56.000000000 +0100
@@ -1,7 +1,5 @@
# coding: utf-8
-import calendar
-
from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
@@ -19,7 +17,7 @@ class Spectacle (models.Model):
date = models.DateTimeField ("Date & heure")
location = models.ForeignKey(Salle)
description = models.TextField ("Description", blank = True)
- slots_description = models.TextField ("Description des places", blank = True)
+ #slots_description = models.TextField ("Description des places", blank = True)
price = models.FloatField("Prix d'une place", blank = True)
slots = models.IntegerField ("Places")
priority = models.IntegerField ("Priorité", default = 1000)
@@ -31,14 +29,11 @@ class Spectacle (models.Model):
def __repr__ (self):
return u"[%s]" % self.__unicode__()
- def timestamp(self):
- return "%d" % calendar.timegm(self.date.utctimetuple())
-
def date_no_seconds(self):
return self.date.strftime('%d %b %Y %H:%M')
def __unicode__ (self):
- return u"%s - %s, %s, %.02f€" % (self.title, self.date_no_seconds(), self.location, self.price)
+ return u"%s - %s @ %s, %.02f€" % (self.title, self.date_no_seconds(), self.location, self.price)
PAYMENT_TYPES = (
("cash",u"Cash"),
@@ -48,7 +43,7 @@ PAYMENT_TYPES = (
)
class Participant (models.Model):
- user = models.ForeignKey(User, unique = True)
+ user = models.ForeignKey(User, unique = True, related_name = "participants2")
choices = models.ManyToManyField(Spectacle, through = "ChoixSpectacle", related_name = "chosen_by")
attributions = models.ManyToManyField(Spectacle, through = "Attribution", related_name = "attributed_to")
paid = models.BooleanField (u"A payé", default = False)
Binary files ../bda/models.pyc and ./models.pyc differ
diff -p -u -r ../bda/views.py ./views.py
--- ../bda/views.py 2014-01-04 21:37:15.000000000 +0100
+++ ./views.py 2013-02-12 22:03:38.000000000 +0100
@@ -1,23 +1,19 @@
# coding: utf-8
-from django.contrib.auth.models import User
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.db import models
from django.http import Http404
from django import forms
from django.forms.models import inlineformset_factory, BaseInlineFormSet
-from django.core import serializers
-import hashlib
from django.core.mail import send_mail
-from datetime import datetime
import time
from gestioncof.decorators import cof_required, buro_required
-from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution
-from bda.algorithm import Algorithm
+from bda3.models import Spectacle, Participant, ChoixSpectacle, Attribution
+from bda3.algorithm import Algorithm
class BaseBdaFormSet(BaseInlineFormSet):
def clean(self):
@@ -37,7 +33,7 @@ class BaseBdaFormSet(BaseInlineFormSet):
raise forms.ValidationError("Vous ne pouvez pas vous inscrire deux fois pour le même spectacle.")
spectacles.append(spectacle)
-@cof_required
+@buro_required
def etat_places(request):
spectacles1 = ChoixSpectacle.objects.all().values('spectacle','spectacle__title').annotate(total = models.Count('spectacle'))
spectacles2 = ChoixSpectacle.objects.filter(double = True).all().values('spectacle','spectacle__title').annotate(total = models.Count('spectacle'))
@@ -46,93 +42,47 @@ def etat_places(request):
total = 0
for spectacle in spectacles:
spectacle.total = 0
- spectacle.ratio = -1.0
spectacles_dict[spectacle.id] = spectacle
for spectacle in spectacles1:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
- spectacles_dict[spectacle["spectacle"]].ratio = spectacles_dict[spectacle["spectacle"]].total/float(spectacles_dict[spectacle["spectacle"]].slots)
total += spectacle["total"]
for spectacle in spectacles2:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
- spectacles_dict[spectacle["spectacle"]].ratio = spectacles_dict[spectacle["spectacle"]].total/float(spectacles_dict[spectacle["spectacle"]].slots)
total += spectacle["total"]
return render(request, "etat-places.html", {"spectacles": spectacles, "total": total})
-def _hash_queryset(queryset):
- data = serializers.serialize("json", queryset)
- hasher = hashlib.sha256()
- hasher.update(data)
- return hasher.hexdigest()
-
-@cof_required
-def places(request):
- participant, created = Participant.objects.get_or_create(user = request.user)
- places = participant.attribution_set.order_by("spectacle__date", "spectacle").all()
- total = sum([place.spectacle.price for place in places])
- filtered_places = []
- places_dict = {}
- spectacles = []
- dates = []
- warning = False
- for place in places:
- if place.spectacle in spectacles:
- places_dict[place.spectacle].double = True
- else:
- place.double = False
- places_dict[place.spectacle] = place
- spectacles.append(place.spectacle)
- filtered_places.append(place)
- date = place.spectacle.date.date()
- if date in dates:
- warning = True
- else:
- dates.append(date)
- return render(request, "resume_places.html",
- {"participant": participant,
- "places": filtered_places,
- "total": total,
- "warning": warning})
-
@cof_required
def inscription(request):
- if datetime.now() > datetime(2013, 10, 6, 23, 59):
- participant, created = Participant.objects.get_or_create(user = request.user)
- choices = participant.choixspectacle_set.order_by("priority").all()
- return render(request, "resume_inscription.html", {"error_title": "C'est fini !", "error_description": u"Tirage au sort le 7 octobre !", "choices": choices})
+ if time.time() > 1354921200:
+ return render(request, "error.html", {"error_title": "C'est fini !", "error_description": u"Tirage au sort le 6 octobre dans la soirée "})
BdaFormSet = inlineformset_factory(Participant, ChoixSpectacle, fields = ("spectacle","double","autoquit","priority",), formset = BaseBdaFormSet)
participant, created = Participant.objects.get_or_create(user = request.user)
success = False
- stateerror = False
if request.method == "POST":
- dbstate = _hash_queryset(participant.choixspectacle_set.all())
- if "dbstate" in request.POST and dbstate != request.POST["dbstate"]:
- stateerror = True
+ formset = BdaFormSet(request.POST, instance = participant)
+ if formset.is_valid():
+ #ChoixSpectacle.objects.filter(participant = participant).delete()
+ formset.save()
+ success = True
formset = BdaFormSet(instance = participant)
- else:
- formset = BdaFormSet(request.POST, instance = participant)
- if formset.is_valid():
- #ChoixSpectacle.objects.filter(participant = participant).delete()
- formset.save()
- success = True
- formset = BdaFormSet(instance = participant)
else:
formset = BdaFormSet(instance = participant)
- dbstate = _hash_queryset(participant.choixspectacle_set.all())
total_price = 0
for choice in participant.choixspectacle_set.all():
total_price += choice.spectacle.price
if choice.double: total_price += choice.spectacle.price
- return render(request, "inscription-bda.html", {"formset": formset, "success": success, "total_price": total_price, "dbstate": dbstate, "stateerror": stateerror})
+ return render(request, "inscription-bda.html", {"formset": formset, "success": success, "total_price": total_price})
+
+Spectacle.deficit = lambda x: (x.slots-x.nrequests)*x.price
def do_tirage(request):
form = TokenForm(request.POST)
if not form.is_valid():
return tirage(request)
- start = time.time()
data = {}
- shows = Spectacle.objects.select_related().all()
+ shows = Spectacle.objects.all()
members = Participant.objects.all()
- choices = ChoixSpectacle.objects.order_by('participant', 'priority').select_related().all()
+ choices = ChoixSpectacle.objects.all()
algo = Algorithm(shows, members, choices)
results = algo(form.cleaned_data["token"])
total_slots = 0
@@ -149,8 +99,8 @@ def do_tirage(request):
total_sold = 0
total_deficit = 0
opera_deficit = 0
- for (show, members, _) in results:
- deficit = (show.slots - len(members)) * show.price
+ for show in shows:
+ deficit = show.deficit()
total_sold += show.slots * show.price
if deficit >= 0:
if u"Opéra" in show.location.name:
@@ -159,23 +109,18 @@ def do_tirage(request):
data["total_sold"] = total_sold - total_deficit
data["total_deficit"] = total_deficit
data["opera_deficit"] = opera_deficit
- data["duration"] = time.time() - start
if request.user.is_authenticated():
members2 = {}
- members_uniq = {} # Participant objects are not shared accross spectacle results,
- # So assign a single object for each Participant id
for (show, members, _) in results:
for (member, _, _, _) in members:
- if member.id not in members_uniq:
- members_uniq[member.id] = member
+ if member not in members2:
members2[member] = []
member.total = 0
- member = members_uniq[member.id]
members2[member].append(show)
member.total += show.price
members2 = members2.items()
data["members2"] = sorted(members2, key = lambda m: m[0].user.last_name)
- if False and request.user.username in ["seguin", "harazi"]:
+ if False and request.user.username == "seguin":
Attribution.objects.all().delete()
for (show, members, _) in results:
for (member, _, _, _) in members:
@@ -220,7 +165,7 @@ Je souhaite revendre %s pour %s le %s (%
Contactez moi par email si vous êtes intéressés !
%s (%s)""" % (places, spectacle.title, spectacle.date_no_seconds(), spectacle.location, spectacle.price, request.user.get_full_name(), request.user.email)
- send_mail("%s" % spectacle, mail,
+ send_mail("Revente de place: %s" % spectacle, mail,
request.user.email, ["bda-revente@lists.ens.fr"],
fail_silently = True)
return render(request, "bda-success.html", {"show": spectacle, "places": places})
Only in .: views.py~

78
bda3/models.py Normal file
View file

@ -0,0 +1,78 @@
# coding: utf-8
import calendar
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 Salle (models.Model):
name = models.CharField ("Nom", max_length = 300)
address = models.TextField ("Adresse")
def __unicode__ (self):
return self.name
class Spectacle (models.Model):
title = models.CharField ("Titre", max_length = 300)
date = models.DateTimeField ("Date & heure")
location = models.ForeignKey(Salle)
description = models.TextField ("Description", blank = True)
slots_description = models.TextField ("Description des places", blank = True)
price = models.FloatField("Prix d'une place", 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 timestamp(self):
return "%d" % calendar.timegm(self.date.utctimetuple())
def date_no_seconds(self):
return self.date.strftime('%d %b %Y %H:%M')
def __unicode__ (self):
return u"%s - %s, %s, %.02f" % (self.title, self.date_no_seconds(), self.location, self.price)
PAYMENT_TYPES = (
("cash",u"Cash"),
("cb","CB"),
("cheque",u"Chèque"),
("autre",u"Autre"),
)
class Participant (models.Model):
user = models.ForeignKey(User, unique = True, related_name = "participants3")
choices = models.ManyToManyField(Spectacle, through = "ChoixSpectacle", related_name = "chosen_by")
attributions = models.ManyToManyField(Spectacle, through = "Attribution", related_name = "attributed_to")
paid = models.BooleanField (u"A payé", default = False)
paymenttype = models.CharField(u"Moyen de paiement", max_length = 6, choices = PAYMENT_TYPES, blank = True)
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>",default=False)
autoquit = models.BooleanField("Abandon<sup>2</sup>",default=False)
class Meta:
ordering = ("priority",)
unique_together = (("participant", "spectacle",),)
verbose_name = "voeu"
verbose_name_plural = "voeux"
class Attribution (models.Model):
participant = models.ForeignKey(Participant)
spectacle = models.ForeignKey(Spectacle, related_name = "attribues")
given = models.BooleanField(u"Donnée", default = False)
def __unicode__ (self):
return u"%s -- %s" % (self.participant, self.spectacle)

16
bda3/tests.py Normal file
View 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)

249
bda3/views.py Normal file
View file

@ -0,0 +1,249 @@
# coding: utf-8
from django.contrib.auth.models import User
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.db import models
from django.http import Http404
from django import forms
from django.forms.models import inlineformset_factory, BaseInlineFormSet
from django.core import serializers
import hashlib
from django.core.mail import send_mail
from datetime import datetime
import time
from gestioncof.decorators import cof_required, buro_required
from bda3.models import Spectacle, Participant, ChoixSpectacle, Attribution
from bda3.algorithm import Algorithm
class BaseBdaFormSet(BaseInlineFormSet):
def clean(self):
"""Checks that no two articles have the same title."""
super(BaseBdaFormSet, self).clean()
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)
@cof_required
def etat_places(request):
spectacles1 = ChoixSpectacle.objects.all().values('spectacle','spectacle__title').annotate(total = models.Count('spectacle'))
spectacles2 = ChoixSpectacle.objects.filter(double = True).all().values('spectacle','spectacle__title').annotate(total = models.Count('spectacle'))
spectacles = Spectacle.objects.all()
spectacles_dict = {}
total = 0
for spectacle in spectacles:
spectacle.total = 0
spectacle.ratio = -1.0
spectacles_dict[spectacle.id] = spectacle
for spectacle in spectacles1:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
spectacles_dict[spectacle["spectacle"]].ratio = spectacles_dict[spectacle["spectacle"]].total/float(spectacles_dict[spectacle["spectacle"]].slots)
total += spectacle["total"]
for spectacle in spectacles2:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
spectacles_dict[spectacle["spectacle"]].ratio = spectacles_dict[spectacle["spectacle"]].total/float(spectacles_dict[spectacle["spectacle"]].slots)
total += spectacle["total"]
return render(request, "etat-places.html", {"spectacles": spectacles, "total": total})
def _hash_queryset(queryset):
data = serializers.serialize("json", queryset)
hasher = hashlib.sha256()
hasher.update(data)
return hasher.hexdigest()
@cof_required
def places(request):
participant, created = Participant.objects.get_or_create(user = request.user)
places = participant.attribution_set.order_by("spectacle__date", "spectacle").all()
total = sum([place.spectacle.price for place in places])
filtered_places = []
places_dict = {}
spectacles = []
dates = []
warning = False
for place in places:
if place.spectacle in spectacles:
places_dict[place.spectacle].double = True
else:
place.double = False
places_dict[place.spectacle] = place
spectacles.append(place.spectacle)
filtered_places.append(place)
date = place.spectacle.date.date()
if date in dates:
warning = True
else:
dates.append(date)
return render(request, "resume_places.html",
{"participant": participant,
"places": filtered_places,
"total": total,
"warning": warning})
@cof_required
def inscription(request):
if datetime.now() > datetime(2016, 4, 10, 11, 59):
participant, created = Participant.objects.get_or_create(user = request.user)
choices = participant.choixspectacle_set.order_by("priority").all()
return render(request, "resume_inscription.html", {"error_title": "C'est fini !", "error_description": u"Tirage au sort très bientôt !", "choices": choices})
BdaFormSet = inlineformset_factory(Participant, ChoixSpectacle, fields = ("spectacle","double","autoquit","priority",), formset = BaseBdaFormSet)
participant, created = Participant.objects.get_or_create(user = request.user)
success = False
stateerror = False
if request.method == "POST":
dbstate = _hash_queryset(participant.choixspectacle_set.all())
if "dbstate" in request.POST and dbstate != request.POST["dbstate"]:
stateerror = True
formset = BdaFormSet(instance = participant)
else:
formset = BdaFormSet(request.POST, instance = participant)
if formset.is_valid():
#ChoixSpectacle.objects.filter(participant = participant).delete()
formset.save()
success = True
formset = BdaFormSet(instance = participant)
else:
formset = BdaFormSet(instance = participant)
dbstate = _hash_queryset(participant.choixspectacle_set.all())
total_price = 0
for choice in participant.choixspectacle_set.all():
total_price += choice.spectacle.price
if choice.double: total_price += choice.spectacle.price
return render(request, "inscription-bda3.html", {"formset": formset, "success": success, "total_price": total_price, "dbstate": dbstate, "stateerror": stateerror})
def do_tirage(request):
form = TokenForm(request.POST)
if not form.is_valid():
return tirage(request)
start = time.time()
data = {}
shows = Spectacle.objects.select_related().all()
members = Participant.objects.all()
choices = ChoixSpectacle.objects.order_by('participant', 'priority').select_related().all()
algo = Algorithm(shows, members, choices)
results = algo(form.cleaned_data["token"])
total_slots = 0
total_losers = 0
for (_, members, losers) in results:
total_slots += len(members)
total_losers += len(losers)
data["total_slots"] = total_slots
data["total_losers"] = total_losers
data["shows"] = shows
data["token"] = form.cleaned_data["token"]
data["members"] = members
data["results"] = results
total_sold = 0
total_deficit = 0
opera_deficit = 0
for (show, members, _) in results:
deficit = (show.slots - len(members)) * show.price
total_sold += show.slots * show.price
if deficit >= 0:
if u"Opéra" in show.location.name:
opera_deficit += deficit
total_deficit += deficit
data["total_sold"] = total_sold - total_deficit
data["total_deficit"] = total_deficit
data["opera_deficit"] = opera_deficit
data["duration"] = time.time() - start
if request.user.is_authenticated():
members2 = {}
members_uniq = {} # Participant objects are not shared accross spectacle results,
# So assign a single object for each Participant id
for (show, members, _) in results:
for (member, _, _, _) in members:
if member.id not in members_uniq:
members_uniq[member.id] = member
members2[member] = []
member.total = 0
member = members_uniq[member.id]
members2[member].append(show)
member.total += show.price
members2 = members2.items()
data["members2"] = sorted(members2, key = lambda m: m[0].user.last_name)
if False and request.user.username in ["seguin", "harazi", "fromherz", "mpepin"]:
Attribution.objects.all().delete()
for (show, members, _) in results:
for (member, _, _, _) in members:
attrib = Attribution(spectacle = show, participant = member)
attrib.save()
return render(request, "bda-attrib-extra.html", data)
else:
return render(request, "bda-attrib.html", data)
class TokenForm(forms.Form):
token = forms.CharField(widget = forms.widgets.Textarea())
@login_required
def tirage(request):
if request.POST:
form = TokenForm(request.POST)
if form.is_valid():
return do_tirage(request)
else:
form = TokenForm()
return render(request, "bda-token.html", {"form": form})
class SpectacleModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return u"%s le %s (%s) à %.02f" % (obj.title, obj.date_no_seconds(), obj.location, obj.price)
class ResellForm(forms.Form):
count = forms.ChoiceField(choices = (("1","1"),("2","2"),))
spectacle = SpectacleModelChoiceField(queryset = Spectacle.objects.none())
def __init__(self, participant, *args, **kwargs):
super(ResellForm, self).__init__(*args, **kwargs)
self.fields['spectacle'].queryset = participant.attributions.all().distinct()
def do_resell(request, form):
spectacle = form.cleaned_data["spectacle"]
count = form.cleaned_data["count"]
places = "2 places" if count == "2" else "une place"
mail = u"""Bonjour,
Je souhaite revendre %s pour %s le %s (%s) à %.02f.
Contactez moi par email si vous êtes intéressés !
%s (%s)""" % (places, spectacle.title, spectacle.date_no_seconds(), spectacle.location, spectacle.price, request.user.get_full_name(), request.user.email)
send_mail("%s" % spectacle, mail,
request.user.email, ["bda-revente@lists.ens.fr"],
fail_silently = True)
return render(request, "bda-success.html", {"show": spectacle, "places": places})
@login_required
def revente(request):
participant, created = Participant.objects.get_or_create(user = request.user)
if not participant.paid:
return render(request, "bda-notpaid.html", {})
if request.POST:
form = ResellForm(participant, request.POST)
if form.is_valid():
return do_resell(request, form)
else:
form = ResellForm(participant)
return render(request, "bda-revente.html", {"form": form})
@buro_required
def spectacle(request, spectacle_id):
spectacle = get_object_or_404(Spectacle, id = spectacle_id)
return render(request, "bda-emails.html", {"spectacle": spectacle})
@buro_required
def unpaid(request):
return render(request, "bda-unpaid.html", {"unpaid": Participant.objects.filter(paid = False).all()})

0
cof/__init__.py Normal file
View file

86
cof/urls.py Normal file
View file

@ -0,0 +1,86 @@
from django.conf.urls import patterns, include, url
import autocomplete_light
autocomplete_light.autodiscover()
from django.contrib import admin
admin.autodiscover()
from django.views.generic.list import ListView
from django.views.generic.base import TemplateView
from bda.models import Spectacle
from bda2.models import Spectacle as Spectacle2
from bda3.models import Spectacle as Spectacle3
from gestioncof.petits_cours_views import DemandeListView
urlpatterns = patterns('',
url(r'^$', 'gestioncof.views.home', name = 'home'),
url(r'^cof/denied$', TemplateView.as_view(template_name = 'cof-denied.html'), name = "cof-denied"),
url(r'^cas/login$', 'django_cas.views.login', name = "cas_login_view"),
url(r'^cas/logout$', 'django_cas.views.logout'),
url(r'^outsider/login$', 'gestioncof.views.login_ext'),
url(r'^outsider/logout$', 'django.contrib.auth.views.logout', {'next_page': '/gestion/'}),
url(r'^outsider/password-change$', 'django.contrib.auth.views.password_change'),
url(r'^outsider/password-change-done$', 'django.contrib.auth.views.password_change_done'),
url(r'^login$', 'gestioncof.views.login'),
url(r'^logout$', 'gestioncof.views.logout'),
url(r'^profile$', 'gestioncof.views.profile'),
url(r'^export/members$', 'gestioncof.views.export_members'),
url(r'^export/mega/avecremarques$', 'gestioncof.views.export_mega_remarksonly'),
url(r'^export/mega/participants$', 'gestioncof.views.export_mega_participants'),
url(r'^export/mega/orgas$', 'gestioncof.views.export_mega_orgas'),
url(r'^export/mega/(?P<type>.+)$', 'gestioncof.views.export_mega_bytype'),
url(r'^export/mega$', 'gestioncof.views.export_mega'),
url(r'^registration$', 'gestioncof.views.registration'),
url(r'^registration/clipper/(?P<login_clipper>[\w-]+)$', 'gestioncof.views.registration_form2', name = "clipper-registration"),
url(r'^registration/user/(?P<username>.+)$', 'gestioncof.views.registration_form2', name = "user-registration"),
url(r'^registration/empty$', 'gestioncof.views.registration_form2', name = "empty-registration"),
url(r'^petitcours/inscription$', 'gestioncof.petits_cours_views.inscription', name = 'petits-cours-inscription'),
url(r'^petitcours/demande$', 'gestioncof.petits_cours_views.demande', name = 'petits-cours-demande'),
url(r'^petitcours/demande-raw$', 'gestioncof.petits_cours_views.demande_raw', name = 'petits-cours-demande-raw'),
url(r'^petitcours/demandes$', DemandeListView.as_view(), name = 'petits-cours-demandes-list'),
url(r'^petitcours/demandes/(?P<demande_id>\d+)$', 'gestioncof.petits_cours_views.details', name = 'petits-cours-demande-details'),
url(r'^petitcours/demandes/(?P<demande_id>\d+)/traitement$', 'gestioncof.petits_cours_views.traitement', name = 'petits-cours-demande-traitement'),
url(r'^petitcours/demandes/(?P<demande_id>\d+)/retraitement$', 'gestioncof.petits_cours_views.retraitement', name = 'petits-cours-demande-retraitement'),
url(r'^bda/inscription$', 'bda.views.inscription', name = 'bda-tirage-inscription'),
url(r'^bda2/inscription$', 'bda2.views.inscription', name = 'bda2-tirage-inscription'),
url(r'^bda3/inscription$', 'bda3.views.inscription', name = 'bda3-tirage-inscription'),
url(r'^bda/places$', 'bda.views.places', name = "bda-places-attribuees"),
url(r'^bda/places/places_bda.ics$', 'bda.views.places_ics', name = "bda-places-attribuees-ics"),
url(r'^bda2/places$', 'bda2.views.places', name = "bda2-places-attribuees"),
url(r'^bda3/places$', 'bda3.views.places', name = "bda3-places-attribuees"),
url(r'^bda/revente$', 'bda.views.revente', name = 'bda-revente'),
url(r'^bda2/revente$', 'bda2.views.revente', name = 'bda2-revente'),
url(r'^bda3/revente$', 'bda3.views.revente', name = 'bda3-revente'),
url(r'^bda/etat-places$', 'bda.views.etat_places', name = 'bda-etat-places'),
url(r'^bda2/etat-places$', 'bda2.views.etat_places', name = 'bda2-etat-places'),
url(r'^bda3/etat-places$', 'bda3.views.etat_places', name = 'bda3-etat-places'),
url(r'^bda/tirage$', 'bda.views.tirage'),
url(r'^bda2/tirage$', 'bda2.views.tirage'),
url(r'^bda3/tirage$', 'bda3.views.tirage'),
url(r'^bda/spectacles/$', ListView.as_view(model = Spectacle), name ="bda-liste-spectacles"),
url(r'^bda/spectacles/liste_spectacles.ics$', 'bda.views.liste_spectacles_ics', name ="bda-liste-spectacles-ics"),
url(r'^bda/spectacles/unpaid$', "bda.views.unpaid", name = "bda-unpaid"),
url(r'^bda/spectacles/(?P<spectacle_id>\d+)$', "bda.views.spectacle", name = "bda-spectacle"),
url(r'^bda/spectacles-2/$', ListView.as_view(model = Spectacle2), name ="bda2-liste-spectacles"),
url(r'^bda/spectacles-2/unpaid$', "bda2.views.unpaid", name = "bda2-unpaid"),
url(r'^bda/spectacles-2/(?P<spectacle_id>\d+)$', "bda2.views.spectacle", name = "bda2-spectacle"),
url(r'^bda/spectacles-3/$', ListView.as_view(model = Spectacle3), name ="bda3-liste-spectacles"),
url(r'^bda/spectacles-3/unpaid$', "bda3.views.unpaid", name = "bda3-unpaid"),
url(r'^bda/spectacles-3/(?P<spectacle_id>\d+)$', "bda3.views.spectacle", name = "bda3-spectacle"),
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'^autocomplete/registration$', 'gestioncof.autocomplete.autocomplete'),
url(r'^autocomplete/', include('autocomplete_light.urls')),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/', 'gestioncof.csv_views.admin_list_export', {'fields': ['username',]}),
url(r'^admin/', include(admin.site.urls)),
url(r'^grappelli/', include('grappelli.urls')),
url(r'^utile_cof$', 'gestioncof.views.utile_cof'),
url(r'^utile_bda$', 'gestioncof.views.utile_bda'),
url(r'^utile_bda/bda_diff$', 'gestioncof.views.liste_bdadiff'),
url(r'^utile_cof/diff_cof$', 'gestioncof.views.liste_diffcof'),
url(r'^utile_bda/bda_revente$', 'gestioncof.views.liste_bdarevente'),
)

View file

@ -131,6 +131,7 @@ class UserProfileAdmin(UserAdmin):
list_display = ('profile_num',) + UserAdmin.list_display + ('profile_login_clipper','profile_phone','profile_occupation','profile_mailing_cof','profile_mailing_bda','profile_mailing_bda_revente','is_cof','is_buro',)
list_display_links = ('username','email','first_name','last_name')
list_filter = UserAdmin.list_filter + ('profile__is_cof', 'profile__is_buro', 'profile__mailing_cof', 'profile__mailing_bda')
search_fields = UserAdmin.search_fields + ('profile__phone',)
inlines = [
CofProfileInline,
]

View file

@ -0,0 +1,49 @@
from django import shortcuts
from django.http import Http404
from django.db.models import Q
from django.contrib.auth.models import User
from gestioncof.models import CofProfile, Clipper
def autocomplete(request):
if "q" not in request.GET:
raise Http404
q = request.GET['q']
data = {
'q': q,
}
queries = {}
bits = q.split()
queries['members'] = CofProfile.objects.filter(Q(is_cof=True))
queries['users'] = User.objects.filter(Q(profile__is_cof=False))
queries['clippers'] = Clipper.objects
for bit in bits:
queries['members'] = queries['members'].filter(
Q(user__first_name__icontains=bit)
|Q(user__last_name__icontains=bit)
|Q(user__username__icontains=bit)
|Q(login_clipper__icontains=bit))
queries['users'] = queries['users'].filter(
Q(first_name__icontains=bit)
|Q(last_name__icontains=bit)
|Q(username__icontains=bit))
queries['clippers'] = queries['clippers'].filter(
Q(fullname__icontains=bit)
|Q(username__icontains=bit))
queries['members'] = queries['members'].distinct()
queries['users'] = queries['users'].distinct()
usernames = list(queries['members'].values_list('login_clipper', flat = 'True')) \
+ list(queries['users'].values_list('profile__login_clipper', flat = 'True'))
queries['clippers'] = queries['clippers'].exclude(username__in = usernames).distinct()
# add clippers
data.update(queries)
options = 0
for query in queries.values():
options += len(query)
data['options'] = options
return shortcuts.render(request, "autocomplete_user.html", data)

View file

@ -0,0 +1,6 @@
import autocomplete_light
from django.contrib.auth.models import User
autocomplete_light.register(User, search_fields=('username','first_name','last_name'),
autocomplete_js_attributes={'placeholder': 'membre...'})

70
gestioncof/csv_views.py Normal file
View file

@ -0,0 +1,70 @@
import csv
from django.http import HttpResponse, HttpResponseForbidden
from django.template.defaultfilters import slugify
from django.db.models.loading import get_model
from django.contrib.auth.models import User
def export(qs, fields=None):
model = qs.model
response = HttpResponse(mimetype='text/csv')
response['Content-Disposition'] = 'attachment; filename=%s.csv' % slugify(model.__name__)
writer = csv.writer(response)
# Write headers to CSV file
if fields:
headers = fields
else:
headers = []
for field in model._meta.fields:
headers.append(field.name)
writer.writerow(headers)
# Write data to CSV file
for obj in qs:
row = []
for field in headers:
if field in headers:
val = getattr(obj, field)
if callable(val):
val = val()
row.append(val)
writer.writerow(row)
# Return CSV file to browser as download
return response
def admin_list_export(request, model_name, app_label, queryset=None, fields=None, list_display=True):
"""
Put the following line in your urls.py BEFORE your admin include
(r'^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/', 'util.csv_view.admin_list_export'),
"""
if not request.user.is_staff:
return HttpResponseForbidden()
if not queryset:
model = get_model(app_label, model_name)
queryset = model.objects.all()
filters = dict()
"""
for key, value in request.GET.items():
if key not in ('ot', 'o'):
filters[str(key)] = str(value)
if len(filters):
queryset = queryset.filter(**filters)
"""
#qs2 = User.objects.filter(profile__eav__a_vot = True)
#queryset = queryset.filter(pk__in = qs2.values_list('id', flat = True))
queryset = queryset.filter(profile__is_cof = True)
if not fields:
if list_display and len(queryset.model._meta.admin.list_display) > 1:
fields = queryset.model._meta.admin.list_display
else:
fields = None
return export(queryset, fields)
"""
Create your own change_list.html for your admin view and put something like this in it:
{% block object-tools %}
<ul class="object-tools">
<li><a href="csv/{%if request.GET%}?{{request.GET.urlencode}}{%endif%}" class="addlink">Export to CSV</a></li>
{% if has_add_permission %}
<li><a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">{% blocktrans with cl.opts.verbose_name|escape as name %}Add {{ name }}{% endblocktrans %}</a></li>
{% endif %}
</ul>
{% endblock %}
"""

View file

@ -11,8 +11,9 @@ def choices_length (choices):
LEVELS_CHOICES = (
('college', _(u"Collège")),
('lycee', _(u"Lycée")),
('prepa1styear', _(u"Prépa 1ère année")),
('prepa2ndyear', _(u"Prépa 2ème année")),
('prepa1styear', _(u"Prépa 1ère année / L1")),
('prepa2ndyear', _(u"Prépa 2ème année / L2")),
('licence3', _(u"Licence 3")),
('other', _(u"Autre (préciser dans les commentaires)")),
)

60
gestioncof/unicodecsv.py Normal file
View file

@ -0,0 +1,60 @@
import csv, codecs, cStringIO
class UTF8Recoder(object):
"""
Iterator that reads an encoded stream and reencodes the input to UTF-8
"""
def __init__(self, f, encoding):
self.reader = codecs.getreader(encoding)(f)
def __iter__(self):
return self
def next(self):
return self.reader.next().encode("utf-8")
class UnicodeReader(object):
"""
A CSV reader which will iterate over lines in the CSV file "f",
which is encoded in the given encoding.
"""
def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
f = UTF8Recoder(f, encoding)
self.reader = csv.reader(f, dialect=dialect, **kwds)
def next(self):
row = self.reader.next()
return [unicode(s, "utf-8") for s in row]
def __iter__(self):
return self
class UnicodeWriter(object):
"""
A CSV writer which will write rows to CSV file "f",
which is encoded in the given encoding.
"""
def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
# Redirect output to a queue
self.queue = cStringIO.StringIO()
self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
self.stream = f
self.encoder = codecs.getincrementalencoder(encoding)()
def writerow(self, row):
self.writer.writerow([s.encode("utf-8") for s in row])
# Fetch UTF-8 output from the queue ...
data = self.queue.getvalue()
data = data.decode("utf-8")
# ... and reencode it into the target encoding
data = self.encoder.encode(data)
# write to the target stream
self.stream.write(data)
# empty queue
self.queue.truncate(0)
def writerows(self, rows):
for row in rows:
self.writerow(row)

View file

@ -343,15 +343,13 @@ def event_status(request, event_id):
user_choices = registrations_query.prefetch_related("user").all()
options = EventOption.objects.filter(event = event).all()
choices_count = {}
total = 0
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
total += 1
return render(request, "event_status.html", {"event": event, "user_choices": user_choices, "options": options, "choices_count": choices_count, "form": form, "total": total})
return render(request, "event_status.html", {"event": event, "user_choices": user_choices, "options": options, "choices_count": choices_count, "form": form})
@buro_required
def survey_status(request, survey_id):
@ -707,6 +705,7 @@ def export_members(request):
return response
@buro_required
def csv_export_mega(filename, qs):
response = HttpResponse(mimetype = 'text/csv')
response['Content-Disposition'] = 'attachment; filename=' + filename
@ -764,6 +763,7 @@ def export_mega_orgas(request):
qs = EventRegistration.objects.filter(event = event).exclude(options__id__in = (participant_type_a, participant_type_b))
return csv_export_mega('orgas_mega_15.csv', qs)
@buro_required
def export_mega_participants(request):
event = Event.objects.get(title = "Mega 15")
type_option = event.options.get(name = "Type")
@ -785,3 +785,22 @@ def utile_cof(request):
@buro_required
def utile_bda(request):
return render(request, "utile_bda.html", {})
@buro_required
def liste_bdadiff(request):
titre = "BdA diffusion"
personnes = CofProfile.objects.filter(mailing_bda = True, is_cof = True).all()
return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes})
@buro_required
def liste_bdarevente(request):
titre = "BdA revente"
personnes = CofProfile.objects.filter(mailing_bda_revente = True, is_cof = True).all()
return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes})
@buro_required
def liste_diffcof(request):
titre = "Diffusion COF"
personnes = CofProfile.objects.filter(mailing_cof = True, is_cof = True).all()
return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes})

12
mails_adherents.sh Executable file
View file

@ -0,0 +1,12 @@
#!/bin/sh
# Lecture de la base
request="SELECT email, username FROM auth_user LEFT JOIN gestioncof_cofprofile ON auth_user.id = gestioncof_cofprofile.user_id WHERE mailing_cof=1 AND is_cof = 1;"
echo $request | python manage.py dbshell | \
# Suppression de l'en-tête
tail -n +2 | \
# Suppression des adhérents sans adresse mail
grep -v -P "^\t"

10
media/bda.css Normal file
View file

@ -0,0 +1,10 @@
form#tokenform {text-align: center; font-size: 2em;}
label {margin-right: 10px; vertical-align: top;}
form#tokenform textarea {font-size: 2em; width: 350px; height: 200px; font-family: 'Droif Serif', serif;}
input {width: 400px; font-size: 2em;}
ul.losers {display: inline; margin: 0; padding: 0;}
ul.losers li {display: inline;}
span.details {font-size: 0.7em;}
table {border: 1px solid black; border-collapse: collapse;}
td {border: 1px solid black; padding: 2px;}
.attribresult {margin: 10px 0px;}

View file

@ -0,0 +1,19 @@
Copyright (c) 2012 Joseph McCullough
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,166 @@
Stupid jQuery Table Sort
========================
This is a stupid jQuery table sorting plugin. Nothing fancy, nothing really
impressive. Overall, stupidly simple.
[View the demo here][0]
See the example.html document to see how to implement it.
Example Usage
-------------
The JS:
$("table").stupidtable();
The HTML:
<table>
<thead>
<tr>
<th data-sort="int">int</th>
<th data-sort="float">float</th>
<th data-sort="string">string</th>
</tr>
</thead>
<tbody>
<tr>
<td>15</td>
<td>-.18</td>
<td>banana</td>
</tr>
...
...
...
The thead and tbody tags must be used.
Add a `data-sort` attribute of "DATATYPE" to the th elements to make them sortable
by that data type. If you don't want that column to be sortable, just omit the
`data-sort` attribute.
Predefined data types
---------------------
Our aim is to keep this plugin as lightweight as possible. Consequently, the
only predefined datatypes that you can pass to the th elements are
* `int`
* `float`
* `string` (case-sensitive)
* `string-ins` (case-insensitive)
These data types will be sufficient for many simple tables. However, if you need
different data types for sorting, you can easily create your own!
Creating your own data types
----------------------------
Creating your own data type for sorting purposes is easy as long as you are
comfortable using custom functions for sorting. Consult [Mozilla's Docs][1]
if you're not.
Let's create an alphanum datatype for a User ID that takes strings in the
form "D10", "A40", and sorts them based on the number.
<thead>
<tr>
<th data-sort="string">Name</th>
<th data-sort="int">Age</th>
<th data-sort="alphanum">UserID</th>
</tr>
</thead>
<tbody>
<tr>
<td>Joseph McCullough</td>
<td>20</td>
<td>D10</td>
</tr>
<tr>
<td>Justin Edwards</td>
<td>29</td>
<td>A40</td>
</tr>
...
...
...
Now we need to specify how the **alphanum** type will be sorted. To do that,
we do the following:
$("table").stupidtable({
"alphanum":function(a,b){
var pattern = "^[A-Z](\\d+)$";
var re = new RegExp(pattern);
var aNum = re.exec(a).slice(1);
var bNum = re.exec(b).slice(1);
return parseInt(aNum,10) - parseInt(bNum,10);
}
});
This extracts the integers from the cell and compares them in the style
that sort functions use.
Callbacks
---------
To execute a callback function after a table column has been sorted, you can
bind on `aftertablesort`.
var table = $("table").stupidtable();
table.bind('aftertablesort', function (event, data) {
// data.column - the index of the column sorted after a click
// data.direction - the sorting direction (either asc or desc)
// $(this) - this table object
console.log("The sorting direction: " + data.direction);
console.log("The column index: " + data.column);
});
Similarly, to execute a callback before a table column has been sorted, you can
bind on `beforetablesort`.
See the complex_example.html file.
Data with multiple representations/predefined order
---------------------------------------------------
Often we find two distinct ways of offering data: In a machine friendly way,
and a Human-friendly way. A clear example is a Timestamp. Additionally,
arbitrary data values may already have a predefined sort order. In either case,
it's to our advantage to have a way to store the "sortable data" while letting
the viewer see the Human-friendly representation of that data. While the
purpose of the custom sort methods is to take data and make it sortable
(machine friendly), sometimes this is too hard or too expensive, computationally
speaking.
To solve this problem, you can specify a `data-sort-value` attribute to
table cells, and the attribute value will be the basis of the sort as opposed
to the text value of the table cell. See the complex_example.html file, where
we sort a column of letters based not on their alphabetical order, but by their
frequency in the English language. You'll still need to specify a sort type
or come up with your own custom sort function, but the presence of the
`data-sort-value` attribute tells the plugin to use the value of the
attribute as the basis of the sort.
License
-------
The Stupid jQuery Plugin is licensed under the MIT license. See the LICENSE
file for full details.
[0]: http://joequery.github.com/Stupid-Table-Plugin/
[1]: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort

View file

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html>
<head>
<title>Stupid jQuery table sort</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="../stupidtable.js?dev"></script>
<script>
$(function(){
$("table").stupidtable();
});
</script>
<style type="text/css">
table {
border-collapse: collapse;
}
th, td {
padding: 5px 10px;
border: 1px solid #999;
}
th {
background-color: #eee;
}
th[data-sort]{
cursor:pointer;
}
tr.awesome{
color: red;
}
</style>
</style>
</head>
<body>
<h1>Stupid jQuery table sort!</h1>
<p>This example shows how a sortable table can be implemented with very little configuration. Simply specify the data type on a <code>&lt;th&gt;</code> element using the <code>data-sort</code> attribute, and the plugin handles the rest.</p>
<table>
<thead>
<tr>
<th data-sort="int">int</th>
<th data-sort="float">float</th>
<th data-sort="string">string</th>
</tr>
</thead>
<tbody>
<tr>
<td>15</td>
<td>-.18</td>
<td>banana</td>
</tr>
<tr class="awesome">
<td>95</td>
<td>36</td>
<td>coke</td>
</tr>
<tr>
<td>2</td>
<td>-152.5</td>
<td>apple</td>
</tr>
<tr>
<td>-53</td>
<td>88.5</td>
<td>zebra</td>
</tr>
<tr>
<td>195</td>
<td>-858</td>
<td>orange</td>
</tr>
</tbody>
</table>
</body>
</html>

View file

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html>
<head>
<title>Stupid jQuery table sort (colspan test)</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="../stupidtable.js?dev"></script>
<script>
$(function(){
$("table").stupidtable();
});
</script>
<style type="text/css">
table {
border-collapse: collapse;
}
th, td {
padding: 5px 10px;
border: 1px solid #999;
}
th {
background-color: #eee;
}
th[data-sort]{
cursor:pointer;
}
tr.awesome{
color: red;
}
</style>
</style>
</head>
<body>
<h1>Stupid jQuery table sort! (colspan test)</h1>
<p>Tables using colspans are handled just fine.</p>
<table id="stupid">
<thead>
<tr>
<th data-sort="string">Letter</td>
<th colspan="2" data-sort="string">colspan=2</th>
<th data-sort="int">Number</td>
</tr>
</thead>
<tbody>
<tr>
<td>def</td>
<td>X</td>
<td>9</td>
<td>1</td>
</tr>
<tr>
<td>abc</td>
<td>Z</td>
<td>8</td>
<td>2</td>
</tr>
<tr>
<td>bcd</td>
<td>Y</td>
<td>7</td>
<td>0</td>
</tr>
</tbody>
</table>
</body>
</html>

View file

@ -0,0 +1,183 @@
<!DOCTYPE html>
<html>
<head>
<title>Stupid jQuery table sort (complex example)</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="../stupidtable.js?dev"></script>
<script>
$(function(){
// Helper function to convert a string of the form "Mar 15, 1987" into
// a Date object.
var date_from_string = function(str){
var months = ["jan","feb","mar","apr","may","jun","jul",
"aug","sep","oct","nov","dec"];
var pattern = "^([a-zA-Z]{3})\\s*(\\d{2}),\\s*(\\d{4})$";
var re = new RegExp(pattern);
var DateParts = re.exec(str).slice(1);
var Year = DateParts[2];
var Month = $.inArray(DateParts[0].toLowerCase(), months);
var Day = DateParts[1];
return new Date(Year, Month, Day);
}
var moveBlanks = function(a, b) {
if ( a < b ){
if (a == "")
return 1;
else
return -1;
}
if ( a > b ){
if (b == "")
return -1;
else
return 1;
}
return 0;
};
var moveBlanksDesc = function(a, b) {
// Blanks are by definition the smallest value, so we don't have to
// worry about them here
if ( a < b )
return 1;
if ( a > b )
return -1;
return 0;
};
var table = $("table").stupidtable({
"date":function(a,b){
// Get these into date objects for comparison.
aDate = date_from_string(a);
bDate = date_from_string(b);
return aDate - bDate;
},
"moveBlanks": moveBlanks,
"moveBlanksDesc": moveBlanksDesc,
});
table.on("beforetablesort", function (event, data) {
// data.column - the index of the column sorted after a click
// data.direction - the sorting direction (either asc or desc)
$("#msg").text("Sorting index " + data.column)
});
table.on("aftertablesort", function (event, data) {
var th = $(this).find("th");
th.find(".arrow").remove();
var dir = $.fn.stupidtable.dir;
var arrow = data.direction === dir.ASC ? "&uarr;" : "&darr;";
th.eq(data.column).append('<span class="arrow">' + arrow +'</span>');
});
$("tr").slice(1).click(function(){
$(".awesome").removeClass("awesome");
$(this).addClass("awesome");
});
});
</script>
<style type="text/css">
table {
border-collapse: collapse;
}
th, td {
padding: 5px 10px;
border: 1px solid #999;
}
th {
background-color: #eee;
}
th[data-sort]{
cursor:pointer;
}
tr.awesome{
color: red;
}
#msg {
color: green;
}
</style>
</head>
<body>
<h1>Stupid jQuery table sort! (complex example)</h1>
<p>This example showcases several of the more advanced features, including specifying sort values, custom data types and callbacks. View the source of this file or see the <a href="http://joequery.github.com/Stupid-Table-Plugin/">documentation</a> for more details on how to implement them.</p>
<p id="msg">&nbsp;</p>
<table>
<thead>
<tr>
<th data-sort="int">int</th>
<th data-sort="int">int</th>
<th data-sort="float">float</th>
<th data-sort="moveBlanks" data-sort-desc="moveBlanksDesc">string</th>
<th data-sort="string-ins">case</th>
<th>Can't sort me!</th>
<th data-sort="date">date</th>
<th data-sort="int">Letter frequency</th>
</tr>
</thead>
<tbody>
<tr>
<td>15</td>
<td>15</td>
<td>-.18</td>
<td>banana</td>
<td>Homer</td>
<td>arbitrary</td>
<td>Sep 15, 2002</td>
<td data-sort-value="0">E</td>
</tr>
<tr class="awesome">
<td>95</td>
<td>95</td>
<td>36</td>
<td></td>
<td>purple</td>
<td>pointless</td>
<td>Aug 07, 2004</td>
<td data-sort-value="1">T</td>
</tr>
<tr>
<td>2</td>
<td>2</td>
<td>-152.5</td>
<td></td>
<td>is</td>
<td>silly</td>
<td>Mar 15, 1986</td>
<td data-sort-value="2">A</td>
</tr>
<tr>
<td>-53</td>
<td>-53</td>
<td>88.5</td>
<td>hello</td>
<td>a</td>
<td>eccentric</td>
<td>Feb 27, 2086</td>
<td data-sort-value="3">O</td>
</tr>
<tr>
<td>195</td>
<td>195</td>
<td>-858</td>
<td>orange</td>
<td>fruit</td>
<td>garbage</td>
<td>Mar 15, 1986</td>
<td data-sort-value="4">I</td>
</tr>
</tbody>
</table>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,158 @@
// Stupid jQuery table plugin.
// Call on a table
// sortFns: Sort functions for your datatypes.
(function($) {
$.fn.stupidtable = function(sortFns) {
return this.each(function() {
var $table = $(this);
sortFns = sortFns || {};
// ==================================================== //
// Utility functions //
// ==================================================== //
// Merge sort functions with some default sort functions.
sortFns = $.extend({}, $.fn.stupidtable.default_sort_fns, sortFns);
// Return the resulting indexes of a sort so we can apply
// this result elsewhere. This returns an array of index numbers.
// return[0] = x means "arr's 0th element is now at x"
var sort_map = function(arr, sort_function, reverse_column) {
var map = [];
var index = 0;
if (reverse_column) {
for (var i = arr.length-1; i >= 0; i--) {
map.push(i);
}
}
else {
var sorted = arr.slice(0).sort(sort_function);
for (var i=0; i<arr.length; i++) {
index = $.inArray(arr[i], sorted);
// If this index is already in the map, look for the next index.
// This handles the case of duplicate entries.
while ($.inArray(index, map) != -1) {
index++;
}
map.push(index);
}
}
return map;
};
// Apply a sort map to the array.
var apply_sort_map = function(arr, map) {
var clone = arr.slice(0),
newIndex = 0;
for (var i=0; i<map.length; i++) {
newIndex = map[i];
clone[newIndex] = arr[i];
}
return clone;
};
// ==================================================== //
// Begin execution! //
// ==================================================== //
// Do sorting when THs are clicked
$table.on("click", "th", function() {
var trs = $table.children("tbody").children("tr");
var $this = $(this);
var th_index = 0;
var dir = $.fn.stupidtable.dir;
$table.find("th").slice(0, $this.index()).each(function() {
var cols = $(this).attr("colspan") || 1;
th_index += parseInt(cols,10);
});
// Determine (and/or reverse) sorting direction, default `asc`
var sort_dir = $this.data("sort-dir") === dir.ASC ? dir.DESC : dir.ASC;
// Choose appropriate sorting function. If we're sorting descending, check
// for a `data-sort-desc` attribute.
if ( sort_dir == dir.DESC )
var type = $this.data("sort-desc") || $this.data("sort") || null;
else
var type = $this.data("sort") || null;
// Prevent sorting if no type defined
if (type === null) {
return;
}
// Trigger `beforetablesort` event that calling scripts can hook into;
// pass parameters for sorted column index and sorting direction
$table.trigger("beforetablesort", {column: th_index, direction: sort_dir});
// More reliable method of forcing a redraw
$table.css("display");
// Run sorting asynchronously on a timout to force browser redraw after
// `beforetablesort` callback. Also avoids locking up the browser too much.
setTimeout(function() {
// Gather the elements for this column
var column = [];
var sortMethod = sortFns[type];
// Push either the value of the `data-order-by` attribute if specified
// or just the text() value in this column to column[] for comparison.
trs.each(function(index,tr) {
var $e = $(tr).children().eq(th_index);
var sort_val = $e.data("sort-value");
var order_by = typeof(sort_val) !== "undefined" ? sort_val : $e.text();
column.push(order_by);
});
// Create the sort map. This column having a sort-dir implies it was
// the last column sorted. As long as no data-sort-desc is specified,
// we're free to just reverse the column.
var reverse_column = !!$this.data("sort-dir") && !$this.data("sort-desc");
var theMap = sort_map(column, sortMethod, reverse_column);
// Reset siblings
$table.find("th").data("sort-dir", null).removeClass("sorting-desc sorting-asc");
$this.data("sort-dir", sort_dir).addClass("sorting-"+sort_dir);
// Replace the content of tbody with the sortedTRs. Strangely (and
// conveniently!) enough, .append accomplishes this for us.
var sortedTRs = $(apply_sort_map(trs, theMap));
$table.children("tbody").append(sortedTRs);
// Trigger `aftertablesort` event. Similar to `beforetablesort`
$table.trigger("aftertablesort", {column: th_index, direction: sort_dir});
// More reliable method of forcing a redraw
$table.css("display");
}, 10);
});
});
};
// Enum containing sorting directions
$.fn.stupidtable.dir = {ASC: "asc", DESC: "desc"};
$.fn.stupidtable.default_sort_fns = {
"int": function(a, b) {
return parseInt(a, 10) - parseInt(b, 10);
},
"float": function(a, b) {
return parseFloat(a) - parseFloat(b);
},
"string": function(a, b) {
if (a < b) return -1;
if (a > b) return +1;
return 0;
},
"string-ins": function(a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
if (a < b) return -1;
if (a > b) return +1;
return 0;
}
};
})(jQuery);

View file

@ -0,0 +1,3 @@
(function(d){d.fn.stupidtable=function(b){return this.each(function(){var a=d(this);b=b||{};b=d.extend({},d.fn.stupidtable.default_sort_fns,b);a.on("click","th",function(){var n=a.children("tbody").children("tr"),e=d(this),j=0,m=d.fn.stupidtable.dir;a.find("th").slice(0,e.index()).each(function(){var a=d(this).attr("colspan")||1;j+=parseInt(a,10)});var l=e.data("sort-dir")===m.ASC?m.DESC:m.ASC,p=l==m.DESC?e.data("sort-desc")||e.data("sort")||null:e.data("sort")||null;null!==p&&(a.trigger("beforetablesort",
{column:j,direction:l}),a.css("display"),setTimeout(function(){var k=[],c=b[p];n.each(function(a,b){var c=d(b).children().eq(j),e=c.data("sort-value"),c="undefined"!==typeof e?e:c.text();k.push(c)});var f=[],g=0;if(e.data("sort-dir")&&!e.data("sort-desc"))for(c=k.length-1;0<=c;c--)f.push(c);else for(var h=k.slice(0).sort(c),c=0;c<k.length;c++){for(g=d.inArray(k[c],h);-1!=d.inArray(g,f);)g++;f.push(g)}a.find("th").data("sort-dir",null).removeClass("sorting-desc sorting-asc");e.data("sort-dir",l).addClass("sorting-"+
l);g=n.slice(0);for(h=c=0;h<f.length;h++)c=f[h],g[c]=n[h];f=d(g);a.children("tbody").append(f);a.trigger("aftertablesort",{column:j,direction:l});a.css("display")},10))})})};d.fn.stupidtable.dir={ASC:"asc",DESC:"desc"};d.fn.stupidtable.default_sort_fns={"int":function(b,a){return parseInt(b,10)-parseInt(a,10)},"float":function(b,a){return parseFloat(b)-parseFloat(a)},string:function(b,a){return b<a?-1:b>a?1:0},"string-ins":function(b,a){b=b.toLowerCase();a=a.toLowerCase();return b<a?-1:b>a?1:0}}})(jQuery);

4
media/js/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

0
pads/__init__.py Normal file
View file

3
pads/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

16
pads/tests.py Normal file
View 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)

1
pads/views.py Normal file
View file

@ -0,0 +1 @@
# Create your views here.

0
rezo/__init__.py Normal file
View file

3
rezo/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

16
rezo/tests.py Normal file
View 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)

1
rezo/views.py Normal file
View file

@ -0,0 +1 @@
# Create your views here.

840
static/admin/css/base.css Normal file
View file

@ -0,0 +1,840 @@
/*
DJANGO Admin styles
*/
body {
margin: 0;
padding: 0;
font-size: 12px;
font-family: "Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif;
color: #333;
background: #fff;
}
/* LINKS */
a:link, a:visited {
color: #5b80b2;
text-decoration: none;
}
a:hover {
color: #036;
}
a img {
border: none;
}
a.section:link, a.section:visited {
color: white;
text-decoration: none;
}
/* GLOBAL DEFAULTS */
p, ol, ul, dl {
margin: .2em 0 .8em 0;
}
p {
padding: 0;
line-height: 140%;
}
h1,h2,h3,h4,h5 {
font-weight: bold;
}
h1 {
font-size: 18px;
color: #666;
padding: 0 6px 0 0;
margin: 0 0 .2em 0;
}
h2 {
font-size: 16px;
margin: 1em 0 .5em 0;
}
h2.subhead {
font-weight: normal;
margin-top: 0;
}
h3 {
font-size: 14px;
margin: .8em 0 .3em 0;
color: #666;
font-weight: bold;
}
h4 {
font-size: 12px;
margin: 1em 0 .8em 0;
padding-bottom: 3px;
}
h5 {
font-size: 10px;
margin: 1.5em 0 .5em 0;
color: #666;
text-transform: uppercase;
letter-spacing: 1px;
}
ul li {
list-style-type: square;
padding: 1px 0;
}
ul.plainlist {
margin-left: 0 !important;
}
ul.plainlist li {
list-style-type: none;
}
li ul {
margin-bottom: 0;
}
li, dt, dd {
font-size: 11px;
line-height: 14px;
}
dt {
font-weight: bold;
margin-top: 4px;
}
dd {
margin-left: 0;
}
form {
margin: 0;
padding: 0;
}
fieldset {
margin: 0;
padding: 0;
}
blockquote {
font-size: 11px;
color: #777;
margin-left: 2px;
padding-left: 10px;
border-left: 5px solid #ddd;
}
code, pre {
font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
background: inherit;
color: #666;
font-size: 11px;
}
pre.literal-block {
margin: 10px;
background: #eee;
padding: 6px 8px;
}
code strong {
color: #930;
}
hr {
clear: both;
color: #eee;
background-color: #eee;
height: 1px;
border: none;
margin: 0;
padding: 0;
font-size: 1px;
line-height: 1px;
}
/* TEXT STYLES & MODIFIERS */
.small {
font-size: 11px;
}
.tiny {
font-size: 10px;
}
p.tiny {
margin-top: -2px;
}
.mini {
font-size: 9px;
}
p.mini {
margin-top: -3px;
}
.help, p.help {
font-size: 10px !important;
color: #999;
}
img.help-tooltip {
cursor: help;
}
p img, h1 img, h2 img, h3 img, h4 img, td img {
vertical-align: middle;
}
.quiet, a.quiet:link, a.quiet:visited {
color: #999 !important;
font-weight: normal !important;
}
.quiet strong {
font-weight: bold !important;
}
.float-right {
float: right;
}
.float-left {
float: left;
}
.clear {
clear: both;
}
.align-left {
text-align: left;
}
.align-right {
text-align: right;
}
.example {
margin: 10px 0;
padding: 5px 10px;
background: #efefef;
}
.nowrap {
white-space: nowrap;
}
/* TABLES */
table {
border-collapse: collapse;
border-color: #ccc;
}
td, th {
font-size: 11px;
line-height: 13px;
border-bottom: 1px solid #eee;
vertical-align: top;
padding: 5px;
font-family: "Lucida Grande", Verdana, Arial, sans-serif;
}
th {
text-align: left;
font-size: 12px;
font-weight: bold;
}
thead th,
tfoot td {
color: #666;
padding: 2px 5px;
font-size: 11px;
background: #e1e1e1 url(../img/nav-bg.gif) top left repeat-x;
border-left: 1px solid #ddd;
border-bottom: 1px solid #ddd;
}
tfoot td {
border-bottom: none;
border-top: 1px solid #ddd;
}
thead th:first-child,
tfoot td:first-child {
border-left: none !important;
}
thead th.optional {
font-weight: normal !important;
}
fieldset table {
border-right: 1px solid #eee;
}
tr.row-label td {
font-size: 9px;
padding-top: 2px;
padding-bottom: 0;
border-bottom: none;
color: #666;
margin-top: -1px;
}
tr.alt {
background: #f6f6f6;
}
.row1 {
background: #EDF3FE;
}
.row2 {
background: white;
}
/* SORTABLE TABLES */
thead th {
padding: 2px 5px;
line-height: normal;
}
thead th a:link, thead th a:visited {
color: #666;
}
thead th.sorted {
background: #c5c5c5 url(../img/nav-bg-selected.gif) top left repeat-x;
}
thead th.sorted .text {
padding-right: 42px;
}
table thead th .text span {
padding: 2px 5px;
display:block;
}
table thead th .text a {
display: block;
cursor: pointer;
padding: 2px 5px;
}
table thead th.sortable:hover {
background: white url(../img/nav-bg-reverse.gif) 0 -5px repeat-x;
}
thead th.sorted a.sortremove {
visibility: hidden;
}
table thead th.sorted:hover a.sortremove {
visibility: visible;
}
table thead th.sorted .sortoptions {
display: block;
padding: 4px 5px 0 5px;
float: right;
text-align: right;
}
table thead th.sorted .sortpriority {
font-size: .8em;
min-width: 12px;
text-align: center;
vertical-align: top;
}
table thead th.sorted .sortoptions a {
width: 14px;
height: 12px;
display: inline-block;
}
table thead th.sorted .sortoptions a.sortremove {
background: url(../img/sorting-icons.gif) -4px -5px no-repeat;
}
table thead th.sorted .sortoptions a.sortremove:hover {
background: url(../img/sorting-icons.gif) -4px -27px no-repeat;
}
table thead th.sorted .sortoptions a.ascending {
background: url(../img/sorting-icons.gif) -5px -50px no-repeat;
}
table thead th.sorted .sortoptions a.ascending:hover {
background: url(../img/sorting-icons.gif) -5px -72px no-repeat;
}
table thead th.sorted .sortoptions a.descending {
background: url(../img/sorting-icons.gif) -5px -94px no-repeat;
}
table thead th.sorted .sortoptions a.descending:hover {
background: url(../img/sorting-icons.gif) -5px -115px no-repeat;
}
/* ORDERABLE TABLES */
table.orderable tbody tr td:hover {
cursor: move;
}
table.orderable tbody tr td:first-child {
padding-left: 14px;
background-image: url(../img/nav-bg-grabber.gif);
background-repeat: repeat-y;
}
table.orderable-initalized .order-cell, body>tr>td.order-cell {
display: none;
}
/* FORM DEFAULTS */
input, textarea, select, .form-row p {
margin: 2px 0;
padding: 2px 3px;
vertical-align: middle;
font-family: "Lucida Grande", Verdana, Arial, sans-serif;
font-weight: normal;
font-size: 11px;
}
textarea {
vertical-align: top !important;
}
input[type=text], input[type=password], input[type=email], input[type=url], input[type=number],
textarea, select, .vTextField {
border: 1px solid #ccc;
}
/* FORM BUTTONS */
.button, input[type=submit], input[type=button], .submit-row input {
background: white url(../img/nav-bg.gif) bottom repeat-x;
padding: 3px 5px;
color: black;
border: 1px solid #bbb;
border-color: #ddd #aaa #aaa #ddd;
}
.button:active, input[type=submit]:active, input[type=button]:active {
background-image: url(../img/nav-bg-reverse.gif);
background-position: top;
}
.button[disabled], input[type=submit][disabled], input[type=button][disabled] {
background-image: url(../img/nav-bg.gif);
background-position: bottom;
opacity: 0.4;
}
.button.default, input[type=submit].default, .submit-row input.default {
border: 2px solid #5b80b2;
background: #7CA0C7 url(../img/default-bg.gif) bottom repeat-x;
font-weight: bold;
color: white;
float: right;
}
.button.default:active, input[type=submit].default:active {
background-image: url(../img/default-bg-reverse.gif);
background-position: top;
}
.button[disabled].default, input[type=submit][disabled].default, input[type=button][disabled].default {
background-image: url(../img/default-bg.gif);
background-position: bottom;
opacity: 0.4;
}
/* MODULES */
.module {
border: 1px solid #ccc;
margin-bottom: 5px;
background: white;
}
.module p, .module ul, .module h3, .module h4, .module dl, .module pre {
padding-left: 10px;
padding-right: 10px;
}
.module blockquote {
margin-left: 12px;
}
.module ul, .module ol {
margin-left: 1.5em;
}
.module h3 {
margin-top: .6em;
}
.module h2, .module caption, .inline-group h2 {
margin: 0;
padding: 2px 5px 3px 5px;
font-size: 11px;
text-align: left;
font-weight: bold;
background: #7CA0C7 url(../img/default-bg.gif) top left repeat-x;
color: white;
}
.module table {
border-collapse: collapse;
}
/* MESSAGES & ERRORS */
ul.messagelist {
padding: 0 0 5px 0;
margin: 0;
}
ul.messagelist li {
font-size: 12px;
display: block;
padding: 4px 5px 4px 25px;
margin: 0 0 3px 0;
border-bottom: 1px solid #ddd;
color: #666;
background: #ffc url(../img/icon_success.gif) 5px .3em no-repeat;
}
ul.messagelist li.warning{
background-image: url(../img/icon_alert.gif);
}
ul.messagelist li.error{
background-image: url(../img/icon_error.gif);
}
.errornote {
font-size: 12px !important;
display: block;
padding: 4px 5px 4px 25px;
margin: 0 0 3px 0;
border: 1px solid red;
color: red;
background: #ffc url(../img/icon_error.gif) 5px .3em no-repeat;
}
ul.errorlist {
margin: 0 !important;
padding: 0 !important;
}
.errorlist li {
font-size: 12px !important;
display: block;
padding: 4px 5px 4px 25px;
margin: 0 0 3px 0;
border: 1px solid red;
color: white;
background: red url(../img/icon_alert.gif) 5px .3em no-repeat;
}
.errorlist li a {
color: white;
text-decoration: underline;
}
td ul.errorlist {
margin: 0 !important;
padding: 0 !important;
}
td ul.errorlist li {
margin: 0 !important;
}
.errors {
background: #ffc;
}
.errors input, .errors select, .errors textarea {
border: 1px solid red;
}
div.system-message {
background: #ffc;
margin: 10px;
padding: 6px 8px;
font-size: .8em;
}
div.system-message p.system-message-title {
padding: 4px 5px 4px 25px;
margin: 0;
color: red;
background: #ffc url(../img/icon_error.gif) 5px .3em no-repeat;
}
.description {
font-size: 12px;
padding: 5px 0 0 12px;
}
/* BREADCRUMBS */
div.breadcrumbs {
background: white url(../img/nav-bg-reverse.gif) 0 -10px repeat-x;
padding: 2px 8px 3px 8px;
font-size: 11px;
color: #999;
border-top: 1px solid white;
border-bottom: 1px solid #ccc;
text-align: left;
}
/* ACTION ICONS */
.addlink {
padding-left: 12px;
background: url(../img/icon_addlink.gif) 0 .2em no-repeat;
}
.changelink {
padding-left: 12px;
background: url(../img/icon_changelink.gif) 0 .2em no-repeat;
}
.deletelink {
padding-left: 12px;
background: url(../img/icon_deletelink.gif) 0 .25em no-repeat;
}
a.deletelink:link, a.deletelink:visited {
color: #CC3434;
}
a.deletelink:hover {
color: #993333;
}
/* OBJECT TOOLS */
.object-tools {
font-size: 10px;
font-weight: bold;
font-family: Arial,Helvetica,sans-serif;
padding-left: 0;
float: right;
position: relative;
margin-top: -2.4em;
margin-bottom: -2em;
}
.form-row .object-tools {
margin-top: 5px;
margin-bottom: 5px;
float: none;
height: 2em;
padding-left: 3.5em;
}
.object-tools li {
display: block;
float: left;
background: url(../img/tool-left.gif) 0 0 no-repeat;
padding: 0 0 0 8px;
margin-left: 2px;
height: 16px;
}
.object-tools li:hover {
background: url(../img/tool-left_over.gif) 0 0 no-repeat;
}
.object-tools a:link, .object-tools a:visited {
display: block;
float: left;
color: white;
padding: .1em 14px .1em 8px;
height: 14px;
background: #999 url(../img/tool-right.gif) 100% 0 no-repeat;
}
.object-tools a:hover, .object-tools li:hover a {
background: #5b80b2 url(../img/tool-right_over.gif) 100% 0 no-repeat;
}
.object-tools a.viewsitelink, .object-tools a.golink {
background: #999 url(../img/tooltag-arrowright.gif) top right no-repeat;
padding-right: 28px;
}
.object-tools a.viewsitelink:hover, .object-tools a.golink:hover {
background: #5b80b2 url(../img/tooltag-arrowright_over.gif) top right no-repeat;
}
.object-tools a.addlink {
background: #999 url(../img/tooltag-add.gif) top right no-repeat;
padding-right: 28px;
}
.object-tools a.addlink:hover {
background: #5b80b2 url(../img/tooltag-add_over.gif) top right no-repeat;
}
/* OBJECT HISTORY */
table#change-history {
width: 100%;
}
table#change-history tbody th {
width: 16em;
}
/* PAGE STRUCTURE */
#container {
position: relative;
width: 100%;
min-width: 760px;
padding: 0;
}
#content {
margin: 10px 15px;
}
#header {
width: 100%;
}
#content-main {
float: left;
width: 100%;
}
#content-related {
float: right;
width: 18em;
position: relative;
margin-right: -19em;
}
#footer {
clear: both;
padding: 10px;
}
/* COLUMN TYPES */
.colMS {
margin-right: 20em !important;
}
.colSM {
margin-left: 20em !important;
}
.colSM #content-related {
float: left;
margin-right: 0;
margin-left: -19em;
}
.colSM #content-main {
float: right;
}
.popup .colM {
width: 95%;
}
.subcol {
float: left;
width: 46%;
margin-right: 15px;
}
.dashboard #content {
width: 500px;
}
/* HEADER */
#header {
background: #417690;
color: #ffc;
overflow: hidden;
}
#header a:link, #header a:visited {
color: white;
}
#header a:hover {
text-decoration: underline;
}
#branding h1 {
padding: 0 10px;
font-size: 18px;
margin: 8px 0;
font-weight: normal;
color: #f4f379;
}
#branding h2 {
padding: 0 10px;
font-size: 14px;
margin: -8px 0 8px 0;
font-weight: normal;
color: #ffc;
}
#user-tools {
position: absolute;
top: 0;
right: 0;
padding: 1.2em 10px;
font-size: 11px;
text-align: right;
}
/* SIDEBAR */
#content-related h3 {
font-size: 12px;
color: #666;
margin-bottom: 3px;
}
#content-related h4 {
font-size: 11px;
}
#content-related .module h2 {
background: #eee url(../img/nav-bg.gif) bottom left repeat-x;
color: #666;
}

View file

@ -0,0 +1,293 @@
/* CHANGELISTS */
#changelist {
position: relative;
width: 100%;
}
#changelist table {
width: 100%;
}
.change-list .hiddenfields { display:none; }
.change-list .filtered table {
border-right: 1px solid #ddd;
}
.change-list .filtered {
min-height: 400px;
}
.change-list .filtered {
background: white url(../img/changelist-bg.gif) top right repeat-y !important;
}
.change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull {
margin-right: 160px !important;
width: auto !important;
}
.change-list .filtered table tbody th {
padding-right: 1em;
}
#changelist-form .results {
overflow-x: auto;
}
#changelist .toplinks {
border-bottom: 1px solid #ccc !important;
}
#changelist .paginator {
color: #666;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
background: white url(../img/nav-bg.gif) 0 180% repeat-x;
overflow: hidden;
}
.change-list .filtered .paginator {
border-right: 1px solid #ddd;
}
/* CHANGELIST TABLES */
#changelist table thead th {
padding: 0;
white-space: nowrap;
vertical-align: middle;
}
#changelist table thead th.action-checkbox-column {
width: 1.5em;
text-align: center;
}
#changelist table tbody td, #changelist table tbody th {
border-left: 1px solid #ddd;
}
#changelist table tbody td:first-child, #changelist table tbody th:first-child {
border-left: 0;
border-right: 1px solid #ddd;
}
#changelist table tbody td.action-checkbox {
text-align:center;
}
#changelist table tfoot {
color: #666;
}
/* TOOLBAR */
#changelist #toolbar {
padding: 3px;
border-bottom: 1px solid #ddd;
background: #e1e1e1 url(../img/nav-bg.gif) top left repeat-x;
color: #666;
}
#changelist #toolbar form input {
font-size: 11px;
padding: 1px 2px;
}
#changelist #toolbar form #searchbar {
padding: 2px;
}
#changelist #changelist-search img {
vertical-align: middle;
}
/* FILTER COLUMN */
#changelist-filter {
position: absolute;
top: 0;
right: 0;
z-index: 1000;
width: 160px;
border-left: 1px solid #ddd;
background: #efefef;
margin: 0;
}
#changelist-filter h2 {
font-size: 11px;
padding: 2px 5px;
border-bottom: 1px solid #ddd;
}
#changelist-filter h3 {
font-size: 12px;
margin-bottom: 0;
}
#changelist-filter ul {
padding-left: 0;
margin-left: 10px;
}
#changelist-filter li {
list-style-type: none;
margin-left: 0;
padding-left: 0;
}
#changelist-filter a {
color: #999;
}
#changelist-filter a:hover {
color: #036;
}
#changelist-filter li.selected {
border-left: 5px solid #ccc;
padding-left: 5px;
margin-left: -10px;
}
#changelist-filter li.selected a {
color: #5b80b2 !important;
}
/* DATE DRILLDOWN */
.change-list ul.toplinks {
display: block;
background: white url(../img/nav-bg-reverse.gif) 0 -10px repeat-x;
border-top: 1px solid white;
float: left;
padding: 0 !important;
margin: 0 !important;
width: 100%;
}
.change-list ul.toplinks li {
padding: 3px 6px;
font-weight: bold;
list-style-type: none;
display: inline-block;
}
.change-list ul.toplinks .date-back a {
color: #999;
}
.change-list ul.toplinks .date-back a:hover {
color: #036;
}
/* PAGINATOR */
.paginator {
font-size: 11px;
padding-top: 10px;
padding-bottom: 10px;
line-height: 22px;
margin: 0;
border-top: 1px solid #ddd;
}
.paginator a:link, .paginator a:visited {
padding: 2px 6px;
border: solid 1px #ccc;
background: white;
text-decoration: none;
}
.paginator a.showall {
padding: 0 !important;
border: none !important;
}
.paginator a.showall:hover {
color: #036 !important;
background: transparent !important;
}
.paginator .end {
border-width: 2px !important;
margin-right: 6px;
}
.paginator .this-page {
padding: 2px 6px;
font-weight: bold;
font-size: 13px;
vertical-align: top;
}
.paginator a:hover {
color: white;
background: #5b80b2;
border-color: #036;
}
/* ACTIONS */
.filtered .actions {
margin-right: 160px !important;
border-right: 1px solid #ddd;
}
#changelist table input {
margin: 0;
}
#changelist table tbody tr.selected {
background-color: #FFFFCC;
}
#changelist .actions {
color: #999;
padding: 3px;
border-top: 1px solid #fff;
border-bottom: 1px solid #ddd;
background: white url(../img/nav-bg-reverse.gif) 0 -10px repeat-x;
}
#changelist .actions.selected {
background: #fffccf;
border-top: 1px solid #fffee8;
border-bottom: 1px solid #edecd6;
}
#changelist .actions span.all,
#changelist .actions span.action-counter,
#changelist .actions span.clear,
#changelist .actions span.question {
font-size: 11px;
margin: 0 0.5em;
display: none;
}
#changelist .actions:last-child {
border-bottom: none;
}
#changelist .actions select {
border: 1px solid #aaa;
margin-left: 0.5em;
padding: 1px 2px;
}
#changelist .actions label {
font-size: 11px;
margin-left: 0.5em;
}
#changelist #action-toggle {
display: none;
}
#changelist .actions .button {
font-size: 11px;
padding: 1px 2px;
}

View file

@ -0,0 +1,30 @@
/* DASHBOARD */
.dashboard .module table th {
width: 100%;
}
.dashboard .module table td {
white-space: nowrap;
}
.dashboard .module table td a {
display: block;
padding-right: .6em;
}
/* RECENT ACTIONS MODULE */
.module ul.actionlist {
margin-left: 0;
}
ul.actionlist li {
list-style-type: none;
}
ul.actionlist li {
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}

364
static/admin/css/forms.css Normal file
View file

@ -0,0 +1,364 @@
@import url('widgets.css');
/* FORM ROWS */
.form-row {
overflow: hidden;
padding: 8px 12px;
font-size: 11px;
border-bottom: 1px solid #eee;
}
.form-row img, .form-row input {
vertical-align: middle;
}
form .form-row p {
padding-left: 0;
font-size: 11px;
}
/* FORM LABELS */
form h4 {
margin: 0 !important;
padding: 0 !important;
border: none !important;
}
label {
font-weight: normal !important;
color: #666;
font-size: 12px;
}
.required label, label.required {
font-weight: bold !important;
color: #333 !important;
}
/* RADIO BUTTONS */
form ul.radiolist li {
list-style-type: none;
}
form ul.radiolist label {
float: none;
display: inline;
}
form ul.inline {
margin-left: 0;
padding: 0;
}
form ul.inline li {
float: left;
padding-right: 7px;
}
/* ALIGNED FIELDSETS */
.aligned label {
display: block;
padding: 3px 10px 0 0;
float: left;
width: 8em;
word-wrap: break-word;
}
.aligned ul label {
display: inline;
float: none;
width: auto;
}
.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField {
width: 350px;
}
form .aligned p, form .aligned ul {
margin-left: 7em;
padding-left: 30px;
}
form .aligned table p {
margin-left: 0;
padding-left: 0;
}
form .aligned p.help {
padding-left: 38px;
}
.aligned .vCheckboxLabel {
float: none !important;
display: inline;
padding-left: 4px;
}
.colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField {
width: 610px;
}
.checkbox-row p.help {
margin-left: 0;
padding-left: 0 !important;
}
fieldset .field-box {
float: left;
margin-right: 20px;
}
/* WIDE FIELDSETS */
.wide label {
width: 15em !important;
}
form .wide p {
margin-left: 15em;
}
form .wide p.help {
padding-left: 38px;
}
.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField {
width: 450px;
}
/* COLLAPSED FIELDSETS */
fieldset.collapsed * {
display: none;
}
fieldset.collapsed h2, fieldset.collapsed {
display: block !important;
}
fieldset.collapsed h2 {
background-image: url(../img/nav-bg.gif);
background-position: bottom left;
color: #999;
}
fieldset.collapsed .collapse-toggle {
background: transparent;
display: inline !important;
}
/* MONOSPACE TEXTAREAS */
fieldset.monospace textarea {
font-family: "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace;
}
/* SUBMIT ROW */
.submit-row {
padding: 5px 7px;
text-align: right;
background: white url(../img/nav-bg.gif) 0 100% repeat-x;
border: 1px solid #ccc;
margin: 5px 0;
overflow: hidden;
}
body.popup .submit-row {
overflow: auto;
}
.submit-row input {
margin: 0 0 0 5px;
}
.submit-row p {
margin: 0.3em;
}
.submit-row p.deletelink-box {
float: left;
}
.submit-row .deletelink {
background: url(../img/icon_deletelink.gif) 0 50% no-repeat;
padding-left: 14px;
}
/* CUSTOM FORM FIELDS */
.vSelectMultipleField {
vertical-align: top !important;
}
.vCheckboxField {
border: none;
}
.vDateField, .vTimeField {
margin-right: 2px;
}
.vURLField {
width: 30em;
}
.vLargeTextField, .vXMLLargeTextField {
width: 48em;
}
.flatpages-flatpage #id_content {
height: 40.2em;
}
.module table .vPositiveSmallIntegerField {
width: 2.2em;
}
.vTextField {
width: 20em;
}
.vIntegerField {
width: 5em;
}
.vBigIntegerField {
width: 10em;
}
.vForeignKeyRawIdAdminField {
width: 5em;
}
/* INLINES */
.inline-group {
padding: 0;
border: 1px solid #ccc;
margin: 10px 0;
}
.inline-group .aligned label {
width: 8em;
}
.inline-related {
position: relative;
}
.inline-related h3 {
margin: 0;
color: #666;
padding: 3px 5px;
font-size: 11px;
background: #e1e1e1 url(../img/nav-bg.gif) top left repeat-x;
border-bottom: 1px solid #ddd;
}
.inline-related h3 span.delete {
float: right;
}
.inline-related h3 span.delete label {
margin-left: 2px;
font-size: 11px;
}
.inline-related fieldset {
margin: 0;
background: #fff;
border: none;
width: 100%;
}
.inline-related fieldset.module h3 {
margin: 0;
padding: 2px 5px 3px 5px;
font-size: 11px;
text-align: left;
font-weight: bold;
background: #bcd;
color: #fff;
}
.inline-group .tabular fieldset.module {
border: none;
border-bottom: 1px solid #ddd;
}
.inline-related.tabular fieldset.module table {
width: 100%;
}
.last-related fieldset {
border: none;
}
.inline-group .tabular tr.has_original td {
padding-top: 2em;
}
.inline-group .tabular tr td.original {
padding: 2px 0 0 0;
width: 0;
_position: relative;
}
.inline-group .tabular th.original {
width: 0px;
padding: 0;
}
.inline-group .tabular td.original p {
position: absolute;
left: 0;
height: 1.1em;
padding: 2px 7px;
overflow: hidden;
font-size: 9px;
font-weight: bold;
color: #666;
_width: 700px;
}
.inline-group ul.tools {
padding: 0;
margin: 0;
list-style: none;
}
.inline-group ul.tools li {
display: inline;
padding: 0 5px;
}
.inline-group div.add-row,
.inline-group .tabular tr.add-row td {
color: #666;
padding: 3px 5px;
border-bottom: 1px solid #ddd;
background: #e1e1e1 url(../img/nav-bg.gif) top left repeat-x;
}
.inline-group .tabular tr.add-row td {
padding: 4px 5px 3px;
border-bottom: none;
}
.inline-group ul.tools a.add,
.inline-group div.add-row a,
.inline-group .tabular tr.add-row td a {
background: url(../img/icon_addlink.gif) 0 50% no-repeat;
padding-left: 14px;
font-size: 11px;
outline: 0; /* Remove dotted border around link */
}
.empty-form {
display: none;
}

63
static/admin/css/ie.css Normal file
View file

@ -0,0 +1,63 @@
/* IE 6 & 7 */
/* Proper fixed width for dashboard in IE6 */
.dashboard #content {
*width: 768px;
}
.dashboard #content-main {
*width: 535px;
}
/* IE 6 ONLY */
/* Keep header from flowing off the page */
#container {
_position: static;
}
/* Put the right sidebars back on the page */
.colMS #content-related {
_margin-right: 0;
_margin-left: 10px;
_position: static;
}
/* Put the left sidebars back on the page */
.colSM #content-related {
_margin-right: 10px;
_margin-left: -115px;
_position: static;
}
.form-row {
_height: 1%;
}
/* Fix right margin for changelist filters in IE6 */
#changelist-filter ul {
_margin-right: -10px;
}
/* IE ignores min-height, but treats height as if it were min-height */
.change-list .filtered {
_height: 400px;
}
/* IE doesn't know alpha transparency in PNGs */
.inline-deletelink {
background: transparent url(../img/inline-delete-8bit.png) no-repeat;
}
/* IE7 doesn't support inline-block */
.change-list ul.toplinks li {
zoom: 1;
*display: inline;
}

View file

@ -0,0 +1,60 @@
/* LOGIN FORM */
body.login {
background: #eee;
}
.login #container {
background: white;
border: 1px solid #ccc;
width: 28em;
min-width: 300px;
margin-left: auto;
margin-right: auto;
margin-top: 100px;
}
.login #content-main {
width: 100%;
}
.login form {
margin-top: 1em;
}
.login .form-row {
padding: 4px 0;
float: left;
width: 100%;
}
.login .form-row label {
padding-right: 0.5em;
line-height: 2em;
font-size: 1em;
clear: both;
color: #333;
}
.login .form-row #id_username, .login .form-row #id_password {
clear: both;
padding: 6px;
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.login span.help {
font-size: 10px;
display: block;
}
.login .submit-row {
clear: both;
padding: 1em 0 0 9.4em;
}
.login .password-reset-link {
text-align: center;
}

250
static/admin/css/rtl.css Normal file
View file

@ -0,0 +1,250 @@
body {
direction: rtl;
}
/* LOGIN */
.login .form-row {
float: right;
}
.login .form-row label {
float: right;
padding-left: 0.5em;
padding-right: 0;
text-align: left;
}
.login .submit-row {
clear: both;
padding: 1em 9.4em 0 0;
}
/* GLOBAL */
th {
text-align: right;
}
.module h2, .module caption {
text-align: right;
}
.addlink, .changelink {
padding-left: 0px;
padding-right: 12px;
background-position: 100% 0.2em;
}
.deletelink {
padding-left: 0px;
padding-right: 12px;
background-position: 100% 0.25em;
}
.object-tools {
float: left;
}
thead th:first-child,
tfoot td:first-child {
border-left: 1px solid #ddd !important;
}
/* LAYOUT */
#user-tools {
right: auto;
left: 0;
text-align: left;
}
div.breadcrumbs {
text-align: right;
}
#content-main {
float: right;
}
#content-related {
float: left;
margin-left: -19em;
margin-right: auto;
}
.colMS {
margin-left: 20em !important;
margin-right: 10px !important;
}
/* SORTABLE TABLES */
table thead th.sorted .sortoptions {
float: left;
}
thead th.sorted .text {
padding-right: 0;
padding-left: 42px;
}
/* dashboard styles */
.dashboard .module table td a {
padding-left: .6em;
padding-right: 12px;
}
/* changelists styles */
.change-list .filtered {
background: white url(../img/changelist-bg_rtl.gif) top left repeat-y !important;
}
.change-list .filtered table {
border-left: 1px solid #ddd;
border-right: 0px none;
}
#changelist-filter {
right: auto;
left: 0;
border-left: 0px none;
border-right: 1px solid #ddd;
}
.change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull {
margin-right: 0px !important;
margin-left: 160px !important;
}
#changelist-filter li.selected {
border-left: 0px none;
padding-left: 0px;
margin-left: 0;
border-right: 5px solid #ccc;
padding-right: 5px;
margin-right: -10px;
}
.filtered .actions {
border-left:1px solid #DDDDDD;
margin-left:160px !important;
border-right: 0 none;
margin-right:0 !important;
}
#changelist table tbody td:first-child, #changelist table tbody th:first-child {
border-right: 0;
border-left: 1px solid #ddd;
}
/* FORMS */
.aligned label {
padding: 0 0 3px 1em;
float: right;
}
.submit-row {
text-align: left
}
.submit-row p.deletelink-box {
float: right;
}
.submit-row .deletelink {
background: url(../img/icon_deletelink.gif) 0 50% no-repeat;
padding-right: 14px;
}
.vDateField, .vTimeField {
margin-left: 2px;
}
form ul.inline li {
float: right;
padding-right: 0;
padding-left: 7px;
}
input[type=submit].default, .submit-row input.default {
float: left;
}
fieldset .field-box {
float: right;
margin-left: 20px;
margin-right: 0;
}
.errorlist li {
background-position: 100% .3em;
padding: 4px 25px 4px 5px;
}
.errornote {
background-position: 100% .3em;
padding: 4px 25px 4px 5px;
}
/* WIDGETS */
.calendarnav-previous {
top: 0;
left: auto;
right: 0;
}
.calendarnav-next {
top: 0;
right: auto;
left: 0;
}
.calendar caption, .calendarbox h2 {
text-align: center;
}
.selector {
float: right;
}
.selector .selector-filter {
text-align: right;
}
.inline-deletelink {
float: left;
}
/* MISC */
.inline-related h2, .inline-group h2 {
text-align: right
}
.inline-related h3 span.delete {
padding-right: 20px;
padding-left: inherit;
left: 10px;
right: inherit;
float:left;
}
.inline-related h3 span.delete label {
margin-left: inherit;
margin-right: 2px;
}
/* IE7 specific bug fixes */
div.colM {
position: relative;
}
.submit-row input {
float: left;
}

View file

@ -0,0 +1,578 @@
/* SELECTOR (FILTER INTERFACE) */
.selector {
width: 840px;
float: left;
}
.selector select {
width: 400px;
height: 17.2em;
}
.selector-available, .selector-chosen {
float: left;
width: 400px;
text-align: center;
margin-bottom: 5px;
}
.selector-chosen select {
border-top: none;
}
.selector-available h2, .selector-chosen h2 {
border: 1px solid #ccc;
}
.selector .selector-available h2 {
background: white url(../img/nav-bg.gif) bottom left repeat-x;
color: #666;
}
.selector .selector-filter {
background: white;
border: 1px solid #ccc;
border-width: 0 1px;
padding: 3px;
color: #999;
font-size: 10px;
margin: 0;
text-align: left;
}
.selector .selector-filter label,
.inline-group .aligned .selector .selector-filter label {
width: 16px;
padding: 2px;
}
.selector .selector-available input {
width: 360px;
}
.selector ul.selector-chooser {
float: left;
width: 22px;
height: 50px;
background: url(../img/chooser-bg.gif) top center no-repeat;
margin: 10em 5px 0 5px;
padding: 0;
}
.selector-chooser li {
margin: 0;
padding: 3px;
list-style-type: none;
}
.selector select {
margin-bottom: 10px;
margin-top: 0;
}
.selector-add, .selector-remove {
width: 16px;
height: 16px;
display: block;
text-indent: -3000px;
overflow: hidden;
}
.selector-add {
background: url(../img/selector-icons.gif) 0 -161px no-repeat;
cursor: default;
margin-bottom: 2px;
}
.active.selector-add {
background: url(../img/selector-icons.gif) 0 -187px no-repeat;
cursor: pointer;
}
.selector-remove {
background: url(../img/selector-icons.gif) 0 -109px no-repeat;
cursor: default;
}
.active.selector-remove {
background: url(../img/selector-icons.gif) 0 -135px no-repeat;
cursor: pointer;
}
a.selector-chooseall, a.selector-clearall {
display: inline-block;
text-align: left;
margin-left: auto;
margin-right: auto;
font-weight: bold;
color: #666;
}
a.selector-chooseall {
padding: 3px 18px 3px 0;
}
a.selector-clearall {
padding: 3px 0 3px 18px;
}
a.active.selector-chooseall:hover, a.active.selector-clearall:hover {
color: #036;
}
a.selector-chooseall {
background: url(../img/selector-icons.gif) right -263px no-repeat;
cursor: default;
}
a.active.selector-chooseall {
background: url(../img/selector-icons.gif) right -289px no-repeat;
cursor: pointer;
}
a.selector-clearall {
background: url(../img/selector-icons.gif) left -211px no-repeat;
cursor: default;
}
a.active.selector-clearall {
background: url(../img/selector-icons.gif) left -237px no-repeat;
cursor: pointer;
}
/* STACKED SELECTORS */
.stacked {
float: left;
width: 500px;
}
.stacked select {
width: 480px;
height: 10.1em;
}
.stacked .selector-available, .stacked .selector-chosen {
width: 480px;
}
.stacked .selector-available {
margin-bottom: 0;
}
.stacked .selector-available input {
width: 442px;
}
.stacked ul.selector-chooser {
height: 22px;
width: 50px;
margin: 0 0 3px 40%;
background: url(../img/chooser_stacked-bg.gif) top center no-repeat;
}
.stacked .selector-chooser li {
float: left;
padding: 3px 3px 3px 5px;
}
.stacked .selector-chooseall, .stacked .selector-clearall {
display: none;
}
.stacked .selector-add {
background: url(../img/selector-icons.gif) 0 -57px no-repeat;
cursor: default;
}
.stacked .active.selector-add {
background: url(../img/selector-icons.gif) 0 -83px no-repeat;
cursor: pointer;
}
.stacked .selector-remove {
background: url(../img/selector-icons.gif) 0 -5px no-repeat;
cursor: default;
}
.stacked .active.selector-remove {
background: url(../img/selector-icons.gif) 0 -31px no-repeat;
cursor: pointer;
}
/* DATE AND TIME */
p.datetime {
line-height: 20px;
margin: 0;
padding: 0;
color: #666;
font-size: 11px;
font-weight: bold;
}
.datetime span {
font-size: 11px;
color: #ccc;
font-weight: normal;
white-space: nowrap;
}
table p.datetime {
font-size: 10px;
margin-left: 0;
padding-left: 0;
}
/* URL */
p.url {
line-height: 20px;
margin: 0;
padding: 0;
color: #666;
font-size: 11px;
font-weight: bold;
}
.url a {
font-weight: normal;
}
/* FILE UPLOADS */
p.file-upload {
line-height: 20px;
margin: 0;
padding: 0;
color: #666;
font-size: 11px;
font-weight: bold;
}
.file-upload a {
font-weight: normal;
}
.file-upload .deletelink {
margin-left: 5px;
}
span.clearable-file-input label {
color: #333;
font-size: 11px;
display: inline;
float: none;
}
/* CALENDARS & CLOCKS */
.calendarbox, .clockbox {
margin: 5px auto;
font-size: 11px;
width: 16em;
text-align: center;
background: white;
position: relative;
}
.clockbox {
width: auto;
}
.calendar {
margin: 0;
padding: 0;
}
.calendar table {
margin: 0;
padding: 0;
border-collapse: collapse;
background: white;
width: 100%;
}
.calendar caption, .calendarbox h2 {
margin: 0;
font-size: 11px;
text-align: center;
border-top: none;
}
.calendar th {
font-size: 10px;
color: #666;
padding: 2px 3px;
text-align: center;
background: #e1e1e1 url(../img/nav-bg.gif) 0 50% repeat-x;
border-bottom: 1px solid #ddd;
}
.calendar td {
font-size: 11px;
text-align: center;
padding: 0;
border-top: 1px solid #eee;
border-bottom: none;
}
.calendar td.selected a {
background: #C9DBED;
}
.calendar td.nonday {
background: #efefef;
}
.calendar td.today a {
background: #ffc;
}
.calendar td a, .timelist a {
display: block;
font-weight: bold;
padding: 4px;
text-decoration: none;
color: #444;
}
.calendar td a:hover, .timelist a:hover {
background: #5b80b2;
color: white;
}
.calendar td a:active, .timelist a:active {
background: #036;
color: white;
}
.calendarnav {
font-size: 10px;
text-align: center;
color: #ccc;
margin: 0;
padding: 1px 3px;
}
.calendarnav a:link, #calendarnav a:visited, #calendarnav a:hover {
color: #999;
}
.calendar-shortcuts {
background: white;
font-size: 10px;
line-height: 11px;
border-top: 1px solid #eee;
padding: 3px 0 4px;
color: #ccc;
}
.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next {
display: block;
position: absolute;
font-weight: bold;
font-size: 12px;
background: #C9DBED url(../img/default-bg.gif) bottom left repeat-x;
padding: 1px 4px 2px 4px;
color: white;
}
.calendarnav-previous:hover, .calendarnav-next:hover {
background: #036;
}
.calendarnav-previous {
top: 0;
left: 0;
}
.calendarnav-next {
top: 0;
right: 0;
}
.calendar-cancel {
margin: 0 !important;
padding: 0 !important;
font-size: 10px;
background: #e1e1e1 url(../img/nav-bg.gif) 0 50% repeat-x;
border-top: 1px solid #ddd;
}
.calendar-cancel:hover {
background: #e1e1e1 url(../img/nav-bg-reverse.gif) 0 50% repeat-x;
}
.calendar-cancel a {
color: black;
display: block;
}
ul.timelist, .timelist li {
list-style-type: none;
margin: 0;
padding: 0;
}
.timelist a {
padding: 2px;
}
/* INLINE ORDERER */
ul.orderer {
position: relative;
padding: 0 !important;
margin: 0 !important;
list-style-type: none;
}
ul.orderer li {
list-style-type: none;
display: block;
padding: 0;
margin: 0;
border: 1px solid #bbb;
border-width: 0 1px 1px 0;
white-space: nowrap;
overflow: hidden;
background: #e2e2e2 url(../img/nav-bg-grabber.gif) repeat-y;
}
ul.orderer li:hover {
cursor: move;
background-color: #ddd;
}
ul.orderer li a.selector {
margin-left: 12px;
overflow: hidden;
width: 83%;
font-size: 10px !important;
padding: 0.6em 0;
}
ul.orderer li a:link, ul.orderer li a:visited {
color: #333;
}
ul.orderer li .inline-deletelink {
position: absolute;
right: 4px;
margin-top: 0.6em;
}
ul.orderer li.selected {
background-color: #f8f8f8;
border-right-color: #f8f8f8;
}
ul.orderer li.deleted {
background: #bbb url(../img/deleted-overlay.gif);
}
ul.orderer li.deleted a:link, ul.orderer li.deleted a:visited {
color: #888;
}
ul.orderer li.deleted .inline-deletelink {
background-image: url(../img/inline-restore.png);
}
ul.orderer li.deleted:hover, ul.orderer li.deleted a.selector:hover {
cursor: default;
}
/* EDIT INLINE */
.inline-deletelink {
float: right;
text-indent: -9999px;
background: transparent url(../img/inline-delete.png) no-repeat;
width: 15px;
height: 15px;
border: 0px none;
outline: 0; /* Remove dotted border around link */
}
.inline-deletelink:hover {
background-position: -15px 0;
cursor: pointer;
}
.editinline button.addlink {
border: 0px none;
color: #5b80b2;
font-size: 100%;
cursor: pointer;
}
.editinline button.addlink:hover {
color: #036;
cursor: pointer;
}
.editinline table .help {
text-align: right;
float: right;
padding-left: 2em;
}
.editinline tfoot .addlink {
white-space: nowrap;
}
.editinline table thead th:last-child {
border-left: none;
}
.editinline tr.deleted {
background: #ddd url(../img/deleted-overlay.gif);
}
.editinline tr.deleted .inline-deletelink {
background-image: url(../img/inline-restore.png);
}
.editinline tr.deleted td:hover {
cursor: default;
}
.editinline tr.deleted td:first-child {
background-image: none !important;
}
/* EDIT INLINE - STACKED */
.editinline-stacked {
min-width: 758px;
}
.editinline-stacked .inline-object {
margin-left: 210px;
background: white;
}
.editinline-stacked .inline-source {
float: left;
width: 200px;
background: #f8f8f8;
}
.editinline-stacked .inline-splitter {
float: left;
width: 9px;
background: #f8f8f8 url(../img/inline-splitter-bg.gif) 50% 50% no-repeat;
border-right: 1px solid #ccc;
}
.editinline-stacked .controls {
clear: both;
background: #e1e1e1 url(../img/nav-bg.gif) top left repeat-x;
padding: 3px 4px;
font-size: 11px;
border-top: 1px solid #ddd;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Some files were not shown because too many files have changed in this diff Show more