Merge branch 'Kerl/supportBDS/events' into supportBDS

Move event-related models from 'cof' app to 'gestion' app.

Add 'Association' model to register name, related groups (buro,
members), etc.

Club is now associated with a single Association instance.

Migrations take care of these changes.
This commit is contained in:
Aurélien Delobelle 2017-08-11 19:59:24 +02:00
commit 3842b5d160
22 changed files with 1061 additions and 466 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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"

View file

@ -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")

View file

@ -23,27 +23,16 @@ def create_profile(apps, schema_editor):
def preserve_perms(apps, schema_editor):
# from django.contrib.auth.management import create_permissions
COFProfile = apps.get_model("cof", "CofProfile")
# 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')
# 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')
# 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)

View file

@ -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),
]

View file

@ -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 = [

View file

@ -0,0 +1,96 @@
# -*- 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
class Migration(migrations.Migration):
dependencies = [
('cof', '0012_remove_club'),
("gestion", "0003_association_and_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',
),
]

View file

@ -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)

View file

@ -9,16 +9,6 @@
<div class="container">
<div class="home-menu row">
<div class="{% if user.profile.cof.is_buro %}col-sm-6 {% else %}col-sm-8 col-sm-offset-2 col-xs-12 {%endif%}normal-user-hm">
<!-- {% if open_events %}
<h3 class="block-title">Événements<span class="pull-right glyphicon glyphicon-calendar"></span></h3>
<div class="hm-block">
<ul>
{% for event in open_events %}
<li><a href="{% url "event" event.id %}">{{ event.title }}</a></li>
{% endfor %}
</ul>
</div>
{% endif %} -->
{% if open_surveys %}
<h3 class="block-title">Sondages en cours<span class="pull-right glyphicon glyphicon-stats"></span></h3>
<div class="hm-block">

View file

@ -53,15 +53,6 @@ surveys_patterns = [
name="survey"),
]
events_patterns = [
url(r'^(?P<event_id>\d+)$',
views.event,
name="event"),
url(r'^(?P<event_id>\d+)/status$',
views.event_status,
name="event.status"),
]
calendar_patterns = [
url(r'^subscription$',
views.calendar,

View file

@ -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:

View file

@ -43,6 +43,7 @@ INSTALLED_APPS = (
'channels',
'widget_tweaks',
'custommail',
'nested_admin',
'bda.apps.BdAConfig',
'bds.apps.BDSConfig',
'cof.apps.COFConfig',

View file

@ -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
@ -83,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:

View file

@ -1,8 +1,14 @@
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
from .models import (
Association, Profile, Club, Event, Location,
EventOption, EventCommentField, EventOptionChoice
)
# ---
@ -19,7 +25,6 @@ class UserProfileAdmin(UserAdmin):
ProfileInline,
]
admin.site.unregister(User)
admin.site.register(User, UserProfileAdmin)
@ -38,3 +43,62 @@ class ClubAdmin(admin.ModelAdmin):
ClubUserInline,
]
exclude = ('members',)
# ---
# Events
# ---
class EventOptionChoiceInline(nested_admin.NestedTabularInline):
model = EventOptionChoice
extra = 1
class EventOptionInline(nested_admin.NestedTabularInline):
model = EventOption
extra = 1
inlines = [EventOptionChoiceInline]
class EventCommentFieldInline(nested_admin.NestedTabularInline):
model = EventCommentField
extra = 1
@admin.register(Event)
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)

View file

@ -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"
}
}
]

View file

@ -0,0 +1,289 @@
# -*- 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(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),
])
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=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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')),
],
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.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')),
],
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(null=True, on_delete=django.db.models.deletion.SET_NULL, 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 lieu')),
],
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=1, 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),
]

View file

@ -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'
),
),
]

View file

@ -7,6 +7,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")),
@ -23,23 +28,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
@ -56,6 +67,49 @@ 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(
Group,
on_delete=models.PROTECT,
related_name="staff_group_of",
blank=True, null=True,
verbose_name=_("groupe des membres du bureau"),
)
members_group = models.ForeignKey(
Group,
on_delete=models.PROTECT,
related_name="member_group_of",
blank=True, null=True,
verbose_name=_("groupe des membres"),
)
def natural_key(self):
return [self.name]
class Meta:
verbose_name = _("association")
verbose_name_plural = _("associations")
def __str__(self):
return self.name
# ---
# Clubs
# ---
class Club(models.Model):
ANNUAL = "ANN"
SEMESTER = "SEM"
@ -67,24 +121,30 @@ class Club(models.Model):
(COURSE, _("Au cours"))
]
associations = models.ManyToManyField(Group, related_name="clubs")
name = models.CharField(_("Nom"), max_length=200, unique=True)
description = models.TextField("Description", blank=True)
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(
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,
@ -94,10 +154,10 @@ class Club(models.Model):
def __str__(self):
template = (
self.needs_cotiz and
"{name} ({price}€ / {cotisation_frequency})" or
"{name}"
"%(name)s (%(price).2f€ / %(cotisation_frequency)s)" or
"%(name)s"
)
return template.format(**vars(self))
return template % vars(self)
def get_absolute_url(self):
return reverse('gestion:club_detail', args=[str(self.pk)])
@ -113,5 +173,202 @@ class Club(models.Model):
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"))
# ---
# Events
# ---
class Location(models.Model):
name = models.CharField(_("nom du lieu"), max_length=200)
class Meta:
verbose_name = _("lieu")
verbose_name_plural = _("lieux")
def __str__(self):
return self.name
class Event(models.Model):
associations = models.ManyToManyField(
Association,
related_name="events",
verbose_name=_("associations"),
)
title = models.CharField(_("titre"), max_length=200)
location = models.ForeignKey(
Location,
blank=True, null=True,
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="public/imgs/events/",
)
registration_open = models.NullBooleanField(
_("les inscriptions sont ouvertes"),
help_text=_("Indéfini signifie « l'inscription n'est pas requise »"),
default=True,
)
old = models.BooleanField(_("archiver (événement fini)"), default=False)
class Meta:
verbose_name = _("événement")
verbose_name_plural = _("événements")
def __str__(self):
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):
TEXT = "text"
CHAR = "char"
FIELD_TYPE = [
(TEXT, _("Texte long")),
(CHAR, _("Texte court")),
]
event = models.ForeignKey(
Event,
on_delete=models.CASCADE,
related_name="commentfields",
verbose_name=_("événement"),
)
name = models.CharField(_("nom du champ"), max_length=200)
fieldtype = models.CharField(
_("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"),
default=0,
help_text=_(
"plus petit en premier, ordre alphabétique sur le nom si "
"ambiguïté"
),
)
class Meta:
verbose_name = _("champ")
verbose_name_plural = _("champs")
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)
class Meta:
unique_together = ("commentfield", "registration")
def __str__(self):
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",
verbose_name=_("événement"),
)
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",
verbose_name=_("événement"),
)
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.SET_NULL,
null=True,
verbose_name=_("utilisateur"),
)
event = models.ForeignKey(
Event,
on_delete=models.CASCADE,
verbose_name=_("événement"),
)
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_plural = _("inscriptions")
unique_together = ("user", "event")
def __str__(self):
return (
_("Inscription de %(user)s à %(event)s")
% {"user": self.user, "event": self.event}
)

View file

@ -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

View file

@ -20,3 +20,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