From c217b549bda986aa05c85767a4daba8308fc218b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 18 Mar 2017 20:48:17 +0000 Subject: [PATCH 01/18] Move the events stuff to gestion - The models are moved to the `gestion` app - A new field `associations` is added - The location and datetime fields are removed in favour of a new model `EventTimeSlot` - The old events are migrated to the new app and linked to the `cof_buro` association --- cof/migrations/0013_move_events_to_gestion.py | 111 ++++++++++++ cof/models.py | 120 ------------- gestion/migrations/0003_events.py | 159 ++++++++++++++++++ gestion/models.py | 138 +++++++++++++++ 4 files changed, 408 insertions(+), 120 deletions(-) create mode 100644 cof/migrations/0013_move_events_to_gestion.py create mode 100644 gestion/migrations/0003_events.py diff --git a/cof/migrations/0013_move_events_to_gestion.py b/cof/migrations/0013_move_events_to_gestion.py new file mode 100644 index 00000000..e5f871b1 --- /dev/null +++ b/cof/migrations/0013_move_events_to_gestion.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11b1 on 2017-03-18 20:02 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("cof", "0012_remove_club"), + ("gestion", "0003_events"), + ] + + operations = [ + migrations.RemoveField( + model_name='eventcommentfield', + name='event', + ), + migrations.RemoveField( + model_name='eventcommentvalue', + name='commentfield', + ), + migrations.RemoveField( + model_name='eventcommentvalue', + name='registration', + ), + migrations.RemoveField( + model_name='eventoption', + name='event', + ), + migrations.RemoveField( + model_name='eventoptionchoice', + name='event_option', + ), + migrations.AlterUniqueTogether( + name='eventregistration', + unique_together=set([]), + ), + migrations.RemoveField( + model_name='eventregistration', + name='event', + ), + migrations.RemoveField( + model_name='eventregistration', + name='filledcomments', + ), + migrations.RemoveField( + model_name='eventregistration', + name='options', + ), + migrations.RemoveField( + model_name='eventregistration', + name='user', + ), + migrations.AlterField( + model_name='petitcoursattribution', + name='matiere', + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to='cof.PetitCoursSubject', + verbose_name='Matière' + ), + ), + migrations.AlterField( + model_name='petitcoursattributioncounter', + name='matiere', + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to='cof.PetitCoursSubject', + verbose_name='Matiere' + ), + ), + migrations.AlterField( + model_name='petitcoursattributioncounter', + name='user', + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL + ), + ), + migrations.AlterField( + model_name='petitcoursdemande', + name='traitee_par', + field=models.ForeignKey( + blank=True, null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL + ), + ), + migrations.DeleteModel( + name='Event', + ), + migrations.DeleteModel( + name='EventCommentField', + ), + migrations.DeleteModel( + name='EventCommentValue', + ), + migrations.DeleteModel( + name='EventOption', + ), + migrations.DeleteModel( + name='EventOptionChoice', + ), + migrations.DeleteModel( + name='EventRegistration', + ), + ] diff --git a/cof/models.py b/cof/models.py index 472937fd..bdbd0e1b 100644 --- a/cof/models.py +++ b/cof/models.py @@ -17,11 +17,6 @@ TYPE_COTIZ_CHOICES = ( ('exterieur', _("Extérieur")), ) -TYPE_COMMENT_FIELD = ( - ('text', _("Texte long")), - ('char', _("Texte court")), -) - class CofProfile(models.Model): profile = models.OneToOneField(Profile, @@ -79,121 +74,6 @@ class CofProfile(models.Model): return self.profile.user.username -@python_2_unicode_compatible -class Event(models.Model): - title = models.CharField("Titre", max_length=200) - location = models.CharField("Lieu", max_length=200) - start_date = models.DateTimeField("Date de début", blank=True, null=True) - end_date = models.DateTimeField("Date de fin", blank=True, null=True) - description = models.TextField("Description", blank=True) - image = models.ImageField("Image", blank=True, null=True, - upload_to="imgs/events/") - registration_open = models.BooleanField("Inscriptions ouvertes", - default=True) - old = models.BooleanField("Archiver (événement fini)", default=False) - - class Meta: - verbose_name = "Événement" - - def __str__(self): - return six.text_type(self.title) - - -@python_2_unicode_compatible -class EventCommentField(models.Model): - event = models.ForeignKey( - Event, - on_delete=models.CASCADE, - related_name="commentfields" - ) - name = models.CharField("Champ", max_length=200) - fieldtype = models.CharField("Type", max_length=10, - choices=TYPE_COMMENT_FIELD, default="text") - default = models.TextField("Valeur par défaut", blank=True) - - class Meta: - verbose_name = "Champ" - - def __str__(self): - return six.text_type(self.name) - - -@python_2_unicode_compatible -class EventCommentValue(models.Model): - commentfield = models.ForeignKey( - EventCommentField, - on_delete=models.CASCADE, - related_name="values" - ) - registration = models.ForeignKey( - "EventRegistration", - on_delete=models.CASCADE, - related_name="comments" - ) - content = models.TextField("Contenu", blank=True, null=True) - - def __str__(self): - return "Commentaire de %s" % self.commentfield - - -@python_2_unicode_compatible -class EventOption(models.Model): - event = models.ForeignKey( - Event, - on_delete=models.CASCADE, - related_name="options" - ) - name = models.CharField("Option", max_length=200) - multi_choices = models.BooleanField("Choix multiples", default=False) - - class Meta: - verbose_name = "Option" - - def __str__(self): - return six.text_type(self.name) - - -@python_2_unicode_compatible -class EventOptionChoice(models.Model): - event_option = models.ForeignKey( - EventOption, - on_delete=models.CASCADE, - related_name="choices" - ) - value = models.CharField("Valeur", max_length=200) - - class Meta: - verbose_name = "Choix" - verbose_name_plural = "Choix" - - def __str__(self): - return six.text_type(self.value) - - -@python_2_unicode_compatible -class EventRegistration(models.Model): - user = models.ForeignKey( - User, - on_delete=models.CASCADE - ) - event = models.ForeignKey( - Event, - on_delete=models.CASCADE - ) - options = models.ManyToManyField(EventOptionChoice) - filledcomments = models.ManyToManyField(EventCommentField, - through=EventCommentValue) - paid = models.BooleanField("A payé", default=False) - - class Meta: - verbose_name = "Inscription" - unique_together = ("user", "event") - - def __str__(self): - return "Inscription de %s à %s" % (six.text_type(self.user), - six.text_type(self.event.title)) - - @python_2_unicode_compatible class Survey(models.Model): title = models.CharField("Titre", max_length=200) diff --git a/gestion/migrations/0003_events.py b/gestion/migrations/0003_events.py new file mode 100644 index 00000000..5de9f17a --- /dev/null +++ b/gestion/migrations/0003_events.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11b1 on 2017-03-18 20:02 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +def import_events(apps, schema_editor): + # Fetching the cof group + cof = apps.get_model("auth", "Group").objects.get(name="cof_buro") + + # First we deal with Events and Timeslots + OldEvent = apps.get_model("cof", "Event") + NewEvent = apps.get_model("gestion", "Event") + TimeSlot = apps.get_model("gestion", "EventTimeSlot") + # TODO: can it be done more efficiently? + for event in OldEvent.objects.all(): + new_event = NewEvent.objects.create( + title=event.title, + description=event.description, + image=event.image, + registration_open=event.registration_open, + old=event.old + ) + new_event.associations.add(cof) + TimeSlot.objects.create( + event=new_event, + location=event.location, + start_date=event.start_date, + end_date=event.end_date + ) + + # Then we migrate the other models + model_names = [ + "EventCommentField", "EventCommentValue", "EventOption", + "EventOptionChoice", "EventRegistration" + ] + models = [ + (apps.get_model("cof", name), apps.get_model("gestion", name)) + for name in model_names + ] + for OldModel, NewModel in models: + NewModel.objects.bulk_create([ + NewModel(**values) + for values in OldModel.objects.values() + ]) + + +def restore_events(apps, schema_editor): + raise NotImplementedError + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('auth', '0008_alter_user_username_max_length'), + ('gestion', '0002_club_support'), + ] + + operations = [ + migrations.CreateModel( + name='Event', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200, verbose_name='Titre')), + ('description', models.TextField(blank=True, verbose_name='Description')), + ('image', models.ImageField(blank=True, null=True, upload_to='imgs/events/', verbose_name='Image')), + ('registration_open', models.BooleanField(default=True, verbose_name='Inscriptions ouvertes')), + ('old', models.BooleanField(default=False, verbose_name='Archiver (événement fini)')), + ('associations', models.ManyToManyField(related_name='events', to='auth.Group')), + ], + options={ + 'verbose_name': 'Événement', + }, + ), + migrations.CreateModel( + name='EventTimeSlot', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('location', models.CharField(max_length=200, verbose_name='Lieu')), + ('start_date', models.DateTimeField(blank=True, null=True, verbose_name='Date de début')), + ('end_date', models.DateTimeField(blank=True, null=True, verbose_name='Date de fin')), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion.Event')), + ], + ), + migrations.CreateModel( + name='EventCommentField', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200, verbose_name='Champ')), + ('fieldtype', models.CharField(choices=[('text', 'Texte long'), ('char', 'Texte court')], default='text', max_length=10, verbose_name='Type')), + ('default', models.TextField(blank=True, verbose_name='Valeur par défaut')), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='commentfields', to='gestion.Event')), + ], + options={ + 'verbose_name': 'Champ', + }, + ), + migrations.CreateModel( + name='EventCommentValue', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content', models.TextField(blank=True, null=True, verbose_name='Contenu')), + ('commentfield', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='values', to='gestion.EventCommentField')), + ], + ), + migrations.CreateModel( + name='EventOption', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200, verbose_name='Option')), + ('multi_choices', models.BooleanField(default=False, verbose_name='Choix multiples')), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='gestion.Event')), + ], + options={ + 'verbose_name': 'Option des événements', + 'verbose_name_plural': 'Options des événements' + }, + ), + migrations.CreateModel( + name='EventOptionChoice', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('value', models.CharField(max_length=200, verbose_name='Valeur')), + ('event_option', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='gestion.EventOption')), + ], + options={ + 'verbose_name_plural': 'Choix', + 'verbose_name': 'Choix', + }, + ), + migrations.CreateModel( + name='EventRegistration', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('paid', models.BooleanField(default=False, verbose_name='A payé')), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion.Event')), + ('filledcomments', models.ManyToManyField(through='gestion.EventCommentValue', to='gestion.EventCommentField')), + ('options', models.ManyToManyField(to='gestion.EventOptionChoice')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Inscription', + }, + ), + migrations.AddField( + model_name='eventcommentvalue', + name='registration', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='gestion.EventRegistration'), + ), + migrations.AlterUniqueTogether( + name='eventregistration', + unique_together=set([('user', 'event')]), + ), + migrations.RunPython(import_events, restore_events), + ] diff --git a/gestion/models.py b/gestion/models.py index bba33dd9..272a18f5 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -6,6 +6,11 @@ from django.utils.translation import ugettext_lazy as _ from cof.petits_cours_models import choices_length + +# --- +# User management +# --- + OCCUPATION_CHOICES = ( ('exterieur', _("Extérieur")), ('1A', _("1A")), @@ -55,6 +60,10 @@ def post_delete_user(sender, instance, *args, **kwargs): instance.user.delete() +# --- +# Clubs +# --- + class Club(models.Model): ANNUAL = "ANN" SEMESTER = "SEM" @@ -103,3 +112,132 @@ class ClubUser(models.Model): club = models.ForeignKey(Club, on_delete=models.CASCADE) is_respo = models.BooleanField(_("Est responsable du club")) has_paid = models.BooleanField(_("A payé sa cotisation")) + + +# --- +# Events +# --- + +class Event(models.Model): + associations = models.ManyToManyField(Group, related_name="events") + title = models.CharField("Titre", max_length=200) + description = models.TextField("Description", blank=True) + image = models.ImageField( + "Image", blank=True, null=True, upload_to="imgs/events/" + ) + registration_open = models.BooleanField( + "Inscriptions ouvertes", default=True + ) + old = models.BooleanField("Archiver (événement fini)", default=False) + + class Meta: + verbose_name = "Événement" + + def __str__(self): + return self.title + + +class EventTimeSlot(models.Model): + event = models.ForeignKey(Event) + location = models.CharField("Lieu", max_length=200) + start_date = models.DateTimeField("Date de début", blank=True, null=True) + end_date = models.DateTimeField("Date de fin", blank=True, null=True) + + +class EventCommentField(models.Model): + TEXT = "text" + CHAR = "char" + + FIELD_TYPE = [ + (TEXT, _("Texte long")), + (CHAR, _("Texte court")), + ] + + event = models.ForeignKey( + Event, + on_delete=models.CASCADE, + related_name="commentfields" + ) + name = models.CharField("Champ", max_length=200) + fieldtype = models.CharField( + "Type", max_length=10, choices=FIELD_TYPE, default=TEXT + ) + default = models.TextField("Valeur par défaut", blank=True) + + class Meta: + verbose_name = "Champ" + + def __str__(self): + return self.name + + +class EventCommentValue(models.Model): + commentfield = models.ForeignKey( + EventCommentField, + on_delete=models.CASCADE, + related_name="values" + ) + registration = models.ForeignKey( + "EventRegistration", + on_delete=models.CASCADE, + related_name="comments" + ) + content = models.TextField("Contenu", blank=True, null=True) + + def __str__(self): + return "Commentaire de {}".format(self.commentfield) + + +class EventOption(models.Model): + event = models.ForeignKey( + Event, + on_delete=models.CASCADE, + related_name="options" + ) + name = models.CharField("Option", max_length=200) + multi_choices = models.BooleanField("Choix multiples", default=False) + + class Meta: + verbose_name = "Option des événements" + verbose_name_plural = "Options des événements" + + def __str__(self): + return self.name + + +class EventOptionChoice(models.Model): + event_option = models.ForeignKey( + EventOption, + on_delete=models.CASCADE, + related_name="choices" + ) + value = models.CharField("Valeur", max_length=200) + + class Meta: + verbose_name = "Choix" + verbose_name_plural = "Choix" + + def __str__(self): + return self.value + + +class EventRegistration(models.Model): + user = models.ForeignKey( + User, + on_delete=models.CASCADE + ) + event = models.ForeignKey( + Event, + on_delete=models.CASCADE + ) + options = models.ManyToManyField(EventOptionChoice) + filledcomments = models.ManyToManyField(EventCommentField, + through=EventCommentValue) + paid = models.BooleanField("A payé", default=False) + + class Meta: + verbose_name = "Inscription" + unique_together = ("user", "event") + + def __str__(self): + return "Inscription de {} à {}".format(self.user, self.event.title) From 745e7a1c0c2d74155917e54ab4bba565e2f91a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 18 Mar 2017 20:48:45 +0000 Subject: [PATCH 02/18] register the events stuff into the admin site A simple admin interface which may be modified later --- cof/admin.py | 44 +------------------------------------------- gestion/admin.py | 42 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 46 deletions(-) diff --git a/cof/admin.py b/cof/admin.py index fc6fe515..59018f20 100644 --- a/cof/admin.py +++ b/cof/admin.py @@ -9,9 +9,7 @@ from .petits_cours_models import PetitCoursDemande, \ PetitCoursSubject, PetitCoursAbility, PetitCoursAttribution, \ PetitCoursAttributionCounter from .models import ( - SurveyQuestionAnswer, SurveyQuestion, CofProfile, EventOption, - EventOptionChoice, Event, EventCommentField, EventRegistration, - Survey + SurveyQuestionAnswer, SurveyQuestion, CofProfile, Survey ) @@ -62,43 +60,6 @@ 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") -class EventOptionInline(admin.TabularInline): - model = EventOption - - -class EventCommentFieldInline(admin.TabularInline): - model = EventCommentField - - -class EventOptionAdmin(admin.ModelAdmin): - search_fields = ('event__title', 'name') - inlines = [ - EventOptionChoiceInline, - ] - - -class EventAdmin(admin.ModelAdmin): - search_fields = ('title', 'location', 'description') - inlines = [ - EventOptionInline, - EventCommentFieldInline, - ] - - -class EventRegistrationAdmin(admin.ModelAdmin): - list_display = ('__unicode__' if six.PY2 else '__str__', 'event', 'user', - 'paid') - list_filter = ('paid',) - 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', @@ -133,8 +94,6 @@ class PetitCoursDemandeAdmin(admin.ModelAdmin): admin.site.register(Survey, SurveyAdmin) admin.site.register(SurveyQuestion, SurveyQuestionAdmin) -admin.site.register(Event, EventAdmin) -admin.site.register(EventOption, EventOptionAdmin) admin.site.register(CofProfile) admin.site.register(PetitCoursSubject) admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin) @@ -142,4 +101,3 @@ admin.site.register(PetitCoursAttribution, PetitCoursAttributionAdmin) admin.site.register(PetitCoursAttributionCounter, PetitCoursAttributionCounterAdmin) admin.site.register(PetitCoursDemande, PetitCoursDemandeAdmin) -admin.site.register(EventRegistration, EventRegistrationAdmin) diff --git a/gestion/admin.py b/gestion/admin.py index a25c618d..82d28233 100644 --- a/gestion/admin.py +++ b/gestion/admin.py @@ -2,7 +2,10 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User -from .models import Profile, Club +from .models import ( + Profile, Club, Event, + EventOption, EventCommentField, EventOptionChoice +) # --- @@ -19,6 +22,9 @@ class UserProfileAdmin(UserAdmin): ProfileInline, ] +admin.site.unregister(User) +admin.site.register(User, UserProfileAdmin) + # --- # Clubs @@ -29,5 +35,35 @@ class ClubAdmin(admin.ModelAdmin): pass -admin.site.unregister(User) -admin.site.register(User, UserProfileAdmin) +# --- +# Events +# --- + +class EventOptionChoiceInline(admin.TabularInline): + model = EventOptionChoice + + +class EventOptionInline(admin.TabularInline): + model = EventOption + show_change_link = True + + +class EventCommentFieldInline(admin.TabularInline): + model = EventCommentField + + +@admin.register(EventOption) +class EventOptionAdmin(admin.ModelAdmin): + search_fields = ('event__title', 'name') + inlines = [ + EventOptionChoiceInline, + ] + + +@admin.register(Event) +class EventAdmin(admin.ModelAdmin): + search_fields = ['title', 'location', 'description'] + inlines = [ + EventOptionInline, + EventCommentFieldInline, + ] From ec3a9a965849184368dbaf3204de74e9680e3241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 18 Mar 2017 20:49:37 +0000 Subject: [PATCH 03/18] Remove every occurrence of Event from views, forms, etc The views have to be re-implemented later --- cof/fixtures/gestion.json | 57 -------------- cof/forms.py | 47 +----------- cof/templates/home.html | 10 --- cof/urls.py | 9 --- cof/views.py | 155 ++++++++++---------------------------- gestioCOF/urls.py | 6 +- 6 files changed, 45 insertions(+), 239 deletions(-) diff --git a/cof/fixtures/gestion.json b/cof/fixtures/gestion.json index 6d1bd76f..c8020cb6 100644 --- a/cof/fixtures/gestion.json +++ b/cof/fixtures/gestion.json @@ -67,63 +67,6 @@ "model": "cof.surveyquestionanswer", "pk": 5 }, -{ - "fields": { - "old": false, - "description": "On va casser du romain.", - "end_date": "2016-09-12T00:00:00Z", - "title": "Bataille de Gergovie", - "image": "", - "location": "Gergovie", - "registration_open": true, - "start_date": "2016-09-09T00:00:00Z" - }, - "model": "cof.event", - "pk": 1 -}, -{ - "fields": { - "default": "", - "event": 1, - "fieldtype": "text", - "name": "Commentaires" - }, - "model": "cof.eventcommentfield", - "pk": 1 -}, -{ - "fields": { - "multi_choices": true, - "event": 1, - "name": "Potion magique" - }, - "model": "cof.eventoption", - "pk": 1 -}, -{ - "fields": { - "event_option": 1, - "value": "Je suis alergique" - }, - "model": "cof.eventoptionchoice", - "pk": 1 -}, -{ - "fields": { - "event_option": 1, - "value": "J'en veux" - }, - "model": "cof.eventoptionchoice", - "pk": 2 -}, -{ - "fields": { - "event_option": 1, - "value": "Je suis tomb\u00e9 dans la marmite quand j'\u00e9tais petit" - }, - "model": "cof.eventoptionchoice", - "pk": 3 -}, { "fields": { "name": "Bagarre" diff --git a/cof/forms.py b/cof/forms.py index 7f3a0614..072d3e70 100644 --- a/cof/forms.py +++ b/cof/forms.py @@ -12,58 +12,15 @@ from django.forms.formsets import BaseFormSet, formset_factory from django.db.models import Max from django.core.validators import MinLengthValidator -from .models import CofProfile, EventCommentValue, CalendarSubscription +from .models import CofProfile, CalendarSubscription from .widgets import TriStateCheckbox -from gestion.models import Profile +from gestion.models import Profile, EventCommentValue from gestion.shared import lock_table, unlock_table from bda.models import Spectacle -class EventForm(forms.Form): - def __init__(self, *args, **kwargs): - event = kwargs.pop("event") - self.event = event - current_choices = kwargs.pop("current_choices", None) - super(EventForm, self).__init__(*args, **kwargs) - choices = {} - if current_choices: - for choice in current_choices.all(): - if choice.event_option.id not in choices: - choices[choice.event_option.id] = [choice.id] - else: - choices[choice.event_option.id].append(choice.id) - all_choices = choices - for option in event.options.all(): - choices = [(choice.id, choice.value) - for choice in option.choices.all()] - if option.multi_choices: - initial = [] if option.id not in all_choices \ - else all_choices[option.id] - field = forms.MultipleChoiceField( - label=option.name, - choices=choices, - widget=CheckboxSelectMultiple, - required=False, - initial=initial) - else: - initial = None if option.id not in all_choices \ - else all_choices[option.id][0] - field = forms.ChoiceField(label=option.name, - choices=choices, - widget=RadioSelect, - required=False, - initial=initial) - field.option_id = option.id - self.fields["option_%d" % option.id] = field - - def choices(self): - for name, value in self.cleaned_data.items(): - if name.startswith('option_'): - yield (self.fields[name].option_id, value) - - class SurveyForm(forms.Form): def __init__(self, *args, **kwargs): survey = kwargs.pop("survey") diff --git a/cof/templates/home.html b/cof/templates/home.html index 7574e06e..bbb9632b 100644 --- a/cof/templates/home.html +++ b/cof/templates/home.html @@ -8,16 +8,6 @@
- {% if open_surveys %}

Sondages en cours

diff --git a/cof/urls.py b/cof/urls.py index 91a73665..9a1dba91 100644 --- a/cof/urls.py +++ b/cof/urls.py @@ -53,15 +53,6 @@ surveys_patterns = [ name="survey"), ] -events_patterns = [ - url(r'^(?P\d+)$', - views.event, - name="event"), - url(r'^(?P\d+)/status$', - views.event_status, - name="event.status"), -] - calendar_patterns = [ url(r'^subscription$', views.calendar, diff --git a/cof/views.py b/cof/views.py index cbb09af4..9f59ece9 100644 --- a/cof/views.py +++ b/cof/views.py @@ -15,18 +15,19 @@ from django.utils import timezone from django.contrib import messages import django.utils.six as six +from gestion.models import ( + Event, EventRegistration, EventOption, EventOptionChoice, + EventCommentField, EventCommentValue +) + from .models import Survey, SurveyAnswer, SurveyQuestion, \ - SurveyQuestionAnswer -from .models import Event, EventRegistration, EventOption, \ - EventOptionChoice -from .models import EventCommentField, EventCommentValue, \ - CalendarSubscription + SurveyQuestionAnswer, CalendarSubscription from .models import CofProfile from .decorators import buro_required, cof_required from .forms import ( - EventStatusFilterForm, SurveyForm, SurveyStatusFilterForm, + SurveyForm, SurveyStatusFilterForm, RegistrationUserForm, RegistrationProfileForm, RegistrationCofProfileForm, - EventForm, CalendarForm, EventFormset, RegistrationPassUserForm + CalendarForm, EventFormset, RegistrationPassUserForm ) from bda.models import Tirage, Spectacle @@ -37,7 +38,6 @@ from gestion.models import Profile @login_required def home(request): data = {"surveys": Survey.objects.filter(old=False).all(), - "events": Event.objects.filter(old=False).all(), "open_surveys": Survey.objects.filter(survey_open=True, old=False).all(), "open_events": @@ -125,72 +125,6 @@ def survey(request, survey_id): }) -def get_event_form_choices(event, form): - all_choices = [] - for option_id, choices_ids in form.choices(): - option = get_object_or_404(EventOption, id=option_id, - event=event) - if type(choices_ids) != list: - choices_ids = [choices_ids] - if not option.multi_choices and len(choices_ids) > 1: - raise Http404 - for choice_id in choices_ids: - if not choice_id: - continue - choice_id = int(choice_id) - choice = EventOptionChoice.objects.get( - id=choice_id, - event_option=option) - all_choices.append(choice) - return all_choices - - -def update_event_form_comments(event, form, registration): - for commentfield_id, value in form.comments(): - field = get_object_or_404(EventCommentField, id=commentfield_id, - event=event) - if value == field.default: - continue - (storage, _) = EventCommentValue.objects.get_or_create( - commentfield=field, - registration=registration) - storage.content = value - storage.save() - - -@login_required -def event(request, event_id): - event = get_object_or_404(Event, id=event_id) - if (not event.registration_open) or event.old: - raise Http404 - success = False - if request.method == "POST": - form = EventForm(request.POST, event=event) - if form.is_valid(): - all_choices = get_event_form_choices(event, form) - (current_registration, _) = \ - EventRegistration.objects.get_or_create(user=request.user, - event=event) - current_registration.options = all_choices - current_registration.save() - success = True - else: - try: - current_registration = \ - EventRegistration.objects.get(user=request.user, event=event) - form = EventForm(event=event, - current_choices=current_registration.options) - except EventRegistration.DoesNotExist: - form = EventForm(event=event) - # Messages - if success: - messages.success(request, "Votre inscription a bien été enregistrée ! " - "Vous pouvez cependant la modifier jusqu'à " - "la fin des inscriptions.") - return render(request, "cof/event.html", - {"event": event, "form": form}) - - def clean_post_for_status(initial): d = initial.copy() for k, v in d.items(): @@ -200,46 +134,6 @@ def clean_post_for_status(initial): return d -@buro_required -def event_status(request, event_id): - event = get_object_or_404(Event, id=event_id) - registrations_query = EventRegistration.objects.filter(event=event) - post_data = clean_post_for_status(request.POST) - form = EventStatusFilterForm(post_data or None, event=event) - if form.is_valid(): - for option_id, choice_id, value in form.filters(): - if option_id == "has_paid": - if value == "yes": - registrations_query = registrations_query.filter(paid=True) - elif value == "no": - registrations_query = registrations_query.filter( - paid=False) - continue - choice = get_object_or_404(EventOptionChoice, id=choice_id, - event_option__id=option_id) - if value == "none": - continue - if value == "yes": - registrations_query = registrations_query.filter( - options__id__exact=choice.id) - elif value == "no": - registrations_query = registrations_query.exclude( - options__id__exact=choice.id) - user_choices = registrations_query.prefetch_related("user").all() - options = EventOption.objects.filter(event=event).all() - choices_count = {} - for option in options: - for choice in option.choices.all(): - choices_count[choice.id] = 0 - for user_choice in user_choices: - for choice in user_choice.options.all(): - choices_count[choice.id] += 1 - return render(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): survey = get_object_or_404(Survey, id=survey_id) @@ -342,6 +236,39 @@ def registration_form2(request, login_clipper=None, username=None, "event_formset": event_formset}) +def get_event_form_choices(event, form): + all_choices = [] + for option_id, choices_ids in form.choices(): + option = get_object_or_404(EventOption, id=option_id, + event=event) + if type(choices_ids) != list: + choices_ids = [choices_ids] + if not option.multi_choices and len(choices_ids) > 1: + raise Http404 + for choice_id in choices_ids: + if not choice_id: + continue + choice_id = int(choice_id) + choice = EventOptionChoice.objects.get( + id=choice_id, + event_option=option) + all_choices.append(choice) + return all_choices + + +def update_event_form_comments(event, form, registration): + for commentfield_id, value in form.comments(): + field = get_object_or_404(EventCommentField, id=commentfield_id, + event=event) + if value == field.default: + continue + (storage, _) = EventCommentValue.objects.get_or_create( + commentfield=field, + registration=registration) + storage.content = value + storage.save() + + @buro_required def registration(request): if request.POST: diff --git a/gestioCOF/urls.py b/gestioCOF/urls.py index e3e6fe04..4918c1ea 100644 --- a/gestioCOF/urls.py +++ b/gestioCOF/urls.py @@ -16,7 +16,7 @@ from django.contrib.auth import views as django_views from cof import views as cof_views from cof.urls import export_patterns, petitcours_patterns, \ - surveys_patterns, events_patterns, calendar_patterns + surveys_patterns, calendar_patterns from cof.autocomplete import autocomplete from gestion import views as gestion_views @@ -40,8 +40,6 @@ urlpatterns = [ url(r'^petitcours/', include(petitcours_patterns)), # Les sondages url(r'^survey/', include(surveys_patterns)), - # Evenements - url(r'^event/', include(events_patterns)), # Calendrier url(r'^calendar/', include(calendar_patterns)), # Infos persos @@ -88,7 +86,7 @@ urlpatterns = [ if 'debug_toolbar' in settings.INSTALLED_APPS: import debug_toolbar urlpatterns += [ - url(r'^__debug__/', debug_toolbar.urls), + url(r'^__debug__/', include(debug_toolbar.urls)), ] # Si on est en production, MEDIA_ROOT est servi par Apache. From 18ee33e1e0daed182439584e3bdc20fee2e1064b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Wed, 5 Apr 2017 00:00:41 +0100 Subject: [PATCH 04/18] Remove the EventTimeSlot model It was pointless and is replaced by 3 additionnal fields in the `Event` model: `location`, `start_date` and `end_date`. --- gestion/migrations/0003_events.py | 49 ++++++++----------------------- gestion/models.py | 10 ++----- 2 files changed, 16 insertions(+), 43 deletions(-) diff --git a/gestion/migrations/0003_events.py b/gestion/migrations/0003_events.py index 5de9f17a..96cb63dd 100644 --- a/gestion/migrations/0003_events.py +++ b/gestion/migrations/0003_events.py @@ -8,45 +8,29 @@ import django.db.models.deletion def import_events(apps, schema_editor): - # Fetching the cof group - cof = apps.get_model("auth", "Group").objects.get(name="cof_buro") - - # First we deal with Events and Timeslots - OldEvent = apps.get_model("cof", "Event") - NewEvent = apps.get_model("gestion", "Event") - TimeSlot = apps.get_model("gestion", "EventTimeSlot") - # TODO: can it be done more efficiently? - for event in OldEvent.objects.all(): - new_event = NewEvent.objects.create( - title=event.title, - description=event.description, - image=event.image, - registration_open=event.registration_open, - old=event.old - ) - new_event.associations.add(cof) - TimeSlot.objects.create( - event=new_event, - location=event.location, - start_date=event.start_date, - end_date=event.end_date - ) - - # Then we migrate the other models + # Fetching the models that have be moved from cof to gestion model_names = [ - "EventCommentField", "EventCommentValue", "EventOption", + "Event", "EventCommentField", "EventCommentValue", "EventOption", "EventOptionChoice", "EventRegistration" ] models = [ (apps.get_model("cof", name), apps.get_model("gestion", name)) for name in model_names ] + + # Moving the data into the new table for OldModel, NewModel in models: NewModel.objects.bulk_create([ NewModel(**values) for values in OldModel.objects.values() ]) + # Linking all the existing event to the COF group + cof = apps.get_model("auth", "Group").objects.get(name="cof_buro") + _, NewEvent = models[0] + for event in NewEvent.objects.all(): + event.associations.add(cof) + def restore_events(apps, schema_editor): raise NotImplementedError @@ -66,6 +50,9 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(max_length=200, verbose_name='Titre')), + ('location', models.CharField(max_length=200, verbose_name='Lieu')), + ('start_date', models.DateTimeField(blank=True, null=True, verbose_name='Date de début')), + ('end_date', models.DateTimeField(blank=True, null=True, verbose_name='Date de fin')), ('description', models.TextField(blank=True, verbose_name='Description')), ('image', models.ImageField(blank=True, null=True, upload_to='imgs/events/', verbose_name='Image')), ('registration_open', models.BooleanField(default=True, verbose_name='Inscriptions ouvertes')), @@ -76,16 +63,6 @@ class Migration(migrations.Migration): 'verbose_name': 'Événement', }, ), - migrations.CreateModel( - name='EventTimeSlot', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('location', models.CharField(max_length=200, verbose_name='Lieu')), - ('start_date', models.DateTimeField(blank=True, null=True, verbose_name='Date de début')), - ('end_date', models.DateTimeField(blank=True, null=True, verbose_name='Date de fin')), - ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion.Event')), - ], - ), migrations.CreateModel( name='EventCommentField', fields=[ diff --git a/gestion/models.py b/gestion/models.py index 272a18f5..c8983c50 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -121,6 +121,9 @@ class ClubUser(models.Model): class Event(models.Model): associations = models.ManyToManyField(Group, related_name="events") title = models.CharField("Titre", max_length=200) + location = models.CharField("Lieu", max_length=200) + start_date = models.DateTimeField("Date de début", blank=True, null=True) + end_date = models.DateTimeField("Date de fin", blank=True, null=True) description = models.TextField("Description", blank=True) image = models.ImageField( "Image", blank=True, null=True, upload_to="imgs/events/" @@ -137,13 +140,6 @@ class Event(models.Model): return self.title -class EventTimeSlot(models.Model): - event = models.ForeignKey(Event) - location = models.CharField("Lieu", max_length=200) - start_date = models.DateTimeField("Date de début", blank=True, null=True) - end_date = models.DateTimeField("Date de fin", blank=True, null=True) - - class EventCommentField(models.Model): TEXT = "text" CHAR = "char" From 8a751e5c85ce611439e89ecc42816c4f089e82b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Tue, 11 Apr 2017 02:48:39 +0100 Subject: [PATCH 05/18] Use a separate models for events' locations --- gestion/migrations/0003_events.py | 64 ++++++++++++++++++++++++++----- gestion/models.py | 10 ++++- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/gestion/migrations/0003_events.py b/gestion/migrations/0003_events.py index 96cb63dd..13d37097 100644 --- a/gestion/migrations/0003_events.py +++ b/gestion/migrations/0003_events.py @@ -18,21 +18,42 @@ def import_events(apps, schema_editor): for name in model_names ] - # Moving the data into the new table - for OldModel, NewModel in models: + # The old Event.location field becomes a table: we need to create an entry + # in this table for each value of the old `location` field. + OldEvent, NewEvent = models[0] + Location = apps.get_model("gestion", "Location") + locations = [] + events = [] + for event in OldEvent.objects.values(): + locations.append(Location(name=event["location"])) + events.append(event) + Location.objects.bulk_create(locations) + map_loc = { + loc.name: loc + for loc in Location.objects.all() + } + for event in events: + event["location"] = map_loc[event["location"]] + NewEvent.objects.bulk_create([ + NewEvent(**event) + for event in events + ]) + + # Do not forget to link all the existing event to the COF group + cof = apps.get_model("auth", "Group").objects.get(name="cof_buro") + for event in NewEvent.objects.all(): + event.associations.add(cof) + + # Migrating the other models is straigtforward + for OldModel, NewModel in models[1:]: NewModel.objects.bulk_create([ NewModel(**values) for values in OldModel.objects.values() ]) - # Linking all the existing event to the COF group - cof = apps.get_model("auth", "Group").objects.get(name="cof_buro") - _, NewEvent = models[0] - for event in NewEvent.objects.all(): - event.associations.add(cof) - def restore_events(apps, schema_editor): + # TODO? raise NotImplementedError @@ -45,12 +66,37 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='Location', + fields=[ + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID' + ) + ), + ( + 'name', + models.CharField(max_length=200, verbose_name='Lieu') + ), + ], + options={ + 'verbose_name': 'lieu', + 'verbose_name_plural': 'lieux', + }, + ), migrations.CreateModel( name='Event', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(max_length=200, verbose_name='Titre')), - ('location', models.CharField(max_length=200, verbose_name='Lieu')), + ('location', models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to='gestion.Location' + )), ('start_date', models.DateTimeField(blank=True, null=True, verbose_name='Date de début')), ('end_date', models.DateTimeField(blank=True, null=True, verbose_name='Date de fin')), ('description', models.TextField(blank=True, verbose_name='Description')), diff --git a/gestion/models.py b/gestion/models.py index c8983c50..8426a89d 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -118,10 +118,18 @@ class ClubUser(models.Model): # Events # --- +class Location(models.Model): + name = models.CharField("Lieu", max_length=200) + + class Meta: + verbose_name = "lieu" + verbose_name_plural = "lieux" + + class Event(models.Model): associations = models.ManyToManyField(Group, related_name="events") title = models.CharField("Titre", max_length=200) - location = models.CharField("Lieu", max_length=200) + location = models.ForeignKey(Location, on_delete=models.PROTECT) start_date = models.DateTimeField("Date de début", blank=True, null=True) end_date = models.DateTimeField("Date de fin", blank=True, null=True) description = models.TextField("Description", blank=True) From cacdde3f872ed008e5fd4eaeb02997fbf7f0e01a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sun, 14 May 2017 15:47:07 +0100 Subject: [PATCH 06/18] Remove duplicate locations --- gestion/migrations/0003_events.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gestion/migrations/0003_events.py b/gestion/migrations/0003_events.py index 13d37097..40faca91 100644 --- a/gestion/migrations/0003_events.py +++ b/gestion/migrations/0003_events.py @@ -22,12 +22,12 @@ def import_events(apps, schema_editor): # in this table for each value of the old `location` field. OldEvent, NewEvent = models[0] Location = apps.get_model("gestion", "Location") - locations = [] + locations = set() # A set to prevent duplicate entries events = [] for event in OldEvent.objects.values(): - locations.append(Location(name=event["location"])) + locations.add(event["location"]) events.append(event) - Location.objects.bulk_create(locations) + Location.objects.bulk_create([Location(name=name) for name in locations]) map_loc = { loc.name: loc for loc in Location.objects.all() From a1ffb630c05d9c0ca6343af61dc47773ad801a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 24 Jun 2017 13:42:33 +0100 Subject: [PATCH 07/18] Setup events + add verbosity in gestion.models - Mark more strings for further translations - in verbose names - in the __str__ method - Turn all verbose names into lowercase - Add more verbose names - Set an ordering on gestion.EventCommentField - More database constraints on EventCommentValue - `TextField` is not nullable (https://docs.djangoproject.com/en/1.11/ref/models/fields/#null) - `unique_together` constraint on the two fk --- gestion/migrations/0004_verbose_names.py | 236 +++++++++++++++++++++++ gestion/models.py | 168 ++++++++++------ 2 files changed, 350 insertions(+), 54 deletions(-) create mode 100644 gestion/migrations/0004_verbose_names.py diff --git a/gestion/migrations/0004_verbose_names.py b/gestion/migrations/0004_verbose_names.py new file mode 100644 index 00000000..a0761c86 --- /dev/null +++ b/gestion/migrations/0004_verbose_names.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11b1 on 2017-06-24 20:08 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('gestion', '0003_events'), + ] + + operations = [ + migrations.AlterModelOptions( + name='event', + options={'verbose_name': 'événement', 'verbose_name_plural': 'événements'}, + ), + migrations.AlterModelOptions( + name='eventcommentfield', + options={'verbose_name': 'champ', 'verbose_name_plural': 'champs'}, + ), + migrations.AlterModelOptions( + name='eventoption', + options={'verbose_name': 'option des événements', 'verbose_name_plural': 'options des événements'}, + ), + migrations.AlterModelOptions( + name='eventoptionchoice', + options={'verbose_name': 'choix', 'verbose_name_plural': 'choix'}, + ), + migrations.AlterModelOptions( + name='eventregistration', + options={'verbose_name': 'inscription', 'verbose_name_plural': 'inscriptions'}, + ), + migrations.AlterModelOptions( + name='profile', + options={'verbose_name': 'profil', 'verbose_name_plural': 'profils'}, + ), + migrations.AddField( + model_name='eventcommentfield', + name='ordering', + field=models.IntegerField(default=0, help_text='plus petit en premier, ordre alphabétique sur le nom si ambiguïté', verbose_name='ordre des champs'), + preserve_default=False, + ), + migrations.AlterField( + model_name='club', + name='cotisation_frequency', + field=models.CharField(blank=True, choices=[('ANN', 'Annuel'), ('SEM', 'Semestriel'), ('COU', 'Au cours')], default='ANN', max_length=3, verbose_name='fréquence de la cotisation'), + ), + migrations.AlterField( + model_name='club', + name='description', + field=models.TextField(blank=True, verbose_name='description'), + ), + migrations.AlterField( + model_name='club', + name='members', + field=models.ManyToManyField(blank=True, related_name='in_clubs', through='gestion.ClubUser', to=settings.AUTH_USER_MODEL, verbose_name='membres du club'), + ), + migrations.AlterField( + model_name='club', + name='name', + field=models.CharField(max_length=200, unique=True, verbose_name='nom'), + ), + migrations.AlterField( + model_name='club', + name='price', + field=models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=5, verbose_name='cotisation (€)'), + ), + migrations.AlterField( + model_name='clubuser', + name='has_paid', + field=models.BooleanField(verbose_name='a payé sa cotisation'), + ), + migrations.AlterField( + model_name='clubuser', + name='is_respo', + field=models.BooleanField(verbose_name='est responsable du club'), + ), + migrations.AlterField( + model_name='event', + name='associations', + field=models.ManyToManyField(related_name='events', to='auth.Group', verbose_name='associations'), + ), + migrations.AlterField( + model_name='event', + name='description', + field=models.TextField(blank=True, verbose_name='description'), + ), + migrations.AlterField( + model_name='event', + name='end_date', + field=models.DateTimeField(blank=True, null=True, verbose_name="fin de l'événement"), + ), + migrations.AlterField( + model_name='event', + name='image', + field=models.ImageField(blank=True, null=True, upload_to='public/imgs/events/', verbose_name='image'), + ), + migrations.AlterField( + model_name='event', + name='location', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='gestion.Location', verbose_name='lieux'), + ), + migrations.AlterField( + model_name='event', + name='old', + field=models.BooleanField(default=False, verbose_name='archiver (événement fini)'), + ), + migrations.AlterField( + model_name='event', + name='registration_open', + field=models.BooleanField(default=True, verbose_name='les inscriptions sont ouvertes'), + ), + migrations.AlterField( + model_name='event', + name='start_date', + field=models.DateTimeField(blank=True, null=True, verbose_name="début de l'événement"), + ), + migrations.AlterField( + model_name='event', + name='title', + field=models.CharField(max_length=200, verbose_name='titre'), + ), + migrations.AlterField( + model_name='eventcommentfield', + name='default', + field=models.TextField(blank=True, verbose_name='valeur par défaut'), + ), + migrations.AlterField( + model_name='eventcommentfield', + name='event', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='commentfields', to='gestion.Event', verbose_name='événement'), + ), + migrations.AlterField( + model_name='eventcommentfield', + name='fieldtype', + field=models.CharField(choices=[('text', 'Texte long'), ('char', 'Texte court')], default='text', max_length=10, verbose_name='type de champ'), + ), + migrations.AlterField( + model_name='eventcommentfield', + name='name', + field=models.CharField(max_length=200, verbose_name='nom du champ'), + ), + migrations.AlterField( + model_name='eventcommentvalue', + name='content', + field=models.TextField(blank=True, verbose_name='contenu'), + ), + migrations.AlterUniqueTogether( + name='eventcommentvalue', + unique_together=set([('commentfield', 'registration')]), + ), + migrations.AlterField( + model_name='eventoption', + name='event', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='gestion.Event', verbose_name='événement'), + ), + migrations.AlterField( + model_name='eventoption', + name='multi_choices', + field=models.BooleanField(default=False, verbose_name='choix multiples'), + ), + migrations.AlterField( + model_name='eventoption', + name='name', + field=models.CharField(max_length=200, verbose_name='option'), + ), + migrations.AlterField( + model_name='eventoptionchoice', + name='event_option', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='gestion.EventOption', verbose_name='événement'), + ), + migrations.AlterField( + model_name='eventregistration', + name='event', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion.Event', verbose_name='événement'), + ), + migrations.AlterField( + model_name='eventregistration', + name='filledcomments', + field=models.ManyToManyField(through='gestion.EventCommentValue', to='gestion.EventCommentField', verbose_name='commentaires'), + ), + migrations.AlterField( + model_name='eventregistration', + name='options', + field=models.ManyToManyField(to='gestion.EventOptionChoice', verbose_name='choix'), + ), + migrations.AlterField( + model_name='eventregistration', + name='paid', + field=models.BooleanField(default=False, verbose_name='a payé'), + ), + migrations.AlterField( + model_name='eventregistration', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='utilisateur'), + ), + migrations.AlterField( + model_name='location', + name='name', + field=models.CharField(max_length=200, verbose_name='nom du lieux'), + ), + migrations.AlterField( + model_name='profile', + name='comments', + field=models.TextField(blank=True, verbose_name="commentaires visibles par l'utilisateur"), + ), + migrations.AlterField( + model_name='profile', + name='departement', + field=models.CharField(blank=True, max_length=50, verbose_name='département'), + ), + migrations.AlterField( + model_name='profile', + name='login_clipper', + field=models.CharField(blank=True, max_length=8, verbose_name='login clipper'), + ), + migrations.AlterField( + model_name='profile', + name='occupation', + field=models.CharField(choices=[('exterieur', 'Extérieur'), ('1A', '1A'), ('2A', '2A'), ('3A', '3A'), ('4A', '4A'), ('archicube', 'Archicube'), ('doctorant', 'Doctorant'), ('CST', 'CST')], default='1A', max_length=9, verbose_name='occupation'), + ), + migrations.AlterField( + model_name='profile', + name='phone', + field=models.CharField(blank=True, max_length=20, verbose_name='téléphone'), + ), + migrations.AlterField( + model_name='profile', + name='user', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL, verbose_name='utilisateur'), + ), + ] diff --git a/gestion/models.py b/gestion/models.py index 8426a89d..8d026006 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -27,23 +27,29 @@ class Profile(models.Model): user = models.OneToOneField( User, on_delete=models.CASCADE, - related_name="profile" + related_name="profile", + verbose_name=_("utilisateur"), ) - login_clipper = models.CharField("Login clipper", max_length=8, blank=True) - phone = models.CharField("Téléphone", max_length=20, blank=True) - occupation = models.CharField(_("Occupation"), + login_clipper = models.CharField( + _("login clipper"), + max_length=8, + blank=True, + ) + phone = models.CharField(_("téléphone"), max_length=20, blank=True) + occupation = models.CharField(_("occupation"), default="1A", choices=OCCUPATION_CHOICES, max_length=choices_length( OCCUPATION_CHOICES)) - departement = models.CharField(_("Département"), max_length=50, + departement = models.CharField(_("département"), max_length=50, blank=True) comments = models.TextField( - "Commentaires visibles par l'utilisateur", blank=True) + _("commentaires visibles par l'utilisateur"), blank=True + ) class Meta: - verbose_name = "Profil" - verbose_name_plural = "Profils" + verbose_name = _("profil") + verbose_name_plural = _("profils") def __str__(self): return self.user.username @@ -76,23 +82,24 @@ class Club(models.Model): ] associations = models.ManyToManyField(Group, related_name="clubs") - name = models.CharField(_("Nom"), max_length=200, unique=True) - description = models.TextField("Description", blank=True) + name = models.CharField(_("nom"), max_length=200, unique=True) + description = models.TextField("description", blank=True) members = models.ManyToManyField( User, through="ClubUser", related_name="in_clubs", - blank=True + blank=True, + verbose_name=_("membres du club"), ) price = models.DecimalField( - _("Cotisation (€)"), + _("cotisation (€)"), decimal_places=2, max_digits=5, blank=True, default=0 ) cotisation_frequency = models.CharField( - _("Fréquence de la cotisation"), + _("fréquence de la cotisation"), default=ANNUAL, choices=COTISATION_FREQUENCY_CHOICES, max_length=3, @@ -101,17 +108,18 @@ class Club(models.Model): def __str__(self): template = ( - self.price and "{name} ({price}€ / {cotisation_frequency})" - or "{name}" + self.price + and _("%(name)s (%(price).2f€ / %(cotisation_frequency)s)") + or _("%(name)s") ) - return template.format(**vars(self)) + return template % vars(self) class ClubUser(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) club = models.ForeignKey(Club, on_delete=models.CASCADE) - is_respo = models.BooleanField(_("Est responsable du club")) - has_paid = models.BooleanField(_("A payé sa cotisation")) + is_respo = models.BooleanField(_("est responsable du club")) + has_paid = models.BooleanField(_("a payé sa cotisation")) # --- @@ -119,30 +127,51 @@ class ClubUser(models.Model): # --- class Location(models.Model): - name = models.CharField("Lieu", max_length=200) + name = models.CharField(_("nom du lieux"), max_length=200) class Meta: - verbose_name = "lieu" - verbose_name_plural = "lieux" + verbose_name = _("lieu") + verbose_name_plural = _("lieux") + + def __str__(self): + return self.name class Event(models.Model): - associations = models.ManyToManyField(Group, related_name="events") - title = models.CharField("Titre", max_length=200) - location = models.ForeignKey(Location, on_delete=models.PROTECT) - start_date = models.DateTimeField("Date de début", blank=True, null=True) - end_date = models.DateTimeField("Date de fin", blank=True, null=True) - description = models.TextField("Description", blank=True) + associations = models.ManyToManyField( + Group, + related_name="events", + verbose_name=_("associations"), + ) + title = models.CharField(_("titre"), max_length=200) + location = models.ForeignKey( + Location, + on_delete=models.PROTECT, + verbose_name=_("lieux"), + ) + start_date = models.DateTimeField( + _("début de l'événement"), + blank=True, null=True, + ) + end_date = models.DateTimeField( + _("fin de l'événement"), + blank=True, null=True, + ) + description = models.TextField(_("description"), blank=True) image = models.ImageField( - "Image", blank=True, null=True, upload_to="imgs/events/" + _("image"), + blank=True, null=True, + upload_to="public/imgs/events/", ) registration_open = models.BooleanField( - "Inscriptions ouvertes", default=True + _("les inscriptions sont ouvertes"), + default=True ) - old = models.BooleanField("Archiver (événement fini)", default=False) + old = models.BooleanField(_("archiver (événement fini)"), default=False) class Meta: - verbose_name = "Événement" + verbose_name = _("événement") + verbose_name_plural = _("événements") def __str__(self): return self.title @@ -160,16 +189,27 @@ class EventCommentField(models.Model): event = models.ForeignKey( Event, on_delete=models.CASCADE, - related_name="commentfields" + related_name="commentfields", + verbose_name=_("événement"), ) - name = models.CharField("Champ", max_length=200) + name = models.CharField(_("nom du champ"), max_length=200) fieldtype = models.CharField( - "Type", max_length=10, choices=FIELD_TYPE, default=TEXT + _("type de champ"), + max_length=10, + choices=FIELD_TYPE, default=TEXT, + ) + default = models.TextField(_("valeur par défaut"), blank=True) + ordering = models.IntegerField( + _("ordre des champs"), + help_text=_( + "plus petit en premier, ordre alphabétique sur le nom si " + "ambiguïté" + ), ) - default = models.TextField("Valeur par défaut", blank=True) class Meta: - verbose_name = "Champ" + verbose_name = _("champ") + verbose_name_plural = _("champs") def __str__(self): return self.name @@ -186,24 +226,31 @@ class EventCommentValue(models.Model): on_delete=models.CASCADE, related_name="comments" ) - content = models.TextField("Contenu", blank=True, null=True) + content = models.TextField(_("contenu"), blank=True) + + class Meta: + unique_together = ("commentfield", "registration") def __str__(self): - return "Commentaire de {}".format(self.commentfield) + return ( + _("Commentaire de %(field_name)s") + % {"field_name": self.commentfield} + ) class EventOption(models.Model): event = models.ForeignKey( Event, on_delete=models.CASCADE, - related_name="options" + related_name="options", + verbose_name=_("événement"), ) - name = models.CharField("Option", max_length=200) - multi_choices = models.BooleanField("Choix multiples", default=False) + name = models.CharField(_("option"), max_length=200) + multi_choices = models.BooleanField(_("choix multiples"), default=False) class Meta: - verbose_name = "Option des événements" - verbose_name_plural = "Options des événements" + verbose_name = _("option des événements") + verbose_name_plural = _("options des événements") def __str__(self): return self.name @@ -213,13 +260,14 @@ class EventOptionChoice(models.Model): event_option = models.ForeignKey( EventOption, on_delete=models.CASCADE, - related_name="choices" + related_name="choices", + verbose_name=_("événement"), ) value = models.CharField("Valeur", max_length=200) class Meta: - verbose_name = "Choix" - verbose_name_plural = "Choix" + verbose_name = _("choix") + verbose_name_plural = _("choix") def __str__(self): return self.value @@ -228,20 +276,32 @@ class EventOptionChoice(models.Model): class EventRegistration(models.Model): user = models.ForeignKey( User, - on_delete=models.CASCADE + on_delete=models.CASCADE, + verbose_name=_("utilisateur"), ) event = models.ForeignKey( Event, - on_delete=models.CASCADE + on_delete=models.CASCADE, + verbose_name=_("événement"), ) - options = models.ManyToManyField(EventOptionChoice) - filledcomments = models.ManyToManyField(EventCommentField, - through=EventCommentValue) - paid = models.BooleanField("A payé", default=False) + options = models.ManyToManyField( + EventOptionChoice, + verbose_name=_("choix"), + ) + filledcomments = models.ManyToManyField( + EventCommentField, + through=EventCommentValue, + verbose_name=_("commentaires"), + ) + paid = models.BooleanField(_("a payé"), default=False) class Meta: - verbose_name = "Inscription" + verbose_name = _("inscription") + verbose_name_plural = _("inscriptions") unique_together = ("user", "event") def __str__(self): - return "Inscription de {} à {}".format(self.user, self.event.title) + return ( + _("Inscription de %(user)s à %(event)s") + % {"user": self.user, "event": self.event} + ) From b68590ffd7e329f7be5caea8723e27233e173be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 24 Jun 2017 20:57:27 +0100 Subject: [PATCH 08/18] Set the permissions for buro and members BDS + COF The permissions assignation is triggered by the `post_migrate` signal since the permission table will only be populated after the migrations have been applied. --- bds/apps.py | 30 +++++++++++++++++++ bds/migrations/0002_add_BDS_groups.py | 10 ------- cof/apps.py | 30 +++++++++++++++++++ cof/migrations/0009_generic_profiles.py | 19 +++--------- cof/migrations/0010_create_cof_group.py | 26 ---------------- .../0011_delete_clipper_and_custommail.py | 2 +- 6 files changed, 65 insertions(+), 52 deletions(-) delete mode 100644 cof/migrations/0010_create_cof_group.py diff --git a/bds/apps.py b/bds/apps.py index 7c08d34e..525776ec 100644 --- a/bds/apps.py +++ b/bds/apps.py @@ -1,6 +1,36 @@ from django.apps import AppConfig +from django.db.models.signals import post_migrate + + +def setup_groups(sender, apps, **kwargs): + """ + Add the appropriate permissions to the "member" and "buro" groups after the + `post_migrate` signal since the permissions will only be inserted in the + database at the very end of the migrations. + """ + Group = apps.get_model("auth", "Group") + Permission = apps.get_model("auth", "Permission") + + # Buro members have perms bds.* and gestion.* + buro, _ = Group.objects.get_or_create(name="bds_buro") + app_perms = Permission.objects.filter( + content_type__app_label__in=["cof", "gestion"] + ) + buro.permissions.add(*app_perms) + + # Members have perm bds.member + members, _ = Group.objects.get_or_create(name="bds_members") + perm = Permission.objects.get( + codename="member", + content_type__app_label="bds" + ) + members.permissions.add(perm) class BDSConfig(AppConfig): name = "bds" verbose_name = "Application de gestion du BDS" + + def ready(self): + # https://docs.djangoproject.com/en/1.11/ref/signals/#post-migrate + post_migrate.connect(setup_groups, sender=self) diff --git a/bds/migrations/0002_add_BDS_groups.py b/bds/migrations/0002_add_BDS_groups.py index 45510e59..33cecf41 100644 --- a/bds/migrations/0002_add_BDS_groups.py +++ b/bds/migrations/0002_add_BDS_groups.py @@ -4,15 +4,6 @@ from __future__ import unicode_literals from django.db import migrations -def create_groups(apps, schema_editor): - """ - Creates the groups for BDS members and staff - """ - Group = apps.get_model("auth", "Group") - Group.objects.get_or_create(name="bds_members") - Group.objects.get_or_create(name="bds_buro") - - class Migration(migrations.Migration): dependencies = [ @@ -31,5 +22,4 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Profils BDS' }, ), - migrations.RunPython(create_groups, migrations.RunPython.noop), ] diff --git a/cof/apps.py b/cof/apps.py index f981ee91..c40e8896 100644 --- a/cof/apps.py +++ b/cof/apps.py @@ -1,6 +1,36 @@ from django.apps import AppConfig +from django.db.models.signals import post_migrate + + +def setup_groups(sender, apps, **kwargs): + """ + Add the appropriate permissions to the "member" and "buro" groups after the + `post_migrate` signal since the permissions will only be inserted in the + database at the very end of the migrations. + """ + Group = apps.get_model("auth", "Group") + Permission = apps.get_model("auth", "Permission") + + # Buro members have perms cof.* and gestion.* + buro, _ = Group.objects.get_or_create(name="cof_buro") + app_perms = Permission.objects.filter( + content_type__app_label__in=["cof", "gestion"] + ) + buro.permissions.add(*app_perms) + + # Members have perm cof.member + members, _ = Group.objects.get_or_create(name="cof_members") + perm = Permission.objects.get( + codename="member", + content_type__app_label="cof" + ) + members.permissions.add(perm) class COFConfig(AppConfig): name = "cof" verbose_name = "Application de gestion du COF" + + def ready(self): + # https://docs.djangoproject.com/en/1.11/ref/signals/#post-migrate + post_migrate.connect(setup_groups, sender=self) diff --git a/cof/migrations/0009_generic_profiles.py b/cof/migrations/0009_generic_profiles.py index e24ebd2f..1996295a 100644 --- a/cof/migrations/0009_generic_profiles.py +++ b/cof/migrations/0009_generic_profiles.py @@ -23,27 +23,16 @@ def create_profile(apps, schema_editor): def preserve_perms(apps, schema_editor): - # from django.contrib.auth.management import create_permissions - - # apps.models_module = True - # create_permissions(apps, verbosity=0) - # apps.models_module = None - - CofProfile = apps.get_model("cof", "CofProfile") - # memberp = Permission.objects.get(codename='member') - # burop = Permission.objects.get(codename='buro') + COFProfile = apps.get_model("cof", "CofProfile") # creates the groups for COF members and member = Group.objects.create(name='cof_members') buro = Group.objects.create(name='cof_buro') - # associate permissions to the respective groups. - # buro.permissions = [burop, memberp] - # member.permissions = [memberp] - - for cofp in CofProfile.objects.filter(is_cof=True): + cprofiles = COFProfile.objects.select_related("profile__user") + for cofp in cprofiles.filter(is_cof=True): cofp.profile.user.groups.add(member) - for cofp in CofProfile.objects.filter(is_buro=True): + for cofp in cprofiles.filter(is_buro=True): cofp.profile.user.groups.add(buro) diff --git a/cof/migrations/0010_create_cof_group.py b/cof/migrations/0010_create_cof_group.py deleted file mode 100644 index f2ce104a..00000000 --- a/cof/migrations/0010_create_cof_group.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations - - -def create_cof_group(apps, schema_editor): - Group = apps.get_model("auth", "Group") - Group.objects.get_or_create(name="cof_members") - - -def create_buro_group(apps, schema_editor): - Group = apps.get_model("auth", "Group") - Group.objects.get_or_create(name="cof_buro") - - -class Migration(migrations.Migration): - - dependencies = [ - ('cof', '0009_generic_profiles'), - ] - - operations = [ - migrations.RunPython(create_cof_group, migrations.RunPython.noop), - migrations.RunPython(create_buro_group, migrations.RunPython.noop), - ] diff --git a/cof/migrations/0011_delete_clipper_and_custommail.py b/cof/migrations/0011_delete_clipper_and_custommail.py index 92ff4d40..1cfae5f5 100644 --- a/cof/migrations/0011_delete_clipper_and_custommail.py +++ b/cof/migrations/0011_delete_clipper_and_custommail.py @@ -7,7 +7,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('cof', '0010_create_cof_group'), + ('cof', '0009_generic_profiles'), ] operations = [ From a9e6ef6c5c387532d5b865efed0c87c9949ab7b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 24 Jun 2017 22:35:25 +0100 Subject: [PATCH 09/18] Create an Association model The previous fk and m2m to groups representing associations are replaced by a proper link to the Association table. --- gestion/migrations/0005_create_association.py | 61 +++++++++++++++++++ gestion/models.py | 34 ++++++++++- 2 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 gestion/migrations/0005_create_association.py diff --git a/gestion/migrations/0005_create_association.py b/gestion/migrations/0005_create_association.py new file mode 100644 index 00000000..90710d56 --- /dev/null +++ b/gestion/migrations/0005_create_association.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11b1 on 2017-06-24 21:21 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +def create_apps(apps, schema_editor): + Association = apps.get_model("gestion", "Association") + Group = apps.get_model("auth", "Group") + + cof_m, _ = Group.objects.get_or_create(name="cof_members") + cof_b, _ = Group.objects.get_or_create(name="cof_buro") + bds_m, _ = Group.objects.get_or_create(name="bds_members") + bds_b, _ = Group.objects.get_or_create(name="bds_buro") + + Association.objects.bulk_create([ + Association(id=1, name="COF", staff_group=cof_b, members_group=cof_m), + Association(id=2, name="BDS", staff_group=bds_b, members_group=bds_m), + ]) + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0008_alter_user_username_max_length'), + ('gestion', '0004_verbose_names'), + ] + + operations = [ + migrations.CreateModel( + name='Association', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=30, verbose_name="nom de l'association")), + ('members_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='member_groups', to='auth.Group', verbose_name='groupe des membres')), + ('staff_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='staff_groups', to='auth.Group', verbose_name='groupe des membre du bureau')), + ], + options={ + 'verbose_name': 'association', + 'verbose_name_plural': 'associations', + }, + ), + migrations.RunPython(create_apps, migrations.RunPython.noop), + migrations.RemoveField( + model_name='club', + name='associations', + ), + migrations.AddField( + model_name='club', + name='association', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, related_name='clubs', to='gestion.Association', verbose_name='association'), + preserve_default=False, + ), + migrations.AlterField( + model_name='event', + name='associations', + field=models.ManyToManyField(related_name='events', to='gestion.Association', verbose_name='associations'), + ), + ] diff --git a/gestion/models.py b/gestion/models.py index 8d026006..3a9e6354 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -66,6 +66,31 @@ def post_delete_user(sender, instance, *args, **kwargs): instance.user.delete() +class Association(models.Model): + name = models.CharField(_("nom de l'association"), max_length=30) + staff_group = models.ForeignKey( + Group, + on_delete=models.PROTECT, + related_name="staff_groups", + blank=True, null=True, + verbose_name=_("groupe des membre du bureau"), + ) + members_group = models.ForeignKey( + Group, + on_delete=models.PROTECT, + related_name="member_groups", + blank=True, null=True, + verbose_name=_("groupe des membres"), + ) + + class Meta: + verbose_name = _("association") + verbose_name_plural = _("associations") + + def __str__(self): + return self.name + + # --- # Clubs # --- @@ -81,7 +106,12 @@ class Club(models.Model): (COURSE, _("Au cours")) ] - associations = models.ManyToManyField(Group, related_name="clubs") + association = models.ForeignKey( + Association, + on_delete=models.PROTECT, + related_name="clubs", + verbose_name=_("association"), + ) name = models.CharField(_("nom"), max_length=200, unique=True) description = models.TextField("description", blank=True) members = models.ManyToManyField( @@ -139,7 +169,7 @@ class Location(models.Model): class Event(models.Model): associations = models.ManyToManyField( - Group, + Association, related_name="events", verbose_name=_("associations"), ) From 7967983b5c864d4acfe9a324ac562cd23f2e9392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sun, 25 Jun 2017 17:57:23 +0100 Subject: [PATCH 10/18] More sophisticated admin site for the gestion app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nested inlines - Limiting access to the events : you can only edit/create events linked to associations you for which you have the `.buro` permission. --- gestioCOF/settings_dev.py | 1 + gestioCOF/urls.py | 1 + gestion/admin.py | 60 ++++++++++++++++++++++++++++----------- requirements.txt | 1 + 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/gestioCOF/settings_dev.py b/gestioCOF/settings_dev.py index 43e7f5b0..91fb5b7e 100644 --- a/gestioCOF/settings_dev.py +++ b/gestioCOF/settings_dev.py @@ -43,6 +43,7 @@ INSTALLED_APPS = ( 'channels', 'widget_tweaks', 'custommail', + 'nested_admin', 'bda.apps.BdAConfig', 'bds.apps.BDSConfig', 'cof.apps.COFConfig', diff --git a/gestioCOF/urls.py b/gestioCOF/urls.py index 4918c1ea..363d9ecd 100644 --- a/gestioCOF/urls.py +++ b/gestioCOF/urls.py @@ -81,6 +81,7 @@ urlpatterns = [ cof_views.liste_bdarevente, name="liste_bdarevente"), url(r'^k-fet/', include(kfet.urls)), + url(r"^_nested_admin/", include("nested_admin.urls")), ] if 'debug_toolbar' in settings.INSTALLED_APPS: diff --git a/gestion/admin.py b/gestion/admin.py index 82d28233..af044aeb 100644 --- a/gestion/admin.py +++ b/gestion/admin.py @@ -1,9 +1,12 @@ +import nested_admin + from django.contrib import admin from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User +from django.forms.models import modelform_factory from .models import ( - Profile, Club, Event, + Association, Profile, Club, Event, Location, EventOption, EventCommentField, EventOptionChoice ) @@ -30,40 +33,63 @@ admin.site.register(User, UserProfileAdmin) # Clubs # --- -@admin.register(Club) -class ClubAdmin(admin.ModelAdmin): - pass +admin.site.register(Club) # --- # Events # --- -class EventOptionChoiceInline(admin.TabularInline): +class EventOptionChoiceInline(nested_admin.NestedTabularInline): model = EventOptionChoice + extra = 1 -class EventOptionInline(admin.TabularInline): +class EventOptionInline(nested_admin.NestedTabularInline): model = EventOption - show_change_link = True + extra = 1 + inlines = [EventOptionChoiceInline] -class EventCommentFieldInline(admin.TabularInline): +class EventCommentFieldInline(nested_admin.NestedTabularInline): model = EventCommentField - - -@admin.register(EventOption) -class EventOptionAdmin(admin.ModelAdmin): - search_fields = ('event__title', 'name') - inlines = [ - EventOptionChoiceInline, - ] + extra = 1 @admin.register(Event) -class EventAdmin(admin.ModelAdmin): +class EventAdmin(nested_admin.NestedModelAdmin): search_fields = ['title', 'location', 'description'] inlines = [ EventOptionInline, EventCommentFieldInline, ] + + def get_queryset(self, request): + """Restrict the event you can edit""" + qs = super().get_queryset(request) + if request.user.is_superuser: + return qs + else: + assocs = Association.objects.filter(staff_group__user=request.user) + return qs.filter(associations__in=assocs) + + def get_form(self, request, obj=None, **kwargs): + """Restrict the applications you can attach to an event""" + if request.user.is_superuser: + return super().get_form(request, obj, **kwargs) + else: + assocs = Association.objects.filter(staff_group__user=request.user) + + def formfield_callback(f, **kwargs): + if f.name == "associations": + kwargs["queryset"] = assocs + return f.formfield(**kwargs) + + return modelform_factory( + Event, + exclude=[], + formfield_callback=formfield_callback + ) + + +admin.site.register(Location) diff --git a/requirements.txt b/requirements.txt index 8da5f3ea..20ee710b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,3 +19,4 @@ django-widget-tweaks==1.4.1 git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail ldap3 git+https://github.com/Aureplop/channels.git#egg=channels +django-nested-admin=3.0.17 From 7633ed1dabe2ffc0459c8c451b1fa4b2c6b89db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sun, 25 Jun 2017 18:04:30 +0100 Subject: [PATCH 11/18] Typo in requirements.txt: '=' -> '==' --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 20ee710b..07fd6f44 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,4 +19,4 @@ django-widget-tweaks==1.4.1 git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail ldap3 git+https://github.com/Aureplop/channels.git#egg=channels -django-nested-admin=3.0.17 +django-nested-admin==3.0.17 From 894c70149c2563f14148b2feadb634fd267cc7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sun, 25 Jun 2017 20:17:16 +0100 Subject: [PATCH 12/18] Add a fixture with 2 events for testing purpose --- gestion/fixtures/events.json | 193 ++++++++++++++++++ gestion/migrations/0005_create_association.py | 21 +- gestion/models.py | 6 +- provisioning/prepare_django.sh | 2 +- 4 files changed, 209 insertions(+), 13 deletions(-) create mode 100644 gestion/fixtures/events.json diff --git a/gestion/fixtures/events.json b/gestion/fixtures/events.json new file mode 100644 index 00000000..bbf49cf2 --- /dev/null +++ b/gestion/fixtures/events.json @@ -0,0 +1,193 @@ +[ +{ + "model": "gestion.location", + "pk": 1, + "fields": { + "name": "Gergovie" + } +}, +{ + "model": "gestion.location", + "pk": 2, + "fields": { + "name": "Lut\u00e8ce" + } +}, +{ + "model": "gestion.event", + "pk": 1, + "fields": { + "title": "Bataille de gergovie", + "location": 1, + "start_date": "2017-06-25T18:00:00Z", + "end_date": "2017-06-27T18:00:00Z", + "description": "Bataille historique \u00e0 ne pas manquer.\r\nOpen sanglier \u00e0 la fin.", + "image": "", + "registration_open": true, + "old": false, + "associations": [ + "COF" + ] + } +}, +{ + "model": "gestion.event", + "pk": 2, + "fields": { + "title": "Course de chars", + "location": 2, + "start_date": "2017-06-25T13:00:00Z", + "end_date": "2017-06-25T16:00:00Z", + "description": "Finale de la league nationale de char \u00e0 b\u0153ufs", + "image": "", + "registration_open": true, + "old": false, + "associations": [ + "BDS" + ] + } +}, +{ + "model": "gestion.eventcommentfield", + "pk": 1, + "fields": { + "event": 1, + "name": "Commentaires", + "fieldtype": "text", + "default": "", + "ordering": 0 + } +}, +{ + "model": "gestion.eventoption", + "pk": 1, + "fields": { + "event": 1, + "name": "Potion magique", + "multi_choices": false + } +}, +{ + "model": "gestion.eventoption", + "pk": 2, + "fields": { + "event": 1, + "name": "Extras pendant le buffet", + "multi_choices": true + } +}, +{ + "model": "gestion.eventoption", + "pk": 3, + "fields": { + "event": 2, + "name": "Type de si\u00e8ge", + "multi_choices": false + } +}, +{ + "model": "gestion.eventoption", + "pk": 4, + "fields": { + "event": 2, + "name": "Forfaits suppl\u00e9mentaires", + "multi_choices": true + } +}, +{ + "model": "gestion.eventoptionchoice", + "pk": 1, + "fields": { + "event_option": 1, + "value": "Oui" + } +}, +{ + "model": "gestion.eventoptionchoice", + "pk": 2, + "fields": { + "event_option": 1, + "value": "Non" + } +}, +{ + "model": "gestion.eventoptionchoice", + "pk": 3, + "fields": { + "event_option": 1, + "value": "Je suis tomb\u00e9 dans la marmitte quand j'\u00e9tais petit" + } +}, +{ + "model": "gestion.eventoptionchoice", + "pk": 4, + "fields": { + "event_option": 2, + "value": "Sanglier" + } +}, +{ + "model": "gestion.eventoptionchoice", + "pk": 5, + "fields": { + "event_option": 2, + "value": "Cervoise" + } +}, +{ + "model": "gestion.eventoptionchoice", + "pk": 6, + "fields": { + "event_option": 2, + "value": "Pot\u00e9e auvergnate" + } +}, +{ + "model": "gestion.eventoptionchoice", + "pk": 7, + "fields": { + "event_option": 2, + "value": "Saint Nectaire" + } +}, +{ + "model": "gestion.eventoptionchoice", + "pk": 8, + "fields": { + "event_option": 3, + "value": "Debout" + } +}, +{ + "model": "gestion.eventoptionchoice", + "pk": 9, + "fields": { + "event_option": 3, + "value": "Assis standard" + } +}, +{ + "model": "gestion.eventoptionchoice", + "pk": 10, + "fields": { + "event_option": 3, + "value": "Premier rang" + } +}, +{ + "model": "gestion.eventoptionchoice", + "pk": 11, + "fields": { + "event_option": 4, + "value": "Popcorn" + } +}, +{ + "model": "gestion.eventoptionchoice", + "pk": 12, + "fields": { + "event_option": 4, + "value": "Coussin moelleux" + } +} +] diff --git a/gestion/migrations/0005_create_association.py b/gestion/migrations/0005_create_association.py index 90710d56..0bbce625 100644 --- a/gestion/migrations/0005_create_association.py +++ b/gestion/migrations/0005_create_association.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11b1 on 2017-06-24 21:21 +# Generated by Django 1.11b1 on 2017-06-25 17:21 from __future__ import unicode_literals from django.db import migrations, models @@ -16,8 +16,8 @@ def create_apps(apps, schema_editor): bds_b, _ = Group.objects.get_or_create(name="bds_buro") Association.objects.bulk_create([ - Association(id=1, name="COF", staff_group=cof_b, members_group=cof_m), - Association(id=2, name="BDS", staff_group=bds_b, members_group=bds_m), + Association(name="COF", staff_group=cof_b, members_group=cof_m), + Association(name="BDS", staff_group=bds_b, members_group=bds_m), ]) @@ -32,8 +32,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Association', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30, verbose_name="nom de l'association")), + ('name', models.CharField(max_length=30, primary_key=True, serialize=False, verbose_name="nom de l'association")), ('members_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='member_groups', to='auth.Group', verbose_name='groupe des membres')), ('staff_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='staff_groups', to='auth.Group', verbose_name='groupe des membre du bureau')), ], @@ -47,15 +46,15 @@ class Migration(migrations.Migration): model_name='club', name='associations', ), - migrations.AddField( - model_name='club', - name='association', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, related_name='clubs', to='gestion.Association', verbose_name='association'), - preserve_default=False, - ), migrations.AlterField( model_name='event', name='associations', field=models.ManyToManyField(related_name='events', to='gestion.Association', verbose_name='associations'), ), + migrations.AddField( + model_name='club', + name='association', + field=models.ForeignKey(default="COF", on_delete=django.db.models.deletion.PROTECT, related_name='clubs', to='gestion.Association', verbose_name='association'), + preserve_default=False, + ), ] diff --git a/gestion/models.py b/gestion/models.py index 3a9e6354..0be3116a 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -67,7 +67,11 @@ def post_delete_user(sender, instance, *args, **kwargs): class Association(models.Model): - name = models.CharField(_("nom de l'association"), max_length=30) + name = models.CharField( + _("nom de l'association"), + primary_key=True, + max_length=30 + ) staff_group = models.ForeignKey( Group, on_delete=models.PROTECT, diff --git a/provisioning/prepare_django.sh b/provisioning/prepare_django.sh index 4ec1a70f..6a9f70c8 100644 --- a/provisioning/prepare_django.sh +++ b/provisioning/prepare_django.sh @@ -3,7 +3,7 @@ source ~/venv/bin/activate python manage.py migrate -python manage.py loaddata gestion sites articles +python manage.py loaddata gestion events sites articles python manage.py loaddevdata python manage.py syncmails python manage.py collectstatic --noinput From e578aef74db94ecf8ee2801bfa9841a95ea1ee62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Wed, 26 Jul 2017 19:24:49 +0100 Subject: [PATCH 13/18] Fix circular deps in the migrations --- bds/migrations/0002_add_BDS_groups.py | 10 + cof/migrations/0009_generic_profiles.py | 2 +- cof/migrations/0013_move_events_to_gestion.py | 29 +- .../migrations/0003_association_and_events.py | 288 ++++++++++++++++++ gestion/migrations/0003_events.py | 182 ----------- gestion/migrations/0004_verbose_names.py | 236 -------------- gestion/migrations/0005_create_association.py | 60 ---- gestion/models.py | 1 + 8 files changed, 307 insertions(+), 501 deletions(-) create mode 100644 gestion/migrations/0003_association_and_events.py delete mode 100644 gestion/migrations/0003_events.py delete mode 100644 gestion/migrations/0004_verbose_names.py delete mode 100644 gestion/migrations/0005_create_association.py diff --git a/bds/migrations/0002_add_BDS_groups.py b/bds/migrations/0002_add_BDS_groups.py index 33cecf41..45510e59 100644 --- a/bds/migrations/0002_add_BDS_groups.py +++ b/bds/migrations/0002_add_BDS_groups.py @@ -4,6 +4,15 @@ from __future__ import unicode_literals from django.db import migrations +def create_groups(apps, schema_editor): + """ + Creates the groups for BDS members and staff + """ + Group = apps.get_model("auth", "Group") + Group.objects.get_or_create(name="bds_members") + Group.objects.get_or_create(name="bds_buro") + + class Migration(migrations.Migration): dependencies = [ @@ -22,4 +31,5 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Profils BDS' }, ), + migrations.RunPython(create_groups, migrations.RunPython.noop), ] diff --git a/cof/migrations/0009_generic_profiles.py b/cof/migrations/0009_generic_profiles.py index 1996295a..7f19dc47 100644 --- a/cof/migrations/0009_generic_profiles.py +++ b/cof/migrations/0009_generic_profiles.py @@ -25,7 +25,7 @@ def create_profile(apps, schema_editor): def preserve_perms(apps, schema_editor): COFProfile = apps.get_model("cof", "CofProfile") - # creates the groups for COF members and + # create the groups for COF members and staff member = Group.objects.create(name='cof_members') buro = Group.objects.create(name='cof_buro') diff --git a/cof/migrations/0013_move_events_to_gestion.py b/cof/migrations/0013_move_events_to_gestion.py index e5f871b1..ea960186 100644 --- a/cof/migrations/0013_move_events_to_gestion.py +++ b/cof/migrations/0013_move_events_to_gestion.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11b1 on 2017-03-18 20:02 +# Generated by Django 1.11.3 on 2017-07-26 18:29 from __future__ import unicode_literals from django.conf import settings @@ -10,8 +10,8 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ("cof", "0012_remove_club"), - ("gestion", "0003_events"), + ('cof', '0012_remove_club'), + ("gestion", "0003_association_and_events") ] operations = [ @@ -58,37 +58,22 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='petitcoursattribution', name='matiere', - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to='cof.PetitCoursSubject', - verbose_name='Matière' - ), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cof.PetitCoursSubject', verbose_name='Matière'), ), migrations.AlterField( model_name='petitcoursattributioncounter', name='matiere', - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to='cof.PetitCoursSubject', - verbose_name='Matiere' - ), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cof.PetitCoursSubject', verbose_name='Matiere'), ), migrations.AlterField( model_name='petitcoursattributioncounter', name='user', - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL - ), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), ), migrations.AlterField( model_name='petitcoursdemande', name='traitee_par', - field=models.ForeignKey( - blank=True, null=True, - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL - ), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), ), migrations.DeleteModel( name='Event', diff --git a/gestion/migrations/0003_association_and_events.py b/gestion/migrations/0003_association_and_events.py new file mode 100644 index 00000000..ea2ecc42 --- /dev/null +++ b/gestion/migrations/0003_association_and_events.py @@ -0,0 +1,288 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-07-26 18:29 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +def create_apps(apps, schema_editor): + Association = apps.get_model("gestion", "Association") + Group = apps.get_model("auth", "Group") + + cof_m = Group.objects.get(name="cof_members") + cof_b = Group.objects.get(name="cof_buro") + bds_m = Group.objects.get(name="bds_members") + bds_b = Group.objects.get(name="bds_buro") + + Association.objects.bulk_create([ + Association(name="COF", staff_group=cof_b, members_group=cof_m), + Association(name="BDS", staff_group=bds_b, members_group=bds_m), + ]) + + +def import_events(apps, schema_editor): + # Fetching the models that have be moved from cof to gestion + model_names = [ + "Event", "EventCommentField", "EventCommentValue", "EventOption", + "EventOptionChoice", "EventRegistration" + ] + models = [ + (apps.get_model("cof", name), apps.get_model("gestion", name)) + for name in model_names + ] + + # The old Event.location field becomes a table: we need to create an entry + # in this table for each value of the old `location` field. + OldEvent, NewEvent = models[0] + Location = apps.get_model("gestion", "Location") + locations = set() # A set to prevent duplicate entries + events = [] + for event in OldEvent.objects.values(): + locations.add(event["location"]) + events.append(event) + Location.objects.bulk_create([Location(name=name) for name in locations]) + map_loc = { + loc.name: loc + for loc in Location.objects.all() + } + for event in events: + event["location"] = map_loc[event["location"]] + NewEvent.objects.bulk_create([ + NewEvent(**event) + for event in events + ]) + + # Do not forget to link all the existing event to the COF group + cof = apps.get_model("gestion", "Association").objects.get(name="COF") + for event in NewEvent.objects.all(): + event.associations.add(cof) + + # Migrating the other models is straigtforward + for OldModel, NewModel in models[1:]: + NewModel.objects.bulk_create([ + NewModel(**values) + for values in OldModel.objects.values() + ]) + + +def restore_events(apps, schema_editor): + # TODO? + raise NotImplementedError + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('auth', '0008_alter_user_username_max_length'), + ('gestion', '0002_club_support'), + ("cof", "0009_generic_profiles"), + ] + + operations = [ + migrations.CreateModel( + name='Association', + fields=[ + ('name', models.CharField(max_length=30, primary_key=True, serialize=False, verbose_name="nom de l'association")), + ('members_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='member_groups', to='auth.Group', verbose_name='groupe des membres')), + ('staff_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='staff_groups', to='auth.Group', verbose_name='groupe des membre du bureau')), + ], + options={ + 'verbose_name': 'association', + 'verbose_name_plural': 'associations', + }, + ), + migrations.CreateModel( + name='Event', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200, verbose_name='titre')), + ('start_date', models.DateTimeField(blank=True, null=True, verbose_name="début de l'événement")), + ('end_date', models.DateTimeField(blank=True, null=True, verbose_name="fin de l'événement")), + ('description', models.TextField(blank=True, verbose_name='description')), + ('image', models.ImageField(blank=True, null=True, upload_to='public/imgs/events/', verbose_name='image')), + ('registration_open', models.BooleanField(default=True, verbose_name='les inscriptions sont ouvertes')), + ('old', models.BooleanField(default=False, verbose_name='archiver (événement fini)')), + ('associations', models.ManyToManyField(related_name='events', to='gestion.Association', verbose_name='associations')), + ], + options={ + 'verbose_name': 'événement', + 'verbose_name_plural': 'événements', + }, + ), + migrations.CreateModel( + name='EventCommentField', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200, verbose_name='nom du champ')), + ('fieldtype', models.CharField(choices=[('text', 'Texte long'), ('char', 'Texte court')], default='text', max_length=10, verbose_name='type de champ')), + ('default', models.TextField(blank=True, verbose_name='valeur par défaut')), + ('ordering', models.IntegerField(default=False, help_text='plus petit en premier, ordre alphabétique sur le nom si ambiguïté', verbose_name='ordre des champs')), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='commentfields', to='gestion.Event', verbose_name='événement')), + ], + options={ + 'verbose_name': 'champ', + 'verbose_name_plural': 'champs', + }, + ), + migrations.CreateModel( + name='EventCommentValue', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content', models.TextField(blank=True, verbose_name='contenu')), + ('commentfield', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='values', to='gestion.EventCommentField')), + ], + ), + migrations.CreateModel( + name='EventOption', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200, verbose_name='option')), + ('multi_choices', models.BooleanField(default=False, verbose_name='choix multiples')), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='gestion.Event', verbose_name='événement')), + ], + options={ + 'verbose_name': 'option des événements', + 'verbose_name_plural': 'options des événements', + }, + ), + migrations.CreateModel( + name='EventOptionChoice', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('value', models.CharField(max_length=200, verbose_name='Valeur')), + ('event_option', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='gestion.EventOption', verbose_name='événement')), + ], + options={ + 'verbose_name': 'choix', + 'verbose_name_plural': 'choix', + }, + ), + migrations.CreateModel( + name='EventRegistration', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('paid', models.BooleanField(default=False, verbose_name='a payé')), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion.Event', verbose_name='événement')), + ('filledcomments', models.ManyToManyField(through='gestion.EventCommentValue', to='gestion.EventCommentField', verbose_name='commentaires')), + ('options', models.ManyToManyField(to='gestion.EventOptionChoice', verbose_name='choix')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='utilisateur')), + ], + options={ + 'verbose_name': 'inscription', + 'verbose_name_plural': 'inscriptions', + }, + ), + migrations.CreateModel( + name='Location', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200, verbose_name='nom du lieux')), + ], + options={ + 'verbose_name': 'lieu', + 'verbose_name_plural': 'lieux', + }, + ), + migrations.AlterModelOptions( + name='profile', + options={'verbose_name': 'profil', 'verbose_name_plural': 'profils'}, + ), + migrations.RemoveField( + model_name='club', + name='associations', + ), + migrations.AlterField( + model_name='club', + name='cotisation_frequency', + field=models.CharField(blank=True, choices=[('ANN', 'Annuel'), ('SEM', 'Semestriel'), ('COU', 'Au cours')], default='ANN', max_length=3, verbose_name='fréquence de la cotisation'), + ), + migrations.AlterField( + model_name='club', + name='description', + field=models.TextField(blank=True, verbose_name='description'), + ), + migrations.AlterField( + model_name='club', + name='members', + field=models.ManyToManyField(blank=True, related_name='in_clubs', through='gestion.ClubUser', to=settings.AUTH_USER_MODEL, verbose_name='membres du club'), + ), + migrations.AlterField( + model_name='club', + name='name', + field=models.CharField(max_length=200, unique=True, verbose_name='nom'), + ), + migrations.AlterField( + model_name='club', + name='price', + field=models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=5, verbose_name='cotisation (€)'), + ), + migrations.AlterField( + model_name='clubuser', + name='has_paid', + field=models.BooleanField(verbose_name='a payé sa cotisation'), + ), + migrations.AlterField( + model_name='clubuser', + name='is_respo', + field=models.BooleanField(verbose_name='est responsable du club'), + ), + migrations.AlterField( + model_name='profile', + name='comments', + field=models.TextField(blank=True, verbose_name="commentaires visibles par l'utilisateur"), + ), + migrations.AlterField( + model_name='profile', + name='departement', + field=models.CharField(blank=True, max_length=50, verbose_name='département'), + ), + migrations.AlterField( + model_name='profile', + name='login_clipper', + field=models.CharField(blank=True, max_length=8, verbose_name='login clipper'), + ), + migrations.AlterField( + model_name='profile', + name='occupation', + field=models.CharField(choices=[('exterieur', 'Extérieur'), ('1A', '1A'), ('2A', '2A'), ('3A', '3A'), ('4A', '4A'), ('archicube', 'Archicube'), ('doctorant', 'Doctorant'), ('CST', 'CST')], default='1A', max_length=9, verbose_name='occupation'), + ), + migrations.AlterField( + model_name='profile', + name='phone', + field=models.CharField(blank=True, max_length=20, verbose_name='téléphone'), + ), + migrations.AlterField( + model_name='profile', + name='user', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL, verbose_name='utilisateur'), + ), + migrations.AddField( + model_name='eventcommentvalue', + name='registration', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='gestion.EventRegistration'), + ), + migrations.AddField( + model_name='event', + name='location', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='gestion.Location', verbose_name='lieux'), + ), + migrations.AddField( + model_name='club', + name='association', + field=models.ForeignKey(default='COF', on_delete=django.db.models.deletion.PROTECT, related_name='clubs', to='gestion.Association', verbose_name='association'), + preserve_default=False, + ), + migrations.AlterUniqueTogether( + name='eventregistration', + unique_together=set([('user', 'event')]), + ), + migrations.AlterUniqueTogether( + name='eventcommentvalue', + unique_together=set([('commentfield', 'registration')]), + ), + migrations.RunPython(create_apps, migrations.RunPython.noop), + migrations.RunPython(import_events, restore_events), + ] diff --git a/gestion/migrations/0003_events.py b/gestion/migrations/0003_events.py deleted file mode 100644 index 40faca91..00000000 --- a/gestion/migrations/0003_events.py +++ /dev/null @@ -1,182 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11b1 on 2017-03-18 20:02 -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -def import_events(apps, schema_editor): - # Fetching the models that have be moved from cof to gestion - model_names = [ - "Event", "EventCommentField", "EventCommentValue", "EventOption", - "EventOptionChoice", "EventRegistration" - ] - models = [ - (apps.get_model("cof", name), apps.get_model("gestion", name)) - for name in model_names - ] - - # The old Event.location field becomes a table: we need to create an entry - # in this table for each value of the old `location` field. - OldEvent, NewEvent = models[0] - Location = apps.get_model("gestion", "Location") - locations = set() # A set to prevent duplicate entries - events = [] - for event in OldEvent.objects.values(): - locations.add(event["location"]) - events.append(event) - Location.objects.bulk_create([Location(name=name) for name in locations]) - map_loc = { - loc.name: loc - for loc in Location.objects.all() - } - for event in events: - event["location"] = map_loc[event["location"]] - NewEvent.objects.bulk_create([ - NewEvent(**event) - for event in events - ]) - - # Do not forget to link all the existing event to the COF group - cof = apps.get_model("auth", "Group").objects.get(name="cof_buro") - for event in NewEvent.objects.all(): - event.associations.add(cof) - - # Migrating the other models is straigtforward - for OldModel, NewModel in models[1:]: - NewModel.objects.bulk_create([ - NewModel(**values) - for values in OldModel.objects.values() - ]) - - -def restore_events(apps, schema_editor): - # TODO? - raise NotImplementedError - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('auth', '0008_alter_user_username_max_length'), - ('gestion', '0002_club_support'), - ] - - operations = [ - migrations.CreateModel( - name='Location', - fields=[ - ( - 'id', - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name='ID' - ) - ), - ( - 'name', - models.CharField(max_length=200, verbose_name='Lieu') - ), - ], - options={ - 'verbose_name': 'lieu', - 'verbose_name_plural': 'lieux', - }, - ), - migrations.CreateModel( - name='Event', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=200, verbose_name='Titre')), - ('location', models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - to='gestion.Location' - )), - ('start_date', models.DateTimeField(blank=True, null=True, verbose_name='Date de début')), - ('end_date', models.DateTimeField(blank=True, null=True, verbose_name='Date de fin')), - ('description', models.TextField(blank=True, verbose_name='Description')), - ('image', models.ImageField(blank=True, null=True, upload_to='imgs/events/', verbose_name='Image')), - ('registration_open', models.BooleanField(default=True, verbose_name='Inscriptions ouvertes')), - ('old', models.BooleanField(default=False, verbose_name='Archiver (événement fini)')), - ('associations', models.ManyToManyField(related_name='events', to='auth.Group')), - ], - options={ - 'verbose_name': 'Événement', - }, - ), - migrations.CreateModel( - name='EventCommentField', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, verbose_name='Champ')), - ('fieldtype', models.CharField(choices=[('text', 'Texte long'), ('char', 'Texte court')], default='text', max_length=10, verbose_name='Type')), - ('default', models.TextField(blank=True, verbose_name='Valeur par défaut')), - ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='commentfields', to='gestion.Event')), - ], - options={ - 'verbose_name': 'Champ', - }, - ), - migrations.CreateModel( - name='EventCommentValue', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content', models.TextField(blank=True, null=True, verbose_name='Contenu')), - ('commentfield', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='values', to='gestion.EventCommentField')), - ], - ), - migrations.CreateModel( - name='EventOption', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, verbose_name='Option')), - ('multi_choices', models.BooleanField(default=False, verbose_name='Choix multiples')), - ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='gestion.Event')), - ], - options={ - 'verbose_name': 'Option des événements', - 'verbose_name_plural': 'Options des événements' - }, - ), - migrations.CreateModel( - name='EventOptionChoice', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('value', models.CharField(max_length=200, verbose_name='Valeur')), - ('event_option', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='gestion.EventOption')), - ], - options={ - 'verbose_name_plural': 'Choix', - 'verbose_name': 'Choix', - }, - ), - migrations.CreateModel( - name='EventRegistration', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('paid', models.BooleanField(default=False, verbose_name='A payé')), - ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion.Event')), - ('filledcomments', models.ManyToManyField(through='gestion.EventCommentValue', to='gestion.EventCommentField')), - ('options', models.ManyToManyField(to='gestion.EventOptionChoice')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Inscription', - }, - ), - migrations.AddField( - model_name='eventcommentvalue', - name='registration', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='gestion.EventRegistration'), - ), - migrations.AlterUniqueTogether( - name='eventregistration', - unique_together=set([('user', 'event')]), - ), - migrations.RunPython(import_events, restore_events), - ] diff --git a/gestion/migrations/0004_verbose_names.py b/gestion/migrations/0004_verbose_names.py deleted file mode 100644 index a0761c86..00000000 --- a/gestion/migrations/0004_verbose_names.py +++ /dev/null @@ -1,236 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11b1 on 2017-06-24 20:08 -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('gestion', '0003_events'), - ] - - operations = [ - migrations.AlterModelOptions( - name='event', - options={'verbose_name': 'événement', 'verbose_name_plural': 'événements'}, - ), - migrations.AlterModelOptions( - name='eventcommentfield', - options={'verbose_name': 'champ', 'verbose_name_plural': 'champs'}, - ), - migrations.AlterModelOptions( - name='eventoption', - options={'verbose_name': 'option des événements', 'verbose_name_plural': 'options des événements'}, - ), - migrations.AlterModelOptions( - name='eventoptionchoice', - options={'verbose_name': 'choix', 'verbose_name_plural': 'choix'}, - ), - migrations.AlterModelOptions( - name='eventregistration', - options={'verbose_name': 'inscription', 'verbose_name_plural': 'inscriptions'}, - ), - migrations.AlterModelOptions( - name='profile', - options={'verbose_name': 'profil', 'verbose_name_plural': 'profils'}, - ), - migrations.AddField( - model_name='eventcommentfield', - name='ordering', - field=models.IntegerField(default=0, help_text='plus petit en premier, ordre alphabétique sur le nom si ambiguïté', verbose_name='ordre des champs'), - preserve_default=False, - ), - migrations.AlterField( - model_name='club', - name='cotisation_frequency', - field=models.CharField(blank=True, choices=[('ANN', 'Annuel'), ('SEM', 'Semestriel'), ('COU', 'Au cours')], default='ANN', max_length=3, verbose_name='fréquence de la cotisation'), - ), - migrations.AlterField( - model_name='club', - name='description', - field=models.TextField(blank=True, verbose_name='description'), - ), - migrations.AlterField( - model_name='club', - name='members', - field=models.ManyToManyField(blank=True, related_name='in_clubs', through='gestion.ClubUser', to=settings.AUTH_USER_MODEL, verbose_name='membres du club'), - ), - migrations.AlterField( - model_name='club', - name='name', - field=models.CharField(max_length=200, unique=True, verbose_name='nom'), - ), - migrations.AlterField( - model_name='club', - name='price', - field=models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=5, verbose_name='cotisation (€)'), - ), - migrations.AlterField( - model_name='clubuser', - name='has_paid', - field=models.BooleanField(verbose_name='a payé sa cotisation'), - ), - migrations.AlterField( - model_name='clubuser', - name='is_respo', - field=models.BooleanField(verbose_name='est responsable du club'), - ), - migrations.AlterField( - model_name='event', - name='associations', - field=models.ManyToManyField(related_name='events', to='auth.Group', verbose_name='associations'), - ), - migrations.AlterField( - model_name='event', - name='description', - field=models.TextField(blank=True, verbose_name='description'), - ), - migrations.AlterField( - model_name='event', - name='end_date', - field=models.DateTimeField(blank=True, null=True, verbose_name="fin de l'événement"), - ), - migrations.AlterField( - model_name='event', - name='image', - field=models.ImageField(blank=True, null=True, upload_to='public/imgs/events/', verbose_name='image'), - ), - migrations.AlterField( - model_name='event', - name='location', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='gestion.Location', verbose_name='lieux'), - ), - migrations.AlterField( - model_name='event', - name='old', - field=models.BooleanField(default=False, verbose_name='archiver (événement fini)'), - ), - migrations.AlterField( - model_name='event', - name='registration_open', - field=models.BooleanField(default=True, verbose_name='les inscriptions sont ouvertes'), - ), - migrations.AlterField( - model_name='event', - name='start_date', - field=models.DateTimeField(blank=True, null=True, verbose_name="début de l'événement"), - ), - migrations.AlterField( - model_name='event', - name='title', - field=models.CharField(max_length=200, verbose_name='titre'), - ), - migrations.AlterField( - model_name='eventcommentfield', - name='default', - field=models.TextField(blank=True, verbose_name='valeur par défaut'), - ), - migrations.AlterField( - model_name='eventcommentfield', - name='event', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='commentfields', to='gestion.Event', verbose_name='événement'), - ), - migrations.AlterField( - model_name='eventcommentfield', - name='fieldtype', - field=models.CharField(choices=[('text', 'Texte long'), ('char', 'Texte court')], default='text', max_length=10, verbose_name='type de champ'), - ), - migrations.AlterField( - model_name='eventcommentfield', - name='name', - field=models.CharField(max_length=200, verbose_name='nom du champ'), - ), - migrations.AlterField( - model_name='eventcommentvalue', - name='content', - field=models.TextField(blank=True, verbose_name='contenu'), - ), - migrations.AlterUniqueTogether( - name='eventcommentvalue', - unique_together=set([('commentfield', 'registration')]), - ), - migrations.AlterField( - model_name='eventoption', - name='event', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='gestion.Event', verbose_name='événement'), - ), - migrations.AlterField( - model_name='eventoption', - name='multi_choices', - field=models.BooleanField(default=False, verbose_name='choix multiples'), - ), - migrations.AlterField( - model_name='eventoption', - name='name', - field=models.CharField(max_length=200, verbose_name='option'), - ), - migrations.AlterField( - model_name='eventoptionchoice', - name='event_option', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='gestion.EventOption', verbose_name='événement'), - ), - migrations.AlterField( - model_name='eventregistration', - name='event', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion.Event', verbose_name='événement'), - ), - migrations.AlterField( - model_name='eventregistration', - name='filledcomments', - field=models.ManyToManyField(through='gestion.EventCommentValue', to='gestion.EventCommentField', verbose_name='commentaires'), - ), - migrations.AlterField( - model_name='eventregistration', - name='options', - field=models.ManyToManyField(to='gestion.EventOptionChoice', verbose_name='choix'), - ), - migrations.AlterField( - model_name='eventregistration', - name='paid', - field=models.BooleanField(default=False, verbose_name='a payé'), - ), - migrations.AlterField( - model_name='eventregistration', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='utilisateur'), - ), - migrations.AlterField( - model_name='location', - name='name', - field=models.CharField(max_length=200, verbose_name='nom du lieux'), - ), - migrations.AlterField( - model_name='profile', - name='comments', - field=models.TextField(blank=True, verbose_name="commentaires visibles par l'utilisateur"), - ), - migrations.AlterField( - model_name='profile', - name='departement', - field=models.CharField(blank=True, max_length=50, verbose_name='département'), - ), - migrations.AlterField( - model_name='profile', - name='login_clipper', - field=models.CharField(blank=True, max_length=8, verbose_name='login clipper'), - ), - migrations.AlterField( - model_name='profile', - name='occupation', - field=models.CharField(choices=[('exterieur', 'Extérieur'), ('1A', '1A'), ('2A', '2A'), ('3A', '3A'), ('4A', '4A'), ('archicube', 'Archicube'), ('doctorant', 'Doctorant'), ('CST', 'CST')], default='1A', max_length=9, verbose_name='occupation'), - ), - migrations.AlterField( - model_name='profile', - name='phone', - field=models.CharField(blank=True, max_length=20, verbose_name='téléphone'), - ), - migrations.AlterField( - model_name='profile', - name='user', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL, verbose_name='utilisateur'), - ), - ] diff --git a/gestion/migrations/0005_create_association.py b/gestion/migrations/0005_create_association.py deleted file mode 100644 index 0bbce625..00000000 --- a/gestion/migrations/0005_create_association.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11b1 on 2017-06-25 17:21 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -def create_apps(apps, schema_editor): - Association = apps.get_model("gestion", "Association") - Group = apps.get_model("auth", "Group") - - cof_m, _ = Group.objects.get_or_create(name="cof_members") - cof_b, _ = Group.objects.get_or_create(name="cof_buro") - bds_m, _ = Group.objects.get_or_create(name="bds_members") - bds_b, _ = Group.objects.get_or_create(name="bds_buro") - - Association.objects.bulk_create([ - Association(name="COF", staff_group=cof_b, members_group=cof_m), - Association(name="BDS", staff_group=bds_b, members_group=bds_m), - ]) - - -class Migration(migrations.Migration): - - dependencies = [ - ('auth', '0008_alter_user_username_max_length'), - ('gestion', '0004_verbose_names'), - ] - - operations = [ - migrations.CreateModel( - name='Association', - fields=[ - ('name', models.CharField(max_length=30, primary_key=True, serialize=False, verbose_name="nom de l'association")), - ('members_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='member_groups', to='auth.Group', verbose_name='groupe des membres')), - ('staff_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='staff_groups', to='auth.Group', verbose_name='groupe des membre du bureau')), - ], - options={ - 'verbose_name': 'association', - 'verbose_name_plural': 'associations', - }, - ), - migrations.RunPython(create_apps, migrations.RunPython.noop), - migrations.RemoveField( - model_name='club', - name='associations', - ), - migrations.AlterField( - model_name='event', - name='associations', - field=models.ManyToManyField(related_name='events', to='gestion.Association', verbose_name='associations'), - ), - migrations.AddField( - model_name='club', - name='association', - field=models.ForeignKey(default="COF", on_delete=django.db.models.deletion.PROTECT, related_name='clubs', to='gestion.Association', verbose_name='association'), - preserve_default=False, - ), - ] diff --git a/gestion/models.py b/gestion/models.py index 0be3116a..5dbdefec 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -235,6 +235,7 @@ class EventCommentField(models.Model): default = models.TextField(_("valeur par défaut"), blank=True) ordering = models.IntegerField( _("ordre des champs"), + default=0, help_text=_( "plus petit en premier, ordre alphabétique sur le nom si " "ambiguïté" From 2fb56afa95e76be87f19e20d1787ec3d1d945964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sun, 6 Aug 2017 19:56:11 +0100 Subject: [PATCH 14/18] typos + renamings + other MR changes --- .../migrations/0003_association_and_events.py | 19 ++++++------ gestion/models.py | 29 ++++++++++++------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/gestion/migrations/0003_association_and_events.py b/gestion/migrations/0003_association_and_events.py index ea2ecc42..158dc151 100644 --- a/gestion/migrations/0003_association_and_events.py +++ b/gestion/migrations/0003_association_and_events.py @@ -17,8 +17,8 @@ def create_apps(apps, schema_editor): bds_b = Group.objects.get(name="bds_buro") Association.objects.bulk_create([ - Association(name="COF", staff_group=cof_b, members_group=cof_m), - Association(name="BDS", staff_group=bds_b, members_group=bds_m), + Association(id=1, name="COF", staff_group=cof_b, members_group=cof_m), + Association(id=2, name="BDS", staff_group=bds_b, members_group=bds_m), ]) @@ -85,9 +85,10 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Association', fields=[ - ('name', models.CharField(max_length=30, primary_key=True, serialize=False, verbose_name="nom de l'association")), - ('members_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='member_groups', to='auth.Group', verbose_name='groupe des membres')), - ('staff_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='staff_groups', to='auth.Group', verbose_name='groupe des membre du bureau')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=30, verbose_name="nom de l'association")), + ('members_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='member_group_of', to='auth.Group', verbose_name='groupe des membres')), + ('staff_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='staff_group_of', to='auth.Group', verbose_name='groupe des membres du bureau')), ], options={ 'verbose_name': 'association', @@ -103,7 +104,7 @@ class Migration(migrations.Migration): ('end_date', models.DateTimeField(blank=True, null=True, verbose_name="fin de l'événement")), ('description', models.TextField(blank=True, verbose_name='description')), ('image', models.ImageField(blank=True, null=True, upload_to='public/imgs/events/', verbose_name='image')), - ('registration_open', models.BooleanField(default=True, verbose_name='les inscriptions sont ouvertes')), + ('registration_open', models.NullBooleanField(default=True, help_text="Indéfini signifie « l'inscription n'est pas requise\xa0»", verbose_name='les inscriptions sont ouvertes')), ('old', models.BooleanField(default=False, verbose_name='archiver (événement fini)')), ('associations', models.ManyToManyField(related_name='events', to='gestion.Association', verbose_name='associations')), ], @@ -168,7 +169,7 @@ class Migration(migrations.Migration): ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion.Event', verbose_name='événement')), ('filledcomments', models.ManyToManyField(through='gestion.EventCommentValue', to='gestion.EventCommentField', verbose_name='commentaires')), ('options', models.ManyToManyField(to='gestion.EventOptionChoice', verbose_name='choix')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='utilisateur')), + ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='utilisateur')), ], options={ 'verbose_name': 'inscription', @@ -179,7 +180,7 @@ class Migration(migrations.Migration): name='Location', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, verbose_name='nom du lieux')), + ('name', models.CharField(max_length=200, verbose_name='nom du lieu')), ], options={ 'verbose_name': 'lieu', @@ -272,7 +273,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='club', name='association', - field=models.ForeignKey(default='COF', on_delete=django.db.models.deletion.PROTECT, related_name='clubs', to='gestion.Association', verbose_name='association'), + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, related_name='clubs', to='gestion.Association', verbose_name='association'), preserve_default=False, ), migrations.AlterUniqueTogether( diff --git a/gestion/models.py b/gestion/models.py index 5dbdefec..57ebbca2 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -69,20 +69,19 @@ def post_delete_user(sender, instance, *args, **kwargs): class Association(models.Model): name = models.CharField( _("nom de l'association"), - primary_key=True, max_length=30 ) staff_group = models.ForeignKey( Group, on_delete=models.PROTECT, - related_name="staff_groups", + related_name="staff_group_of", blank=True, null=True, - verbose_name=_("groupe des membre du bureau"), + verbose_name=_("groupe des membres du bureau"), ) members_group = models.ForeignKey( Group, on_delete=models.PROTECT, - related_name="member_groups", + related_name="member_group_of", blank=True, null=True, verbose_name=_("groupe des membres"), ) @@ -117,7 +116,7 @@ class Club(models.Model): verbose_name=_("association"), ) name = models.CharField(_("nom"), max_length=200, unique=True) - description = models.TextField("description", blank=True) + description = models.TextField(_("description"), blank=True) members = models.ManyToManyField( User, through="ClubUser", @@ -161,7 +160,7 @@ class ClubUser(models.Model): # --- class Location(models.Model): - name = models.CharField(_("nom du lieux"), max_length=200) + name = models.CharField(_("nom du lieu"), max_length=200) class Meta: verbose_name = _("lieu") @@ -197,9 +196,10 @@ class Event(models.Model): blank=True, null=True, upload_to="public/imgs/events/", ) - registration_open = models.BooleanField( + registration_open = models.NullBooleanField( _("les inscriptions sont ouvertes"), - default=True + help_text=_("Indéfini signifie « l'inscription n'est pas requise »"), + default=True, ) old = models.BooleanField(_("archiver (événement fini)"), default=False) @@ -208,7 +208,15 @@ class Event(models.Model): verbose_name_plural = _("événements") def __str__(self): - return self.title + title, location = self.title, self.location + start = self.start_date.date() + end = self.end_date.date() + if not self.start_date.date(): + return "{} @ {}".format(title, location) + elif start == end: + return "{} @ {} ({})".format(title, location, start) + else: + return "{} @ {} ({} → {})".format(title, location, start, end) class EventCommentField(models.Model): @@ -311,7 +319,8 @@ class EventOptionChoice(models.Model): class EventRegistration(models.Model): user = models.ForeignKey( User, - on_delete=models.CASCADE, + on_delete=models.SET_NULL, + null=True, verbose_name=_("utilisateur"), ) event = models.ForeignKey( From b1a56b07f313133057729ca6a65529016ac63635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sun, 6 Aug 2017 20:55:12 +0100 Subject: [PATCH 15/18] Bump django-bootstrap-form to 3.3 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 07fd6f44..537e4be4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ Pillow six unicodecsv icalendar -django-bootstrap-form==3.2.1 +django-bootstrap-form==3.3 asgiref==1.0.0 daphne==1.0.3 asgi-redis==1.0.0 From 10543341b753ff846f474625fd32514d38c9cf8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Mon, 7 Aug 2017 20:51:08 +0100 Subject: [PATCH 16/18] The location is not mandatory for an event --- .../0004_event_location_not_mandatory.py | 26 +++++++++++++++++++ gestion/models.py | 1 + 2 files changed, 27 insertions(+) create mode 100644 gestion/migrations/0004_event_location_not_mandatory.py diff --git a/gestion/migrations/0004_event_location_not_mandatory.py b/gestion/migrations/0004_event_location_not_mandatory.py new file mode 100644 index 00000000..31835f2f --- /dev/null +++ b/gestion/migrations/0004_event_location_not_mandatory.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11b1 on 2017-08-07 19:49 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('gestion', '0003_association_and_events'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='location', + field=models.ForeignKey( + blank=True, null=True, + on_delete=django.db.models.deletion.PROTECT, + to='gestion.Location', + verbose_name='lieux' + ), + ), + ] diff --git a/gestion/models.py b/gestion/models.py index 57ebbca2..67757744 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -179,6 +179,7 @@ class Event(models.Model): title = models.CharField(_("titre"), max_length=200) location = models.ForeignKey( Location, + blank=True, null=True, on_delete=models.PROTECT, verbose_name=_("lieux"), ) From 714e702af70e7eda0161de64fa5a645df16ddae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Fri, 11 Aug 2017 14:56:41 +0100 Subject: [PATCH 17/18] Use natural foreign keys to refer associations --- gestion/fixtures/events.json | 4 ++-- gestion/migrations/0003_association_and_events.py | 2 +- gestion/models.py | 11 +++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/gestion/fixtures/events.json b/gestion/fixtures/events.json index bbf49cf2..4a9383bd 100644 --- a/gestion/fixtures/events.json +++ b/gestion/fixtures/events.json @@ -26,7 +26,7 @@ "registration_open": true, "old": false, "associations": [ - "COF" + ["COF"] ] } }, @@ -43,7 +43,7 @@ "registration_open": true, "old": false, "associations": [ - "BDS" + ["BDS"] ] } }, diff --git a/gestion/migrations/0003_association_and_events.py b/gestion/migrations/0003_association_and_events.py index 158dc151..12f5732a 100644 --- a/gestion/migrations/0003_association_and_events.py +++ b/gestion/migrations/0003_association_and_events.py @@ -86,7 +86,7 @@ class Migration(migrations.Migration): name='Association', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30, verbose_name="nom de l'association")), + ('name', models.CharField(max_length=30, unique=True, verbose_name="nom de l'association")), ('members_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='member_group_of', to='auth.Group', verbose_name='groupe des membres')), ('staff_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='staff_group_of', to='auth.Group', verbose_name='groupe des membres du bureau')), ], diff --git a/gestion/models.py b/gestion/models.py index 67757744..bdf2aa1c 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -66,9 +66,17 @@ def post_delete_user(sender, instance, *args, **kwargs): instance.user.delete() +class AssociationManager(models.Manager): + def get_by_natural_key(self, name): + return self.get(name=name) + + class Association(models.Model): + objects = AssociationManager() + name = models.CharField( _("nom de l'association"), + unique=True, max_length=30 ) staff_group = models.ForeignKey( @@ -86,6 +94,9 @@ class Association(models.Model): verbose_name=_("groupe des membres"), ) + def natural_key(self): + return self.name + class Meta: verbose_name = _("association") verbose_name_plural = _("associations") From d4669ec8733372484ea3326ede741d3db1ab9f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Fri, 11 Aug 2017 15:42:33 +0100 Subject: [PATCH 18/18] Model.natural_key should return a list/tuple --- gestion/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gestion/models.py b/gestion/models.py index bdf2aa1c..2183e359 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -95,7 +95,7 @@ class Association(models.Model): ) def natural_key(self): - return self.name + return [self.name] class Meta: verbose_name = _("association")