Commit gore : premier push vers git.eleves
3
.gitignore
vendored
|
@ -1,3 +1,6 @@
|
|||
*.pyc
|
||||
*.swp
|
||||
*.swo
|
||||
cof/settings.py
|
||||
settings.py
|
||||
*~
|
||||
|
|
12
apache/wsgi.py
Normal 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()
|
9
bda/autocomplete_light_registry.py
Normal 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...'})
|
37
bda/views.py
|
@ -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
177
bda2/admin.py
Normal 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
|
@ -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
|
9
bda2/autocomplete_light_registry.py
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
180
bda3/admin.py
Normal 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
|
@ -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
|
9
bda3/autocomplete_light_registry.py
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
86
cof/urls.py
Normal 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'),
|
||||
)
|
|
@ -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,
|
||||
]
|
||||
|
|
49
gestioncof/autocomplete.py
Normal 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)
|
6
gestioncof/autocomplete_light_registry.py
Normal 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
|
@ -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 %}
|
||||
"""
|
|
@ -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
|
@ -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)
|
|
@ -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
|
@ -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
|
@ -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;}
|
19
media/js/joequery-Stupid-Table-Plugin/LICENSE
Normal 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.
|
166
media/js/joequery-Stupid-Table-Plugin/README.md
Normal 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
|
77
media/js/joequery-Stupid-Table-Plugin/examples/basic.html
Normal 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><th></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>
|
70
media/js/joequery-Stupid-Table-Plugin/examples/colspan.html
Normal 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>
|
183
media/js/joequery-Stupid-Table-Plugin/examples/complex.html
Normal 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 ? "↑" : "↓";
|
||||
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"> </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>
|
5524
media/js/joequery-Stupid-Table-Plugin/examples/large-table.html
Normal file
158
media/js/joequery-Stupid-Table-Plugin/stupidtable.js
Normal 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);
|
3
media/js/joequery-Stupid-Table-Plugin/stupidtable.min.js
vendored
Normal 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
0
pads/__init__.py
Normal file
3
pads/models.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
16
pads/tests.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
This file demonstrates writing tests using the unittest module. These will pass
|
||||
when you run "manage.py test".
|
||||
|
||||
Replace this with more appropriate tests for your application.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
class SimpleTest(TestCase):
|
||||
def test_basic_addition(self):
|
||||
"""
|
||||
Tests that 1 + 1 always equals 2.
|
||||
"""
|
||||
self.assertEqual(1 + 1, 2)
|
1
pads/views.py
Normal file
|
@ -0,0 +1 @@
|
|||
# Create your views here.
|
0
rezo/__init__.py
Normal file
3
rezo/models.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
16
rezo/tests.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
This file demonstrates writing tests using the unittest module. These will pass
|
||||
when you run "manage.py test".
|
||||
|
||||
Replace this with more appropriate tests for your application.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
class SimpleTest(TestCase):
|
||||
def test_basic_addition(self):
|
||||
"""
|
||||
Tests that 1 + 1 always equals 2.
|
||||
"""
|
||||
self.assertEqual(1 + 1, 2)
|
1
rezo/views.py
Normal file
|
@ -0,0 +1 @@
|
|||
# Create your views here.
|
840
static/admin/css/base.css
Normal 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;
|
||||
}
|
||||
|
293
static/admin/css/changelists.css
Normal 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;
|
||||
}
|
30
static/admin/css/dashboard.css
Normal 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
|
@ -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
|
@ -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;
|
||||
}
|
60
static/admin/css/login.css
Normal 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
|
@ -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;
|
||||
}
|
578
static/admin/css/widgets.css
Normal 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;
|
||||
}
|
||||
|
BIN
static/admin/img/admin/arrow-down.gif
Normal file
After Width: | Height: | Size: 80 B |
BIN
static/admin/img/admin/arrow-up.gif
Normal file
After Width: | Height: | Size: 838 B |
BIN
static/admin/img/admin/changelist-bg.gif
Normal file
After Width: | Height: | Size: 58 B |
BIN
static/admin/img/admin/changelist-bg_rtl.gif
Normal file
After Width: | Height: | Size: 75 B |
BIN
static/admin/img/admin/chooser-bg.gif
Normal file
After Width: | Height: | Size: 199 B |
BIN
static/admin/img/admin/chooser_stacked-bg.gif
Normal file
After Width: | Height: | Size: 212 B |
BIN
static/admin/img/admin/default-bg-reverse.gif
Normal file
After Width: | Height: | Size: 843 B |
BIN
static/admin/img/admin/default-bg.gif
Normal file
After Width: | Height: | Size: 844 B |
BIN
static/admin/img/admin/deleted-overlay.gif
Normal file
After Width: | Height: | Size: 45 B |
BIN
static/admin/img/admin/icon-no.gif
Normal file
After Width: | Height: | Size: 176 B |
BIN
static/admin/img/admin/icon-unknown.gif
Normal file
After Width: | Height: | Size: 130 B |
BIN
static/admin/img/admin/icon-yes.gif
Normal file
After Width: | Height: | Size: 299 B |
BIN
static/admin/img/admin/icon_addlink.gif
Normal file
After Width: | Height: | Size: 119 B |
BIN
static/admin/img/admin/icon_alert.gif
Normal file
After Width: | Height: | Size: 145 B |
BIN
static/admin/img/admin/icon_calendar.gif
Normal file
After Width: | Height: | Size: 192 B |
BIN
static/admin/img/admin/icon_changelink.gif
Normal file
After Width: | Height: | Size: 119 B |
BIN
static/admin/img/admin/icon_clock.gif
Normal file
After Width: | Height: | Size: 390 B |
BIN
static/admin/img/admin/icon_deletelink.gif
Normal file
After Width: | Height: | Size: 181 B |
BIN
static/admin/img/admin/icon_error.gif
Normal file
After Width: | Height: | Size: 319 B |
BIN
static/admin/img/admin/icon_searchbox.png
Normal file
After Width: | Height: | Size: 368 B |
BIN
static/admin/img/admin/icon_success.gif
Normal file
After Width: | Height: | Size: 341 B |
BIN
static/admin/img/admin/inline-delete-8bit.png
Normal file
After Width: | Height: | Size: 395 B |
BIN
static/admin/img/admin/inline-delete.png
Normal file
After Width: | Height: | Size: 707 B |
BIN
static/admin/img/admin/inline-restore-8bit.png
Normal file
After Width: | Height: | Size: 363 B |
BIN
static/admin/img/admin/inline-restore.png
Normal file
After Width: | Height: | Size: 557 B |
BIN
static/admin/img/admin/inline-splitter-bg.gif
Normal file
After Width: | Height: | Size: 102 B |
BIN
static/admin/img/admin/nav-bg-grabber.gif
Normal file
After Width: | Height: | Size: 116 B |
BIN
static/admin/img/admin/nav-bg-reverse.gif
Normal file
After Width: | Height: | Size: 186 B |
BIN
static/admin/img/admin/nav-bg.gif
Normal file
After Width: | Height: | Size: 273 B |
BIN
static/admin/img/admin/selector-add.gif
Normal file
After Width: | Height: | Size: 606 B |
BIN
static/admin/img/admin/selector-addall.gif
Normal file
After Width: | Height: | Size: 358 B |
BIN
static/admin/img/admin/selector-remove.gif
Normal file
After Width: | Height: | Size: 398 B |
BIN
static/admin/img/admin/selector-removeall.gif
Normal file
After Width: | Height: | Size: 355 B |
BIN
static/admin/img/admin/selector-search.gif
Normal file
After Width: | Height: | Size: 552 B |
BIN
static/admin/img/admin/selector_stacked-add.gif
Normal file
After Width: | Height: | Size: 612 B |
BIN
static/admin/img/admin/selector_stacked-remove.gif
Normal file
After Width: | Height: | Size: 401 B |
BIN
static/admin/img/admin/tool-left.gif
Normal file
After Width: | Height: | Size: 197 B |
BIN
static/admin/img/admin/tool-left_over.gif
Normal file
After Width: | Height: | Size: 203 B |
BIN
static/admin/img/admin/tool-right.gif
Normal file
After Width: | Height: | Size: 198 B |
BIN
static/admin/img/admin/tool-right_over.gif
Normal file
After Width: | Height: | Size: 200 B |
BIN
static/admin/img/admin/tooltag-add.gif
Normal file
After Width: | Height: | Size: 932 B |
BIN
static/admin/img/admin/tooltag-add_over.gif
Normal file
After Width: | Height: | Size: 336 B |
BIN
static/admin/img/admin/tooltag-arrowright.gif
Normal file
After Width: | Height: | Size: 351 B |
BIN
static/admin/img/admin/tooltag-arrowright_over.gif
Normal file
After Width: | Height: | Size: 354 B |