diff --git a/bda/admin.py b/bda/admin.py index 975c71ab..f3a60d22 100644 --- a/bda/admin.py +++ b/bda/admin.py @@ -5,6 +5,7 @@ from django.core.mail import send_mail from django.contrib import admin from django.db.models import Sum, Count from bda.models import Spectacle, Salle, Participant, ChoixSpectacle, Attribution, Tirage +from django import forms from datetime import timedelta @@ -121,6 +122,16 @@ Le Bureau des Arts 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 AttributionAdminForm(forms.ModelForm): + def clean(self): + cleaned_data=super(AttributionAdminForm, self).clean() + participant = cleaned_data.get("participant") + spectacle = cleaned_data.get("spectacle") + if participant and spectacle: + if participant.tirage != spectacle.tirage: + raise forms.ValidationError(u"Erreur : le participant et le spectacle n'appartiennent pas au même tirage") + return cleaned_data + class AttributionAdmin(admin.ModelAdmin): def paid(self, obj): return obj.participant.paid @@ -128,18 +139,22 @@ class AttributionAdmin(admin.ModelAdmin): 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') + form = AttributionAdminForm import autocomplete_light class ChoixSpectacleAdmin(admin.ModelAdmin): form = autocomplete_light.modelform_factory(ChoixSpectacle, exclude=[]) - list_display = ("participant", "spectacle", "priority", "double_choice") - list_filter = ("double_choice",) + def tirage(self, obj): + return obj.participant.tirage + list_display = ("participant", "tirage", "spectacle", "priority", "double_choice") + list_filter = ("double_choice", "participant__tirage") 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", "listing") - list_filter = ("location",) + list_display = ("title", "date", "tirage", "location", "slots", "price", + "listing") + list_filter = ("location", "tirage",) search_fields = ("title", "location__name") class TirageAdmin(admin.ModelAdmin): diff --git a/bda/templates/bda-participants.html b/bda/templates/bda-participants.html index c21d4248..a54242d5 100644 --- a/bda/templates/bda-participants.html +++ b/bda/templates/bda-participants.html @@ -2,6 +2,7 @@ {% block realcontent %}

{{ spectacle }}

+

Ajouter une attribution

@@ -25,11 +26,16 @@ - {% endfor %}
-
- {% if participant.given %}Oui{% else %}Non{%endif%} +
+ {% if participant.given == participant.nb_places %}Oui + {% elif participant.given == 0 %}Non + {% else %}{{participant.given}}/{{participant.nb_places}} + {%endif%}
@@ -41,8 +47,7 @@
diff --git a/bda/templates/bda-unpaid.html b/bda/templates/bda-unpaid.html index 5596dd82..a424dce2 100644 --- a/bda/templates/bda-unpaid.html +++ b/bda/templates/bda-unpaid.html @@ -4,4 +4,5 @@

Impayés

+

Total : {{ unpaid|length }}

{% endblock %} diff --git a/bda/views.py b/bda/views.py index ddb1b640..bca5c2c5 100644 --- a/bda/views.py +++ b/bda/views.py @@ -5,6 +5,7 @@ from __future__ import division from django.shortcuts import render, get_object_or_404 from django.contrib.auth.decorators import login_required from django.db import models +from django.db.models import Count from django.core import serializers from django.forms.models import inlineformset_factory import hashlib @@ -302,11 +303,12 @@ def spectacle(request, tirage_id, spectacle_id): 'name': participant.user.get_full_name, 'username': participant.user.username, 'email': participant.user.email, - 'given': attrib.given, + 'given': int(attrib.given), 'paid': participant.paid, 'nb_places': 1} if participant.id in participants: - participants[participant.id]['nb_places'] += 1 + participants[participant.id]['nb_places'] += 1 + participants[participant.id]['given'] += attrib.given else: participants[participant.id] = participant_info @@ -331,7 +333,9 @@ class SpectacleListView(ListView): @buro_required def unpaid(request, tirage_id): tirage = get_object_or_404(Tirage, id=tirage_id) - unpaid = tirage.participant_set.filter(paid=False).all() + unpaid = tirage.participant_set \ + .annotate(nb_attributions=Count('attribution')) \ + .filter(paid=False, nb_attributions__gt=0).all() return render(request, "bda-unpaid.html", {"unpaid": unpaid}) @buro_required diff --git a/cof/urls.py b/cof/urls.py index ce7cd6f6..f0b3c207 100644 --- a/cof/urls.py +++ b/cof/urls.py @@ -53,6 +53,7 @@ urlpatterns = patterns('', url(r'^autocomplete/registration$', 'gestioncof.autocomplete.autocomplete'), url(r'^autocomplete/', include('autocomplete_light.urls')), # Interface admin + url(r'^admin/logout/', 'gestioncof.views.logout'), url(r'^admin/doc/', include('django.contrib.admindocs.urls')), url(r'^admin/(?P[\d\w]+)/(?P[\d\w]+)/csv/', 'gestioncof.csv_views.admin_list_export', diff --git a/gestioncof/admin.py b/gestioncof/admin.py index 03cb3b33..09ac61fd 100644 --- a/gestioncof/admin.py +++ b/gestioncof/admin.py @@ -9,7 +9,7 @@ from django.core.urlresolvers import reverse from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ -def add_link_field(target_model = '', field = '', link_text = unicode, desc_text = unicode): +def add_link_field(target_model='', field='', link_text=unicode, desc_text=unicode): def add_link(cls): reverse_name = target_model or cls.model.__name__.lower() def link(self, instance): @@ -30,7 +30,8 @@ def add_link_field(target_model = '', field = '', link_text = unicode, desc_text class SurveyQuestionAnswerInline(admin.TabularInline): model = SurveyQuestionAnswer -@add_link_field(desc_text = lambda x: "Réponses", link_text = lambda x: "Éditer les réponses") +@add_link_field(desc_text=lambda x: "Réponses", + link_text=lambda x: "Éditer les réponses") class SurveyQuestionInline(admin.TabularInline): model = SurveyQuestion @@ -47,7 +48,8 @@ class SurveyAdmin(admin.ModelAdmin): class EventOptionChoiceInline(admin.TabularInline): model = EventOptionChoice -@add_link_field(desc_text = lambda x: "Choix", link_text = lambda x: "Éditer les choix") +@add_link_field(desc_text=lambda x: "Choix", + link_text=lambda x: "Éditer les choix") class EventOptionInline(admin.TabularInline): model = EventOption @@ -67,7 +69,6 @@ class EventAdmin(admin.ModelAdmin): class CofProfileInline(admin.StackedInline): model = CofProfile - #form = BaseDynamicEntityForm inline_classes = ("collapse open",) class FkeyLookup(object): @@ -80,14 +81,15 @@ class FkeyLookup(object): def __get__(self, obj, klass): if obj is None: - return self # hack required to make Django validate (if obj is None, then we're a class, and classes are callable ) - + return self # hack required to make Django validate (if obj is + # None, then we're a class, and classes are callable + # ) item = getattr(obj, self.fk) for attr in self.fkattrs: item = getattr(item, attr) return item -def ProfileInfo(field, short_description, boolean = False): +def ProfileInfo(field, short_description, boolean=False): def getter(self): try: return getattr(self.profile, field) @@ -121,9 +123,14 @@ class UserProfileAdmin(UserAdmin): return False is_cof.short_description = 'Membre du COF' is_cof.boolean = True - 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 = ('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') + 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, @@ -140,11 +147,13 @@ class EventRegistrationAdmin(admin.ModelAdmin): form = autocomplete_light.modelform_factory(EventRegistration, exclude=[]) list_display = ('__unicode__','event','user','paid') list_filter = ('paid',) - search_fields = ('user__username', 'user__first_name', 'user__last_name', 'user__email', 'event__title') + search_fields = ('user__username', 'user__first_name', 'user__last_name', + 'user__email', 'event__title') class PetitCoursAbilityAdmin(admin.ModelAdmin): list_display = ('user','matiere','niveau','agrege') - search_fields = ('user__username', 'user__first_name', 'user__last_name', 'user__email', 'matiere__name', 'niveau') + search_fields = ('user__username', 'user__first_name', 'user__last_name', + 'user__email', 'matiere__name', 'niveau') list_filter = ('matiere','niveau','agrege') class PetitCoursAttributionAdmin(admin.ModelAdmin): @@ -153,7 +162,8 @@ class PetitCoursAttributionAdmin(admin.ModelAdmin): class PetitCoursAttributionCounterAdmin(admin.ModelAdmin): list_display = ('user','matiere','count',) list_filter = ('matiere',) - search_fields = ('user__username', 'user__first_name', 'user__last_name', 'user__email', 'matiere__name') + search_fields = ('user__username', 'user__first_name', 'user__last_name', + 'user__email', 'matiere__name') actions = ['reset',] actions_on_bottom = True @@ -161,9 +171,9 @@ class PetitCoursAttributionCounterAdmin(admin.ModelAdmin): queryset.update(count=0) reset.short_description = u"Remise à zéro du compteur" - class PetitCoursDemandeAdmin(admin.ModelAdmin): - list_display = ('name','email','agrege_requis','niveau','created','traitee','processed') + list_display = ('name','email','agrege_requis','niveau','created', + 'traitee','processed') list_filter = ('traitee','niveau') admin.site.register(Survey, SurveyAdmin) diff --git a/gestioncof/migrations/0002_enable_unprocessed_demandes.py b/gestioncof/migrations/0002_enable_unprocessed_demandes.py new file mode 100644 index 00000000..18006588 --- /dev/null +++ b/gestioncof/migrations/0002_enable_unprocessed_demandes.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gestioncof', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='petitcoursdemande', + name='processed', + field=models.DateTimeField(null=True, verbose_name='Date de traitement', blank=True), + ), + ] diff --git a/gestioncof/petits_cours_models.py b/gestioncof/petits_cours_models.py index b9e03794..47339a4e 100644 --- a/gestioncof/petits_cours_models.py +++ b/gestioncof/petits_cours_models.py @@ -17,9 +17,9 @@ LEVELS_CHOICES = ( ) class PetitCoursSubject(models.Model): - name = models.CharField(_(u"Matière"), max_length = 30) - users = models.ManyToManyField(User, related_name = "petits_cours_matieres", - through = "PetitCoursAbility") + name = models.CharField(_(u"Matière"), max_length=30) + users = models.ManyToManyField(User, related_name="petits_cours_matieres", + through="PetitCoursAbility") class Meta: verbose_name = "Matière de petits cours" @@ -30,11 +30,11 @@ class PetitCoursSubject(models.Model): class PetitCoursAbility(models.Model): user = models.ForeignKey(User) - matiere = models.ForeignKey(PetitCoursSubject, verbose_name = _(u"Matière")) + matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_(u"Matière")) niveau = models.CharField (_(u"Niveau"), - choices = LEVELS_CHOICES, - max_length = choices_length (LEVELS_CHOICES)) - agrege = models.BooleanField(_(u"Agrégé"), default = False) + choices=LEVELS_CHOICES, + max_length=choices_length (LEVELS_CHOICES)) + agrege = models.BooleanField(_(u"Agrégé"), default=False) class Meta: verbose_name = "Compétence petits cours" @@ -44,27 +44,41 @@ class PetitCoursAbility(models.Model): return u"%s - %s - %s" % (self.user.username, self.matiere, self.niveau) class PetitCoursDemande(models.Model): - name = models.CharField(_(u"Nom/prénom"), max_length = 200) - email = models.CharField(_(u"Adresse email"), max_length = 300) - phone = models.CharField(_(u"Téléphone (facultatif)"), max_length = 20, blank = True) - quand = models.CharField(_(u"Quand ?"), help_text = _(u"Indiquez ici la période désirée pour les petits cours (vacances scolaires, semaine, week-end)."), max_length = 300, blank = True) - freq = models.CharField(_(u"Fréquence"), help_text = _(u"Indiquez ici la fréquence envisagée (hebdomadaire, 2 fois par semaine, ...)"), max_length = 300, blank = True) - lieu = models.CharField(_(u"Lieu (si préférence)"), help_text = _(u"Si vous avez avez une préférence sur le lieu."), max_length = 300, blank = True) + name = models.CharField(_(u"Nom/prénom"), max_length=200) + email = models.CharField(_(u"Adresse email"), max_length=300) + phone = models.CharField(_(u"Téléphone (facultatif)"), + max_length=20, blank=True) + quand = models.CharField( + _(u"Quand ?"), + help_text=_(u"Indiquez ici la période désirée pour les petits" \ + + " cours (vacances scolaires, semaine, week-end)."), + max_length=300, blank=True) + freq = models.CharField( + _(u"Fréquence"), + help_text=_(u"Indiquez ici la fréquence envisagée " \ + + "(hebdomadaire, 2 fois par semaine, ...)"), + max_length=300, blank=True) + lieu = models.CharField( + _(u"Lieu (si préférence)"), + help_text=_(u"Si vous avez avez une préférence sur le lieu."), + max_length=300, blank=True) - matieres = models.ManyToManyField(PetitCoursSubject, verbose_name = _(u"Matières"), - related_name = "demandes") - agrege_requis = models.BooleanField(_(u"Agrégé requis"), default = False) + matieres = models.ManyToManyField( + PetitCoursSubject, verbose_name=_(u"Matières"), + related_name="demandes") + agrege_requis = models.BooleanField(_(u"Agrégé requis"), default=False) niveau = models.CharField (_(u"Niveau"), - default = "", - choices = LEVELS_CHOICES, - max_length = choices_length (LEVELS_CHOICES)) + default="", + choices=LEVELS_CHOICES, + max_length=choices_length (LEVELS_CHOICES)) - remarques = models.TextField(_(u"Remarques et précisions"), blank = True) + remarques = models.TextField(_(u"Remarques et précisions"), blank=True) - traitee = models.BooleanField(_(u"Traitée"), default = False) - traitee_par = models.ForeignKey(User, blank = True, null = True) - processed = models.DateTimeField(_(u"Date de traitement"), blank = True) - created = models.DateTimeField(_(u"Date de création"), auto_now_add = True) + traitee = models.BooleanField(_(u"Traitée"), default=False) + traitee_par = models.ForeignKey(User, blank=True, null=True) + processed = models.DateTimeField(_(u"Date de traitement"), + blank=True, null=True) + created = models.DateTimeField(_(u"Date de création"), auto_now_add=True) class Meta: verbose_name = "Demande de petits cours" @@ -75,27 +89,31 @@ class PetitCoursDemande(models.Model): class PetitCoursAttribution(models.Model): user = models.ForeignKey(User) - demande = models.ForeignKey(PetitCoursDemande, verbose_name = _("Demande")) - matiere = models.ForeignKey(PetitCoursSubject, verbose_name = _(u"Matière")) - date = models.DateTimeField(_(u"Date d'attribution"), auto_now_add = True) + demande = models.ForeignKey(PetitCoursDemande, verbose_name=_("Demande")) + matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_(u"Matière")) + date = models.DateTimeField(_(u"Date d'attribution"), auto_now_add=True) rank = models.IntegerField("Rang dans l'email") - selected = models.BooleanField(_(u"Sélectionné par le demandeur"), default = False) + selected = models.BooleanField(_(u"Sélectionné par le demandeur"), + default=False) class Meta: verbose_name = "Attribution de petits cours" verbose_name_plural = "Attributions de petits cours" def __unicode__(self): - return u"Attribution de la demande %d à %s pour %s" % (self.demande.id, self.user.username, self.matiere) + return u"Attribution de la demande %d à %s pour %s" \ + % (self.demande.id, self.user.username, self.matiere) class PetitCoursAttributionCounter(models.Model): user = models.ForeignKey(User) - matiere = models.ForeignKey(PetitCoursSubject, verbose_name = _("Matiere")) - count = models.IntegerField("Nombre d'envois", default = 0) + matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matiere")) + count = models.IntegerField("Nombre d'envois", default=0) class Meta: verbose_name = "Compteur d'attribution de petits cours" verbose_name_plural = "Compteurs d'attributions de petits cours" def __unicode__(self): - return u"%d demandes envoyées à %s pour %s" % (self.count, self.user.username, self.matiere) + return u"%d demandes envoyées à %s pour %s" \ + % (self.count, self.user.username, self.matiere) + diff --git a/gestioncof/templates/home.html b/gestioncof/templates/home.html index 52c0ebfd..ed567455 100644 --- a/gestioncof/templates/home.html +++ b/gestioncof/templates/home.html @@ -63,15 +63,21 @@
  • Sondage : {{ survey.title }}
  • {% endfor %}
    +

    Gestion tirages BDA

    + -
    -
  • Liens utiles du COF
  • -
  • Liens utiles BdA
  • - +

    Liens utiles

    + {% endif %}

    Pour tout problème : cof@ens.fr.

    diff --git a/gestioncof/templatetags/utils.py b/gestioncof/templatetags/utils.py index c8bd96ea..5847f9a6 100644 --- a/gestioncof/templatetags/utils.py +++ b/gestioncof/templatetags/utils.py @@ -35,4 +35,3 @@ def highlight_clipper(clipper, q): text = clipper.username return highlight_text(text, q) - diff --git a/gestioncof/urls.py b/gestioncof/urls.py index f8693d57..a8a38dbc 100644 --- a/gestioncof/urls.py +++ b/gestioncof/urls.py @@ -2,41 +2,41 @@ from django.conf.urls import url from gestioncof.petits_cours_views import DemandeListView export_patterns = [ - url(r'members$', 'gestioncof.views.export_members'), - url(r'mega/avecremarques$', 'gestioncof.views.export_mega_remarksonly'), - url(r'mega/participants$', 'gestioncof.views.export_mega_participants'), - url(r'mega/orgas$', 'gestioncof.views.export_mega_orgas'), - url(r'mega/(?P.+)$', 'gestioncof.views.export_mega_bytype'), - url(r'mega$', 'gestioncof.views.export_mega'), + url(r'^members$', 'gestioncof.views.export_members'), + url(r'^mega/avecremarques$', 'gestioncof.views.export_mega_remarksonly'), + url(r'^mega/participants$', 'gestioncof.views.export_mega_participants'), + url(r'^mega/orgas$', 'gestioncof.views.export_mega_orgas'), + url(r'^mega/(?P.+)$', 'gestioncof.views.export_mega_bytype'), + url(r'^mega$', 'gestioncof.views.export_mega'), ] petitcours_patterns = [ - url(r'inscription$', 'gestioncof.petits_cours_views.inscription', + url(r'^inscription$', 'gestioncof.petits_cours_views.inscription', name='petits-cours-inscription'), - url(r'demande$', 'gestioncof.petits_cours_views.demande', + url(r'^demande$', 'gestioncof.petits_cours_views.demande', name='petits-cours-demande'), - url(r'demande-raw$', 'gestioncof.petits_cours_views.demande_raw', + url(r'^demande-raw$', 'gestioncof.petits_cours_views.demande_raw', name='petits-cours-demande-raw'), - url(r'demandes$', DemandeListView.as_view(), + url(r'^demandes$', DemandeListView.as_view(), name='petits-cours-demandes-list'), - url(r'demandes/(?P\d+)$', 'gestioncof.petits_cours_views.details', + url(r'^demandes/(?P\d+)$', 'gestioncof.petits_cours_views.details', name='petits-cours-demande-details'), - url(r'demandes/(?P\d+)/traitement$', + url(r'^demandes/(?P\d+)/traitement$', 'gestioncof.petits_cours_views.traitement', name='petits-cours-demande-traitement'), - url(r'demandes/(?P\d+)/retraitement$', + url(r'^demandes/(?P\d+)/retraitement$', 'gestioncof.petits_cours_views.retraitement', name='petits-cours-demande-retraitement'), ] surveys_patterns = [ - url(r'^survey/(?P\d+)/status$', 'gestioncof.views.survey_status'), - url(r'^survey/(?P\d+)$', 'gestioncof.views.survey'), + url(r'^(?P\d+)/status$', 'gestioncof.views.survey_status'), + url(r'^(?P\d+)$', 'gestioncof.views.survey'), ] events_patterns = [ - url(r'^event/(?P\d+)$', 'gestioncof.views.event'), - url(r'^event/(?P\d+)/status$', 'gestioncof.views.event_status'), + url(r'^(?P\d+)$', 'gestioncof.views.event'), + url(r'^(?P\d+)/status$', 'gestioncof.views.event_status'), ] diff --git a/gestioncof/views.py b/gestioncof/views.py index 70084e14..a862c773 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -403,7 +403,7 @@ def export_members(request): response = HttpResponse(content_type = 'text/csv') response['Content-Disposition'] = 'attachment; filename=membres_cof.csv' - writer = unicodecsv.UnicodeWriter(response) + writer = unicodecsv.writer(response) for profile in CofProfile.objects.filter(is_cof = True).all(): user = profile.user bits = [profile.num, user.username, user.first_name, user.last_name, user.email, profile.phone, profile.occupation, profile.departement, profile.type_cotiz] @@ -415,7 +415,7 @@ def export_members(request): def csv_export_mega(filename, qs): response = HttpResponse(content_type = 'text/csv') response['Content-Disposition'] = 'attachment; filename=' + filename - writer = unicodecsv.UnicodeWriter(response) + writer = unicodecsv.writer(response) for reg in qs.all(): user = reg.user @@ -431,7 +431,7 @@ def export_mega_remarksonly(request): filename = 'remarques_mega_2015.csv' response = HttpResponse(content_type = 'text/csv') response['Content-Disposition'] = 'attachment; filename=' + filename - writer = unicodecsv.UnicodeWriter(response) + writer = unicodecsv.writer(response) event = Event.objects.get(title = "Mega 15") commentfield = event.commentfields.get(name = "Commentaires")