Merge branch 'master' into Roussille/bda
This commit is contained in:
commit
556c354f8a
|
@ -7,6 +7,7 @@ variables:
|
||||||
DJANGO_SETTINGS_MODULE: "cof.settings.prod"
|
DJANGO_SETTINGS_MODULE: "cof.settings.prod"
|
||||||
DBHOST: "postgres"
|
DBHOST: "postgres"
|
||||||
REDIS_HOST: "redis"
|
REDIS_HOST: "redis"
|
||||||
|
REDIS_PASSWD: "dummy"
|
||||||
|
|
||||||
# Cached packages
|
# Cached packages
|
||||||
PYTHONPATH: "$CI_PROJECT_DIR/vendor/python"
|
PYTHONPATH: "$CI_PROJECT_DIR/vendor/python"
|
||||||
|
@ -16,6 +17,8 @@ variables:
|
||||||
POSTGRES_USER: "cof_gestion"
|
POSTGRES_USER: "cof_gestion"
|
||||||
POSTGRES_DB: "cof_gestion"
|
POSTGRES_DB: "cof_gestion"
|
||||||
|
|
||||||
|
# psql password authentication
|
||||||
|
PGPASSWORD: $POSTGRES_PASSWORD
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
paths:
|
paths:
|
||||||
|
@ -27,10 +30,10 @@ before_script:
|
||||||
- mkdir -p vendor/{python,pip,apt}
|
- mkdir -p vendor/{python,pip,apt}
|
||||||
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client
|
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client
|
||||||
- sed -E 's/^REDIS_HOST.*/REDIS_HOST = "redis"/' cof/settings/secret_example.py > cof/settings/secret.py
|
- sed -E 's/^REDIS_HOST.*/REDIS_HOST = "redis"/' cof/settings/secret_example.py > cof/settings/secret.py
|
||||||
|
- sed -i.bak -E 's;^REDIS_PASSWD = .*$;REDIS_PASSWD = "";' cof/settings/secret.py
|
||||||
# Remove the old test database if it has not been done yet
|
# Remove the old test database if it has not been done yet
|
||||||
- psql --username=cof_gestion --password="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4" --host="$DBHOST"
|
- psql --username=$POSTGRES_USER --host=$DBHOST -c "DROP DATABASE IF EXISTS test_$POSTGRES_DB"
|
||||||
-e "DROP DATABASE test_$DBNAME" || true
|
- pip install --upgrade --cache-dir vendor/pip -t vendor/python -r requirements.txt
|
||||||
- pip install --cache-dir vendor/pip -t vendor/python -r requirements.txt
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# GestioCOF
|
# GestioCOF
|
||||||
|
|
||||||
|
![build_status](https://git.eleves.ens.fr/cof-geek/gestioCOF/badges/master/build.svg)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Vagrant
|
### Vagrant
|
||||||
|
|
1
TODO_PROD.md
Normal file
1
TODO_PROD.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
- Changer les urls dans les mails "bda-revente" et "bda-shotgun"
|
29
bda/admin.py
29
bda/admin.py
|
@ -1,6 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import autocomplete_light
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from custommail.shortcuts import send_mass_custom_mail
|
from custommail.shortcuts import send_mass_custom_mail
|
||||||
|
|
||||||
|
@ -9,6 +8,9 @@ from django.db.models import Sum, Count
|
||||||
from django.template.defaultfilters import pluralize
|
from django.template.defaultfilters import pluralize
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
|
from dal.autocomplete import ModelSelect2
|
||||||
|
|
||||||
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
||||||
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
||||||
|
|
||||||
|
@ -24,8 +26,17 @@ class ReadOnlyMixin(object):
|
||||||
return readonly_fields + self.readonly_fields_update
|
return readonly_fields + self.readonly_fields_update
|
||||||
|
|
||||||
|
|
||||||
|
class ChoixSpectacleAdminForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
widgets = {
|
||||||
|
'participant': ModelSelect2(url='bda-participant-autocomplete'),
|
||||||
|
'spectacle': ModelSelect2(url='bda-spectacle-autocomplete'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ChoixSpectacleInline(admin.TabularInline):
|
class ChoixSpectacleInline(admin.TabularInline):
|
||||||
model = ChoixSpectacle
|
model = ChoixSpectacle
|
||||||
|
form = ChoixSpectacleAdminForm
|
||||||
sortable_field_name = "priority"
|
sortable_field_name = "priority"
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,17 +67,17 @@ class AttributionInline(admin.TabularInline):
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
qs = super().get_queryset(request)
|
qs = super().get_queryset(request)
|
||||||
if self.listing is not None:
|
if self.listing is not None:
|
||||||
qs.filter(spectacle__listing=self.listing)
|
qs = qs.filter(spectacle__listing=self.listing)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class WithListingAttributionInline(AttributionInline):
|
class WithListingAttributionInline(AttributionInline):
|
||||||
|
exclude = ('given', )
|
||||||
form = WithListingAttributionTabularAdminForm
|
form = WithListingAttributionTabularAdminForm
|
||||||
listing = True
|
listing = True
|
||||||
|
|
||||||
|
|
||||||
class WithoutListingAttributionInline(AttributionInline):
|
class WithoutListingAttributionInline(AttributionInline):
|
||||||
exclude = ('given', )
|
|
||||||
form = WithoutListingAttributionTabularAdminForm
|
form = WithoutListingAttributionTabularAdminForm
|
||||||
listing = False
|
listing = False
|
||||||
|
|
||||||
|
@ -180,7 +191,7 @@ class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
class ChoixSpectacleAdmin(admin.ModelAdmin):
|
class ChoixSpectacleAdmin(admin.ModelAdmin):
|
||||||
form = autocomplete_light.modelform_factory(ChoixSpectacle, exclude=[])
|
form = ChoixSpectacleAdminForm
|
||||||
|
|
||||||
def tirage(self, obj):
|
def tirage(self, obj):
|
||||||
return obj.participant.tirage
|
return obj.participant.tirage
|
||||||
|
@ -225,7 +236,7 @@ class SpectacleReventeAdminForm(forms.ModelForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['answered_mail'].queryset = (
|
self.fields['confirmed_entry'].queryset = (
|
||||||
Participant.objects
|
Participant.objects
|
||||||
.select_related('user', 'tirage')
|
.select_related('user', 'tirage')
|
||||||
)
|
)
|
||||||
|
@ -288,13 +299,7 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||||
count = queryset.count()
|
count = queryset.count()
|
||||||
for revente in queryset.filter(
|
for revente in queryset.filter(
|
||||||
attribution__spectacle__date__gte=timezone.now()):
|
attribution__spectacle__date__gte=timezone.now()):
|
||||||
revente.date = timezone.now() - timedelta(hours=1)
|
revente.reset(new_date=timezone.now() - timedelta(hours=1))
|
||||||
revente.soldTo = None
|
|
||||||
revente.notif_sent = False
|
|
||||||
revente.tirage_done = False
|
|
||||||
if revente.answered_mail:
|
|
||||||
revente.answered_mail.clear()
|
|
||||||
revente.save()
|
|
||||||
self.message_user(
|
self.message_user(
|
||||||
request,
|
request,
|
||||||
"%d attribution%s %s été réinitialisée%s avec succès." % (
|
"%d attribution%s %s été réinitialisée%s avec succès." % (
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import autocomplete_light
|
|
||||||
|
|
||||||
from bda.models import Participant, Spectacle
|
|
||||||
|
|
||||||
autocomplete_light.register(
|
|
||||||
Participant, search_fields=('user__username', 'user__first_name',
|
|
||||||
'user__last_name'),
|
|
||||||
autocomplete_js_attributes={'placeholder': 'participant...'})
|
|
||||||
|
|
||||||
autocomplete_light.register(
|
|
||||||
Spectacle, search_fields=('title', ),
|
|
||||||
autocomplete_js_attributes={'placeholder': 'spectacle...'})
|
|
103
bda/forms.py
103
bda/forms.py
|
@ -4,7 +4,7 @@ from django import forms
|
||||||
from django.forms.models import BaseInlineFormSet
|
from django.forms.models import BaseInlineFormSet
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from bda.models import Attribution, Spectacle
|
from bda.models import Attribution, Spectacle, SpectacleRevente
|
||||||
|
|
||||||
|
|
||||||
class InscriptionInlineFormSet(BaseInlineFormSet):
|
class InscriptionInlineFormSet(BaseInlineFormSet):
|
||||||
|
@ -43,7 +43,33 @@ class TokenForm(forms.Form):
|
||||||
|
|
||||||
class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
||||||
def label_from_instance(self, obj):
|
def label_from_instance(self, obj):
|
||||||
return "%s" % str(obj.spectacle)
|
return str(obj.spectacle)
|
||||||
|
|
||||||
|
|
||||||
|
class ReventeModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
||||||
|
def __init__(self, *args, own=True, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.own = own
|
||||||
|
|
||||||
|
def label_from_instance(self, obj):
|
||||||
|
label = "{show}{suffix}"
|
||||||
|
suffix = ""
|
||||||
|
if self.own:
|
||||||
|
# C'est notre propre revente : pas besoin de spécifier le vendeur
|
||||||
|
if obj.soldTo is not None:
|
||||||
|
suffix = " -- Vendue à {firstname} {lastname}".format(
|
||||||
|
firstname=obj.soldTo.user.first_name,
|
||||||
|
lastname=obj.soldTo.user.last_name,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Ce n'est pas à nous : on ne voit jamais l'acheteur
|
||||||
|
suffix = " -- Vendue par {firstname} {lastname}".format(
|
||||||
|
firstname=obj.seller.user.first_name,
|
||||||
|
lastname=obj.seller.user.last_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
return label.format(show=str(obj.attribution.spectacle),
|
||||||
|
suffix=suffix)
|
||||||
|
|
||||||
|
|
||||||
class ResellForm(forms.Form):
|
class ResellForm(forms.Form):
|
||||||
|
@ -65,7 +91,8 @@ class ResellForm(forms.Form):
|
||||||
|
|
||||||
|
|
||||||
class AnnulForm(forms.Form):
|
class AnnulForm(forms.Form):
|
||||||
attributions = AttributionModelMultipleChoiceField(
|
reventes = ReventeModelMultipleChoiceField(
|
||||||
|
own=True,
|
||||||
label='',
|
label='',
|
||||||
queryset=Attribution.objects.none(),
|
queryset=Attribution.objects.none(),
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
@ -73,14 +100,13 @@ class AnnulForm(forms.Form):
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
def __init__(self, participant, *args, **kwargs):
|
||||||
super(AnnulForm, self).__init__(*args, **kwargs)
|
super(AnnulForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['attributions'].queryset = (
|
self.fields['reventes'].queryset = (
|
||||||
participant.attribution_set
|
participant.original_shows
|
||||||
.filter(spectacle__date__gte=timezone.now(),
|
.filter(attribution__spectacle__date__gte=timezone.now(),
|
||||||
revente__isnull=False,
|
notif_sent=False,
|
||||||
revente__notif_sent=False,
|
soldTo__isnull=True)
|
||||||
revente__soldTo__isnull=True)
|
.select_related('attribution__spectacle',
|
||||||
.select_related('spectacle', 'spectacle__location',
|
'attribution__spectacle__location')
|
||||||
'participant__user')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,19 +125,58 @@ class InscriptionReventeForm(forms.Form):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReventeTirageAnnulForm(forms.Form):
|
||||||
|
reventes = ReventeModelMultipleChoiceField(
|
||||||
|
own=False,
|
||||||
|
label='',
|
||||||
|
queryset=SpectacleRevente.objects.none(),
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, participant, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['reventes'].queryset = (
|
||||||
|
participant.entered.filter(soldTo__isnull=True)
|
||||||
|
.select_related('attribution__spectacle',
|
||||||
|
'seller__user')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReventeTirageForm(forms.Form):
|
||||||
|
reventes = ReventeModelMultipleChoiceField(
|
||||||
|
own=False,
|
||||||
|
label='',
|
||||||
|
queryset=SpectacleRevente.objects.none(),
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, participant, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['reventes'].queryset = (
|
||||||
|
SpectacleRevente.objects.filter(
|
||||||
|
notif_sent=True,
|
||||||
|
shotgun=False,
|
||||||
|
tirage_done=False
|
||||||
|
).exclude(confirmed_entry=participant)
|
||||||
|
.select_related('attribution__spectacle')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SoldForm(forms.Form):
|
class SoldForm(forms.Form):
|
||||||
attributions = AttributionModelMultipleChoiceField(
|
reventes = ReventeModelMultipleChoiceField(
|
||||||
|
own=True,
|
||||||
label='',
|
label='',
|
||||||
queryset=Attribution.objects.none(),
|
queryset=Attribution.objects.none(),
|
||||||
widget=forms.CheckboxSelectMultiple)
|
widget=forms.CheckboxSelectMultiple)
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
def __init__(self, participant, *args, **kwargs):
|
||||||
super(SoldForm, self).__init__(*args, **kwargs)
|
super(SoldForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['attributions'].queryset = (
|
self.fields['reventes'].queryset = (
|
||||||
participant.attribution_set
|
participant.original_shows
|
||||||
.filter(revente__isnull=False,
|
.filter(soldTo__isnull=False)
|
||||||
revente__soldTo__isnull=False)
|
.exclude(soldTo=participant)
|
||||||
.exclude(revente__soldTo=participant)
|
.select_related('attribution__spectacle',
|
||||||
.select_related('spectacle', 'spectacle__location',
|
'attribution__spectacle__location')
|
||||||
'participant__user')
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,7 +6,6 @@ Gestion en ligne de commande des reventes.
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
from django.core.management import BaseCommand
|
from django.core.management import BaseCommand
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from bda.models import SpectacleRevente
|
from bda.models import SpectacleRevente
|
||||||
|
@ -21,23 +20,36 @@ class Command(BaseCommand):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
reventes = SpectacleRevente.objects.all()
|
reventes = SpectacleRevente.objects.all()
|
||||||
for revente in reventes:
|
for revente in reventes:
|
||||||
# Check si < 24h
|
# Le spectacle est bientôt et on a pas encore envoyé de mail :
|
||||||
if (revente.attribution.spectacle.date <=
|
# on met la place au shotgun et on prévient.
|
||||||
revente.date + timedelta(days=1)) and \
|
if revente.is_urgent and not revente.notif_sent:
|
||||||
now >= revente.date + timedelta(minutes=15) and \
|
if revente.can_notif:
|
||||||
not revente.notif_sent:
|
|
||||||
self.stdout.write(str(now))
|
self.stdout.write(str(now))
|
||||||
revente.mail_shotgun()
|
revente.mail_shotgun()
|
||||||
self.stdout.write("Mail de disponibilité immédiate envoyé")
|
self.stdout.write(
|
||||||
# Check si délai de retrait dépassé
|
"Mails de disponibilité immédiate envoyés "
|
||||||
elif (now >= revente.date + timedelta(hours=1) and
|
"pour la revente [%s]" % revente
|
||||||
not revente.notif_sent):
|
)
|
||||||
|
|
||||||
|
# Le spectacle est dans plus longtemps : on prévient
|
||||||
|
elif (revente.can_notif and not revente.notif_sent):
|
||||||
self.stdout.write(str(now))
|
self.stdout.write(str(now))
|
||||||
revente.send_notif()
|
revente.send_notif()
|
||||||
self.stdout.write("Mail d'inscription à une revente envoyé")
|
self.stdout.write(
|
||||||
# Check si tirage à faire
|
"Mails d'inscription à la revente [%s] envoyés"
|
||||||
elif (now >= revente.date_tirage and
|
% revente
|
||||||
not revente.tirage_done):
|
)
|
||||||
|
|
||||||
|
# On fait le tirage
|
||||||
|
elif (now >= revente.date_tirage and not revente.tirage_done):
|
||||||
self.stdout.write(str(now))
|
self.stdout.write(str(now))
|
||||||
revente.tirage()
|
winner = revente.tirage()
|
||||||
self.stdout.write("Tirage effectué, mails envoyés")
|
self.stdout.write(
|
||||||
|
"Tirage effectué pour la revente [%s]"
|
||||||
|
% revente
|
||||||
|
)
|
||||||
|
|
||||||
|
if winner:
|
||||||
|
self.stdout.write("Gagnant : %s" % winner.user)
|
||||||
|
else:
|
||||||
|
self.stdout.write("Pas de gagnant ; place au shotgun")
|
||||||
|
|
|
@ -59,7 +59,7 @@ class Migration(migrations.Migration):
|
||||||
('price', models.FloatField(verbose_name=b"Prix d'une place", blank=True)),
|
('price', models.FloatField(verbose_name=b"Prix d'une place", blank=True)),
|
||||||
('slots', models.IntegerField(verbose_name=b'Places')),
|
('slots', models.IntegerField(verbose_name=b'Places')),
|
||||||
('priority', models.IntegerField(default=1000, verbose_name=b'Priorit\xc3\xa9')),
|
('priority', models.IntegerField(default=1000, verbose_name=b'Priorit\xc3\xa9')),
|
||||||
('location', models.ForeignKey(to='bda.Salle')),
|
('location', models.ForeignKey(to='bda.Salle', on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('priority', 'date', 'title'),
|
'ordering': ('priority', 'date', 'title'),
|
||||||
|
@ -79,27 +79,27 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='participant',
|
model_name='participant',
|
||||||
name='user',
|
name='user',
|
||||||
field=models.OneToOneField(to=settings.AUTH_USER_MODEL),
|
field=models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='choixspectacle',
|
model_name='choixspectacle',
|
||||||
name='participant',
|
name='participant',
|
||||||
field=models.ForeignKey(to='bda.Participant'),
|
field=models.ForeignKey(to='bda.Participant', on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='choixspectacle',
|
model_name='choixspectacle',
|
||||||
name='spectacle',
|
name='spectacle',
|
||||||
field=models.ForeignKey(related_name='participants', to='bda.Spectacle'),
|
field=models.ForeignKey(related_name='participants', to='bda.Spectacle', on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='attribution',
|
model_name='attribution',
|
||||||
name='participant',
|
name='participant',
|
||||||
field=models.ForeignKey(to='bda.Participant'),
|
field=models.ForeignKey(to='bda.Participant', on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='attribution',
|
model_name='attribution',
|
||||||
name='spectacle',
|
name='spectacle',
|
||||||
field=models.ForeignKey(related_name='attribues', to='bda.Spectacle'),
|
field=models.ForeignKey(related_name='attribues', to='bda.Spectacle', on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='choixspectacle',
|
name='choixspectacle',
|
||||||
|
|
|
@ -5,17 +5,34 @@ from django.db import migrations, models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
def forwards_func(apps, schema_editor):
|
|
||||||
|
def fill_tirage_fields(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Create a `Tirage` to fill new field `tirage` of `Participant`
|
||||||
|
and `Spectacle` already existing.
|
||||||
|
"""
|
||||||
|
Participant = apps.get_model("bda", "Participant")
|
||||||
|
Spectacle = apps.get_model("bda", "Spectacle")
|
||||||
Tirage = apps.get_model("bda", "Tirage")
|
Tirage = apps.get_model("bda", "Tirage")
|
||||||
db_alias = schema_editor.connection.alias
|
|
||||||
Tirage.objects.using(db_alias).bulk_create([
|
# These querysets only contains instances not linked to any `Tirage`.
|
||||||
Tirage(
|
participants = Participant.objects.filter(tirage=None)
|
||||||
id=1,
|
spectacles = Spectacle.objects.filter(tirage=None)
|
||||||
|
|
||||||
|
if not participants.count() and not spectacles.count():
|
||||||
|
# No need to create a "trash" tirage.
|
||||||
|
return
|
||||||
|
|
||||||
|
tirage = Tirage.objects.create(
|
||||||
title="Tirage de test (migration)",
|
title="Tirage de test (migration)",
|
||||||
active=False,
|
active=False,
|
||||||
ouverture=timezone.now(),
|
ouverture=timezone.now(),
|
||||||
fermeture=timezone.now()),
|
fermeture=timezone.now(),
|
||||||
])
|
)
|
||||||
|
|
||||||
|
participants.update(tirage=tirage)
|
||||||
|
spectacles.update(tirage=tirage)
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
@ -35,22 +52,33 @@ class Migration(migrations.Migration):
|
||||||
('active', models.BooleanField(default=True, verbose_name=b'Tirage actif')),
|
('active', models.BooleanField(default=True, verbose_name=b'Tirage actif')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='participant',
|
model_name='participant',
|
||||||
name='user',
|
name='user',
|
||||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
|
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
|
# Create fields `spectacle` for `Participant` and `Spectacle` models.
|
||||||
|
# These fields are not nullable, but we first create them as nullable
|
||||||
|
# to give a default value for existing instances of these models.
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='participant',
|
model_name='participant',
|
||||||
name='tirage',
|
name='tirage',
|
||||||
field=models.ForeignKey(default=1, to='bda.Tirage'),
|
field=models.ForeignKey(to='bda.Tirage', null=True, on_delete=models.CASCADE),
|
||||||
preserve_default=False,
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name='spectacle',
|
||||||
name='tirage',
|
name='tirage',
|
||||||
field=models.ForeignKey(default=1, to='bda.Tirage'),
|
field=models.ForeignKey(to='bda.Tirage', null=True, on_delete=models.CASCADE),
|
||||||
preserve_default=False,
|
),
|
||||||
|
migrations.RunPython(fill_tirage_fields, migrations.RunPython.noop),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='participant',
|
||||||
|
name='tirage',
|
||||||
|
field=models.ForeignKey(to='bda.Tirage', on_delete=models.CASCADE),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='spectacle',
|
||||||
|
name='tirage',
|
||||||
|
field=models.ForeignKey(to='bda.Tirage', on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -73,6 +73,7 @@ class Migration(migrations.Migration):
|
||||||
model_name='spectacle',
|
model_name='spectacle',
|
||||||
name='category',
|
name='category',
|
||||||
field=models.ForeignKey(blank=True, to='bda.CategorieSpectacle',
|
field=models.ForeignKey(blank=True, to='bda.CategorieSpectacle',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
null=True),
|
null=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
|
@ -84,6 +85,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='quote',
|
model_name='quote',
|
||||||
name='spectacle',
|
name='spectacle',
|
||||||
field=models.ForeignKey(to='bda.Spectacle'),
|
field=models.ForeignKey(to='bda.Spectacle',
|
||||||
|
on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -47,12 +47,14 @@ class Migration(migrations.Migration):
|
||||||
model_name='spectaclerevente',
|
model_name='spectaclerevente',
|
||||||
name='attribution',
|
name='attribution',
|
||||||
field=models.OneToOneField(to='bda.Attribution',
|
field=models.OneToOneField(to='bda.Attribution',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
related_name='revente'),
|
related_name='revente'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectaclerevente',
|
model_name='spectaclerevente',
|
||||||
name='seller',
|
name='seller',
|
||||||
field=models.ForeignKey(to='bda.Participant',
|
field=models.ForeignKey(to='bda.Participant',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
verbose_name='Vendeur',
|
verbose_name='Vendeur',
|
||||||
related_name='original_shows'),
|
related_name='original_shows'),
|
||||||
),
|
),
|
||||||
|
@ -60,6 +62,7 @@ class Migration(migrations.Migration):
|
||||||
model_name='spectaclerevente',
|
model_name='spectaclerevente',
|
||||||
name='soldTo',
|
name='soldTo',
|
||||||
field=models.ForeignKey(to='bda.Participant',
|
field=models.ForeignKey(to='bda.Participant',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
verbose_name='Vendue à', null=True,
|
verbose_name='Vendue à', null=True,
|
||||||
blank=True),
|
blank=True),
|
||||||
),
|
),
|
||||||
|
|
29
bda/migrations/0012_notif_time.py
Normal file
29
bda/migrations/0012_notif_time.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('bda', '0011_tirage_appear_catalogue'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='spectaclerevente',
|
||||||
|
old_name='answered_mail',
|
||||||
|
new_name='confirmed_entry',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='spectaclerevente',
|
||||||
|
name='confirmed_entry',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='entered', to='bda.Participant'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='spectaclerevente',
|
||||||
|
name='notif_time',
|
||||||
|
field=models.DateTimeField(blank=True, verbose_name="Moment d'envoi de la notification", null=True),
|
||||||
|
),
|
||||||
|
]
|
180
bda/models.py
180
bda/models.py
|
@ -6,12 +6,15 @@ from datetime import timedelta
|
||||||
from custommail.shortcuts import send_mass_custom_mail
|
from custommail.shortcuts import send_mass_custom_mail
|
||||||
|
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
|
from django.core import mail
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone, formats
|
from django.utils import timezone, formats
|
||||||
|
|
||||||
|
from custommail.models import CustomMail
|
||||||
|
|
||||||
|
|
||||||
def get_generic_user():
|
def get_generic_user():
|
||||||
generic, _ = User.objects.get_or_create(
|
generic, _ = User.objects.get_or_create(
|
||||||
|
@ -59,9 +62,12 @@ class CategorieSpectacle(models.Model):
|
||||||
|
|
||||||
class Spectacle(models.Model):
|
class Spectacle(models.Model):
|
||||||
title = models.CharField("Titre", max_length=300)
|
title = models.CharField("Titre", max_length=300)
|
||||||
category = models.ForeignKey(CategorieSpectacle, blank=True, null=True)
|
category = models.ForeignKey(
|
||||||
|
CategorieSpectacle, on_delete=models.CASCADE,
|
||||||
|
blank=True, null=True,
|
||||||
|
)
|
||||||
date = models.DateTimeField("Date & heure")
|
date = models.DateTimeField("Date & heure")
|
||||||
location = models.ForeignKey(Salle)
|
location = models.ForeignKey(Salle, on_delete=models.CASCADE)
|
||||||
vips = models.TextField('Personnalités', blank=True)
|
vips = models.TextField('Personnalités', blank=True)
|
||||||
description = models.TextField("Description", blank=True)
|
description = models.TextField("Description", blank=True)
|
||||||
slots_description = models.TextField("Description des places", blank=True)
|
slots_description = models.TextField("Description des places", blank=True)
|
||||||
|
@ -71,7 +77,7 @@ class Spectacle(models.Model):
|
||||||
max_length=500)
|
max_length=500)
|
||||||
price = models.FloatField("Prix d'une place")
|
price = models.FloatField("Prix d'une place")
|
||||||
slots = models.IntegerField("Places")
|
slots = models.IntegerField("Places")
|
||||||
tirage = models.ForeignKey(Tirage)
|
tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
|
||||||
listing = models.BooleanField("Les places sont sur listing")
|
listing = models.BooleanField("Les places sont sur listing")
|
||||||
rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True,
|
rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True,
|
||||||
null=True)
|
null=True)
|
||||||
|
@ -135,7 +141,7 @@ class Spectacle(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class Quote(models.Model):
|
class Quote(models.Model):
|
||||||
spectacle = models.ForeignKey(Spectacle)
|
spectacle = models.ForeignKey(Spectacle, on_delete=models.CASCADE)
|
||||||
text = models.TextField('Citation')
|
text = models.TextField('Citation')
|
||||||
author = models.CharField('Auteur', max_length=200)
|
author = models.CharField('Auteur', max_length=200)
|
||||||
|
|
||||||
|
@ -149,7 +155,7 @@ PAYMENT_TYPES = (
|
||||||
|
|
||||||
|
|
||||||
class Participant(models.Model):
|
class Participant(models.Model):
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
choices = models.ManyToManyField(Spectacle,
|
choices = models.ManyToManyField(Spectacle,
|
||||||
through="ChoixSpectacle",
|
through="ChoixSpectacle",
|
||||||
related_name="chosen_by")
|
related_name="chosen_by")
|
||||||
|
@ -160,7 +166,7 @@ class Participant(models.Model):
|
||||||
paymenttype = models.CharField("Moyen de paiement",
|
paymenttype = models.CharField("Moyen de paiement",
|
||||||
max_length=6, choices=PAYMENT_TYPES,
|
max_length=6, choices=PAYMENT_TYPES,
|
||||||
blank=True)
|
blank=True)
|
||||||
tirage = models.ForeignKey(Tirage)
|
tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
|
||||||
choicesrevente = models.ManyToManyField(Spectacle,
|
choicesrevente = models.ManyToManyField(Spectacle,
|
||||||
related_name="subscribed",
|
related_name="subscribed",
|
||||||
blank=True)
|
blank=True)
|
||||||
|
@ -168,6 +174,7 @@ class Participant(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s - %s" % (self.user, self.tirage.title)
|
return "%s - %s" % (self.user, self.tirage.title)
|
||||||
|
|
||||||
|
|
||||||
DOUBLE_CHOICES = (
|
DOUBLE_CHOICES = (
|
||||||
("1", "1 place"),
|
("1", "1 place"),
|
||||||
("autoquit", "2 places si possible, 1 sinon"),
|
("autoquit", "2 places si possible, 1 sinon"),
|
||||||
|
@ -176,8 +183,11 @@ DOUBLE_CHOICES = (
|
||||||
|
|
||||||
|
|
||||||
class ChoixSpectacle(models.Model):
|
class ChoixSpectacle(models.Model):
|
||||||
participant = models.ForeignKey(Participant)
|
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
|
||||||
spectacle = models.ForeignKey(Spectacle, related_name="participants")
|
spectacle = models.ForeignKey(
|
||||||
|
Spectacle, on_delete=models.CASCADE,
|
||||||
|
related_name="participants",
|
||||||
|
)
|
||||||
priority = models.PositiveIntegerField("Priorité")
|
priority = models.PositiveIntegerField("Priorité")
|
||||||
double_choice = models.CharField("Nombre de places",
|
double_choice = models.CharField("Nombre de places",
|
||||||
default="1", choices=DOUBLE_CHOICES,
|
default="1", choices=DOUBLE_CHOICES,
|
||||||
|
@ -204,8 +214,11 @@ class ChoixSpectacle(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class Attribution(models.Model):
|
class Attribution(models.Model):
|
||||||
participant = models.ForeignKey(Participant)
|
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
|
||||||
spectacle = models.ForeignKey(Spectacle, related_name="attribues")
|
spectacle = models.ForeignKey(
|
||||||
|
Spectacle, on_delete=models.CASCADE,
|
||||||
|
related_name="attribues",
|
||||||
|
)
|
||||||
given = models.BooleanField("Donnée", default=False)
|
given = models.BooleanField("Donnée", default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -214,36 +227,83 @@ class Attribution(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class SpectacleRevente(models.Model):
|
class SpectacleRevente(models.Model):
|
||||||
attribution = models.OneToOneField(Attribution,
|
attribution = models.OneToOneField(
|
||||||
related_name="revente")
|
Attribution, on_delete=models.CASCADE,
|
||||||
|
related_name="revente",
|
||||||
|
)
|
||||||
date = models.DateTimeField("Date de mise en vente",
|
date = models.DateTimeField("Date de mise en vente",
|
||||||
default=timezone.now)
|
default=timezone.now)
|
||||||
answered_mail = models.ManyToManyField(Participant,
|
confirmed_entry = models.ManyToManyField(Participant,
|
||||||
related_name="wanted",
|
related_name="entered",
|
||||||
blank=True)
|
blank=True)
|
||||||
seller = models.ForeignKey(Participant,
|
seller = models.ForeignKey(
|
||||||
|
Participant, on_delete=models.CASCADE,
|
||||||
|
verbose_name="Vendeur",
|
||||||
related_name="original_shows",
|
related_name="original_shows",
|
||||||
verbose_name="Vendeur")
|
)
|
||||||
soldTo = models.ForeignKey(Participant, blank=True, null=True,
|
soldTo = models.ForeignKey(
|
||||||
verbose_name="Vendue à")
|
Participant, on_delete=models.CASCADE,
|
||||||
|
verbose_name="Vendue à",
|
||||||
|
blank=True, null=True,
|
||||||
|
)
|
||||||
|
|
||||||
notif_sent = models.BooleanField("Notification envoyée",
|
notif_sent = models.BooleanField("Notification envoyée",
|
||||||
default=False)
|
default=False)
|
||||||
|
|
||||||
|
notif_time = models.DateTimeField("Moment d'envoi de la notification",
|
||||||
|
blank=True, null=True)
|
||||||
|
|
||||||
tirage_done = models.BooleanField("Tirage effectué",
|
tirage_done = models.BooleanField("Tirage effectué",
|
||||||
default=False)
|
default=False)
|
||||||
|
|
||||||
shotgun = models.BooleanField("Disponible immédiatement",
|
shotgun = models.BooleanField("Disponible immédiatement",
|
||||||
default=False)
|
default=False)
|
||||||
|
####
|
||||||
|
# Some class attributes
|
||||||
|
###
|
||||||
|
# TODO : settings ?
|
||||||
|
|
||||||
|
# Temps minimum entre le tirage et le spectacle
|
||||||
|
min_margin = timedelta(days=5)
|
||||||
|
|
||||||
|
# Temps entre la création d'une revente et l'envoi du mail
|
||||||
|
remorse_time = timedelta(hours=1)
|
||||||
|
|
||||||
|
# Temps min/max d'attente avant le tirage
|
||||||
|
max_wait_time = timedelta(days=3)
|
||||||
|
min_wait_time = timedelta(days=1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def real_notif_time(self):
|
||||||
|
if self.notif_time:
|
||||||
|
return self.notif_time
|
||||||
|
else:
|
||||||
|
return self.date + self.remorse_time
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def date_tirage(self):
|
def date_tirage(self):
|
||||||
"""Renvoie la date du tirage au sort de la revente."""
|
"""Renvoie la date du tirage au sort de la revente."""
|
||||||
# L'acheteur doit être connu au plus 12h avant le spectacle
|
|
||||||
remaining_time = (self.attribution.spectacle.date
|
remaining_time = (self.attribution.spectacle.date
|
||||||
- self.date - timedelta(hours=13))
|
- self.real_notif_time - self.min_margin)
|
||||||
# Au minimum, on attend 2 jours avant le tirage
|
|
||||||
delay = min(remaining_time, timedelta(days=2))
|
delay = min(remaining_time, self.max_wait_time)
|
||||||
# Le vendeur a aussi 1h pour changer d'avis
|
|
||||||
return self.date + delay + timedelta(hours=1)
|
return self.real_notif_time + delay
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_urgent(self):
|
||||||
|
"""
|
||||||
|
Renvoie True iff la revente doit être mise au shotgun directement.
|
||||||
|
Plus précisément, on doit avoir min_margin + min_wait_time de marge.
|
||||||
|
"""
|
||||||
|
spectacle_date = self.attribution.spectacle.date
|
||||||
|
return (spectacle_date <= timezone.now() + self.min_margin
|
||||||
|
+ self.min_wait_time)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_notif(self):
|
||||||
|
return (timezone.now() >= self.date + self.remorse_time)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s -- %s" % (self.seller,
|
return "%s -- %s" % (self.seller,
|
||||||
|
@ -252,6 +312,18 @@ class SpectacleRevente(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Revente"
|
verbose_name = "Revente"
|
||||||
|
|
||||||
|
def reset(self, new_date=timezone.now()):
|
||||||
|
"""Réinitialise la revente pour permettre une remise sur le marché"""
|
||||||
|
self.seller = self.attribution.participant
|
||||||
|
self.date = new_date
|
||||||
|
self.confirmed_entry.clear()
|
||||||
|
self.soldTo = None
|
||||||
|
self.notif_sent = False
|
||||||
|
self.notif_time = None
|
||||||
|
self.tirage_done = False
|
||||||
|
self.shotgun = False
|
||||||
|
self.save()
|
||||||
|
|
||||||
def send_notif(self):
|
def send_notif(self):
|
||||||
"""
|
"""
|
||||||
Envoie une notification pour indiquer la mise en vente d'une place sur
|
Envoie une notification pour indiquer la mise en vente d'une place sur
|
||||||
|
@ -272,6 +344,7 @@ class SpectacleRevente(models.Model):
|
||||||
]
|
]
|
||||||
send_mass_custom_mail(datatuple)
|
send_mass_custom_mail(datatuple)
|
||||||
self.notif_sent = True
|
self.notif_sent = True
|
||||||
|
self.notif_time = timezone.now()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def mail_shotgun(self):
|
def mail_shotgun(self):
|
||||||
|
@ -293,58 +366,79 @@ class SpectacleRevente(models.Model):
|
||||||
]
|
]
|
||||||
send_mass_custom_mail(datatuple)
|
send_mass_custom_mail(datatuple)
|
||||||
self.notif_sent = True
|
self.notif_sent = True
|
||||||
|
self.notif_time = timezone.now()
|
||||||
# Flag inutile, sauf si l'horloge interne merde
|
# Flag inutile, sauf si l'horloge interne merde
|
||||||
self.tirage_done = True
|
self.tirage_done = True
|
||||||
self.shotgun = True
|
self.shotgun = True
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def tirage(self):
|
def tirage(self, send_mails=True):
|
||||||
"""
|
"""
|
||||||
Lance le tirage au sort associé à la revente. Un gagnant est choisi
|
Lance le tirage au sort associé à la revente. Un gagnant est choisi
|
||||||
parmis les personnes intéressées par le spectacle. Les personnes sont
|
parmis les personnes intéressées par le spectacle. Les personnes sont
|
||||||
ensuites prévenues par mail du résultat du tirage.
|
ensuites prévenues par mail du résultat du tirage.
|
||||||
"""
|
"""
|
||||||
inscrits = list(self.answered_mail.all())
|
inscrits = list(self.confirmed_entry.all())
|
||||||
spectacle = self.attribution.spectacle
|
spectacle = self.attribution.spectacle
|
||||||
seller = self.seller
|
seller = self.seller
|
||||||
|
winner = None
|
||||||
|
|
||||||
if inscrits:
|
if inscrits:
|
||||||
# Envoie un mail au gagnant et au vendeur
|
# Envoie un mail au gagnant et au vendeur
|
||||||
winner = random.choice(inscrits)
|
winner = random.choice(inscrits)
|
||||||
self.soldTo = winner
|
self.soldTo = winner
|
||||||
datatuple = []
|
if send_mails:
|
||||||
|
mails = []
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'acheteur': winner.user,
|
'acheteur': winner.user,
|
||||||
'vendeur': seller.user,
|
'vendeur': seller.user,
|
||||||
'show': spectacle,
|
'show': spectacle,
|
||||||
}
|
}
|
||||||
datatuple.append((
|
|
||||||
'bda-revente-winner',
|
c_mails_qs = CustomMail.objects.filter(shortname__in=[
|
||||||
context,
|
'bda-revente-winner', 'bda-revente-loser',
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
[winner.user.email],
|
|
||||||
))
|
|
||||||
datatuple.append((
|
|
||||||
'bda-revente-seller',
|
'bda-revente-seller',
|
||||||
|
])
|
||||||
|
|
||||||
|
c_mails = {cm.shortname: cm for cm in c_mails_qs}
|
||||||
|
|
||||||
|
mails.append(
|
||||||
|
c_mails['bda-revente-winner'].get_message(
|
||||||
context,
|
context,
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
from_email=settings.MAIL_DATA['revente']['FROM'],
|
||||||
[seller.user.email]
|
to=[winner.user.email],
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mails.append(
|
||||||
|
c_mails['bda-revente-seller'].get_message(
|
||||||
|
context,
|
||||||
|
from_email=settings.MAIL_DATA['revente']['FROM'],
|
||||||
|
to=[seller.user.email],
|
||||||
|
reply_to=[winner.user.email],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Envoie un mail aux perdants
|
# Envoie un mail aux perdants
|
||||||
for inscrit in inscrits:
|
for inscrit in inscrits:
|
||||||
if inscrit != winner:
|
if inscrit != winner:
|
||||||
new_context = dict(context)
|
new_context = dict(context)
|
||||||
new_context['acheteur'] = inscrit.user
|
new_context['acheteur'] = inscrit.user
|
||||||
datatuple.append((
|
|
||||||
'bda-revente-loser',
|
mails.append(
|
||||||
|
c_mails['bda-revente-loser'].get_message(
|
||||||
new_context,
|
new_context,
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
from_email=settings.MAIL_DATA['revente']['FROM'],
|
||||||
[inscrit.user.email]
|
to=[inscrit.user.email],
|
||||||
))
|
)
|
||||||
send_mass_custom_mail(datatuple)
|
)
|
||||||
|
|
||||||
|
mail_conn = mail.get_connection()
|
||||||
|
mail_conn.send_messages(mails)
|
||||||
# Si personne ne veut de la place, elle part au shotgun
|
# Si personne ne veut de la place, elle part au shotgun
|
||||||
else:
|
else:
|
||||||
self.shotgun = True
|
self.shotgun = True
|
||||||
self.tirage_done = True
|
self.tirage_done = True
|
||||||
self.save()
|
self.save()
|
||||||
|
return winner
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
</tr></thead>
|
</tr></thead>
|
||||||
<tbody class="bda_formset_content">
|
<tbody class="bda_formset_content">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr class="{% cycle row1,row2 %} dynamic-form {% if form.instance.pk %}has_original{% endif %}">
|
<tr class="{% cycle 'row1' 'row2' %} dynamic-form {% if form.instance.pk %}has_original{% endif %}">
|
||||||
{% for field in form.visible_fields %}
|
{% for field in form.visible_fields %}
|
||||||
{% if field.name != "DELETE" and field.name != "priority" %}
|
{% if field.name != "DELETE" and field.name != "priority" %}
|
||||||
<td class="bda-field-{{ field.name }}">
|
<td class="bda-field-{{ field.name }}">
|
||||||
|
|
|
@ -27,6 +27,14 @@ var django = {
|
||||||
var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
|
var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
|
||||||
$(this).attr('for', newFor);
|
$(this).attr('for', newFor);
|
||||||
});
|
});
|
||||||
|
// Cloning <select> element doesn't properly propagate the default
|
||||||
|
// selected <option>, so we set it manually.
|
||||||
|
newElement.find('select').each(function (index, select) {
|
||||||
|
var defaultValue = $(select).find('option[selected]').val();
|
||||||
|
if (typeof defaultValue !== 'undefined') {
|
||||||
|
$(select).val(defaultValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
total++;
|
total++;
|
||||||
$('#id_' + type + '-TOTAL_FORMS').val(total);
|
$('#id_' + type + '-TOTAL_FORMS').val(total);
|
||||||
$(selector).after(newElement);
|
$(selector).after(newElement);
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load bootstrap %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>Inscriptions pour BdA-Revente</h2>
|
|
||||||
<form action="" class="form-horizontal" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="form-group">
|
|
||||||
<h3>Spectacles</h3>
|
|
||||||
<br/>
|
|
||||||
<button type="button" class="btn btn-primary" onClick="select(true)">Tout sélectionner</button>
|
|
||||||
<button type="button" class="btn btn-primary" onClick="select(false)">Tout désélectionner</button>
|
|
||||||
|
|
||||||
<div class="multiple-checkbox">
|
|
||||||
<ul>
|
|
||||||
{% for checkbox in form.spectacles %}
|
|
||||||
<li>{{checkbox}}</li>
|
|
||||||
{%endfor%}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="submit" class="btn btn-primary" value="S'inscrire pour les places sélectionnées">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<script language="JavaScript">
|
|
||||||
function select(check) {
|
|
||||||
checkboxes = document.getElementsByName("spectacles");
|
|
||||||
for(var i=0, n=checkboxes.length;i<n;i++) {
|
|
||||||
checkboxes[i].checked = check;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
|
@ -47,7 +47,7 @@
|
||||||
<div>
|
<div>
|
||||||
<button class="btn btn-default" type="button" onclick="toggle('export-salle')">Afficher/Cacher liste noms</button>
|
<button class="btn btn-default" type="button" onclick="toggle('export-salle')">Afficher/Cacher liste noms</button>
|
||||||
<pre id="export-salle" style="display:none">{% spaceless %}
|
<pre id="export-salle" style="display:none">{% spaceless %}
|
||||||
{% for participant in participants %}{{participant.name}} : {{participant.nb_places}} places
|
{% for participant in participants %}{{ participant.name }} : {{ participant.nb_places }} place{{ participant.nb_places|pluralize }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endspaceless %}</pre>
|
{% endspaceless %}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<h4 class="bda-prix">Total à payer : {{ total|floatformat }}€</h4>
|
<h4 class="bda-prix">Total à payer : {{ total|floatformat }}€</h4>
|
||||||
<br/>
|
<br/>
|
||||||
<p>Ne manque pas un spectacle avec le
|
<p>Ne manque pas un spectacle avec le
|
||||||
<a href="{% url "gestioncof.views.calendar" %}">calendrier
|
<a href="{% url "calendar" %}">calendrier
|
||||||
automatique !</a></p>
|
automatique !</a></p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<h3>Vous n'avez aucune place :(</h3>
|
<h3>Vous n'avez aucune place :(</h3>
|
||||||
|
|
90
bda/templates/bda/revente/manage.html
Normal file
90
bda/templates/bda/revente/manage.html
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
{% load bootstrap %}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
|
||||||
|
<h2>Gestion des places que je revends</h2>
|
||||||
|
{% with resell_attributions=resellform.attributions annul_reventes=annulform.reventes sold_reventes=soldform.reventes %}
|
||||||
|
|
||||||
|
{% if resellform.attributions %}
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<h3>Places non revendues</h3>
|
||||||
|
<form class="form-horizontal" action="" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Cochez les places que vous souhaitez revendre, et validez. Vous aurez
|
||||||
|
ensuite 1h pour changer d'avis avant que la revente soit confirmée et
|
||||||
|
que les notifications soient envoyées aux intéressé·e·s.
|
||||||
|
</div>
|
||||||
|
<div class="bootstrap-form-reduce">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ resellform|bootstrap }}
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if annul_reventes or overdue %}
|
||||||
|
<h3>Places en cours de revente</h3>
|
||||||
|
<form action="" method="post">
|
||||||
|
{% if annul_reventes %}
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Vous pouvez annuler les places mises en vente il y a moins d'une heure.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class='form-group'>
|
||||||
|
<div class='multiple-checkbox'>
|
||||||
|
<ul>
|
||||||
|
{% for revente in annul_reventes %}
|
||||||
|
<li>{{ revente.tag }} {{ revente.choice_label }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% for attrib in overdue %}
|
||||||
|
<li>
|
||||||
|
<input type="checkbox" style="visibility:hidden">
|
||||||
|
{{ attrib.spectacle }}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if annul_reventes %}
|
||||||
|
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if sold_reventes %}
|
||||||
|
<h3>Places revendues</h3>
|
||||||
|
<form action="" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Pour chaque revente, vous devez soit l'annuler soit la confirmer pour
|
||||||
|
transférer la place la place à la personne tirée au sort.
|
||||||
|
|
||||||
|
L'annulation sert par exemple à pouvoir remettre la place en jeu si
|
||||||
|
vous ne parvenez pas à entrer en contact avec la personne tirée au
|
||||||
|
sort.
|
||||||
|
</div>
|
||||||
|
<div class="bootstrap-form-reduce">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ soldform|bootstrap }}
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary" name="transfer">Transférer</button>
|
||||||
|
<button type="submit" class="btn btn-primary" name="reinit">Réinitialiser</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% if not resell_attributions and not annul_attributions and not overdue and not sold_reventes %}
|
||||||
|
<p>Plus de reventes possibles !</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock %}
|
|
@ -5,7 +5,7 @@
|
||||||
{% if shotgun %}
|
{% if shotgun %}
|
||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
{% for spectacle in shotgun %}
|
{% for spectacle in shotgun %}
|
||||||
<li><a href="{% url "bda-buy-revente" spectacle.id %}">{{spectacle}}</a></li>
|
<li><a href="{% url "bda-revente-buy" spectacle.id %}">{{spectacle}}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p> Pas de places disponibles immédiatement, désolé !</p>
|
<p> Pas de places disponibles immédiatement, désolé !</p>
|
46
bda/templates/bda/revente/subscribe.html
Normal file
46
bda/templates/bda/revente/subscribe.html
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
{% load bootstrap %}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
<h2>Inscriptions pour BdA-Revente</h2>
|
||||||
|
<form action="" class="form-horizontal" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Cochez les spectacles pour lesquels vous souhaitez recevoir un
|
||||||
|
notification quand une place est disponible en revente. <br />
|
||||||
|
Lorsque vous validez vos choix, si un tirage au sort est en cours pour
|
||||||
|
un des spectacles que vous avez sélectionné, vous serez automatiquement
|
||||||
|
inscrit à ce tirage.
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
onClick="select(true)">Tout sélectionner</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
onClick="select(false)">Tout désélectionner</button>
|
||||||
|
|
||||||
|
<div class="multiple-checkbox">
|
||||||
|
<ul>
|
||||||
|
{% for checkbox in form.spectacles %}
|
||||||
|
<li>{{ checkbox }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="submit"
|
||||||
|
class="btn btn-primary"
|
||||||
|
value="S'inscrire pour les places sélectionnées">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script language="JavaScript">
|
||||||
|
function select(check) {
|
||||||
|
checkboxes = document.getElementsByName("spectacles");
|
||||||
|
for(var i=0, n=checkboxes.length; i < n; i++) {
|
||||||
|
checkboxes[i].checked = check;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
52
bda/templates/bda/revente/tirages.html
Normal file
52
bda/templates/bda/revente/tirages.html
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
{% load bootstrap %}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
|
||||||
|
<h2>Tirages au sort de reventes</h2>
|
||||||
|
|
||||||
|
{% if annulform.reventes %}
|
||||||
|
<h3>Les reventes auxquelles vous êtes inscrit·e</h3>
|
||||||
|
<form class="form-horizontal" action="" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Vous pouvez vous désinscrire des reventes suivantes tant que le tirage n'a
|
||||||
|
pas eu lieu.
|
||||||
|
</div>
|
||||||
|
<div class="bootstrap-form-reduce">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ annulform|bootstrap }}
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<input type="submit"
|
||||||
|
class="btn btn-primary"
|
||||||
|
name="annul"
|
||||||
|
value="Se désinscrire des tirages sélectionnés">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if subform.reventes %}
|
||||||
|
|
||||||
|
<h3>Tirages en cours</h3>
|
||||||
|
<form class="form-horizontal" action="" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Vous pouvez vous inscrire aux tirage en cours suivants.
|
||||||
|
</div>
|
||||||
|
<div class="bootstrap-form-reduce">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ subform|bootstrap }}
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<input type="submit"
|
||||||
|
class="btn btn-primary"
|
||||||
|
name="subscribe"
|
||||||
|
value="S'inscrire aux tirages sélectionnés">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -6,7 +6,7 @@
|
||||||
<p>Le tirage au sort de cette revente a déjà été effectué !</p>
|
<p>Le tirage au sort de cette revente a déjà été effectué !</p>
|
||||||
|
|
||||||
<p>Si personne n'était intéressé, elle est maintenant disponible
|
<p>Si personne n'était intéressé, elle est maintenant disponible
|
||||||
<a href="{% url "bda-buy-revente" revente.attribution.spectacle.id %}">ici</a>.</p>
|
<a href="{% url "bda-revente-buy" revente.attribution.spectacle.id %}">ici</a>.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p> Il n'est pas encore possible de s'inscrire à cette revente, réessaie dans quelque temps !</p>
|
<p> Il n'est pas encore possible de s'inscrire à cette revente, réessaie dans quelque temps !</p>
|
||||||
{% endif %}
|
{% endif %}
|
|
@ -1,56 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load bootstrap %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
|
|
||||||
<h2>Revente de place</h2>
|
|
||||||
{% with resell_attributions=resellform.attributions annul_attributions=annulform.attributions sold_attributions=soldform.attributions %}
|
|
||||||
|
|
||||||
{% if resellform.attributions %}
|
|
||||||
<h3>Places non revendues</h3>
|
|
||||||
<form class="form-horizontal" action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{resellform|bootstrap}}
|
|
||||||
<div class="form-actions">
|
|
||||||
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
|
||||||
{% if annul_attributions or overdue %}
|
|
||||||
<h3>Places en cours de revente</h3>
|
|
||||||
<form action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class='form-group'>
|
|
||||||
<div class='multiple-checkbox'>
|
|
||||||
<ul>
|
|
||||||
{% for attrib in annul_attributions %}
|
|
||||||
<li>{{attrib.tag}} {{attrib.choice_label}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% for attrib in overdue %}
|
|
||||||
<li>
|
|
||||||
<input type="checkbox" style="visibility:hidden">
|
|
||||||
{{attrib.spectacle}}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% if annul_attributions %}
|
|
||||||
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
|
||||||
{% if sold_attributions %}
|
|
||||||
<h3>Places revendues</h3>
|
|
||||||
<form action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{soldform|bootstrap}}
|
|
||||||
<button type="submit" class="btn btn-primary" name="transfer">Transférer</button>
|
|
||||||
<button type="submit" class="btn btn-primary" name="reinit">Réinitialiser</button>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
{% if not resell_attributions and not annul_attributions and not overdue and not sold_attributions %}
|
|
||||||
<p>Plus de reventes possibles !</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock %}
|
|
0
bda/tests/__init__.py
Normal file
0
bda/tests/__init__.py
Normal file
100
bda/tests/test_models.py
Normal file
100
bda/tests/test_models.py
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.core import mail
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from bda.models import (
|
||||||
|
Attribution, Participant, Salle, Spectacle, SpectacleRevente, Tirage,
|
||||||
|
)
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class SpectacleReventeTests(TestCase):
|
||||||
|
fixtures = ['gestioncof/management/data/custommail.json']
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
now = timezone.now()
|
||||||
|
|
||||||
|
self.t = Tirage.objects.create(
|
||||||
|
title='Tirage',
|
||||||
|
ouverture=now - timedelta(days=7),
|
||||||
|
fermeture=now - timedelta(days=3),
|
||||||
|
active=True,
|
||||||
|
)
|
||||||
|
self.s = Spectacle.objects.create(
|
||||||
|
title='Spectacle',
|
||||||
|
date=now + timedelta(days=20),
|
||||||
|
location=Salle.objects.create(name='Salle', address='Address'),
|
||||||
|
price=10.5,
|
||||||
|
slots=5,
|
||||||
|
tirage=self.t,
|
||||||
|
listing=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.seller = Participant.objects.create(
|
||||||
|
user=User.objects.create(
|
||||||
|
username='seller', email='seller@mail.net'),
|
||||||
|
tirage=self.t,
|
||||||
|
)
|
||||||
|
self.p1 = Participant.objects.create(
|
||||||
|
user=User.objects.create(username='part1', email='part1@mail.net'),
|
||||||
|
tirage=self.t,
|
||||||
|
)
|
||||||
|
self.p2 = Participant.objects.create(
|
||||||
|
user=User.objects.create(username='part2', email='part2@mail.net'),
|
||||||
|
tirage=self.t,
|
||||||
|
)
|
||||||
|
self.p3 = Participant.objects.create(
|
||||||
|
user=User.objects.create(username='part3', email='part3@mail.net'),
|
||||||
|
tirage=self.t,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.attr = Attribution.objects.create(
|
||||||
|
participant=self.seller,
|
||||||
|
spectacle=self.s,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.rev = SpectacleRevente.objects.create(
|
||||||
|
attribution=self.attr,
|
||||||
|
seller=self.seller,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_tirage(self):
|
||||||
|
revente = self.rev
|
||||||
|
|
||||||
|
wanted_by = [self.p1, self.p2, self.p3]
|
||||||
|
revente.confirmed_entry = wanted_by
|
||||||
|
|
||||||
|
with mock.patch('bda.models.random.choice') as mc:
|
||||||
|
# Set winner to self.p1.
|
||||||
|
mc.return_value = self.p1
|
||||||
|
|
||||||
|
revente.tirage()
|
||||||
|
|
||||||
|
# Call to random.choice used participants in wanted_by.
|
||||||
|
mc_args, _ = mc.call_args
|
||||||
|
|
||||||
|
self.assertEqual(set(mc_args[0]), set(wanted_by))
|
||||||
|
|
||||||
|
self.assertEqual(revente.soldTo, self.p1)
|
||||||
|
self.assertTrue(revente.tirage_done)
|
||||||
|
|
||||||
|
mails = {m.to[0]: m for m in mail.outbox}
|
||||||
|
|
||||||
|
self.assertEqual(len(mails), 4)
|
||||||
|
|
||||||
|
m_seller = mails['seller@mail.net']
|
||||||
|
self.assertListEqual(m_seller.to, ['seller@mail.net'])
|
||||||
|
self.assertListEqual(m_seller.reply_to, ['part1@mail.net'])
|
||||||
|
|
||||||
|
m_winner = mails['part1@mail.net']
|
||||||
|
self.assertListEqual(m_winner.to, ['part1@mail.net'])
|
||||||
|
|
||||||
|
self.assertCountEqual(
|
||||||
|
[mails['part2@mail.net'].to, mails['part3@mail.net'].to],
|
||||||
|
[['part2@mail.net'], ['part3@mail.net']],
|
||||||
|
)
|
69
bda/tests/test_revente.py
Normal file
69
bda/tests/test_revente.py
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase, Client
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from bda.models import (Tirage, Spectacle, Salle, CategorieSpectacle,
|
||||||
|
SpectacleRevente, Attribution, Participant)
|
||||||
|
|
||||||
|
|
||||||
|
class TestModels(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.tirage = Tirage.objects.create(
|
||||||
|
title="Tirage test",
|
||||||
|
appear_catalogue=True,
|
||||||
|
ouverture=timezone.now(),
|
||||||
|
fermeture=timezone.now()
|
||||||
|
)
|
||||||
|
self.category = CategorieSpectacle.objects.create(name="Category")
|
||||||
|
self.location = Salle.objects.create(name="here")
|
||||||
|
self.spectacle_soon = Spectacle.objects.create(
|
||||||
|
title="foo", date=timezone.now()+timedelta(days=1),
|
||||||
|
location=self.location, price=0, slots=42,
|
||||||
|
tirage=self.tirage, listing=False, category=self.category
|
||||||
|
)
|
||||||
|
self.spectacle_later = Spectacle.objects.create(
|
||||||
|
title="bar", date=timezone.now()+timedelta(days=30),
|
||||||
|
location=self.location, price=0, slots=42,
|
||||||
|
tirage=self.tirage, listing=False, category=self.category
|
||||||
|
)
|
||||||
|
|
||||||
|
user_buyer = User.objects.create_user(
|
||||||
|
username="bda_buyer", password="testbuyer"
|
||||||
|
)
|
||||||
|
user_seller = User.objects.create_user(
|
||||||
|
username="bda_seller", password="testseller"
|
||||||
|
)
|
||||||
|
self.buyer = Participant.objects.create(
|
||||||
|
user=user_buyer, tirage=self.tirage
|
||||||
|
)
|
||||||
|
self.seller = Participant.objects.create(
|
||||||
|
user=user_seller, tirage=self.tirage
|
||||||
|
)
|
||||||
|
|
||||||
|
self.attr_soon = Attribution.objects.create(
|
||||||
|
participant=self.seller, spectacle=self.spectacle_soon
|
||||||
|
)
|
||||||
|
self.attr_later = Attribution.objects.create(
|
||||||
|
participant=self.seller, spectacle=self.spectacle_later
|
||||||
|
)
|
||||||
|
self.revente_soon = SpectacleRevente.objects.create(
|
||||||
|
seller=self.seller,
|
||||||
|
attribution=self.attr_soon
|
||||||
|
)
|
||||||
|
self.revente_later = SpectacleRevente.objects.create(
|
||||||
|
seller=self.seller,
|
||||||
|
attribution=self.attr_later
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_urgent(self):
|
||||||
|
self.assertTrue(self.revente_soon.is_urgent)
|
||||||
|
self.assertFalse(self.revente_later.is_urgent)
|
||||||
|
|
||||||
|
def test_tirage(self):
|
||||||
|
self.revente_soon.confirmed_entry.add(self.buyer)
|
||||||
|
|
||||||
|
self.assertEqual(self.revente_soon.tirage(send_mails=False),
|
||||||
|
self.buyer)
|
||||||
|
self.assertIsNone(self.revente_later.tirage(send_mails=False))
|
|
@ -4,7 +4,7 @@ from django.contrib.auth.models import User
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from .models import Tirage, Spectacle, Salle, CategorieSpectacle
|
from bda.models import Tirage, Spectacle, Salle, CategorieSpectacle
|
||||||
|
|
||||||
|
|
||||||
class TestBdAViews(TestCase):
|
class TestBdAViews(TestCase):
|
41
bda/urls.py
41
bda/urls.py
|
@ -16,9 +16,6 @@ urlpatterns = [
|
||||||
url(r'^places/(?P<tirage_id>\d+)$',
|
url(r'^places/(?P<tirage_id>\d+)$',
|
||||||
views.places,
|
views.places,
|
||||||
name="bda-places-attribuees"),
|
name="bda-places-attribuees"),
|
||||||
url(r'^revente/(?P<tirage_id>\d+)$',
|
|
||||||
views.revente,
|
|
||||||
name='bda-revente'),
|
|
||||||
url(r'^etat-places/(?P<tirage_id>\d+)$',
|
url(r'^etat-places/(?P<tirage_id>\d+)$',
|
||||||
views.etat_places,
|
views.etat_places,
|
||||||
name='bda-etat-places'),
|
name='bda-etat-places'),
|
||||||
|
@ -32,18 +29,34 @@ urlpatterns = [
|
||||||
url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$',
|
url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$',
|
||||||
views.unpaid,
|
views.unpaid,
|
||||||
name="bda-unpaid"),
|
name="bda-unpaid"),
|
||||||
url(r'^liste-revente/(?P<tirage_id>\d+)$',
|
url(r'^spectacles/autocomplete$',
|
||||||
views.list_revente,
|
views.spectacle_autocomplete,
|
||||||
name="bda-liste-revente"),
|
name="bda-spectacle-autocomplete"),
|
||||||
url(r'^buy-revente/(?P<spectacle_id>\d+)$',
|
url(r'^participants/autocomplete$',
|
||||||
views.buy_revente,
|
views.participant_autocomplete,
|
||||||
name="bda-buy-revente"),
|
name="bda-participant-autocomplete"),
|
||||||
url(r'^revente-interested/(?P<revente_id>\d+)$',
|
|
||||||
views.revente_interested,
|
# Urls BdA-Revente
|
||||||
name='bda-revente-interested'),
|
|
||||||
url(r'^revente-immediat/(?P<tirage_id>\d+)$',
|
url(r'^revente/(?P<tirage_id>\d+)/manage$',
|
||||||
|
views.revente_manage,
|
||||||
|
name='bda-revente-manage'),
|
||||||
|
url(r'^revente/(?P<tirage_id>\d+)/subscribe$',
|
||||||
|
views.revente_subscribe,
|
||||||
|
name="bda-revente-subscribe"),
|
||||||
|
url(r'^revente/(?P<tirage_id>\d+)/tirages$',
|
||||||
|
views.revente_tirages,
|
||||||
|
name="bda-revente-tirages"),
|
||||||
|
url(r'^revente/(?P<spectacle_id>\d+)/buy$',
|
||||||
|
views.revente_buy,
|
||||||
|
name="bda-revente-buy"),
|
||||||
|
url(r'^revente/(?P<revente_id>\d+)/confirm$',
|
||||||
|
views.revente_confirm,
|
||||||
|
name='bda-revente-confirm'),
|
||||||
|
url(r'^revente/(?P<tirage_id>\d+)/shotgun$',
|
||||||
views.revente_shotgun,
|
views.revente_shotgun,
|
||||||
name="bda-shotgun"),
|
name="bda-revente-shotgun"),
|
||||||
|
|
||||||
url(r'^mails-rappel/(?P<spectacle_id>\d+)$',
|
url(r'^mails-rappel/(?P<spectacle_id>\d+)$',
|
||||||
views.send_rappel,
|
views.send_rappel,
|
||||||
name="bda-rappels"
|
name="bda-rappels"
|
||||||
|
|
170
bda/views.py
170
bda/views.py
|
@ -5,7 +5,6 @@ import random
|
||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
from datetime import timedelta
|
|
||||||
from custommail.shortcuts import send_mass_custom_mail, send_custom_mail
|
from custommail.shortcuts import send_mass_custom_mail, send_custom_mail
|
||||||
from custommail.models import CustomMail
|
from custommail.models import CustomMail
|
||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
|
@ -14,6 +13,7 @@ from django.contrib import messages
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
from django.db.models import Count, Q, Prefetch
|
from django.db.models import Count, Q, Prefetch
|
||||||
|
from django.template.defaultfilters import pluralize
|
||||||
from django.forms.models import inlineformset_factory
|
from django.forms.models import inlineformset_factory
|
||||||
from django.http import (
|
from django.http import (
|
||||||
HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
|
HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
|
||||||
|
@ -30,9 +30,11 @@ from bda.models import (
|
||||||
from bda.algorithm import Algorithm
|
from bda.algorithm import Algorithm
|
||||||
from bda.forms import (
|
from bda.forms import (
|
||||||
TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, SoldForm,
|
TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, SoldForm,
|
||||||
InscriptionInlineFormSet,
|
InscriptionInlineFormSet, ReventeTirageForm, ReventeTirageAnnulForm
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from utils.views.autocomplete import Select2QuerySetView
|
||||||
|
|
||||||
|
|
||||||
@cof_required
|
@cof_required
|
||||||
def etat_places(request, tirage_id):
|
def etat_places(request, tirage_id):
|
||||||
|
@ -349,13 +351,21 @@ def tirage(request, tirage_id):
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def revente(request, tirage_id):
|
def revente_manage(request, tirage_id):
|
||||||
|
"""
|
||||||
|
Gestion de ses propres reventes :
|
||||||
|
- Création d'une revente
|
||||||
|
- Annulation d'une revente
|
||||||
|
- Confirmation d'une revente = transfert de la place à la personne qui
|
||||||
|
rachète
|
||||||
|
- Annulation d'une revente après que le tirage a eu lieu
|
||||||
|
"""
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||||
participant, created = Participant.objects.get_or_create(
|
participant, created = Participant.objects.get_or_create(
|
||||||
user=request.user, tirage=tirage)
|
user=request.user, tirage=tirage)
|
||||||
|
|
||||||
if not participant.paid:
|
if not participant.paid:
|
||||||
return render(request, "bda-notpaid.html", {})
|
return render(request, "bda/revente/notpaid.html", {})
|
||||||
|
|
||||||
resellform = ResellForm(participant, prefix='resell')
|
resellform = ResellForm(participant, prefix='resell')
|
||||||
annulform = AnnulForm(participant, prefix='annul')
|
annulform = AnnulForm(participant, prefix='annul')
|
||||||
|
@ -375,12 +385,8 @@ def revente(request, tirage_id):
|
||||||
attribution=attribution,
|
attribution=attribution,
|
||||||
defaults={'seller': participant})
|
defaults={'seller': participant})
|
||||||
if not created:
|
if not created:
|
||||||
revente.seller = participant
|
revente.reset()
|
||||||
revente.date = timezone.now()
|
|
||||||
revente.soldTo = None
|
|
||||||
revente.notif_sent = False
|
|
||||||
revente.tirage_done = False
|
|
||||||
revente.shotgun = False
|
|
||||||
context = {
|
context = {
|
||||||
'vendeur': participant.user,
|
'vendeur': participant.user,
|
||||||
'show': attribution.spectacle,
|
'show': attribution.spectacle,
|
||||||
|
@ -397,18 +403,18 @@ def revente(request, tirage_id):
|
||||||
elif 'annul' in request.POST:
|
elif 'annul' in request.POST:
|
||||||
annulform = AnnulForm(participant, request.POST, prefix='annul')
|
annulform = AnnulForm(participant, request.POST, prefix='annul')
|
||||||
if annulform.is_valid():
|
if annulform.is_valid():
|
||||||
attributions = annulform.cleaned_data["attributions"]
|
reventes = annulform.cleaned_data["reventes"]
|
||||||
for attribution in attributions:
|
for revente in reventes:
|
||||||
attribution.revente.delete()
|
revente.delete()
|
||||||
# On confirme une vente en transférant la place à la personne qui a
|
# On confirme une vente en transférant la place à la personne qui a
|
||||||
# gagné le tirage
|
# gagné le tirage
|
||||||
elif 'transfer' in request.POST:
|
elif 'transfer' in request.POST:
|
||||||
soldform = SoldForm(participant, request.POST, prefix='sold')
|
soldform = SoldForm(participant, request.POST, prefix='sold')
|
||||||
if soldform.is_valid():
|
if soldform.is_valid():
|
||||||
attributions = soldform.cleaned_data['attributions']
|
reventes = soldform.cleaned_data['reventes']
|
||||||
for attribution in attributions:
|
for reventes in reventes:
|
||||||
attribution.participant = attribution.revente.soldTo
|
revente.attribution.participant = revente.soldTo
|
||||||
attribution.save()
|
revente.attribution.save()
|
||||||
|
|
||||||
# On annule la revente après le tirage au sort (par exemple si
|
# On annule la revente après le tirage au sort (par exemple si
|
||||||
# la personne qui a gagné le tirage ne se manifeste pas). La place est
|
# la personne qui a gagné le tirage ne se manifeste pas). La place est
|
||||||
|
@ -416,18 +422,13 @@ def revente(request, tirage_id):
|
||||||
elif 'reinit' in request.POST:
|
elif 'reinit' in request.POST:
|
||||||
soldform = SoldForm(participant, request.POST, prefix='sold')
|
soldform = SoldForm(participant, request.POST, prefix='sold')
|
||||||
if soldform.is_valid():
|
if soldform.is_valid():
|
||||||
attributions = soldform.cleaned_data['attributions']
|
reventes = soldform.cleaned_data['reventes']
|
||||||
for attribution in attributions:
|
for revente in reventes:
|
||||||
if attribution.spectacle.date > timezone.now():
|
if revente.attribution.spectacle.date > timezone.now():
|
||||||
revente = attribution.revente
|
# On antidate pour envoyer le mail plus vite
|
||||||
revente.date = timezone.now() - timedelta(minutes=65)
|
new_date = (timezone.now()
|
||||||
revente.soldTo = None
|
- SpectacleRevente.remorse_time)
|
||||||
revente.notif_sent = False
|
revente.reset(new_date=new_date)
|
||||||
revente.tirage_done = False
|
|
||||||
revente.shotgun = False
|
|
||||||
if revente.answered_mail:
|
|
||||||
revente.answered_mail.clear()
|
|
||||||
revente.save()
|
|
||||||
|
|
||||||
overdue = participant.attribution_set.filter(
|
overdue = participant.attribution_set.filter(
|
||||||
spectacle__date__gte=timezone.now(),
|
spectacle__date__gte=timezone.now(),
|
||||||
|
@ -437,28 +438,80 @@ def revente(request, tirage_id):
|
||||||
.filter(
|
.filter(
|
||||||
Q(revente__soldTo__isnull=True) | Q(revente__soldTo=participant))
|
Q(revente__soldTo__isnull=True) | Q(revente__soldTo=participant))
|
||||||
|
|
||||||
return render(request, "bda/reventes.html",
|
return render(request, "bda/revente/manage.html",
|
||||||
{'tirage': tirage, 'overdue': overdue, "soldform": soldform,
|
{'tirage': tirage, 'overdue': overdue, "soldform": soldform,
|
||||||
"annulform": annulform, "resellform": resellform})
|
"annulform": annulform, "resellform": resellform})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def revente_interested(request, revente_id):
|
def revente_tirages(request, tirage_id):
|
||||||
|
"""
|
||||||
|
Affiche à un participant la liste de toutes les reventes en cours (pour un
|
||||||
|
tirage donné) et lui permet de s'inscrire et se désinscrire à ces reventes.
|
||||||
|
"""
|
||||||
|
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||||
|
participant, _ = Participant.objects.get_or_create(
|
||||||
|
user=request.user, tirage=tirage)
|
||||||
|
subform = ReventeTirageForm(participant, prefix="subscribe")
|
||||||
|
annulform = ReventeTirageAnnulForm(participant, prefix="annul")
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
if "subscribe" in request.POST:
|
||||||
|
subform = ReventeTirageForm(participant, request.POST,
|
||||||
|
prefix="subscribe")
|
||||||
|
if subform.is_valid():
|
||||||
|
reventes = subform.cleaned_data['reventes']
|
||||||
|
count = reventes.count()
|
||||||
|
for revente in reventes:
|
||||||
|
revente.confirmed_entry.add(participant)
|
||||||
|
if count > 0:
|
||||||
|
messages.success(
|
||||||
|
request,
|
||||||
|
"Tu as bien été inscrit à {} revente{}"
|
||||||
|
.format(count, pluralize(count))
|
||||||
|
)
|
||||||
|
elif "annul" in request.POST:
|
||||||
|
annulform = ReventeTirageAnnulForm(participant, request.POST,
|
||||||
|
prefix="annul")
|
||||||
|
if annulform.is_valid():
|
||||||
|
reventes = annulform.cleaned_data['reventes']
|
||||||
|
count = reventes.count()
|
||||||
|
for revente in reventes:
|
||||||
|
revente.confirmed_entry.remove(participant)
|
||||||
|
if count > 0:
|
||||||
|
messages.success(
|
||||||
|
request,
|
||||||
|
"Tu as bien été désinscrit de {} revente{}"
|
||||||
|
.format(count, pluralize(count))
|
||||||
|
)
|
||||||
|
|
||||||
|
return render(request, "bda/revente/tirages.html",
|
||||||
|
{"annulform": annulform, "subform": subform})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def revente_confirm(request, revente_id):
|
||||||
revente = get_object_or_404(SpectacleRevente, id=revente_id)
|
revente = get_object_or_404(SpectacleRevente, id=revente_id)
|
||||||
participant, _ = Participant.objects.get_or_create(
|
participant, _ = Participant.objects.get_or_create(
|
||||||
user=request.user, tirage=revente.attribution.spectacle.tirage)
|
user=request.user, tirage=revente.attribution.spectacle.tirage)
|
||||||
if (timezone.now() < revente.date + timedelta(hours=1)) or revente.shotgun:
|
if not revente.notif_sent or revente.shotgun:
|
||||||
return render(request, "bda-wrongtime.html",
|
return render(request, "bda/revente/wrongtime.html",
|
||||||
{"revente": revente})
|
{"revente": revente})
|
||||||
|
|
||||||
revente.answered_mail.add(participant)
|
revente.confirmed_entry.add(participant)
|
||||||
return render(request, "bda-interested.html",
|
return render(request, "bda/revente/confirmed.html",
|
||||||
{"spectacle": revente.attribution.spectacle,
|
{"spectacle": revente.attribution.spectacle,
|
||||||
"date": revente.date_tirage})
|
"date": revente.date_tirage})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def list_revente(request, tirage_id):
|
def revente_subscribe(request, tirage_id):
|
||||||
|
"""
|
||||||
|
Permet à un participant de sélectionner ses préférences pour les reventes.
|
||||||
|
Il recevra des notifications pour les spectacles qui l'intéressent et il
|
||||||
|
est automatiquement inscrit aux reventes en cours au moment où il ajoute un
|
||||||
|
spectacle à la liste des spectacles qui l'intéressent.
|
||||||
|
"""
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||||
participant, _ = Participant.objects.get_or_create(
|
participant, _ = Participant.objects.get_or_create(
|
||||||
user=request.user, tirage=tirage)
|
user=request.user, tirage=tirage)
|
||||||
|
@ -484,12 +537,12 @@ def list_revente(request, tirage_id):
|
||||||
# la revente ayant le moins d'inscrits
|
# la revente ayant le moins d'inscrits
|
||||||
min_resell = (
|
min_resell = (
|
||||||
qset.filter(shotgun=False)
|
qset.filter(shotgun=False)
|
||||||
.annotate(nb_subscribers=Count('answered_mail'))
|
.annotate(nb_subscribers=Count('confirmed_entry'))
|
||||||
.order_by('nb_subscribers')
|
.order_by('nb_subscribers')
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
if min_resell is not None:
|
if min_resell is not None:
|
||||||
min_resell.answered_mail.add(participant)
|
min_resell.confirmed_entry.add(participant)
|
||||||
inscrit_revente.append(spectacle)
|
inscrit_revente.append(spectacle)
|
||||||
success = True
|
success = True
|
||||||
else:
|
else:
|
||||||
|
@ -512,11 +565,11 @@ def list_revente(request, tirage_id):
|
||||||
)
|
)
|
||||||
messages.info(request, msg, extra_tags="safe")
|
messages.info(request, msg, extra_tags="safe")
|
||||||
|
|
||||||
return render(request, "bda/liste-reventes.html", {"form": form})
|
return render(request, "bda/revente/subscribe.html", {"form": form})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def buy_revente(request, spectacle_id):
|
def revente_buy(request, spectacle_id):
|
||||||
spectacle = get_object_or_404(Spectacle, id=spectacle_id)
|
spectacle = get_object_or_404(Spectacle, id=spectacle_id)
|
||||||
tirage = spectacle.tirage
|
tirage = spectacle.tirage
|
||||||
participant, _ = Participant.objects.get_or_create(
|
participant, _ = Participant.objects.get_or_create(
|
||||||
|
@ -530,13 +583,13 @@ def buy_revente(request, spectacle_id):
|
||||||
own_reventes = reventes.filter(seller=participant)
|
own_reventes = reventes.filter(seller=participant)
|
||||||
if len(own_reventes) > 0:
|
if len(own_reventes) > 0:
|
||||||
own_reventes[0].delete()
|
own_reventes[0].delete()
|
||||||
return HttpResponseRedirect(reverse("bda-shotgun",
|
return HttpResponseRedirect(reverse("bda-revente-shotgun",
|
||||||
args=[tirage.id]))
|
args=[tirage.id]))
|
||||||
|
|
||||||
reventes_shotgun = reventes.filter(shotgun=True)
|
reventes_shotgun = reventes.filter(shotgun=True)
|
||||||
|
|
||||||
if not reventes_shotgun:
|
if not reventes_shotgun:
|
||||||
return render(request, "bda-no-revente.html", {})
|
return render(request, "bda/revente/none.html", {})
|
||||||
|
|
||||||
if request.POST:
|
if request.POST:
|
||||||
revente = random.choice(reventes_shotgun)
|
revente = random.choice(reventes_shotgun)
|
||||||
|
@ -553,11 +606,11 @@ def buy_revente(request, spectacle_id):
|
||||||
[revente.seller.user.email],
|
[revente.seller.user.email],
|
||||||
context=context,
|
context=context,
|
||||||
)
|
)
|
||||||
return render(request, "bda-success.html",
|
return render(request, "bda/revente/mail-success.html",
|
||||||
{"seller": revente.attribution.participant.user,
|
{"seller": revente.attribution.participant.user,
|
||||||
"spectacle": spectacle})
|
"spectacle": spectacle})
|
||||||
|
|
||||||
return render(request, "revente-confirm.html",
|
return render(request, "bda/revente/confirm-shotgun.html",
|
||||||
{"spectacle": spectacle,
|
{"spectacle": spectacle,
|
||||||
"user": request.user})
|
"user": request.user})
|
||||||
|
|
||||||
|
@ -581,7 +634,7 @@ def revente_shotgun(request, tirage_id):
|
||||||
)
|
)
|
||||||
shotgun = [sp for sp in spectacles if len(sp.shotguns) > 0]
|
shotgun = [sp for sp in spectacles if len(sp.shotguns) > 0]
|
||||||
|
|
||||||
return render(request, "bda-shotgun.html",
|
return render(request, "bda/revente/shotgun.html",
|
||||||
{"shotgun": shotgun})
|
{"shotgun": shotgun})
|
||||||
|
|
||||||
|
|
||||||
|
@ -782,9 +835,9 @@ def catalogue(request, request_type):
|
||||||
.select_related('location')
|
.select_related('location')
|
||||||
.prefetch_related('quote_set')
|
.prefetch_related('quote_set')
|
||||||
)
|
)
|
||||||
if categories_id:
|
if categories_id and 0 not in categories_id:
|
||||||
shows_qs = shows_qs.filter(category__id__in=categories_id)
|
shows_qs = shows_qs.filter(category__id__in=categories_id)
|
||||||
if locations_id:
|
if locations_id and 0 not in locations_id:
|
||||||
shows_qs = shows_qs.filter(location__id__in=locations_id)
|
shows_qs = shows_qs.filter(location__id__in=locations_id)
|
||||||
|
|
||||||
# On convertit les descriptions à envoyer en une liste facilement
|
# On convertit les descriptions à envoyer en une liste facilement
|
||||||
|
@ -813,3 +866,26 @@ def catalogue(request, request_type):
|
||||||
return JsonResponse(data_return, safe=False)
|
return JsonResponse(data_return, safe=False)
|
||||||
# Si la requête n'est pas de la forme attendue, on quitte avec une erreur
|
# Si la requête n'est pas de la forme attendue, on quitte avec une erreur
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Autocomplete views
|
||||||
|
#
|
||||||
|
# https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#create-an-autocomplete-view
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
class ParticipantAutocomplete(Select2QuerySetView):
|
||||||
|
model = Participant
|
||||||
|
search_fields = ('user__username', 'user__first_name', 'user__last_name')
|
||||||
|
|
||||||
|
|
||||||
|
participant_autocomplete = buro_required(ParticipantAutocomplete.as_view())
|
||||||
|
|
||||||
|
|
||||||
|
class SpectacleAutocomplete(Select2QuerySetView):
|
||||||
|
model = Spectacle
|
||||||
|
search_fields = ('title',)
|
||||||
|
|
||||||
|
|
||||||
|
spectacle_autocomplete = buro_required(SpectacleAutocomplete.as_view())
|
||||||
|
|
0
cof/settings/__init__.py
Normal file
0
cof/settings/__init__.py
Normal file
|
@ -7,6 +7,7 @@ the local development server should be here.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from . import secret
|
from . import secret
|
||||||
|
@ -31,6 +32,7 @@ def import_secret(name):
|
||||||
SECRET_KEY = import_secret("SECRET_KEY")
|
SECRET_KEY = import_secret("SECRET_KEY")
|
||||||
ADMINS = import_secret("ADMINS")
|
ADMINS = import_secret("ADMINS")
|
||||||
SERVER_EMAIL = import_secret("SERVER_EMAIL")
|
SERVER_EMAIL = import_secret("SERVER_EMAIL")
|
||||||
|
EMAIL_HOST = import_secret("EMAIL_HOST")
|
||||||
|
|
||||||
DBNAME = import_secret("DBNAME")
|
DBNAME = import_secret("DBNAME")
|
||||||
DBUSER = import_secret("DBUSER")
|
DBUSER = import_secret("DBUSER")
|
||||||
|
@ -45,27 +47,37 @@ RECAPTCHA_PUBLIC_KEY = import_secret("RECAPTCHA_PUBLIC_KEY")
|
||||||
RECAPTCHA_PRIVATE_KEY = import_secret("RECAPTCHA_PRIVATE_KEY")
|
RECAPTCHA_PRIVATE_KEY = import_secret("RECAPTCHA_PRIVATE_KEY")
|
||||||
|
|
||||||
KFETOPEN_TOKEN = import_secret("KFETOPEN_TOKEN")
|
KFETOPEN_TOKEN = import_secret("KFETOPEN_TOKEN")
|
||||||
|
LDAP_SERVER_URL = import_secret("LDAP_SERVER_URL")
|
||||||
|
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(
|
BASE_DIR = os.path.dirname(
|
||||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TESTING = sys.argv[1] == 'test'
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
'shared',
|
||||||
|
|
||||||
'gestioncof',
|
'gestioncof',
|
||||||
|
|
||||||
|
# Must be before 'django.contrib.admin'.
|
||||||
|
# https://django-autocomplete-light.readthedocs.io/en/master/install.html
|
||||||
|
'dal',
|
||||||
|
'dal_select2',
|
||||||
|
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'grappelli',
|
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.admindocs',
|
'django.contrib.admindocs',
|
||||||
|
|
||||||
'bda',
|
'bda',
|
||||||
'autocomplete_light',
|
|
||||||
'captcha',
|
'captcha',
|
||||||
'django_cas_ng',
|
'django_cas_ng',
|
||||||
'bootstrapform',
|
'bootstrapform',
|
||||||
|
@ -90,18 +102,19 @@ INSTALLED_APPS = [
|
||||||
'wagtailmenus',
|
'wagtailmenus',
|
||||||
'modelcluster',
|
'modelcluster',
|
||||||
'taggit',
|
'taggit',
|
||||||
|
'kfet.auth',
|
||||||
'kfet.cms',
|
'kfet.cms',
|
||||||
'corsheaders',
|
'corsheaders',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = [
|
MIDDLEWARE = [
|
||||||
'corsheaders.middleware.CorsMiddleware',
|
'corsheaders.middleware.CorsMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||||
'kfet.middleware.KFetAuthenticationMiddleware',
|
'kfet.auth.middleware.TemporaryAuthMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
@ -123,13 +136,13 @@ TEMPLATES = [
|
||||||
'django.template.context_processors.request',
|
'django.template.context_processors.request',
|
||||||
'django.contrib.auth.context_processors.auth',
|
'django.contrib.auth.context_processors.auth',
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
'django.core.context_processors.i18n',
|
'django.template.context_processors.i18n',
|
||||||
'django.core.context_processors.media',
|
'django.template.context_processors.media',
|
||||||
'django.core.context_processors.static',
|
'django.template.context_processors.static',
|
||||||
'wagtailmenus.context_processors.wagtailmenus',
|
'wagtailmenus.context_processors.wagtailmenus',
|
||||||
'djconfig.context_processors.config',
|
'djconfig.context_processors.config',
|
||||||
'gestioncof.shared.context_processor',
|
'gestioncof.shared.context_processor',
|
||||||
'kfet.context_processors.auth',
|
'kfet.auth.context_processors.temporary_auth',
|
||||||
'kfet.context_processors.config',
|
'kfet.context_processors.config',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -192,7 +205,7 @@ CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
|
||||||
AUTHENTICATION_BACKENDS = (
|
AUTHENTICATION_BACKENDS = (
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
'gestioncof.shared.COFCASBackend',
|
'gestioncof.shared.COFCASBackend',
|
||||||
'kfet.backends.GenericTeamBackend',
|
'kfet.auth.backends.GenericBackend',
|
||||||
)
|
)
|
||||||
|
|
||||||
RECAPTCHA_USE_SSL = True
|
RECAPTCHA_USE_SSL = True
|
||||||
|
|
|
@ -4,13 +4,18 @@ The settings that are not listed here are imported from .common
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .common import * # NOQA
|
from .common import * # NOQA
|
||||||
from .common import INSTALLED_APPS, MIDDLEWARE_CLASSES
|
from .common import INSTALLED_APPS, MIDDLEWARE, TESTING
|
||||||
|
|
||||||
|
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
|
if TESTING:
|
||||||
|
PASSWORD_HASHERS = [
|
||||||
|
'django.contrib.auth.hashers.MD5PasswordHasher',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Apache static/media config
|
# Apache static/media config
|
||||||
|
@ -36,11 +41,13 @@ def show_toolbar(request):
|
||||||
"""
|
"""
|
||||||
return DEBUG
|
return DEBUG
|
||||||
|
|
||||||
INSTALLED_APPS += ["debug_toolbar", "debug_panel"]
|
if not TESTING:
|
||||||
MIDDLEWARE_CLASSES = (
|
INSTALLED_APPS += ["debug_toolbar", "debug_panel"]
|
||||||
["debug_panel.middleware.DebugPanelMiddleware"]
|
|
||||||
+ MIDDLEWARE_CLASSES
|
MIDDLEWARE = [
|
||||||
)
|
"debug_panel.middleware.DebugPanelMiddleware"
|
||||||
DEBUG_TOOLBAR_CONFIG = {
|
] + MIDDLEWARE
|
||||||
|
|
||||||
|
DEBUG_TOOLBAR_CONFIG = {
|
||||||
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
|
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
SECRET_KEY = 'q()(zn4m63i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah'
|
SECRET_KEY = 'q()(zn4m63i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah'
|
||||||
ADMINS = None
|
ADMINS = None
|
||||||
SERVER_EMAIL = "root@vagrant"
|
SERVER_EMAIL = "root@vagrant"
|
||||||
|
EMAIL_HOST = "localhost"
|
||||||
|
|
||||||
DBUSER = "cof_gestion"
|
DBUSER = "cof_gestion"
|
||||||
DBNAME = "cof_gestion"
|
DBNAME = "cof_gestion"
|
||||||
|
|
31
cof/urls.py
31
cof/urls.py
|
@ -4,8 +4,6 @@
|
||||||
Fichier principal de configuration des urls du projet GestioCOF
|
Fichier principal de configuration des urls du projet GestioCOF
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import autocomplete_light
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
@ -24,7 +22,6 @@ from gestioncof.urls import export_patterns, petitcours_patterns, \
|
||||||
clubs_patterns
|
clubs_patterns
|
||||||
from gestioncof.autocomplete import autocomplete
|
from gestioncof.autocomplete import autocomplete
|
||||||
|
|
||||||
autocomplete_light.autodiscover()
|
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
@ -49,18 +46,22 @@ urlpatterns = [
|
||||||
name="cof-denied"),
|
name="cof-denied"),
|
||||||
url(r'^cas/login$', django_cas_views.login, name="cas_login_view"),
|
url(r'^cas/login$', django_cas_views.login, name="cas_login_view"),
|
||||||
url(r'^cas/logout$', django_cas_views.logout),
|
url(r'^cas/logout$', django_cas_views.logout),
|
||||||
url(r'^outsider/login$', gestioncof_views.login_ext),
|
url(r'^outsider/login$', gestioncof_views.login_ext,
|
||||||
|
name="ext_login_view"),
|
||||||
url(r'^outsider/logout$', django_views.logout, {'next_page': 'home'}),
|
url(r'^outsider/logout$', django_views.logout, {'next_page': 'home'}),
|
||||||
url(r'^login$', gestioncof_views.login, name="cof-login"),
|
url(r'^login$', gestioncof_views.login, name="cof-login"),
|
||||||
url(r'^logout$', gestioncof_views.logout, name="cof-logout"),
|
url(r'^logout$', gestioncof_views.logout, name="cof-logout"),
|
||||||
# Infos persos
|
# Infos persos
|
||||||
url(r'^profile$', gestioncof_views.profile),
|
url(r'^profile$', gestioncof_views.profile,
|
||||||
url(r'^outsider/password-change$', django_views.password_change),
|
name='profile'),
|
||||||
|
url(r'^outsider/password-change$', django_views.password_change,
|
||||||
|
name='password_change'),
|
||||||
url(r'^outsider/password-change-done$',
|
url(r'^outsider/password-change-done$',
|
||||||
django_views.password_change_done,
|
django_views.password_change_done,
|
||||||
name='password_change_done'),
|
name='password_change_done'),
|
||||||
# Inscription d'un nouveau membre
|
# Inscription d'un nouveau membre
|
||||||
url(r'^registration$', gestioncof_views.registration),
|
url(r'^registration$', gestioncof_views.registration,
|
||||||
|
name='registration'),
|
||||||
url(r'^registration/clipper/(?P<login_clipper>[\w-]+)/'
|
url(r'^registration/clipper/(?P<login_clipper>[\w-]+)/'
|
||||||
r'(?P<fullname>.*)$',
|
r'(?P<fullname>.*)$',
|
||||||
gestioncof_views.registration_form2, name="clipper-registration"),
|
gestioncof_views.registration_form2, name="clipper-registration"),
|
||||||
|
@ -70,7 +71,8 @@ urlpatterns = [
|
||||||
name="empty-registration"),
|
name="empty-registration"),
|
||||||
# Autocompletion
|
# Autocompletion
|
||||||
url(r'^autocomplete/registration$', autocomplete),
|
url(r'^autocomplete/registration$', autocomplete),
|
||||||
url(r'^autocomplete/', include('autocomplete_light.urls')),
|
url(r'^user/autocomplete$', gestioncof_views.user_autocomplete,
|
||||||
|
name='cof-user-autocomplete'),
|
||||||
# Interface admin
|
# Interface admin
|
||||||
url(r'^admin/logout/', gestioncof_views.logout),
|
url(r'^admin/logout/', gestioncof_views.logout),
|
||||||
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||||
|
@ -78,18 +80,21 @@ urlpatterns = [
|
||||||
csv_views.admin_list_export,
|
csv_views.admin_list_export,
|
||||||
{'fields': ['username', ]}),
|
{'fields': ['username', ]}),
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
url(r'^admin/', include(admin.site.urls)),
|
||||||
url(r'^grappelli/', include('grappelli.urls')),
|
|
||||||
# Liens utiles du COF et du BdA
|
# Liens utiles du COF et du BdA
|
||||||
url(r'^utile_cof$', gestioncof_views.utile_cof),
|
url(r'^utile_cof$', gestioncof_views.utile_cof,
|
||||||
url(r'^utile_bda$', gestioncof_views.utile_bda),
|
name='utile_cof'),
|
||||||
|
url(r'^utile_bda$', gestioncof_views.utile_bda,
|
||||||
|
name='utile_bda'),
|
||||||
url(r'^utile_bda/bda_diff$', gestioncof_views.liste_bdadiff),
|
url(r'^utile_bda/bda_diff$', gestioncof_views.liste_bdadiff),
|
||||||
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof),
|
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof,
|
||||||
|
name='ml_diffcof'),
|
||||||
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente),
|
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente),
|
||||||
url(r'^k-fet/', include('kfet.urls')),
|
url(r'^k-fet/', include('kfet.urls')),
|
||||||
url(r'^cms/', include(wagtailadmin_urls)),
|
url(r'^cms/', include(wagtailadmin_urls)),
|
||||||
url(r'^documents/', include(wagtaildocs_urls)),
|
url(r'^documents/', include(wagtaildocs_urls)),
|
||||||
# djconfig
|
# djconfig
|
||||||
url(r"^config", gestioncof_views.ConfigUpdate.as_view()),
|
url(r"^config", gestioncof_views.ConfigUpdate.as_view(),
|
||||||
|
name='config.edit'),
|
||||||
]
|
]
|
||||||
|
|
||||||
if 'debug_toolbar' in settings.INSTALLED_APPS:
|
if 'debug_toolbar' in settings.INSTALLED_APPS:
|
||||||
|
|
|
@ -13,7 +13,7 @@ from django.core.urlresolvers import reverse
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
import autocomplete_light
|
from dal.autocomplete import ModelSelect2
|
||||||
|
|
||||||
|
|
||||||
def add_link_field(target_model='', field='', link_text=str,
|
def add_link_field(target_model='', field='', link_text=str,
|
||||||
|
@ -217,8 +217,16 @@ def user_str(self):
|
||||||
User.__str__ = user_str
|
User.__str__ = user_str
|
||||||
|
|
||||||
|
|
||||||
|
class EventRegistrationAdminForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
widgets = {
|
||||||
|
'user': ModelSelect2(url='cof-user-autocomplete'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class EventRegistrationAdmin(admin.ModelAdmin):
|
class EventRegistrationAdmin(admin.ModelAdmin):
|
||||||
form = autocomplete_light.modelform_factory(EventRegistration, exclude=[])
|
form = EventRegistrationAdminForm
|
||||||
|
|
||||||
list_display = ('__str__', 'event', 'user', 'paid')
|
list_display = ('__str__', 'event', 'user', 'paid')
|
||||||
list_filter = ('paid',)
|
list_filter = ('paid',)
|
||||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
||||||
|
|
|
@ -58,7 +58,7 @@ def autocomplete(request):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fetching data from the SPI
|
# Fetching data from the SPI
|
||||||
if hasattr(settings, 'LDAP_SERVER_URL'):
|
if getattr(settings, 'LDAP_SERVER_URL', None):
|
||||||
# Fetching
|
# Fetching
|
||||||
ldap_query = '(&{:s})'.format(''.join(
|
ldap_query = '(&{:s})'.format(''.join(
|
||||||
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=bit)
|
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=bit)
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import autocomplete_light
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
|
|
||||||
autocomplete_light.register(
|
|
||||||
User, search_fields=('username', 'first_name', 'last_name'),
|
|
||||||
attrs={'placeholder': 'membre...'}
|
|
||||||
)
|
|
|
@ -351,10 +351,12 @@ EventFormset = formset_factory(AdminEventForm, BaseEventRegistrationFormset)
|
||||||
class CalendarForm(forms.ModelForm):
|
class CalendarForm(forms.ModelForm):
|
||||||
subscribe_to_events = forms.BooleanField(
|
subscribe_to_events = forms.BooleanField(
|
||||||
initial=True,
|
initial=True,
|
||||||
label="Événements du COF")
|
label="Événements du COF",
|
||||||
|
required=False)
|
||||||
subscribe_to_my_shows = forms.BooleanField(
|
subscribe_to_my_shows = forms.BooleanField(
|
||||||
initial=True,
|
initial=True,
|
||||||
label="Les spectacles pour lesquels j'ai obtenu une place")
|
label="Les spectacles pour lesquels j'ai obtenu une place",
|
||||||
|
required=False)
|
||||||
other_shows = forms.ModelMultipleChoiceField(
|
other_shows = forms.ModelMultipleChoiceField(
|
||||||
label="Spectacles supplémentaires",
|
label="Spectacles supplémentaires",
|
||||||
queryset=Spectacle.objects.filter(tirage__active=True),
|
queryset=Spectacle.objects.filter(tirage__active=True),
|
||||||
|
|
|
@ -63,6 +63,7 @@ class Command(BaseCommand):
|
||||||
except CustomMail.DoesNotExist:
|
except CustomMail.DoesNotExist:
|
||||||
mail = CustomMail.objects.create(**fields)
|
mail = CustomMail.objects.create(**fields)
|
||||||
status['synced'] += 1
|
status['synced'] += 1
|
||||||
|
if options['verbosity']:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
'SYNCED {:s}'.format(fields['shortname']))
|
'SYNCED {:s}'.format(fields['shortname']))
|
||||||
assoc['mails'][obj['pk']] = mail
|
assoc['mails'][obj['pk']] = mail
|
||||||
|
@ -79,6 +80,7 @@ class Command(BaseCommand):
|
||||||
except Variable.DoesNotExist:
|
except Variable.DoesNotExist:
|
||||||
Variable.objects.create(**fields)
|
Variable.objects.create(**fields)
|
||||||
|
|
||||||
|
if options['verbosity']:
|
||||||
# C'est agréable d'avoir le résultat affiché
|
# C'est agréable d'avoir le résultat affiché
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
'{synced:d} mails synchronized {unchanged:d} unchanged'
|
'{synced:d} mails synchronized {unchanged:d} unchanged'
|
||||||
|
|
|
@ -1,148 +1,161 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "model",
|
||||||
"content_type": [
|
"content_type": [
|
||||||
"auth",
|
"auth",
|
||||||
"user"
|
"user"
|
||||||
],
|
],
|
||||||
"inner1": null,
|
"inner1": null,
|
||||||
"kind": "model",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "int",
|
||||||
"content_type": null,
|
"content_type": null,
|
||||||
"inner1": null,
|
"inner1": null,
|
||||||
"kind": "int",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "model",
|
||||||
"content_type": [
|
"content_type": [
|
||||||
"bda",
|
"bda",
|
||||||
"spectacle"
|
"spectacle"
|
||||||
],
|
],
|
||||||
"inner1": null,
|
"inner1": null,
|
||||||
"kind": "model",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "model",
|
||||||
"content_type": [
|
"content_type": [
|
||||||
"bda",
|
"bda",
|
||||||
"spectaclerevente"
|
"spectaclerevente"
|
||||||
],
|
],
|
||||||
"inner1": null,
|
"inner1": null,
|
||||||
"kind": "model",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "model",
|
||||||
"content_type": [
|
"content_type": [
|
||||||
"sites",
|
"sites",
|
||||||
"site"
|
"site"
|
||||||
],
|
],
|
||||||
"inner1": null,
|
"inner1": null,
|
||||||
"kind": "model",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "model",
|
||||||
"content_type": [
|
"content_type": [
|
||||||
"gestioncof",
|
"gestioncof",
|
||||||
"petitcoursdemande"
|
"petitcoursdemande"
|
||||||
],
|
],
|
||||||
"inner1": null,
|
"inner1": null,
|
||||||
"kind": "model",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 6
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 7,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"content_type": null,
|
|
||||||
"inner1": null,
|
|
||||||
"kind": "list",
|
"kind": "list",
|
||||||
|
"content_type": null,
|
||||||
|
"inner1": 12,
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 7
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 8,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "list",
|
||||||
"content_type": null,
|
"content_type": null,
|
||||||
"inner1": 1,
|
"inner1": 1,
|
||||||
"kind": "list",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 9,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"content_type": null,
|
|
||||||
"inner1": null,
|
|
||||||
"kind": "pair",
|
"kind": "pair",
|
||||||
|
"content_type": null,
|
||||||
|
"inner1": 12,
|
||||||
"inner2": 8
|
"inner2": 8
|
||||||
}
|
},
|
||||||
|
"pk": 9
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 10,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "list",
|
||||||
"content_type": null,
|
"content_type": null,
|
||||||
"inner1": 9,
|
"inner1": 9,
|
||||||
"kind": "list",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 11,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "list",
|
||||||
"content_type": null,
|
"content_type": null,
|
||||||
"inner1": 3,
|
"inner1": 3,
|
||||||
"kind": "list",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "custommail.type",
|
||||||
|
"fields": {
|
||||||
|
"kind": "model",
|
||||||
|
"content_type": [
|
||||||
|
"gestioncof",
|
||||||
|
"petitcourssubject"
|
||||||
|
],
|
||||||
|
"inner1": null,
|
||||||
|
"inner2": null
|
||||||
|
},
|
||||||
|
"pk": 12
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "welcome",
|
"shortname": "welcome",
|
||||||
"subject": "Bienvenue au COF",
|
"subject": "Bienvenue au COF",
|
||||||
"description": "Mail de bienvenue au COF envoy\u00e9 automatiquement \u00e0 l'inscription d'un nouveau membre",
|
"body": "Bonjour {{ member.first_name }} et bienvenue au COF !\r\n\r\nTu trouveras plein de trucs cool sur le site du COF : https://www.cof.ens.fr/ et notre page Facebook : https://www.facebook.com/cof.ulm\r\nEt n'oublie pas d'aller d\u00e9couvrir GestioCOF, la plateforme de gestion du COF !\r\nSi tu as des questions, tu peux nous envoyer un mail \u00e0 cof@ens.fr (on aime le spam), ou passer nous voir au Bur\u00f4 pr\u00e8s de la Cour\u00f4 du lundi au vendredi de 12h \u00e0 14h et de 18h \u00e0 20h.\r\n\r\nRetrouvez les \u00e9v\u00e8nements de rentr\u00e9e pour les conscrit.e.s et les vieux/vieilles organis\u00e9s par le COF et ses clubs ici : http://www.cof.ens.fr/depot/Rentree.pdf \r\n\r\nAmicalement,\r\n\r\nTon COF qui t'aime.",
|
||||||
"body": "Bonjour {{ member.first_name }} et bienvenue au COF !\r\n\r\nTu trouveras plein de trucs cool sur le site du COF : https://www.cof.ens.fr/ et notre page Facebook : https://www.facebook.com/cof.ulm\r\nEt n'oublie pas d'aller d\u00e9couvrir GestioCOF, la plateforme de gestion du COF !\r\nSi tu as des questions, tu peux nous envoyer un mail \u00e0 cof@ens.fr (on aime le spam), ou passer nous voir au Bur\u00f4 pr\u00e8s de la Cour\u00f4 du lundi au vendredi de 12h \u00e0 14h et de 18h \u00e0 20h.\r\n\r\nRetrouvez les \u00e9v\u00e8nements de rentr\u00e9e pour les conscrit.e.s et les vieux/vieilles organis\u00e9s par le COF et ses clubs ici : http://www.cof.ens.fr/depot/Rentree.pdf \r\n\r\nAmicalement,\r\n\r\nTon COF qui t'aime."
|
"description": "Mail de bienvenue au COF envoy\u00e9 automatiquement \u00e0 l'inscription d'un nouveau membre"
|
||||||
}
|
},
|
||||||
|
"pk": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-rappel",
|
"shortname": "bda-rappel",
|
||||||
"subject": "{{ show }}",
|
"subject": "{{ show }}",
|
||||||
"description": "Mail de rappel pour les spectacles BdA",
|
"body": "Bonjour {{ member.first_name }},\r\n\r\nNous te rappellons que tu as eu la chance d'obtenir {{ nb_attr|pluralize:\"une place,deux places\" }}\r\npour {{ show.title }}, le {{ show.date }} au {{ show.location }}. N'oublie pas de t'y rendre !\r\n{% if nb_attr == 2 %}\r\nTu as obtenu deux places pour ce spectacle. Nous te rappelons que\r\nces places sont strictement r\u00e9serv\u00e9es aux personnes de moins de 28 ans.\r\n{% endif %}\r\n{% if show.listing %}Pour ce spectacle, tu as re\u00e7u des places sur\r\nlisting. Il te faudra donc te rendre 15 minutes en avance sur les lieux de la repr\u00e9sentation\r\npour retirer {{ nb_attr|pluralize:\"ta place,tes places\" }}.\r\n{% else %}Pour assister \u00e0 ce spectacle, tu dois pr\u00e9senter les billets qui ont\r\n\u00e9t\u00e9 distribu\u00e9s au bur\u00f4.\r\n{% endif %}\r\n\r\nSi tu ne peux plus assister \u00e0 cette repr\u00e9sentation, tu peux\r\nrevendre ta place via BdA-revente, accessible directement sur\r\nGestioCOF (lien \"revendre une place du premier tirage\" sur la page\r\nd'accueil https://www.cof.ens.fr/gestion/).\r\n\r\nEn te souhaitant un excellent spectacle,\r\n\r\nLe Bureau des Arts",
|
||||||
"body": "Bonjour {{ member.first_name }},\r\n\r\nNous te rappellons que tu as eu la chance d'obtenir {{ nb_attr|pluralize:\"une place,deux places\" }}\r\npour {{ show.title }}, le {{ show.date }} au {{ show.location }}. N'oublie pas de t'y rendre !\r\n{% if nb_attr == 2 %}\r\nTu as obtenu deux places pour ce spectacle. Nous te rappelons que\r\nces places sont strictement r\u00e9serv\u00e9es aux personnes de moins de 28 ans.\r\n{% endif %}\r\n{% if show.listing %}Pour ce spectacle, tu as re\u00e7u des places sur\r\nlisting. Il te faudra donc te rendre 15 minutes en avance sur les lieux de la repr\u00e9sentation\r\npour retirer {{ nb_attr|pluralize:\"ta place,tes places\" }}.\r\n{% else %}Pour assister \u00e0 ce spectacle, tu dois pr\u00e9senter les billets qui ont\r\n\u00e9t\u00e9 distribu\u00e9s au bur\u00f4.\r\n{% endif %}\r\n\r\nSi tu ne peux plus assister \u00e0 cette repr\u00e9sentation, tu peux\r\nrevendre ta place via BdA-revente, accessible directement sur\r\nGestioCOF (lien \"revendre une place du premier tirage\" sur la page\r\nd'accueil https://www.cof.ens.fr/gestion/).\r\n\r\nEn te souhaitant un excellent spectacle,\r\n\r\nLe Bureau des Arts"
|
"description": "Mail de rappel pour les spectacles BdA"
|
||||||
}
|
},
|
||||||
|
"pk": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
|
@ -150,8 +163,8 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-revente",
|
"shortname": "bda-revente",
|
||||||
"subject": "{{ show }}",
|
"subject": "{{ show }}",
|
||||||
"description": "Notification envoy\u00e9e \u00e0 toutes les personnes int\u00e9ress\u00e9es par un spectacle pour le signaler qu'une place vient d'\u00eatre mise en vente.",
|
"description": "Notification envoy\u00e9e \u00e0 toutes les personnes int\u00e9ress\u00e9es par un spectacle pour leur signaler qu'une place vient d'\u00eatre mise en vente.",
|
||||||
"body": "Bonjour {{ member.first_name }}\r\n\r\nUne place pour le spectacle {{ show.title }} ({{ show.date }})\r\na \u00e9t\u00e9 post\u00e9e sur BdA-Revente.\r\n\r\nSi ce spectacle t'int\u00e9resse toujours, merci de nous le signaler en cliquant\r\nsur ce lien : http://{{ site }}{% url \"bda-revente-interested\" revente.id %}.\r\nDans le cas o\u00f9 plusieurs personnes seraient int\u00e9ress\u00e9es, nous proc\u00e8derons \u00e0\r\nun tirage au sort le {{ revente.date_tirage|date:\"DATE_FORMAT\" }}.\r\n\r\nChaleureusement,\r\nLe BdA"
|
"body": "Bonjour {{ member.first_name }}\r\n\r\nUne place pour le spectacle {{ show.title }} ({{ show.date }})\r\na \u00e9t\u00e9 post\u00e9e sur BdA-Revente.\r\n\r\nSi ce spectacle t'int\u00e9resse toujours, merci de nous le signaler en cliquant\r\nsur ce lien : http://{{ site }}{% url \"bda-revente-confirm\" revente.id %}.\r\nDans le cas o\u00f9 plusieurs personnes seraient int\u00e9ress\u00e9es, nous proc\u00e8derons \u00e0\r\nun tirage au sort le {{ revente.date_tirage|date:\"DATE_FORMAT\" }}.\r\n\r\nChaleureusement,\r\nLe BdA"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -161,427 +174,427 @@
|
||||||
"shortname": "bda-shotgun",
|
"shortname": "bda-shotgun",
|
||||||
"subject": "{{ show }}",
|
"subject": "{{ show }}",
|
||||||
"description": "Notification signalant qu'une place est au shotgun aux personnes int\u00e9ress\u00e9es.",
|
"description": "Notification signalant qu'une place est au shotgun aux personnes int\u00e9ress\u00e9es.",
|
||||||
"body": "Bonjour {{ member.first_name }}\r\n\r\nUne place pour le spectacle {{ show.title }} ({{ show.date }})\r\na \u00e9t\u00e9 post\u00e9e sur BdA-Revente.\r\n\r\nPuisque ce spectacle a lieu dans moins de 24h, il n'y a pas de tirage au sort pour\r\ncette place : elle est disponible imm\u00e9diatement \u00e0 l'adresse\r\nhttp://{{ site }}{% url \"bda-buy-revente\" show.id %}, \u00e0 la disposition de tous.\r\n\r\nChaleureusement,\r\nLe BdA"
|
"body": "Bonjour {{ member.first_name }}\r\n\r\nUne place pour le spectacle {{ show.title }} ({{ show.date }})\r\na \u00e9t\u00e9 post\u00e9e sur BdA-Revente.\r\n\r\nPuisque ce spectacle a lieu dans moins de 24h, il n'y a pas de tirage au sort pour\r\ncette place : elle est disponible imm\u00e9diatement \u00e0 l'adresse\r\nhttp://{{ site }}{% url \"bda-revente-buy\" show.id %}, \u00e0 la disposition de tous.\r\n\r\nChaleureusement,\r\nLe BdA"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-revente-winner",
|
"shortname": "bda-revente-winner",
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
"subject": "BdA-Revente : {{ show.title }}",
|
||||||
"description": "Mail envoy\u00e9 au gagnant d'un tirage BdA-Revente",
|
"body": "Bonjour {{ acheteur.first_name }},\r\n\r\nTu as \u00e9t\u00e9 tir\u00e9-e au sort pour racheter une place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) \u00e0 {{ show.price|floatformat:2 }}\u20ac.\r\nTu peux contacter le/la vendeur-se \u00e0 l'adresse {{ vendeur.email }}.\r\n\r\nChaleureusement,\r\nLe BdA",
|
||||||
"body": "Bonjour {{ acheteur.first_name }},\r\n\r\nTu as \u00e9t\u00e9 tir\u00e9-e au sort pour racheter une place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) \u00e0 {{ show.price|floatformat:2 }}\u20ac.\r\nTu peux contacter le/la vendeur-se \u00e0 l'adresse {{ vendeur.email }}.\r\n\r\nChaleureusement,\r\nLe BdA"
|
"description": "Mail envoy\u00e9 au gagnant d'un tirage BdA-Revente"
|
||||||
}
|
},
|
||||||
|
"pk": 5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-revente-loser",
|
"shortname": "bda-revente-loser",
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
"subject": "BdA-Revente : {{ show.title }}",
|
||||||
"description": "Notification envoy\u00e9e aux perdants d'un tirage de revente.",
|
"body": "Bonjour {{ acheteur.first_name }},\r\n\r\nTu t'\u00e9tais inscrit-e pour la revente de la place de {{ vendeur.get_full_name }}\r\npour {{ show.title }}.\r\nMalheureusement, une autre personne a \u00e9t\u00e9 tir\u00e9e au sort pour racheter la place.\r\nTu pourras certainement retenter ta chance pour une autre revente !\r\n\r\n\u00c0 tr\u00e8s bient\u00f4t,\r\nLe Bureau des Arts",
|
||||||
"body": "Bonjour {{ acheteur.first_name }},\r\n\r\nTu t'\u00e9tais inscrit-e pour la revente de la place de {{ vendeur.get_full_name }}\r\npour {{ show.title }}.\r\nMalheureusement, une autre personne a \u00e9t\u00e9 tir\u00e9e au sort pour racheter la place.\r\nTu pourras certainement retenter ta chance pour une autre revente !\r\n\r\n\u00c0 tr\u00e8s bient\u00f4t,\r\nLe Bureau des Arts"
|
"description": "Notification envoy\u00e9e aux perdants d'un tirage de revente."
|
||||||
}
|
},
|
||||||
|
"pk": 6
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
"pk": 7,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-revente-seller",
|
"shortname": "bda-revente-seller",
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
"subject": "BdA-Revente : {{ show.title }}",
|
||||||
"description": "Notification envoy\u00e9e au vendeur d'une place pour lui indiquer qu'elle vient d'\u00eatre attribu\u00e9e",
|
"body": "Bonjour {{ vendeur.first_name }},\r\n\r\nLa personne tir\u00e9e au sort pour racheter ta place pour {{ show.title }} est {{ acheteur.get_full_name }}.\r\nTu peux le/la contacter \u00e0 l'adresse {{ acheteur.email }}, ou en r\u00e9pondant \u00e0 ce mail.\r\n\r\nChaleureusement,\r\nLe BdA",
|
||||||
"body": "Bonjour {{ vendeur.first_name }},\r\n\r\nLa personne tir\u00e9e au sort pour racheter ta place pour {{ show.title }} est {{ acheteur.get_full_name }}.\r\nTu peux le/la contacter \u00e0 l'adresse {{ acheteur.email }}, ou en r\u00e9pondant \u00e0 ce mail.\r\n\r\nChaleureusement,\r\nLe BdA"
|
"description": "Notification envoy\u00e9e au vendeur d'une place pour lui indiquer qu'elle vient d'\u00eatre attribu\u00e9e"
|
||||||
}
|
},
|
||||||
|
"pk": 7
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
"pk": 8,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-revente-new",
|
"shortname": "bda-revente-new",
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
"subject": "BdA-Revente : {{ show.title }}",
|
||||||
"description": "Notification signalant au vendeur d'une place que sa mise en vente a bien eu lieu et lui donnant quelques informations compl\u00e9mentaires.",
|
"body": "Bonjour {{ vendeur.first_name }},\r\n\r\nTu t\u2019es bien inscrit-e pour la revente de {{ show.title }}.\r\n\r\n{% with revente.date_tirage as time %}\r\nLe tirage au sort entre tout-e-s les racheteuse-eur-s potentiel-le-s aura lieu\r\nle {{ time|date:\"DATE_FORMAT\" }} \u00e0 {{ time|time:\"TIME_FORMAT\" }} (dans {{time|timeuntil }}).\r\nSi personne ne s\u2019est inscrit pour racheter la place, celle-ci apparaitra parmi\r\nles \u00ab Places disponibles imm\u00e9diatement \u00e0 la revente \u00bb sur GestioCOF.\r\n{% endwith %}\r\n\r\nBonne revente !\r\nLe Bureau des Arts",
|
||||||
"body": "Bonjour {{ vendeur.first_name }},\r\n\r\nTu t\u2019es bien inscrit-e pour la revente de {{ show.title }}.\r\n\r\n{% with revente.date_tirage as time %}\r\nLe tirage au sort entre tout-e-s les racheteuse-eur-s potentiel-le-s aura lieu\r\nle {{ time|date:\"DATE_FORMAT\" }} \u00e0 {{ time|time:\"TIME_FORMAT\" }} (dans {{time|timeuntil }}).\r\nSi personne ne s\u2019est inscrit pour racheter la place, celle-ci apparaitra parmi\r\nles \u00ab Places disponibles imm\u00e9diatement \u00e0 la revente \u00bb sur GestioCOF.\r\n{% endwith %}\r\n\r\nBonne revente !\r\nLe Bureau des Arts"
|
"description": "Notification signalant au vendeur d'une place que sa mise en vente a bien eu lieu et lui donnant quelques informations compl\u00e9mentaires."
|
||||||
}
|
},
|
||||||
|
"pk": 8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
"pk": 9,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-buy-shotgun",
|
"shortname": "bda-buy-shotgun",
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
"subject": "BdA-Revente : {{ show.title }}",
|
||||||
"description": "Mail envoy\u00e9 au revendeur lors d'un achat au shotgun.",
|
"body": "Bonjour {{ vendeur.first_name }} !\r\n\r\nJe souhaiterais racheter ta place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) \u00e0 {{ show.price|floatformat:2 }}\u20ac.\r\nContacte-moi si tu es toujours int\u00e9ress\u00e9\u00b7e !\r\n\r\n{{ acheteur.get_full_name }} ({{ acheteur.email }})",
|
||||||
"body": "Bonjour {{ vendeur.first_name }} !\r\n\r\nJe souhaiterais racheter ta place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) \u00e0 {{ show.price|floatformat:2 }}\u20ac.\r\nContacte-moi si tu es toujours int\u00e9ress\u00e9\u00b7e !\r\n\r\n{{ acheteur.get_full_name }} ({{ acheteur.email }})"
|
"description": "Mail envoy\u00e9 au revendeur lors d'un achat au shotgun."
|
||||||
}
|
},
|
||||||
|
"pk": 9
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
"pk": 10,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "petit-cours-mail-eleve",
|
"shortname": "petit-cours-mail-eleve",
|
||||||
"subject": "Petits cours ENS par le COF",
|
"subject": "Petits cours ENS par le COF",
|
||||||
"description": "Mail envoy\u00e9 aux personnes dont ont a donn\u00e9 les contacts \u00e0 des demandeurs de petits cours",
|
"body": "Salut,\r\n\r\nLe COF a re\u00e7u une demande de petit cours qui te correspond. Tu es en haut de la liste d'attente donc on a transmis tes coordonn\u00e9es, ainsi que celles de 2 autres qui correspondaient aussi (c'est la vie, on donne les num\u00e9ros 3 par 3 pour que ce soit plus souple). Voici quelques infos sur l'annonce en question :\r\n\r\n\u00a4 Nom : {{ demande.name }}\r\n\r\n\u00a4 P\u00e9riode : {{ demande.quand }}\r\n\r\n\u00a4 Fr\u00e9quence : {{ demande.freq }}\r\n\r\n\u00a4 Lieu (si pr\u00e9f\u00e9r\u00e9) : {{ demande.lieu }}\r\n\r\n\u00a4 Niveau : {{ demande.get_niveau_display }}\r\n\r\n\u00a4 Remarques diverses (d\u00e9sol\u00e9 pour les balises HTML) : {{ demande.remarques }}\r\n\r\n{% if matieres|length > 1 %}\u00a4 Mati\u00e8res :\r\n{% for matiere in matieres %} \u00a4 {{ matiere }}\r\n{% endfor %}{% else %}\u00a4 Mati\u00e8re : {% for matiere in matieres %}{{ matiere }}\r\n{% endfor %}{% endif %}\r\nVoil\u00e0, cette personne te contactera peut-\u00eatre sous peu, tu pourras voir les d\u00e9tails directement avec elle (prix, modalit\u00e9s, ...). Pour indication, 30 Euro/h semble \u00eatre la moyenne.\r\n\r\nSi tu te rends compte qu'en fait tu ne peux pas/plus donner de cours en ce moment, \u00e7a serait cool que tu d\u00e9coches la case \"Recevoir des propositions de petits cours\" sur GestioCOF. Ensuite d\u00e8s que tu voudras r\u00e9appara\u00eetre tu pourras recocher la case et tu seras \u00e0 nouveau sur la liste.\r\n\r\n\u00c0 bient\u00f4t,\r\n\r\n--\r\nLe COF, pour les petits cours",
|
||||||
"body": "Salut,\r\n\r\nLe COF a re\u00e7u une demande de petit cours qui te correspond. Tu es en haut de la liste d'attente donc on a transmis tes coordonn\u00e9es, ainsi que celles de 2 autres qui correspondaient aussi (c'est la vie, on donne les num\u00e9ros 3 par 3 pour que ce soit plus souple). Voici quelques infos sur l'annonce en question :\r\n\r\n\u00a4 Nom : {{ demande.name }}\r\n\r\n\u00a4 P\u00e9riode : {{ demande.quand }}\r\n\r\n\u00a4 Fr\u00e9quence : {{ demande.freq }}\r\n\r\n\u00a4 Lieu (si pr\u00e9f\u00e9r\u00e9) : {{ demande.lieu }}\r\n\r\n\u00a4 Niveau : {{ demande.get_niveau_display }}\r\n\r\n\u00a4 Remarques diverses (d\u00e9sol\u00e9 pour les balises HTML) : {{ demande.remarques }}\r\n\r\n{% if matieres|length > 1 %}\u00a4 Mati\u00e8res :\r\n{% for matiere in matieres %} \u00a4 {{ matiere }}\r\n{% endfor %}{% else %}\u00a4 Mati\u00e8re : {% for matiere in matieres %}{{ matiere }}\r\n{% endfor %}{% endif %}\r\nVoil\u00e0, cette personne te contactera peut-\u00eatre sous peu, tu pourras voir les d\u00e9tails directement avec elle (prix, modalit\u00e9s, ...). Pour indication, 30 Euro/h semble \u00eatre la moyenne.\r\n\r\nSi tu te rends compte qu'en fait tu ne peux pas/plus donner de cours en ce moment, \u00e7a serait cool que tu d\u00e9coches la case \"Recevoir des propositions de petits cours\" sur GestioCOF. Ensuite d\u00e8s que tu voudras r\u00e9appara\u00eetre tu pourras recocher la case et tu seras \u00e0 nouveau sur la liste.\r\n\r\n\u00c0 bient\u00f4t,\r\n\r\n--\r\nLe COF, pour les petits cours"
|
"description": "Mail envoy\u00e9 aux personnes dont ont a donn\u00e9 les contacts \u00e0 des demandeurs de petits cours"
|
||||||
}
|
},
|
||||||
|
"pk": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
"pk": 11,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "petits-cours-mail-demandeur",
|
"shortname": "petits-cours-mail-demandeur",
|
||||||
"subject": "Cours particuliers ENS",
|
"subject": "Cours particuliers ENS",
|
||||||
"description": "Mail envoy\u00e9 aux personnent qui demandent des petits cours lorsque leur demande est trait\u00e9e.\r\n\r\n(Ne pas toucher \u00e0 {{ extra|safe }})",
|
"body": "Bonjour,\r\n\r\nJe vous contacte au sujet de votre annonce pass\u00e9e sur le site du COF pour rentrer en contact avec un \u00e9l\u00e8ve normalien pour des cours particuliers. Voici les coordonn\u00e9es d'\u00e9l\u00e8ves qui sont motiv\u00e9s par de tels cours et correspondent aux crit\u00e8res que vous nous aviez transmis :\r\n\r\n{% for matiere, proposed in proposals %}\u00a4 {{ matiere }} :{% for user in proposed %}\r\n \u00a4 {{ user.get_full_name }}{% if user.profile.phone %}, {{ user.profile.phone }}{% endif %}{% if user.email %}, {{ user.email }}{% endif %}{% endfor %}\r\n\r\n{% endfor %}{% if unsatisfied %}Nous n'avons cependant pas pu trouver d'\u00e9l\u00e8ve disponible pour des cours de {% for matiere in unsatisfied %}{% if forloop.counter0 > 0 %}, {% endif %}{{ matiere }}{% endfor %}.\r\n\r\n{% endif %}Si pour une raison ou une autre ces num\u00e9ros ne suffisaient pas, n'h\u00e9sitez pas \u00e0 r\u00e9pondre \u00e0 cet e-mail et je vous en ferai parvenir d'autres sans probl\u00e8me.\r\n{% if extra|length > 0 %}\r\n{{ extra|safe }}\r\n{% endif %}\r\nCordialement,\r\n\r\n--\r\nLe COF, BdE de l'ENS",
|
||||||
"body": "Bonjour,\r\n\r\nJe vous contacte au sujet de votre annonce pass\u00e9e sur le site du COF pour rentrer en contact avec un \u00e9l\u00e8ve normalien pour des cours particuliers. Voici les coordonn\u00e9es d'\u00e9l\u00e8ves qui sont motiv\u00e9s par de tels cours et correspondent aux crit\u00e8res que vous nous aviez transmis :\r\n\r\n{% for matiere, proposed in proposals %}\u00a4 {{ matiere }} :{% for user in proposed %}\r\n \u00a4 {{ user.get_full_name }}{% if user.profile.phone %}, {{ user.profile.phone }}{% endif %}{% if user.email %}, {{ user.email }}{% endif %}{% endfor %}\r\n\r\n{% endfor %}{% if unsatisfied %}Nous n'avons cependant pas pu trouver d'\u00e9l\u00e8ve disponible pour des cours de {% for matiere in unsatisfied %}{% if forloop.counter0 > 0 %}, {% endif %}{{ matiere }}{% endfor %}.\r\n\r\n{% endif %}Si pour une raison ou une autre ces num\u00e9ros ne suffisaient pas, n'h\u00e9sitez pas \u00e0 r\u00e9pondre \u00e0 cet e-mail et je vous en ferai parvenir d'autres sans probl\u00e8me.\r\n{% if extra|length > 0 %}\r\n{{ extra|safe }}\r\n{% endif %}\r\nCordialement,\r\n\r\n--\r\nLe COF, BdE de l'ENS"
|
"description": "Mail envoy\u00e9 aux personnes qui demandent des petits cours lorsque leur demande est trait\u00e9e.\r\n\r\n(Ne pas toucher \u00e0 {{ extra|safe }})"
|
||||||
}
|
},
|
||||||
|
"pk": 11
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
"pk": 12,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-attributions",
|
"shortname": "bda-attributions",
|
||||||
"subject": "R\u00e9sultats du tirage au sort",
|
"subject": "R\u00e9sultats du tirage au sort",
|
||||||
"description": "Mail annon\u00e7ant les r\u00e9sultats du tirage au sort du BdA aux gagnants d'une ou plusieurs places",
|
"body": "Cher-e {{ member.first_name }},\r\n\r\nTu t'es inscrit-e pour le tirage au sort du BdA. Tu as \u00e9t\u00e9 s\u00e9lectionn\u00e9-e\r\npour les spectacles suivants :\r\n{% for place in places %}\r\n- 1 place pour {{ place }}{% endfor %}\r\n\r\n*Paiement*\r\nL'int\u00e9gralit\u00e9 de ces places de spectacles est \u00e0 r\u00e9gler d\u00e8s maintenant et AVANT\r\nvendredi prochain, au bureau du COF pendant les heures de permanences (du lundi au vendredi\r\nentre 12h et 14h, et entre 18h et 20h). Des facilit\u00e9s de paiement sont bien\r\n\u00e9videmment possibles : nous pouvons ne pas encaisser le ch\u00e8que imm\u00e9diatement,\r\nou bien d\u00e9couper votre paiement en deux fois. Pour ceux qui ne pourraient pas\r\nvenir payer au bureau, merci de nous contacter par mail.\r\n\r\n*Mode de retrait des places*\r\nAu moment du paiement, certaines places vous seront remises directement,\r\nd'autres seront \u00e0 r\u00e9cup\u00e9rer au cours de l'ann\u00e9e, d'autres encore seront\r\nnominatives et \u00e0 retirer le soir m\u00eame dans les the\u00e2tres correspondants.\r\nPour chaque spectacle, vous recevrez un mail quelques jours avant la\r\nrepr\u00e9sentation vous indiquant le mode de retrait.\r\n\r\nNous vous rappelons que l'obtention de places du BdA vous engage \u00e0\r\nrespecter les r\u00e8gles de fonctionnement :\r\nhttp://www.cof.ens.fr/bda/?page_id=1370\r\nUn syst\u00e8me de revente des places via les mails BdA-revente disponible\r\ndirectement sur votre compte GestioCOF.\r\n\r\nEn vous souhaitant de tr\u00e8s beaux spectacles tout au long de l'ann\u00e9e,\r\n--\r\nLe Bureau des Arts",
|
||||||
"body": "Cher-e {{ member.first_name }},\r\n\r\nTu t'es inscrit-e pour le tirage au sort du BdA. Tu as \u00e9t\u00e9 s\u00e9lectionn\u00e9-e\r\npour les spectacles suivants :\r\n{% for place in places %}\r\n- 1 place pour {{ place }}{% endfor %}\r\n\r\n*Paiement*\r\nL'int\u00e9gralit\u00e9 de ces places de spectacles est \u00e0 r\u00e9gler d\u00e8s maintenant et AVANT\r\nvendredi prochain, au bureau du COF pendant les heures de permanences (du lundi au vendredi\r\nentre 12h et 14h, et entre 18h et 20h). Des facilit\u00e9s de paiement sont bien\r\n\u00e9videmment possibles : nous pouvons ne pas encaisser le ch\u00e8que imm\u00e9diatement,\r\nou bien d\u00e9couper votre paiement en deux fois. Pour ceux qui ne pourraient pas\r\nvenir payer au bureau, merci de nous contacter par mail.\r\n\r\n*Mode de retrait des places*\r\nAu moment du paiement, certaines places vous seront remises directement,\r\nd'autres seront \u00e0 r\u00e9cup\u00e9rer au cours de l'ann\u00e9e, d'autres encore seront\r\nnominatives et \u00e0 retirer le soir m\u00eame dans les the\u00e2tres correspondants.\r\nPour chaque spectacle, vous recevrez un mail quelques jours avant la\r\nrepr\u00e9sentation vous indiquant le mode de retrait.\r\n\r\nNous vous rappelons que l'obtention de places du BdA vous engage \u00e0\r\nrespecter les r\u00e8gles de fonctionnement :\r\nhttp://www.cof.ens.fr/bda/?page_id=1370\r\nUn syst\u00e8me de revente des places via les mails BdA-revente disponible\r\ndirectement sur votre compte GestioCOF.\r\n\r\nEn vous souhaitant de tr\u00e8s beaux spectacles tout au long de l'ann\u00e9e,\r\n--\r\nLe Bureau des Arts"
|
"description": "Mail annon\u00e7ant les r\u00e9sultats du tirage au sort du BdA aux gagnants d'une ou plusieurs places"
|
||||||
}
|
},
|
||||||
|
"pk": 12
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
"pk": 13,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-attributions-decus",
|
"shortname": "bda-attributions-decus",
|
||||||
"subject": "R\u00e9sultats du tirage au sort",
|
"subject": "R\u00e9sultats du tirage au sort",
|
||||||
"description": "Mail annon\u00e7ant les r\u00e9sultats du tirage au sort du BdA aux personnes n'ayant pas obtenu de place",
|
"body": "Cher-e {{ member.first_name }},\r\n\r\nTu t'es inscrit-e pour le tirage au sort du BdA. Malheureusement, tu n'as\r\nobtenu aucune place.\r\n\r\nNous proposons cependant de nombreuses offres hors-tirage tout au long de\r\nl'ann\u00e9e, et nous t'invitons \u00e0 nous contacter si l'une d'entre elles\r\nt'int\u00e9resse !\r\n--\r\nLe Bureau des Arts",
|
||||||
"body": "Cher-e {{ member.first_name }},\r\n\r\nTu t'es inscrit-e pour le tirage au sort du BdA. Malheureusement, tu n'as\r\nobtenu aucune place.\r\n\r\nNous proposons cependant de nombreuses offres hors-tirage tout au long de\r\nl'ann\u00e9e, et nous t'invitons \u00e0 nous contacter si l'une d'entre elles\r\nt'int\u00e9resse !\r\n--\r\nLe Bureau des Arts"
|
"description": "Mail annon\u00e7ant les r\u00e9sultats du tirage au sort du BdA aux personnes n'ayant pas obtenu de place"
|
||||||
}
|
},
|
||||||
|
"pk": 13
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "member",
|
|
||||||
"description": "Utilisateur de GestioCOF",
|
|
||||||
"custommail": 1,
|
"custommail": 1,
|
||||||
"type": 1
|
"type": 1,
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommailvariable",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"name": "member",
|
"name": "member",
|
||||||
"description": "Utilisateur ayant eu une place pour ce spectacle",
|
"description": "Utilisateur de GestioCOF"
|
||||||
"custommail": 2,
|
},
|
||||||
"type": 1
|
"pk": 1
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 2,
|
||||||
|
"type": 1,
|
||||||
|
"name": "member",
|
||||||
|
"description": "Utilisateur ayant eu une place pour ce spectacle"
|
||||||
|
},
|
||||||
|
"pk": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "custommail.variable",
|
||||||
|
"fields": {
|
||||||
|
"custommail": 2,
|
||||||
|
"type": 3,
|
||||||
"name": "show",
|
"name": "show",
|
||||||
"description": "Spectacle",
|
"description": "Spectacle"
|
||||||
"custommail": 2,
|
},
|
||||||
"type": 3
|
"pk": 3
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 2,
|
||||||
|
"type": 2,
|
||||||
"name": "nb_attr",
|
"name": "nb_attr",
|
||||||
"description": "Nombre de places obtenues",
|
"description": "Nombre de places obtenues"
|
||||||
"custommail": 2,
|
},
|
||||||
"type": 2
|
"pk": 4
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 3,
|
||||||
|
"type": 4,
|
||||||
"name": "revente",
|
"name": "revente",
|
||||||
"description": "Revente mentionn\u00e9e dans le mail",
|
"description": "Revente mentionn\u00e9e dans le mail"
|
||||||
"custommail": 3,
|
},
|
||||||
"type": 4
|
"pk": 5
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 3,
|
||||||
|
"type": 1,
|
||||||
"name": "member",
|
"name": "member",
|
||||||
"description": "Personne int\u00e9ress\u00e9e par la place",
|
"description": "Personne int\u00e9ress\u00e9e par la place"
|
||||||
"custommail": 3,
|
},
|
||||||
"type": 1
|
"pk": 6
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 7,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 3,
|
||||||
|
"type": 3,
|
||||||
"name": "show",
|
"name": "show",
|
||||||
"description": "Spectacle",
|
"description": "Spectacle"
|
||||||
"custommail": 3,
|
},
|
||||||
"type": 3
|
"pk": 7
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 8,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "site",
|
|
||||||
"description": "Site web (gestioCOF)",
|
|
||||||
"custommail": 3,
|
"custommail": 3,
|
||||||
"type": 5
|
"type": 5,
|
||||||
}
|
"name": "site",
|
||||||
|
"description": "Site web (gestioCOF)"
|
||||||
|
},
|
||||||
|
"pk": 8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 9,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "site",
|
|
||||||
"description": "Site web (gestioCOF)",
|
|
||||||
"custommail": 4,
|
"custommail": 4,
|
||||||
"type": 5
|
"type": 5,
|
||||||
}
|
"name": "site",
|
||||||
|
"description": "Site web (gestioCOF)"
|
||||||
|
},
|
||||||
|
"pk": 9
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 10,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 4,
|
||||||
|
"type": 3,
|
||||||
"name": "show",
|
"name": "show",
|
||||||
"description": "Spectacle",
|
"description": "Spectacle"
|
||||||
"custommail": 4,
|
},
|
||||||
"type": 3
|
"pk": 10
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 11,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 4,
|
||||||
|
"type": 1,
|
||||||
"name": "member",
|
"name": "member",
|
||||||
"description": "Personne int\u00e9ress\u00e9e par la place",
|
"description": "Personne int\u00e9ress\u00e9e par la place"
|
||||||
"custommail": 4,
|
},
|
||||||
"type": 1
|
"pk": 11
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 12,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "acheteur",
|
|
||||||
"description": "Gagnant-e du tirage",
|
|
||||||
"custommail": 5,
|
"custommail": 5,
|
||||||
"type": 1
|
"type": 1,
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommailvariable",
|
|
||||||
"pk": 13,
|
|
||||||
"fields": {
|
|
||||||
"name": "vendeur",
|
|
||||||
"description": "Personne qui vend une place",
|
|
||||||
"custommail": 5,
|
|
||||||
"type": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommailvariable",
|
|
||||||
"pk": 14,
|
|
||||||
"fields": {
|
|
||||||
"name": "show",
|
|
||||||
"description": "Spectacle",
|
|
||||||
"custommail": 5,
|
|
||||||
"type": 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommailvariable",
|
|
||||||
"pk": 15,
|
|
||||||
"fields": {
|
|
||||||
"name": "show",
|
|
||||||
"description": "Spectacle",
|
|
||||||
"custommail": 6,
|
|
||||||
"type": 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommailvariable",
|
|
||||||
"pk": 16,
|
|
||||||
"fields": {
|
|
||||||
"name": "vendeur",
|
|
||||||
"description": "Personne qui vend une place",
|
|
||||||
"custommail": 6,
|
|
||||||
"type": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommailvariable",
|
|
||||||
"pk": 17,
|
|
||||||
"fields": {
|
|
||||||
"name": "acheteur",
|
"name": "acheteur",
|
||||||
"description": "Personne inscrite au tirage qui n'a pas eu la place",
|
"description": "Gagnant-e du tirage"
|
||||||
"custommail": 6,
|
},
|
||||||
"type": 1
|
"pk": 12
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 18,
|
|
||||||
"fields": {
|
|
||||||
"name": "acheteur",
|
|
||||||
"description": "Gagnant-e du tirage",
|
|
||||||
"custommail": 7,
|
|
||||||
"type": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommailvariable",
|
|
||||||
"pk": 19,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 5,
|
||||||
|
"type": 1,
|
||||||
"name": "vendeur",
|
"name": "vendeur",
|
||||||
"description": "Personne qui vend une place",
|
"description": "Personne qui vend une place"
|
||||||
"custommail": 7,
|
},
|
||||||
"type": 1
|
"pk": 13
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 20,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 5,
|
||||||
|
"type": 3,
|
||||||
"name": "show",
|
"name": "show",
|
||||||
"description": "Spectacle",
|
"description": "Spectacle"
|
||||||
"custommail": 7,
|
},
|
||||||
"type": 3
|
"pk": 14
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 21,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 6,
|
||||||
|
"type": 3,
|
||||||
"name": "show",
|
"name": "show",
|
||||||
"description": "Spectacle",
|
"description": "Spectacle"
|
||||||
|
},
|
||||||
|
"pk": 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "custommail.variable",
|
||||||
|
"fields": {
|
||||||
|
"custommail": 6,
|
||||||
|
"type": 1,
|
||||||
|
"name": "vendeur",
|
||||||
|
"description": "Personne qui vend une place"
|
||||||
|
},
|
||||||
|
"pk": 16
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "custommail.variable",
|
||||||
|
"fields": {
|
||||||
|
"custommail": 6,
|
||||||
|
"type": 1,
|
||||||
|
"name": "acheteur",
|
||||||
|
"description": "Personne inscrite au tirage qui n'a pas eu la place"
|
||||||
|
},
|
||||||
|
"pk": 17
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "custommail.variable",
|
||||||
|
"fields": {
|
||||||
|
"custommail": 7,
|
||||||
|
"type": 1,
|
||||||
|
"name": "acheteur",
|
||||||
|
"description": "Gagnant-e du tirage"
|
||||||
|
},
|
||||||
|
"pk": 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "custommail.variable",
|
||||||
|
"fields": {
|
||||||
|
"custommail": 7,
|
||||||
|
"type": 1,
|
||||||
|
"name": "vendeur",
|
||||||
|
"description": "Personne qui vend une place"
|
||||||
|
},
|
||||||
|
"pk": 19
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "custommail.variable",
|
||||||
|
"fields": {
|
||||||
|
"custommail": 7,
|
||||||
|
"type": 3,
|
||||||
|
"name": "show",
|
||||||
|
"description": "Spectacle"
|
||||||
|
},
|
||||||
|
"pk": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "custommail.variable",
|
||||||
|
"fields": {
|
||||||
"custommail": 8,
|
"custommail": 8,
|
||||||
"type": 3
|
"type": 3,
|
||||||
}
|
"name": "show",
|
||||||
|
"description": "Spectacle"
|
||||||
|
},
|
||||||
|
"pk": 21
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 22,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "vendeur",
|
|
||||||
"description": "Personne qui vend la place",
|
|
||||||
"custommail": 8,
|
"custommail": 8,
|
||||||
"type": 1
|
"type": 1,
|
||||||
}
|
"name": "vendeur",
|
||||||
|
"description": "Personne qui vend la place"
|
||||||
|
},
|
||||||
|
"pk": 22
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 23,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 8,
|
||||||
|
"type": 4,
|
||||||
"name": "revente",
|
"name": "revente",
|
||||||
"description": "Revente mentionn\u00e9e dans le mail",
|
"description": "Revente mentionn\u00e9e dans le mail"
|
||||||
"custommail": 8,
|
},
|
||||||
"type": 4
|
"pk": 23
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 24,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 9,
|
||||||
|
"type": 1,
|
||||||
"name": "vendeur",
|
"name": "vendeur",
|
||||||
"description": "Personne qui vend la place",
|
"description": "Personne qui vend la place"
|
||||||
"custommail": 9,
|
},
|
||||||
"type": 1
|
"pk": 24
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 25,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 9,
|
||||||
|
"type": 3,
|
||||||
"name": "show",
|
"name": "show",
|
||||||
"description": "Spectacle",
|
"description": "Spectacle"
|
||||||
"custommail": 9,
|
},
|
||||||
"type": 3
|
"pk": 25
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 26,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 9,
|
||||||
|
"type": 1,
|
||||||
"name": "acheteur",
|
"name": "acheteur",
|
||||||
"description": "Personne qui prend la place au shotgun",
|
"description": "Personne qui prend la place au shotgun"
|
||||||
"custommail": 9,
|
},
|
||||||
"type": 1
|
"pk": 26
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 27,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 10,
|
||||||
|
"type": 6,
|
||||||
"name": "demande",
|
"name": "demande",
|
||||||
"description": "Demande de petit cours",
|
"description": "Demande de petit cours"
|
||||||
"custommail": 10,
|
},
|
||||||
"type": 6
|
"pk": 27
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 28,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 10,
|
||||||
|
"type": 7,
|
||||||
"name": "matieres",
|
"name": "matieres",
|
||||||
"description": "Liste des mati\u00e8res concern\u00e9es par la demande",
|
"description": "Liste des mati\u00e8res concern\u00e9es par la demande"
|
||||||
"custommail": 10,
|
},
|
||||||
"type": 7
|
"pk": 28
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 29,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 11,
|
||||||
|
"type": 10,
|
||||||
"name": "proposals",
|
"name": "proposals",
|
||||||
"description": "Liste associant une liste d'enseignants \u00e0 chaque mati\u00e8re",
|
"description": "Liste associant une liste d'enseignants \u00e0 chaque mati\u00e8re"
|
||||||
"custommail": 11,
|
},
|
||||||
"type": 10
|
"pk": 29
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 30,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 11,
|
||||||
|
"type": 7,
|
||||||
"name": "unsatisfied",
|
"name": "unsatisfied",
|
||||||
"description": "Liste des mati\u00e8res pour lesquelles on n'a pas d'enseigant \u00e0 proposer",
|
"description": "Liste des mati\u00e8res pour lesquelles on n'a pas d'enseigant \u00e0 proposer"
|
||||||
"custommail": 11,
|
},
|
||||||
"type": 7
|
"pk": 30
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 31,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 12,
|
||||||
|
"type": 11,
|
||||||
"name": "places",
|
"name": "places",
|
||||||
"description": "Places de spectacle du participant",
|
"description": "Places de spectacle du participant"
|
||||||
"custommail": 12,
|
},
|
||||||
"type": 11
|
"pk": 31
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 32,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "member",
|
|
||||||
"description": "Participant du tirage au sort",
|
|
||||||
"custommail": 12,
|
"custommail": 12,
|
||||||
"type": 1
|
"type": 1,
|
||||||
}
|
"name": "member",
|
||||||
|
"description": "Participant du tirage au sort"
|
||||||
|
},
|
||||||
|
"pk": 32
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 33,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "member",
|
|
||||||
"description": "Participant du tirage au sort",
|
|
||||||
"custommail": 13,
|
"custommail": 13,
|
||||||
"type": 1
|
"type": 1,
|
||||||
}
|
"name": "member",
|
||||||
|
"description": "Participant du tirage au sort"
|
||||||
|
},
|
||||||
|
"pk": 33
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -48,7 +48,7 @@ class Migration(migrations.Migration):
|
||||||
('is_buro', models.BooleanField(default=False, verbose_name=b'Membre du Bur\xc3\xb4')),
|
('is_buro', models.BooleanField(default=False, verbose_name=b'Membre du Bur\xc3\xb4')),
|
||||||
('petits_cours_accept', models.BooleanField(default=False, verbose_name=b'Recevoir des petits cours')),
|
('petits_cours_accept', models.BooleanField(default=False, verbose_name=b'Recevoir des petits cours')),
|
||||||
('petits_cours_remarques', models.TextField(default=b'', verbose_name='Remarques et pr\xe9cisions pour les petits cours', blank=True)),
|
('petits_cours_remarques', models.TextField(default=b'', verbose_name='Remarques et pr\xe9cisions pour les petits cours', blank=True)),
|
||||||
('user', models.OneToOneField(related_name='profile', to=settings.AUTH_USER_MODEL)),
|
('user', models.OneToOneField(related_name='profile', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Profil COF',
|
'verbose_name': 'Profil COF',
|
||||||
|
@ -91,7 +91,7 @@ class Migration(migrations.Migration):
|
||||||
('name', models.CharField(max_length=200, verbose_name=b'Champ')),
|
('name', models.CharField(max_length=200, verbose_name=b'Champ')),
|
||||||
('fieldtype', models.CharField(default=b'text', max_length=10, verbose_name=b'Type', choices=[(b'text', 'Texte long'), (b'char', 'Texte court')])),
|
('fieldtype', models.CharField(default=b'text', max_length=10, verbose_name=b'Type', choices=[(b'text', 'Texte long'), (b'char', 'Texte court')])),
|
||||||
('default', models.TextField(verbose_name=b'Valeur par d\xc3\xa9faut', blank=True)),
|
('default', models.TextField(verbose_name=b'Valeur par d\xc3\xa9faut', blank=True)),
|
||||||
('event', models.ForeignKey(related_name='commentfields', to='gestioncof.Event')),
|
('event', models.ForeignKey(related_name='commentfields', to='gestioncof.Event', on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Champ',
|
'verbose_name': 'Champ',
|
||||||
|
@ -102,7 +102,7 @@ class Migration(migrations.Migration):
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
('content', models.TextField(null=True, verbose_name=b'Contenu', blank=True)),
|
('content', models.TextField(null=True, verbose_name=b'Contenu', blank=True)),
|
||||||
('commentfield', models.ForeignKey(related_name='values', to='gestioncof.EventCommentField')),
|
('commentfield', models.ForeignKey(related_name='values', to='gestioncof.EventCommentField', on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
|
@ -111,7 +111,7 @@ class Migration(migrations.Migration):
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
('name', models.CharField(max_length=200, verbose_name=b'Option')),
|
('name', models.CharField(max_length=200, verbose_name=b'Option')),
|
||||||
('multi_choices', models.BooleanField(default=False, verbose_name=b'Choix multiples')),
|
('multi_choices', models.BooleanField(default=False, verbose_name=b'Choix multiples')),
|
||||||
('event', models.ForeignKey(related_name='options', to='gestioncof.Event')),
|
('event', models.ForeignKey(related_name='options', to='gestioncof.Event', on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Option',
|
'verbose_name': 'Option',
|
||||||
|
@ -122,7 +122,7 @@ class Migration(migrations.Migration):
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
('value', models.CharField(max_length=200, verbose_name=b'Valeur')),
|
('value', models.CharField(max_length=200, verbose_name=b'Valeur')),
|
||||||
('event_option', models.ForeignKey(related_name='choices', to='gestioncof.EventOption')),
|
('event_option', models.ForeignKey(related_name='choices', to='gestioncof.EventOption', on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Choix',
|
'verbose_name': 'Choix',
|
||||||
|
@ -133,10 +133,10 @@ class Migration(migrations.Migration):
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
('paid', models.BooleanField(default=False, verbose_name=b'A pay\xc3\xa9')),
|
('paid', models.BooleanField(default=False, verbose_name=b'A pay\xc3\xa9')),
|
||||||
('event', models.ForeignKey(to='gestioncof.Event')),
|
('event', models.ForeignKey(to='gestioncof.Event', on_delete=models.CASCADE)),
|
||||||
('filledcomments', models.ManyToManyField(to='gestioncof.EventCommentField', through='gestioncof.EventCommentValue')),
|
('filledcomments', models.ManyToManyField(to='gestioncof.EventCommentField', through='gestioncof.EventCommentValue')),
|
||||||
('options', models.ManyToManyField(to='gestioncof.EventOptionChoice')),
|
('options', models.ManyToManyField(to='gestioncof.EventOptionChoice')),
|
||||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Inscription',
|
'verbose_name': 'Inscription',
|
||||||
|
@ -240,7 +240,7 @@ class Migration(migrations.Migration):
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
('question', models.CharField(max_length=200, verbose_name=b'Question')),
|
('question', models.CharField(max_length=200, verbose_name=b'Question')),
|
||||||
('multi_answers', models.BooleanField(default=False, verbose_name=b'Choix multiples')),
|
('multi_answers', models.BooleanField(default=False, verbose_name=b'Choix multiples')),
|
||||||
('survey', models.ForeignKey(related_name='questions', to='gestioncof.Survey')),
|
('survey', models.ForeignKey(related_name='questions', to='gestioncof.Survey', on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Question',
|
'verbose_name': 'Question',
|
||||||
|
@ -251,7 +251,7 @@ class Migration(migrations.Migration):
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
('answer', models.CharField(max_length=200, verbose_name=b'R\xc3\xa9ponse')),
|
('answer', models.CharField(max_length=200, verbose_name=b'R\xc3\xa9ponse')),
|
||||||
('survey_question', models.ForeignKey(related_name='answers', to='gestioncof.SurveyQuestion')),
|
('survey_question', models.ForeignKey(related_name='answers', to='gestioncof.SurveyQuestion', on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'R\xe9ponse',
|
'verbose_name': 'R\xe9ponse',
|
||||||
|
@ -265,12 +265,12 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='surveyanswer',
|
model_name='surveyanswer',
|
||||||
name='survey',
|
name='survey',
|
||||||
field=models.ForeignKey(to='gestioncof.Survey'),
|
field=models.ForeignKey(to='gestioncof.Survey', on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='surveyanswer',
|
model_name='surveyanswer',
|
||||||
name='user',
|
name='user',
|
||||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
|
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='petitcoursdemande',
|
model_name='petitcoursdemande',
|
||||||
|
@ -280,47 +280,47 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='petitcoursdemande',
|
model_name='petitcoursdemande',
|
||||||
name='traitee_par',
|
name='traitee_par',
|
||||||
field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True),
|
field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='petitcoursattributioncounter',
|
model_name='petitcoursattributioncounter',
|
||||||
name='matiere',
|
name='matiere',
|
||||||
field=models.ForeignKey(verbose_name='Matiere', to='gestioncof.PetitCoursSubject'),
|
field=models.ForeignKey(verbose_name='Matiere', to='gestioncof.PetitCoursSubject', on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='petitcoursattributioncounter',
|
model_name='petitcoursattributioncounter',
|
||||||
name='user',
|
name='user',
|
||||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
|
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='petitcoursattribution',
|
model_name='petitcoursattribution',
|
||||||
name='demande',
|
name='demande',
|
||||||
field=models.ForeignKey(verbose_name='Demande', to='gestioncof.PetitCoursDemande'),
|
field=models.ForeignKey(verbose_name='Demande', to='gestioncof.PetitCoursDemande', on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='petitcoursattribution',
|
model_name='petitcoursattribution',
|
||||||
name='matiere',
|
name='matiere',
|
||||||
field=models.ForeignKey(verbose_name='Mati\xe8re', to='gestioncof.PetitCoursSubject'),
|
field=models.ForeignKey(verbose_name='Mati\xe8re', to='gestioncof.PetitCoursSubject', on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='petitcoursattribution',
|
model_name='petitcoursattribution',
|
||||||
name='user',
|
name='user',
|
||||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
|
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='petitcoursability',
|
model_name='petitcoursability',
|
||||||
name='matiere',
|
name='matiere',
|
||||||
field=models.ForeignKey(verbose_name='Mati\xe8re', to='gestioncof.PetitCoursSubject'),
|
field=models.ForeignKey(verbose_name='Mati\xe8re', to='gestioncof.PetitCoursSubject', on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='petitcoursability',
|
model_name='petitcoursability',
|
||||||
name='user',
|
name='user',
|
||||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
|
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='eventcommentvalue',
|
model_name='eventcommentvalue',
|
||||||
name='registration',
|
name='registration',
|
||||||
field=models.ForeignKey(related_name='comments', to='gestioncof.EventRegistration'),
|
field=models.ForeignKey(related_name='comments', to='gestioncof.EventRegistration', on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='surveyanswer',
|
name='surveyanswer',
|
||||||
|
|
|
@ -23,7 +23,8 @@ class Migration(migrations.Migration):
|
||||||
('subscribe_to_events', models.BooleanField(default=True)),
|
('subscribe_to_events', models.BooleanField(default=True)),
|
||||||
('subscribe_to_my_shows', models.BooleanField(default=True)),
|
('subscribe_to_my_shows', models.BooleanField(default=True)),
|
||||||
('other_shows', models.ManyToManyField(to='bda.Spectacle')),
|
('other_shows', models.ManyToManyField(to='bda.Spectacle')),
|
||||||
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)),
|
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
|
|
47
gestioncof/migrations/0013_pei.py
Normal file
47
gestioncof/migrations/0013_pei.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gestioncof', '0012_merge'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cofprofile',
|
||||||
|
name='occupation',
|
||||||
|
field=models.CharField(
|
||||||
|
verbose_name='Occupation',
|
||||||
|
max_length=9,
|
||||||
|
default='1A',
|
||||||
|
choices=[
|
||||||
|
('exterieur', 'Extérieur'),
|
||||||
|
('1A', '1A'),
|
||||||
|
('2A', '2A'),
|
||||||
|
('3A', '3A'),
|
||||||
|
('4A', '4A'),
|
||||||
|
('archicube', 'Archicube'),
|
||||||
|
('doctorant', 'Doctorant'),
|
||||||
|
('CST', 'CST'),
|
||||||
|
('PEI', 'PEI')
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cofprofile',
|
||||||
|
name='type_cotiz',
|
||||||
|
field=models.CharField(
|
||||||
|
verbose_name='Type de cotisation',
|
||||||
|
max_length=9,
|
||||||
|
default='normalien',
|
||||||
|
choices=[
|
||||||
|
('etudiant', 'Normalien étudiant'),
|
||||||
|
('normalien', 'Normalien élève'),
|
||||||
|
('exterieur', 'Extérieur'),
|
||||||
|
('gratis', 'Gratuit')
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -8,23 +8,6 @@ from gestioncof.petits_cours_models import choices_length
|
||||||
|
|
||||||
from bda.models import Spectacle
|
from bda.models import Spectacle
|
||||||
|
|
||||||
OCCUPATION_CHOICES = (
|
|
||||||
('exterieur', _("Extérieur")),
|
|
||||||
('1A', _("1A")),
|
|
||||||
('2A', _("2A")),
|
|
||||||
('3A', _("3A")),
|
|
||||||
('4A', _("4A")),
|
|
||||||
('archicube', _("Archicube")),
|
|
||||||
('doctorant', _("Doctorant")),
|
|
||||||
('CST', _("CST")),
|
|
||||||
)
|
|
||||||
|
|
||||||
TYPE_COTIZ_CHOICES = (
|
|
||||||
('etudiant', _("Normalien étudiant")),
|
|
||||||
('normalien', _("Normalien élève")),
|
|
||||||
('exterieur', _("Extérieur")),
|
|
||||||
)
|
|
||||||
|
|
||||||
TYPE_COMMENT_FIELD = (
|
TYPE_COMMENT_FIELD = (
|
||||||
('text', _("Texte long")),
|
('text', _("Texte long")),
|
||||||
('char', _("Texte court")),
|
('char', _("Texte court")),
|
||||||
|
@ -32,7 +15,44 @@ TYPE_COMMENT_FIELD = (
|
||||||
|
|
||||||
|
|
||||||
class CofProfile(models.Model):
|
class CofProfile(models.Model):
|
||||||
user = models.OneToOneField(User, related_name="profile")
|
STATUS_EXTE = "exterieur"
|
||||||
|
STATUS_1A = "1A"
|
||||||
|
STATUS_2A = "2A"
|
||||||
|
STATUS_3A = "3A"
|
||||||
|
STATUS_4A = "4A"
|
||||||
|
STATUS_ARCHI = "archicube"
|
||||||
|
STATUS_DOCTORANT = "doctorant"
|
||||||
|
STATUS_CST = "CST"
|
||||||
|
STATUS_PEI = "PEI"
|
||||||
|
|
||||||
|
OCCUPATION_CHOICES = (
|
||||||
|
(STATUS_EXTE, _("Extérieur")),
|
||||||
|
(STATUS_1A, _("1A")),
|
||||||
|
(STATUS_2A, _("2A")),
|
||||||
|
(STATUS_3A, _("3A")),
|
||||||
|
(STATUS_4A, _("4A")),
|
||||||
|
(STATUS_ARCHI, _("Archicube")),
|
||||||
|
(STATUS_DOCTORANT, _("Doctorant")),
|
||||||
|
(STATUS_CST, _("CST")),
|
||||||
|
(STATUS_PEI, _("PEI")),
|
||||||
|
)
|
||||||
|
|
||||||
|
COTIZ_ETUDIANT = "etudiant"
|
||||||
|
COTIZ_NORMALIEN = "normalien"
|
||||||
|
COTIZ_EXTE = "exterieur"
|
||||||
|
COTIZ_GRATIS = "gratis"
|
||||||
|
|
||||||
|
TYPE_COTIZ_CHOICES = (
|
||||||
|
(COTIZ_ETUDIANT, _("Normalien étudiant")),
|
||||||
|
(COTIZ_NORMALIEN, _("Normalien élève")),
|
||||||
|
(COTIZ_EXTE, _("Extérieur")),
|
||||||
|
(COTIZ_GRATIS, _("Gratuit")),
|
||||||
|
)
|
||||||
|
|
||||||
|
user = models.OneToOneField(
|
||||||
|
User, on_delete=models.CASCADE,
|
||||||
|
related_name="profile",
|
||||||
|
)
|
||||||
login_clipper = models.CharField(
|
login_clipper = models.CharField(
|
||||||
"Login clipper", max_length=32, blank=True
|
"Login clipper", max_length=32, blank=True
|
||||||
)
|
)
|
||||||
|
@ -113,7 +133,10 @@ class Event(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class EventCommentField(models.Model):
|
class EventCommentField(models.Model):
|
||||||
event = models.ForeignKey(Event, related_name="commentfields")
|
event = models.ForeignKey(
|
||||||
|
Event, on_delete=models.CASCADE,
|
||||||
|
related_name="commentfields",
|
||||||
|
)
|
||||||
name = models.CharField("Champ", max_length=200)
|
name = models.CharField("Champ", max_length=200)
|
||||||
fieldtype = models.CharField("Type", max_length=10,
|
fieldtype = models.CharField("Type", max_length=10,
|
||||||
choices=TYPE_COMMENT_FIELD, default="text")
|
choices=TYPE_COMMENT_FIELD, default="text")
|
||||||
|
@ -127,9 +150,14 @@ class EventCommentField(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class EventCommentValue(models.Model):
|
class EventCommentValue(models.Model):
|
||||||
commentfield = models.ForeignKey(EventCommentField, related_name="values")
|
commentfield = models.ForeignKey(
|
||||||
registration = models.ForeignKey("EventRegistration",
|
EventCommentField, on_delete=models.CASCADE,
|
||||||
related_name="comments")
|
related_name="values",
|
||||||
|
)
|
||||||
|
registration = models.ForeignKey(
|
||||||
|
"EventRegistration", on_delete=models.CASCADE,
|
||||||
|
related_name="comments",
|
||||||
|
)
|
||||||
content = models.TextField("Contenu", blank=True, null=True)
|
content = models.TextField("Contenu", blank=True, null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -137,7 +165,10 @@ class EventCommentValue(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class EventOption(models.Model):
|
class EventOption(models.Model):
|
||||||
event = models.ForeignKey(Event, related_name="options")
|
event = models.ForeignKey(
|
||||||
|
Event, on_delete=models.CASCADE,
|
||||||
|
related_name="options",
|
||||||
|
)
|
||||||
name = models.CharField("Option", max_length=200)
|
name = models.CharField("Option", max_length=200)
|
||||||
multi_choices = models.BooleanField("Choix multiples", default=False)
|
multi_choices = models.BooleanField("Choix multiples", default=False)
|
||||||
|
|
||||||
|
@ -149,7 +180,10 @@ class EventOption(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class EventOptionChoice(models.Model):
|
class EventOptionChoice(models.Model):
|
||||||
event_option = models.ForeignKey(EventOption, related_name="choices")
|
event_option = models.ForeignKey(
|
||||||
|
EventOption, on_delete=models.CASCADE,
|
||||||
|
related_name="choices",
|
||||||
|
)
|
||||||
value = models.CharField("Valeur", max_length=200)
|
value = models.CharField("Valeur", max_length=200)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -161,8 +195,8 @@ class EventOptionChoice(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class EventRegistration(models.Model):
|
class EventRegistration(models.Model):
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
event = models.ForeignKey(Event)
|
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||||
options = models.ManyToManyField(EventOptionChoice)
|
options = models.ManyToManyField(EventOptionChoice)
|
||||||
filledcomments = models.ManyToManyField(EventCommentField,
|
filledcomments = models.ManyToManyField(EventCommentField,
|
||||||
through=EventCommentValue)
|
through=EventCommentValue)
|
||||||
|
@ -190,7 +224,10 @@ class Survey(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class SurveyQuestion(models.Model):
|
class SurveyQuestion(models.Model):
|
||||||
survey = models.ForeignKey(Survey, related_name="questions")
|
survey = models.ForeignKey(
|
||||||
|
Survey, on_delete=models.CASCADE,
|
||||||
|
related_name="questions",
|
||||||
|
)
|
||||||
question = models.CharField("Question", max_length=200)
|
question = models.CharField("Question", max_length=200)
|
||||||
multi_answers = models.BooleanField("Choix multiples", default=False)
|
multi_answers = models.BooleanField("Choix multiples", default=False)
|
||||||
|
|
||||||
|
@ -202,7 +239,10 @@ class SurveyQuestion(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class SurveyQuestionAnswer(models.Model):
|
class SurveyQuestionAnswer(models.Model):
|
||||||
survey_question = models.ForeignKey(SurveyQuestion, related_name="answers")
|
survey_question = models.ForeignKey(
|
||||||
|
SurveyQuestion, on_delete=models.CASCADE,
|
||||||
|
related_name="answers",
|
||||||
|
)
|
||||||
answer = models.CharField("Réponse", max_length=200)
|
answer = models.CharField("Réponse", max_length=200)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -213,8 +253,8 @@ class SurveyQuestionAnswer(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class SurveyAnswer(models.Model):
|
class SurveyAnswer(models.Model):
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
survey = models.ForeignKey(Survey)
|
survey = models.ForeignKey(Survey, on_delete=models.CASCADE)
|
||||||
answers = models.ManyToManyField(SurveyQuestionAnswer,
|
answers = models.ManyToManyField(SurveyQuestionAnswer,
|
||||||
related_name="selected_by")
|
related_name="selected_by")
|
||||||
|
|
||||||
|
@ -230,7 +270,7 @@ class SurveyAnswer(models.Model):
|
||||||
|
|
||||||
class CalendarSubscription(models.Model):
|
class CalendarSubscription(models.Model):
|
||||||
token = models.UUIDField()
|
token = models.UUIDField()
|
||||||
user = models.OneToOneField(User)
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||||
other_shows = models.ManyToManyField(Spectacle)
|
other_shows = models.ManyToManyField(Spectacle)
|
||||||
subscribe_to_events = models.BooleanField(default=True)
|
subscribe_to_events = models.BooleanField(default=True)
|
||||||
subscribe_to_my_shows = models.BooleanField(default=True)
|
subscribe_to_my_shows = models.BooleanField(default=True)
|
||||||
|
|
|
@ -35,8 +35,11 @@ class PetitCoursSubject(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAbility(models.Model):
|
class PetitCoursAbility(models.Model):
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matière"))
|
matiere = models.ForeignKey(
|
||||||
|
PetitCoursSubject, on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Matière"),
|
||||||
|
)
|
||||||
niveau = models.CharField(_("Niveau"),
|
niveau = models.CharField(_("Niveau"),
|
||||||
choices=LEVELS_CHOICES,
|
choices=LEVELS_CHOICES,
|
||||||
max_length=choices_length(LEVELS_CHOICES))
|
max_length=choices_length(LEVELS_CHOICES))
|
||||||
|
@ -84,7 +87,10 @@ class PetitCoursDemande(models.Model):
|
||||||
remarques = models.TextField(_("Remarques et précisions"), blank=True)
|
remarques = models.TextField(_("Remarques et précisions"), blank=True)
|
||||||
|
|
||||||
traitee = models.BooleanField(_("Traitée"), default=False)
|
traitee = models.BooleanField(_("Traitée"), default=False)
|
||||||
traitee_par = models.ForeignKey(User, blank=True, null=True)
|
traitee_par = models.ForeignKey(
|
||||||
|
User, on_delete=models.CASCADE,
|
||||||
|
blank=True, null=True,
|
||||||
|
)
|
||||||
processed = models.DateTimeField(_("Date de traitement"),
|
processed = models.DateTimeField(_("Date de traitement"),
|
||||||
blank=True, null=True)
|
blank=True, null=True)
|
||||||
created = models.DateTimeField(_("Date de création"), auto_now_add=True)
|
created = models.DateTimeField(_("Date de création"), auto_now_add=True)
|
||||||
|
@ -126,9 +132,15 @@ class PetitCoursDemande(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAttribution(models.Model):
|
class PetitCoursAttribution(models.Model):
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
demande = models.ForeignKey(PetitCoursDemande, verbose_name=_("Demande"))
|
demande = models.ForeignKey(
|
||||||
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matière"))
|
PetitCoursDemande, on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Demande"),
|
||||||
|
)
|
||||||
|
matiere = models.ForeignKey(
|
||||||
|
PetitCoursSubject, on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Matière"),
|
||||||
|
)
|
||||||
date = models.DateTimeField(_("Date d'attribution"), auto_now_add=True)
|
date = models.DateTimeField(_("Date d'attribution"), auto_now_add=True)
|
||||||
rank = models.IntegerField("Rang dans l'email")
|
rank = models.IntegerField("Rang dans l'email")
|
||||||
selected = models.BooleanField(_("Sélectionné par le demandeur"),
|
selected = models.BooleanField(_("Sélectionné par le demandeur"),
|
||||||
|
@ -145,8 +157,11 @@ class PetitCoursAttribution(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAttributionCounter(models.Model):
|
class PetitCoursAttributionCounter(models.Model):
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matiere"))
|
matiere = models.ForeignKey(
|
||||||
|
PetitCoursSubject, on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Matiere"),
|
||||||
|
)
|
||||||
count = models.IntegerField("Nombre d'envois", default=0)
|
count = models.IntegerField("Nombre d'envois", default=0)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -157,14 +172,16 @@ class PetitCoursAttributionCounter(models.Model):
|
||||||
compteurs de tout le monde.
|
compteurs de tout le monde.
|
||||||
"""
|
"""
|
||||||
counter, created = cls.objects.get_or_create(
|
counter, created = cls.objects.get_or_create(
|
||||||
user=user, matiere=matiere)
|
user=user,
|
||||||
|
matiere=matiere,
|
||||||
|
)
|
||||||
if created:
|
if created:
|
||||||
mincount = (
|
mincount = (
|
||||||
cls.objects.filter(matiere=matiere).exclude(user=user)
|
cls.objects.filter(matiere=matiere).exclude(user=user)
|
||||||
.aggregate(Min('count'))
|
.aggregate(Min('count'))
|
||||||
['count__min']
|
['count__min']
|
||||||
)
|
)
|
||||||
counter.count = mincount
|
counter.count = mincount or 0
|
||||||
counter.save()
|
counter.save()
|
||||||
return counter
|
return counter
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
|
||||||
from custommail.shortcuts import render_custom_mail
|
from custommail.shortcuts import render_custom_mail
|
||||||
|
|
||||||
from django.shortcuts import render, get_object_or_404, redirect
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
|
@ -13,6 +12,7 @@ from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from gestioncof.models import CofProfile
|
from gestioncof.models import CofProfile
|
||||||
from gestioncof.petits_cours_models import (
|
from gestioncof.petits_cours_models import (
|
||||||
|
@ -287,7 +287,7 @@ def _traitement_post(request, demande):
|
||||||
attrib.save()
|
attrib.save()
|
||||||
demande.traitee = True
|
demande.traitee = True
|
||||||
demande.traitee_par = request.user
|
demande.traitee_par = request.user
|
||||||
demande.processed = datetime.now()
|
demande.processed = timezone.now()
|
||||||
demande.save()
|
demande.save()
|
||||||
return render(request,
|
return render(request,
|
||||||
"gestioncof/traitement_demande_petit_cours_success.html",
|
"gestioncof/traitement_demande_petit_cours_success.html",
|
||||||
|
|
|
@ -1140,3 +1140,14 @@ p.help-block {
|
||||||
margin: 5px auto;
|
margin: 5px auto;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.bg-info {
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 0.3em 1em;
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bootstrap-form-reduce > .form-group {
|
||||||
|
margin-top: -16px;
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
{% extends "admin/base.html" %}
|
|
||||||
|
|
||||||
{% block extrahead %}
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
|
|
||||||
{% include 'autocomplete_light/static.html' %}
|
|
||||||
{% endblock %}
|
|
|
@ -1,78 +0,0 @@
|
||||||
{% extends "admin/base_site.html" %}
|
|
||||||
|
|
||||||
<!-- LOADING -->
|
|
||||||
{% load i18n grp_tags log %}
|
|
||||||
|
|
||||||
<!-- JAVASCRIPTS -->
|
|
||||||
{% block javascripts %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
<!-- COLTYPE/BODYCLASS-- >
|
|
||||||
{% block bodyclass %}dashboard{% endblock %}
|
|
||||||
{% block content-class %}content-grid{% endblock %}
|
|
||||||
|
|
||||||
<!-- BREADCRUMBS -->
|
|
||||||
{% block breadcrumbs %}
|
|
||||||
<ul class="grp-horizontal-list">
|
|
||||||
<li>{% trans "Home" %}</li>
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
||||||
{% block content_title %}
|
|
||||||
{% if title %}
|
|
||||||
<header><h1>{{ title }}</h1></header>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
<!-- CONTENT -->
|
|
||||||
{% block content %}
|
|
||||||
<div class="g-d-c">
|
|
||||||
<div class="g-d-12 g-d-f">
|
|
||||||
|
|
||||||
{% for app in app_list %}
|
|
||||||
<div class="grp-module" id="app_{{ app.name|lower }}">
|
|
||||||
<h2><a href="{{ app.app_url }}" class="grp-section">{% trans app.name %}</a></h2>
|
|
||||||
{% for model in app.models %}
|
|
||||||
<div class="grp-row">
|
|
||||||
{% if model.perms.change %}<a href="{{ model.admin_url }}"><strong>{{ model.name }}</strong></a>{% else %}<span><strong>{{ model.name }}</strong></span>{% endif %}
|
|
||||||
{% if model.perms.add or model.perms.change %}
|
|
||||||
<ul class="grp-actions">
|
|
||||||
{% if model.perms.add %}<li class="grp-add-link"><a href="{{ model.admin_url }}add/">{% trans 'Add' %}</a></li>{% endif %}
|
|
||||||
{% if model.perms.change %}<li class="grp-change-link"><a href="{{ model.admin_url }}">{% trans 'Change' %}</a></li>{% endif %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% empty %}
|
|
||||||
<p>{% trans "You don´t have permission to edit anything." %}</p>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<div class="g-d-6 g-d-l">
|
|
||||||
<div class="grp-module" id="grp-recent-actions-module">
|
|
||||||
<h2>{% trans 'Recent Actions' %}</h2>
|
|
||||||
<div class="grp-module">
|
|
||||||
<h3>{% trans 'My Actions' %}</h3>
|
|
||||||
{% get_admin_log 20 as admin_log for_user user %}
|
|
||||||
{% if not admin_log %}
|
|
||||||
<p>{% trans 'None available' %}</p>
|
|
||||||
{% else %}
|
|
||||||
<ul class="grp-listing-small">
|
|
||||||
{% for entry in admin_log %}
|
|
||||||
<li class="grp-row{% if entry.is_addition %} grp-add-link{% endif %}{% if entry.is_change %} grp-change-link{% endif %}{% if entry.is_deletion %} grp-delete-link{% endif %}">
|
|
||||||
{% if entry.is_deletion %}
|
|
||||||
<span>{{ entry.object_repr }}</span>
|
|
||||||
{% else %}
|
|
||||||
<a href="{{ entry.get_admin_url }}">{{ entry.object_repr }}</a>
|
|
||||||
{% endif %}
|
|
||||||
<span class="grp-font-color-quiet">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include "tristate_js.html" %}
|
{% include "tristate_js.html" %}
|
||||||
<h3>Filtres</h3>
|
<h3>Filtres</h3>
|
||||||
<form method="post" action="{% url 'gestioncof.views.event_status' event.id %}">
|
<form method="post" action="{% url 'event.details.status' event.id %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
<input style="margin-top:10px;" type="submit" class="btn btn-primary" value="Filtrer" />
|
<input style="margin-top:10px;" type="submit" class="btn btn-primary" value="Filtrer" />
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<header>
|
<header>
|
||||||
<div class="container banner">
|
<div class="container banner">
|
||||||
<a href="{% url "gestioncof.views.home" %}">
|
<a href="{% url "home" %}">
|
||||||
<h1>GestioCOF</h1>
|
<h1>GestioCOF</h1>
|
||||||
{% block homelink %}
|
{% block homelink %}
|
||||||
<span class="glyphicon glyphicon-home" aria-hidden=true></span>
|
<span class="glyphicon glyphicon-home" aria-hidden=true></span>
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
</a>
|
</a>
|
||||||
<div class="secondary">
|
<div class="secondary">
|
||||||
<span class="hidden-xxs"> | </span>
|
<span class="hidden-xxs"> | </span>
|
||||||
<span><a href="{% url "gestioncof.views.logout" %}">Se déconnecter <span class="glyphicon glyphicon-log-out"></span></a></span>
|
<span><a href="{% url "cof-logout" %}">Se déconnecter <span class="glyphicon glyphicon-log-out"></span></a></span>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="member-status">{% if user.first_name %}{{ user.first_name }}{% else %}<tt>{{ user.username }}</tt>{% endif %}, {% if user.profile.is_cof %}<tt class="user-is-cof">au COF{% else %}<tt class="user-is-not-cof">non-COF{% endif %}</tt></h2>
|
<h2 class="member-status">{% if user.first_name %}{{ user.first_name }}{% else %}<tt>{{ user.username }}</tt>{% endif %}, {% if user.profile.is_cof %}<tt class="user-is-cof">au COF{% else %}<tt class="user-is-not-cof">non-COF{% endif %}</tt></h2>
|
||||||
</div><!-- /.container -->
|
</div><!-- /.container -->
|
||||||
|
|
|
@ -12,7 +12,7 @@ souscrire aux événements du COF et/ou aux spectacles BdA.
|
||||||
|
|
||||||
{% if token %}
|
{% if token %}
|
||||||
<p>Votre calendrier (compatible avec toutes les applications d'agenda) se trouve à
|
<p>Votre calendrier (compatible avec toutes les applications d'agenda) se trouve à
|
||||||
<a href="{% url 'gestioncof.views.calendar_ics' token %}">cette adresse</a>.</p>
|
<a href="{% url 'calendar.ics' token %}">cette adresse</a>.</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Pour l'ajouter à Thunderbird (lightning), il faut copier ce lien et aller
|
<li>Pour l'ajouter à Thunderbird (lightning), il faut copier ce lien et aller
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
{% if event.details %}
|
{% if event.details %}
|
||||||
<p>{{ event.details }}</p>
|
<p>{{ event.details }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form method="post" action="{% url 'gestioncof.views.event' event.id %}">
|
<form method="post" action="{% url 'event.details' event.id %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
<input type="submit" class="btn-submit" value="Enregistrer" />
|
<input type="submit" class="btn-submit" value="Enregistrer" />
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
<h2>Modifier mon profil</h2>
|
<h2>Modifier mon profil</h2>
|
||||||
<form id="profile form-horizontal" method="post" action="{% url 'gestioncof.views.profile' %}">
|
<form id="profile form-horizontal" method="post" action="{% url 'profile' %}">
|
||||||
<div class="row" style="margin: 0 15%;">
|
<div class="row" style="margin: 0 15%;">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<fieldset"center-block">
|
<fieldset"center-block">
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
{% if survey.details %}
|
{% if survey.details %}
|
||||||
<p>{{ survey.details }}</p>
|
<p>{{ survey.details }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form class="form-horizontal" method="post" action="{% url 'gestioncof.views.survey' survey.id %}">
|
<form class="form-horizontal" method="post" action="{% url 'survey.details' survey.id %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form | bootstrap}}
|
{{ form | bootstrap}}
|
||||||
|
|
||||||
|
|
|
@ -7,15 +7,15 @@
|
||||||
<h2>Liens utiles du COF</h2>
|
<h2>Liens utiles du COF</h2>
|
||||||
<h3>COF</h3>
|
<h3>COF</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{% url 'gestioncof.views.export_members' %}">Export des membres du COF</a></li>
|
<li><a href="{% url 'cof.membres_export' %}">Export des membres du COF</a></li>
|
||||||
<li><a href="{% url 'gestioncof.views.liste_diffcof' %}">Diffusion COF</a></li>
|
<li><a href="{% url 'ml_diffcof' %}">Diffusion COF</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>Mega</h3>
|
<h3>Mega</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{% url 'gestioncof.views.export_mega_participants' %}">Export des non-orgas uniquement</a></li>
|
<li><a href="{% url 'cof.mega_export_participants' %}">Export des non-orgas uniquement</a></li>
|
||||||
<li><a href="{% url 'gestioncof.views.export_mega_orgas' %}">Export des orgas uniquement</a></li>
|
<li><a href="{% url 'cof.mega_export_orgas' %}">Export des orgas uniquement</a></li>
|
||||||
<li><a href="{% url 'gestioncof.views.export_mega' %}">Export de tout le monde</a></li>
|
<li><a href="{% url 'cof.mega_export' %}">Export de tout le monde</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>Note : pour ouvrir les fichiers .csv avec Excel, il faut
|
<p>Note : pour ouvrir les fichiers .csv avec Excel, il faut
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<div class="hm-block">
|
<div class="hm-block">
|
||||||
<ul>
|
<ul>
|
||||||
{% for event in open_events %}
|
{% for event in open_events %}
|
||||||
<li><a href="{% url "gestioncof.views.event" event.id %}">{{ event.title }}</a></li>
|
<li><a href="{% url "event.details" event.id %}">{{ event.title }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
<div class="hm-block">
|
<div class="hm-block">
|
||||||
<ul>
|
<ul>
|
||||||
{% for survey in open_surveys %}
|
{% for survey in open_surveys %}
|
||||||
<li><a href="{% url "gestioncof.views.survey" survey.id %}">{{ survey.title }}</a></li>
|
<li><a href="{% url "survey.details" survey.id %}">{{ survey.title }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,9 +43,10 @@
|
||||||
<li><a href="{% url "bda-etat-places" tirage.id %}">État des demandes</a></li>
|
<li><a href="{% url "bda-etat-places" tirage.id %}">État des demandes</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{% url "bda-places-attribuees" tirage.id %}">Mes places</a></li>
|
<li><a href="{% url "bda-places-attribuees" tirage.id %}">Mes places</a></li>
|
||||||
<li><a href="{% url "bda-revente" tirage.id %}">Revendre une place</a></li>
|
<li><a href="{% url "bda-revente-manage" tirage.id %}">Gérer les places que je revends</a></li>
|
||||||
<li><a href="{% url "bda-liste-revente" tirage.id %}">S'inscrire à BdA-Revente</a></li>
|
<li><a href="{% url "bda-revente-tirages" tirage.id %}">Voir les reventes en cours</a></li>
|
||||||
<li><a href="{% url "bda-shotgun" tirage.id %}">Places disponibles immédiatement</a></li>
|
<li><a href="{% url "bda-revente-subscribe" tirage.id %}">Indiquer les spectacles qui m'intéressent</a></li>
|
||||||
|
<li><a href="{% url "bda-revente-shotgun" tirage.id %}">Places disponibles immédiatement</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -69,11 +70,11 @@
|
||||||
<h3 class="block-title">Divers<span class="pull-right glyphicon glyphicon-question-sign"></span></h3>
|
<h3 class="block-title">Divers<span class="pull-right glyphicon glyphicon-question-sign"></span></h3>
|
||||||
<div class="hm-block">
|
<div class="hm-block">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{% url "gestioncof.views.calendar" %}">Calendrier dynamique</a></li>
|
<li><a href="{% url "calendar" %}">Calendrier dynamique</a></li>
|
||||||
{% if user.profile.is_cof %}<li><a href="{% url "petits-cours-inscription" %}">Inscription pour donner des petits cours</a></li>{% endif %}
|
{% if user.profile.is_cof %}<li><a href="{% url "petits-cours-inscription" %}">Inscription pour donner des petits cours</a></li>{% endif %}
|
||||||
|
|
||||||
<li><a href="{% url "gestioncof.views.profile" %}">Éditer mon profil</a></li>
|
<li><a href="{% url "profile" %}">Éditer mon profil</a></li>
|
||||||
{% if not user.profile.login_clipper %}<li><a href="{% url "django.contrib.auth.views.password_change" %}">Changer mon mot de passe</a></li>{% endif %}
|
{% if not user.profile.login_clipper %}<li><a href="{% url "password_change" %}">Changer mon mot de passe</a></li>{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -86,16 +87,16 @@
|
||||||
<h4>Général</h4>
|
<h4>Général</h4>
|
||||||
<li><a href="{% url "admin:index" %}">Administration générale</a></li>
|
<li><a href="{% url "admin:index" %}">Administration générale</a></li>
|
||||||
<li><a href="{% url "petits-cours-demandes-list" %}">Demandes de petits cours</a></li>
|
<li><a href="{% url "petits-cours-demandes-list" %}">Demandes de petits cours</a></li>
|
||||||
<li><a href="{% url "gestioncof.views.registration" %}">Inscription d'un nouveau membre</a></li>
|
<li><a href="{% url "registration" %}">Inscription d'un nouveau membre</a></li>
|
||||||
<li><a href="{% url "liste-clubs" %}">Gestion des clubs</a></li>
|
<li><a href="{% url "liste-clubs" %}">Gestion des clubs</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
<h4>Évènements & Sondages</h4>
|
<h4>Évènements & Sondages</h4>
|
||||||
{% for event in events %}
|
{% for event in events %}
|
||||||
<li><a href="{% url "gestioncof.views.event_status" event.id %}">Événement : {{ event.title }}</a></li>
|
<li><a href="{% url "event.details.status" event.id %}">Événement : {{ event.title }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for survey in surveys %}
|
{% for survey in surveys %}
|
||||||
<li><a href="{% url "gestioncof.views.survey_status" survey.id %}">Sondage : {{ survey.title }}</a></li>
|
<li><a href="{% url "survey.details.status" survey.id %}">Sondage : {{ survey.title }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -120,8 +121,8 @@
|
||||||
<h3 class="block-title">Liens utiles<span class="pull-right glyphicon glyphicon-link"></span></h3>
|
<h3 class="block-title">Liens utiles<span class="pull-right glyphicon glyphicon-link"></span></h3>
|
||||||
<div class="hm-block">
|
<div class="hm-block">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{% url "gestioncof.views.utile_cof" %}">Liens utiles du COF</a></li>
|
<li><a href="{% url "utile_cof" %}">Liens utiles du COF</a></li>
|
||||||
<li><a href="{% url "gestioncof.views.utile_bda" %}">Liens utiles BdA</a></li>
|
<li><a href="{% url "utile_bda" %}">Liens utiles BdA</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
</tr></thead>
|
</tr></thead>
|
||||||
<tbody class="bda_formset_content">
|
<tbody class="bda_formset_content">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr class="{% cycle row1,row2 %} dynamic-form {% if form.instance.pk %}has_original{% endif %}">
|
<tr class="{% cycle 'row1' 'row2' %} dynamic-form {% if form.instance.pk %}has_original{% endif %}">
|
||||||
{% for field in form.visible_fields %}
|
{% for field in form.visible_fields %}
|
||||||
{% if field.name != "DELETE" and field.name != "priority" %}
|
{% if field.name != "DELETE" and field.name != "priority" %}
|
||||||
<td class="bda-field-{{ field.name }}">
|
<td class="bda-field-{{ field.name }}">
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<p class="error">Identifiants incorrects.</p>
|
<p class="error">Identifiants incorrects.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form class="form-horizontal" method="post"
|
<form class="form-horizontal" method="post"
|
||||||
action="{% url 'gestioncof.views.login_ext' %}?next={{ next|urlencode }}">
|
action="{% url 'ext_login_view' %}?next={{ next|urlencode }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input class="form-control" id="id_username" maxlength="254" name="username" type="text" placeholder="Nom d'utilisateur">
|
<input class="form-control" id="id_username" maxlength="254" name="username" type="text" placeholder="Nom d'utilisateur">
|
||||||
|
|
|
@ -12,13 +12,13 @@
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row" style="margin:0;">
|
<div class="row" style="margin:0;">
|
||||||
<a aria-label="Compte clipper"
|
<a aria-label="Compte clipper"
|
||||||
href="{% url 'django_cas_ng.views.login' %}?next={{ next|urlencode }}">
|
href="{% url 'cas_login_view' %}?next={{ next|urlencode }}">
|
||||||
<div class="col-xs-12 col-sm-6" id="login_clipper">
|
<div class="col-xs-12 col-sm-6" id="login_clipper">
|
||||||
Compte clipper
|
Compte clipper
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a aria-label="Extérieur"
|
<a aria-label="Extérieur"
|
||||||
href="{% url 'gestioncof.views.login_ext' %}?next={{ next|urlencode }}">
|
href="{% url 'ext_login_view' %}?next={{ next|urlencode }}">
|
||||||
<div class="col-xs-12 col-sm-6" id="login_outsider">
|
<div class="col-xs-12 col-sm-6" id="login_outsider">
|
||||||
Extérieur
|
Extérieur
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
{% block page_size %}col-sm-8{% endblock %}
|
{% block page_size %}col-sm-8{% endblock %}
|
||||||
|
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<script src="{% static "autocomplete_light/autocomplete.js" %}" type="text/javascript"></script>
|
<script src="{% static "vendor/jquery.autocomplete-light/3.5.0/dist/jquery.autocomplete-light.min.js" %}" type="text/javascript"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
|
|
|
@ -5,5 +5,5 @@
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
<h2>Mot de passe modifié avec succès !</h2>
|
<h2>Mot de passe modifié avec succès !</h2>
|
||||||
<h3><a href="{% url "gestioncof.views.home" %}">Retour au menu principal</a></h3>
|
<h3><a href="{% url "home" %}">Retour au menu principal</a></h3>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
<h2>Changement de mot de passe</h2>
|
<h2>Changement de mot de passe</h2>
|
||||||
<form class="form-horizontal" method="post" action="{% url 'django.contrib.auth.views.password_change' %}">
|
<form class="form-horizontal" method="post" action="{% url 'password_change' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form | bootstrap }}
|
{{ form | bootstrap }}
|
||||||
<input type="submit" class="btn btn-primary pull-right" value="Changer" />
|
<input type="submit" class="btn btn-primary pull-right" value="Changer" />
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h3>Filtres</h3>
|
<h3>Filtres</h3>
|
||||||
{% include "tristate_js.html" %}
|
{% include "tristate_js.html" %}
|
||||||
<form method="post" action="{% url 'gestioncof.views.survey_status' survey.id %}">
|
<form method="post" action="{% url 'survey.details.status' survey.id %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
<input style="margin-top:10px;" type="submit" class="btn btn-primary" value="Filtrer" />
|
<input style="margin-top:10px;" type="submit" class="btn btn-primary" value="Filtrer" />
|
||||||
|
|
0
gestioncof/tests/__init__.py
Normal file
0
gestioncof/tests/__init__.py
Normal file
874
gestioncof/tests/test_views.py
Normal file
874
gestioncof/tests/test_views.py
Normal file
|
@ -0,0 +1,874 @@
|
||||||
|
import csv
|
||||||
|
import uuid
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.messages.api import get_messages
|
||||||
|
from django.contrib.messages.storage.base import Message
|
||||||
|
from django.test import Client, TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from bda.models import Salle, Tirage
|
||||||
|
from gestioncof.models import (
|
||||||
|
CalendarSubscription, Club, Event, Survey, SurveyAnswer
|
||||||
|
)
|
||||||
|
from gestioncof.tests.testcases import ViewTestCaseMixin
|
||||||
|
|
||||||
|
from .utils import create_member, create_root, create_user
|
||||||
|
|
||||||
|
|
||||||
|
class HomeViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'home'
|
||||||
|
url_expected = '/'
|
||||||
|
|
||||||
|
auth_user = 'user'
|
||||||
|
auth_forbidden = [None]
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class ProfileViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'profile'
|
||||||
|
url_expected = '/profile'
|
||||||
|
|
||||||
|
http_methods = ['GET', 'POST']
|
||||||
|
|
||||||
|
auth_user = 'member'
|
||||||
|
auth_forbidden = [None, 'user']
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
def test_post(self):
|
||||||
|
u = self.users['member']
|
||||||
|
|
||||||
|
r = self.client.post(self.url, {
|
||||||
|
'first_name': 'First',
|
||||||
|
'last_name': 'Last',
|
||||||
|
'phone': '',
|
||||||
|
# 'mailing_cof': '1',
|
||||||
|
# 'mailing_bda': '1',
|
||||||
|
# 'mailing_bda_revente': '1',
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
expected_message = Message(messages.SUCCESS, (
|
||||||
|
"Votre profil a été mis à jour avec succès !"
|
||||||
|
))
|
||||||
|
self.assertIn(expected_message, get_messages(r.wsgi_request))
|
||||||
|
u.refresh_from_db()
|
||||||
|
self.assertEqual(u.first_name, 'First')
|
||||||
|
self.assertEqual(u.last_name, 'Last')
|
||||||
|
self.assertFalse(u.profile.mailing_cof)
|
||||||
|
self.assertFalse(u.profile.mailing_bda)
|
||||||
|
self.assertFalse(u.profile.mailing_bda_revente)
|
||||||
|
|
||||||
|
|
||||||
|
class UtilsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'utile_cof'
|
||||||
|
url_expected = '/utile_cof'
|
||||||
|
|
||||||
|
auth_user = 'staff'
|
||||||
|
auth_forbidden = [None, 'user', 'member']
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class MailingListDiffCof(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'ml_diffcof'
|
||||||
|
url_expected = '/utile_cof/diff_cof'
|
||||||
|
|
||||||
|
auth_user = 'staff'
|
||||||
|
auth_forbidden = [None, 'user', 'member']
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.u1 = create_member('u1', attrs={'mailing_cof': True})
|
||||||
|
self.u2 = create_member('u2', attrs={'mailing_cof': False})
|
||||||
|
self.u3 = create_user('u3', attrs={'mailing_cof': True})
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.context['personnes'].get(), self.u1.profile)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigUpdateViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'config.edit'
|
||||||
|
url_expected = '/config'
|
||||||
|
|
||||||
|
http_methods = ['GET', 'POST']
|
||||||
|
|
||||||
|
auth_user = 'root'
|
||||||
|
auth_forbidden = [None, 'user', 'member', 'staff']
|
||||||
|
|
||||||
|
def get_users_extra(self):
|
||||||
|
return {
|
||||||
|
'root': create_root('root'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
def test_post(self):
|
||||||
|
r = self.client.post(self.url, {
|
||||||
|
'gestion_banner': 'Announcement !',
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertRedirects(r, reverse('home'))
|
||||||
|
|
||||||
|
|
||||||
|
class UserAutocompleteViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'cof-user-autocomplete'
|
||||||
|
url_expected = '/user/autocomplete'
|
||||||
|
|
||||||
|
auth_user = 'staff'
|
||||||
|
auth_forbidden = [None, 'user', 'member']
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
r = self.client.get(self.url, {'q': 'user'})
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class ExportMembersViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'cof.membres_export'
|
||||||
|
url_expected = '/export/members'
|
||||||
|
|
||||||
|
auth_user = 'staff'
|
||||||
|
auth_forbidden = [None, 'user', 'member']
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
u1, u2 = self.users['member'], self.users['staff']
|
||||||
|
u1.first_name = 'first'
|
||||||
|
u1.last_name = 'last'
|
||||||
|
u1.email = 'user@mail.net'
|
||||||
|
u1.save()
|
||||||
|
u1.profile.phone = '0123456789'
|
||||||
|
u1.profile.departement = 'Dept'
|
||||||
|
u1.profile.save()
|
||||||
|
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
data = list(csv.reader(r.content.decode('utf-8').split('\n')[:-1]))
|
||||||
|
expected = [
|
||||||
|
[
|
||||||
|
str(u1.pk), 'member', 'first', 'last', 'user@mail.net',
|
||||||
|
'0123456789', '1A', 'Dept', 'normalien',
|
||||||
|
],
|
||||||
|
[str(u2.pk), 'staff', '', '', '', '', '1A', '', 'normalien'],
|
||||||
|
]
|
||||||
|
# Sort before checking equality, the order of the output of csv.reader
|
||||||
|
# does not seem deterministic
|
||||||
|
expected.sort(key=lambda row: int(row[0]))
|
||||||
|
data.sort(key=lambda row: int(row[0]))
|
||||||
|
self.assertListEqual(data, expected)
|
||||||
|
|
||||||
|
|
||||||
|
class MegaHelpers:
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
u1 = create_user('u1')
|
||||||
|
u1.first_name = 'first'
|
||||||
|
u1.last_name = 'last'
|
||||||
|
u1.email = 'user@mail.net'
|
||||||
|
u1.save()
|
||||||
|
u1.profile.phone = '0123456789'
|
||||||
|
u1.profile.departement = 'Dept'
|
||||||
|
u1.profile.comments = 'profile.comments'
|
||||||
|
u1.profile.save()
|
||||||
|
|
||||||
|
u2 = create_user('u2')
|
||||||
|
u2.profile.save()
|
||||||
|
|
||||||
|
m = Event.objects.create(title='MEGA 2017')
|
||||||
|
|
||||||
|
cf1 = m.commentfields.create(name='Commentaire')
|
||||||
|
cf2 = m.commentfields.create(
|
||||||
|
name='Comment Field 2', fieldtype='char',
|
||||||
|
)
|
||||||
|
|
||||||
|
option_type = m.options.create(name='Conscrit/Orga ?')
|
||||||
|
choice_orga = option_type.choices.create(value='Orga')
|
||||||
|
choice_conscrit = option_type.choices.create(value='Conscrit')
|
||||||
|
|
||||||
|
mr1 = m.eventregistration_set.create(user=u1)
|
||||||
|
mr1.options.add(choice_orga)
|
||||||
|
mr1.comments.create(commentfield=cf1, content='Comment 1')
|
||||||
|
mr1.comments.create(commentfield=cf2, content='Comment 2')
|
||||||
|
|
||||||
|
mr2 = m.eventregistration_set.create(user=u2)
|
||||||
|
mr2.options.add(choice_conscrit)
|
||||||
|
|
||||||
|
self.u1 = u1
|
||||||
|
self.u2 = u2
|
||||||
|
self.m = m
|
||||||
|
self.choice_orga = choice_orga
|
||||||
|
self.choice_conscrit = choice_conscrit
|
||||||
|
|
||||||
|
|
||||||
|
class ExportMegaViewTests(MegaHelpers, ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'cof.mega_export'
|
||||||
|
url_expected = '/export/mega'
|
||||||
|
|
||||||
|
auth_user = 'staff'
|
||||||
|
auth_forbidden = [None, 'user', 'member']
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertListEqual(self.load_from_csv_response(r), [
|
||||||
|
[
|
||||||
|
'u1', 'first', 'last', 'user@mail.net', '0123456789',
|
||||||
|
str(self.u1.pk), 'profile.comments', 'Comment 1---Comment 2',
|
||||||
|
],
|
||||||
|
['u2', '', '', '', '', str(self.u2.pk), '', ''],
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class ExportMegaOrgasViewTests(MegaHelpers, ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'cof.mega_export_orgas'
|
||||||
|
url_expected = '/export/mega/orgas'
|
||||||
|
|
||||||
|
auth_user = 'staff'
|
||||||
|
auth_forbidden = [None, 'user', 'member']
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertListEqual(self.load_from_csv_response(r), [
|
||||||
|
[
|
||||||
|
'u1', 'first', 'last', 'user@mail.net', '0123456789',
|
||||||
|
str(self.u1.pk), 'profile.comments', 'Comment 1---Comment 2',
|
||||||
|
],
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class ExportMegaParticipantsViewTests(
|
||||||
|
MegaHelpers, ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'cof.mega_export_participants'
|
||||||
|
url_expected = '/export/mega/participants'
|
||||||
|
|
||||||
|
auth_user = 'staff'
|
||||||
|
auth_forbidden = [None, 'user', 'member']
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertListEqual(self.load_from_csv_response(r), [
|
||||||
|
['u2', '', '', '', '', str(self.u2.pk), '', ''],
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class ExportMegaRemarksViewTests(
|
||||||
|
MegaHelpers, ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'cof.mega_export_remarks'
|
||||||
|
url_expected = '/export/mega/avecremarques'
|
||||||
|
|
||||||
|
auth_user = 'staff'
|
||||||
|
auth_forbidden = [None, 'user', 'member']
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertListEqual(self.load_from_csv_response(r), [
|
||||||
|
[
|
||||||
|
'u1', 'first', 'last', 'user@mail.net', '0123456789',
|
||||||
|
str(self.u1.pk), 'profile.comments', 'Comment 1',
|
||||||
|
],
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class ClubListViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'liste-clubs'
|
||||||
|
url_expected = '/clubs/liste'
|
||||||
|
|
||||||
|
auth_user = 'member'
|
||||||
|
auth_forbidden = [None, 'user']
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.c1 = Club.objects.create(name='Club1')
|
||||||
|
self.c2 = Club.objects.create(name='Club2')
|
||||||
|
|
||||||
|
m = self.users['member']
|
||||||
|
self.c1.membres.add(m)
|
||||||
|
self.c1.respos.add(m)
|
||||||
|
|
||||||
|
def test_as_member(self):
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.context['owned_clubs'].get(), self.c1)
|
||||||
|
self.assertEqual(r.context['other_clubs'].get(), self.c2)
|
||||||
|
|
||||||
|
def test_as_staff(self):
|
||||||
|
u = self.users['staff']
|
||||||
|
c = Client()
|
||||||
|
c.force_login(u)
|
||||||
|
|
||||||
|
r = c.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
r.context['owned_clubs'], map(repr, [self.c1, self.c2]),
|
||||||
|
ordered=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ClubMembersViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'membres-club'
|
||||||
|
|
||||||
|
auth_user = 'staff'
|
||||||
|
auth_forbidden = [None, 'user', 'member']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {'name': self.c.name}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return '/clubs/membres/{}'.format(self.c.name)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.u1 = create_user('u1')
|
||||||
|
self.u2 = create_user('u2')
|
||||||
|
|
||||||
|
self.c = Club.objects.create(name='Club')
|
||||||
|
self.c.membres.add(self.u1, self.u2)
|
||||||
|
self.c.respos.add(self.u1)
|
||||||
|
|
||||||
|
def test_as_staff(self):
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.context['members_no_respo'].get(), self.u2)
|
||||||
|
|
||||||
|
def test_as_respo(self):
|
||||||
|
u = self.users['user']
|
||||||
|
self.c.respos.add(u)
|
||||||
|
|
||||||
|
c = Client()
|
||||||
|
c.force_login(u)
|
||||||
|
r = c.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class ClubChangeRespoViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'change-respo'
|
||||||
|
|
||||||
|
auth_user = 'staff'
|
||||||
|
auth_forbidden = [None, 'user', 'member']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {'club_name': self.c.name, 'user_id': self.users['user'].pk}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return '/clubs/change_respo/{}/{}'.format(
|
||||||
|
self.c.name, self.users['user'].pk,
|
||||||
|
)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.c = Club.objects.create(name='Club')
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
u = self.users['user']
|
||||||
|
expected_redirect = reverse('membres-club', kwargs={
|
||||||
|
'name': self.c.name,
|
||||||
|
})
|
||||||
|
self.c.membres.add(u)
|
||||||
|
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertRedirects(r, expected_redirect)
|
||||||
|
self.assertIn(u, self.c.respos.all())
|
||||||
|
|
||||||
|
self.client.get(self.url)
|
||||||
|
self.assertNotIn(u, self.c.respos.all())
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'calendar'
|
||||||
|
url_expected = '/calendar/subscription'
|
||||||
|
|
||||||
|
auth_user = 'member'
|
||||||
|
auth_forbidden = [None, 'user']
|
||||||
|
|
||||||
|
post_expected_message = Message(
|
||||||
|
messages.SUCCESS, "Calendrier mis à jour avec succès.")
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
def test_post_new(self):
|
||||||
|
r = self.client.post(self.url, {
|
||||||
|
'subscribe_to_events': True,
|
||||||
|
'subscribe_to_my_shows': True,
|
||||||
|
'other_shows': [],
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertIn(self.post_expected_message, get_messages(r.wsgi_request))
|
||||||
|
cs = self.users['member'].calendarsubscription
|
||||||
|
self.assertTrue(cs.subscribe_to_events)
|
||||||
|
self.assertTrue(cs.subscribe_to_my_shows)
|
||||||
|
|
||||||
|
def test_post_edit(self):
|
||||||
|
u = self.users['member']
|
||||||
|
token = uuid.uuid4()
|
||||||
|
cs = CalendarSubscription.objects.create(token=token, user=u)
|
||||||
|
|
||||||
|
r = self.client.post(self.url, {
|
||||||
|
'other_shows': [],
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertIn(self.post_expected_message, get_messages(r.wsgi_request))
|
||||||
|
cs.refresh_from_db()
|
||||||
|
self.assertEqual(cs.token, token)
|
||||||
|
self.assertFalse(cs.subscribe_to_events)
|
||||||
|
self.assertFalse(cs.subscribe_to_my_shows)
|
||||||
|
|
||||||
|
def test_post_other_shows(self):
|
||||||
|
t = Tirage.objects.create(
|
||||||
|
ouverture=self.now,
|
||||||
|
fermeture=self.now,
|
||||||
|
active=True,
|
||||||
|
)
|
||||||
|
l = Salle.objects.create()
|
||||||
|
s = t.spectacle_set.create(
|
||||||
|
date=self.now, price=3.5, slots=20, location=l, listing=True)
|
||||||
|
|
||||||
|
r = self.client.post(self.url, {'other_shows': [str(s.pk)]})
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarICSViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'calendar.ics'
|
||||||
|
|
||||||
|
auth_user = None
|
||||||
|
auth_forbidden = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {'token': self.token}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return '/calendar/{}/calendar.ics'.format(self.token)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.token = uuid.uuid4()
|
||||||
|
|
||||||
|
self.t = Tirage.objects.create(
|
||||||
|
ouverture=self.now,
|
||||||
|
fermeture=self.now,
|
||||||
|
active=True,
|
||||||
|
)
|
||||||
|
l = Salle.objects.create(name='Location')
|
||||||
|
self.s1 = self.t.spectacle_set.create(
|
||||||
|
price=1, slots=10, location=l, listing=True,
|
||||||
|
title='Spectacle 1', date=self.now + timedelta(days=1),
|
||||||
|
)
|
||||||
|
self.s2 = self.t.spectacle_set.create(
|
||||||
|
price=2, slots=20, location=l, listing=True,
|
||||||
|
title='Spectacle 2', date=self.now + timedelta(days=2),
|
||||||
|
)
|
||||||
|
self.s3 = self.t.spectacle_set.create(
|
||||||
|
price=3, slots=30, location=l, listing=True,
|
||||||
|
title='Spectacle 3', date=self.now + timedelta(days=3),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
u = self.users['user']
|
||||||
|
p = u.participant_set.create(tirage=self.t)
|
||||||
|
p.attribution_set.create(spectacle=self.s1)
|
||||||
|
|
||||||
|
self.cs = CalendarSubscription.objects.create(
|
||||||
|
user=u, token=self.token,
|
||||||
|
subscribe_to_my_shows=True, subscribe_to_events=True,
|
||||||
|
)
|
||||||
|
self.cs.other_shows.add(self.s2)
|
||||||
|
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
|
def get_dt_from_ical(v):
|
||||||
|
return v.dt
|
||||||
|
|
||||||
|
self.assertCalEqual(r.content.decode('utf-8'), [
|
||||||
|
{
|
||||||
|
'summary': 'Spectacle 1',
|
||||||
|
'dtstart': (get_dt_from_ical, (
|
||||||
|
(self.now + timedelta(days=1)).replace(microsecond=0)
|
||||||
|
)),
|
||||||
|
'dtend': (get_dt_from_ical, (
|
||||||
|
(self.now + timedelta(days=1, hours=2)).replace(
|
||||||
|
microsecond=0)
|
||||||
|
)),
|
||||||
|
'location': 'Location',
|
||||||
|
'uid': 'show-{}-{}@example.com'.format(self.s1.pk, self.t.pk),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'summary': 'Spectacle 2',
|
||||||
|
'dtstart': (get_dt_from_ical, (
|
||||||
|
(self.now + timedelta(days=2)).replace(microsecond=0)
|
||||||
|
)),
|
||||||
|
'dtend': (get_dt_from_ical, (
|
||||||
|
(self.now + timedelta(days=2, hours=2)).replace(
|
||||||
|
microsecond=0)
|
||||||
|
)),
|
||||||
|
'location': 'Location',
|
||||||
|
'uid': 'show-{}-{}@example.com'.format(self.s2.pk, self.t.pk),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class EventViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'event.details'
|
||||||
|
http_methods = ['GET', 'POST']
|
||||||
|
|
||||||
|
auth_user = 'user'
|
||||||
|
auth_forbidden = [None]
|
||||||
|
|
||||||
|
post_expected_message = Message(messages.SUCCESS, (
|
||||||
|
"Votre inscription a bien été enregistrée ! Vous pouvez cependant la "
|
||||||
|
"modifier jusqu'à la fin des inscriptions."
|
||||||
|
))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {'event_id': self.e.pk}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return '/event/{}'.format(self.e.pk)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.e = Event.objects.create()
|
||||||
|
|
||||||
|
self.ecf1 = self.e.commentfields.create(name='Comment Field 1')
|
||||||
|
self.ecf2 = self.e.commentfields.create(
|
||||||
|
name='Comment Field 2', fieldtype='char',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.o1 = self.e.options.create(name='Option 1')
|
||||||
|
self.o2 = self.e.options.create(name='Option 2', multi_choices=True)
|
||||||
|
|
||||||
|
self.oc1 = self.o1.choices.create(value='O1 - Choice 1')
|
||||||
|
self.oc2 = self.o1.choices.create(value='O1 - Choice 2')
|
||||||
|
self.oc3 = self.o2.choices.create(value='O2 - Choice 1')
|
||||||
|
self.oc4 = self.o2.choices.create(value='O2 - Choice 2')
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
def test_post_new(self):
|
||||||
|
r = self.client.post(self.url, {
|
||||||
|
'option_{}'.format(self.o1.pk): [str(self.oc1.pk)],
|
||||||
|
'option_{}'.format(self.o2.pk): [
|
||||||
|
str(self.oc3.pk), str(self.oc4.pk),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertIn(self.post_expected_message, get_messages(r.wsgi_request))
|
||||||
|
|
||||||
|
er = self.e.eventregistration_set.get(user=self.users['user'])
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
er.options.all(), map(repr, [self.oc1, self.oc3, self.oc4]),
|
||||||
|
ordered=False,
|
||||||
|
)
|
||||||
|
# TODO: Make the view care about comments.
|
||||||
|
# self.assertQuerysetEqual(
|
||||||
|
# er.comments.all(), map(repr, []),
|
||||||
|
# ordered=False,
|
||||||
|
# )
|
||||||
|
|
||||||
|
def test_post_edit(self):
|
||||||
|
er = self.e.eventregistration_set.create(user=self.users['user'])
|
||||||
|
er.options.add(self.oc1, self.oc3, self.oc4)
|
||||||
|
er.comments.create(
|
||||||
|
commentfield=self.ecf1, content='Comment 1',
|
||||||
|
)
|
||||||
|
|
||||||
|
r = self.client.post(self.url, {
|
||||||
|
'option_{}'.format(self.o1.pk): [],
|
||||||
|
'option_{}'.format(self.o2.pk): [str(self.oc3.pk)],
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertIn(self.post_expected_message, get_messages(r.wsgi_request))
|
||||||
|
|
||||||
|
er.refresh_from_db()
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
er.options.all(), map(repr, [self.oc3]),
|
||||||
|
ordered=False,
|
||||||
|
)
|
||||||
|
# TODO: Make the view care about comments.
|
||||||
|
# self.assertQuerysetEqual(
|
||||||
|
# er.comments.all(), map(repr, []),
|
||||||
|
# ordered=False,
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
class EventStatusViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'event.details.status'
|
||||||
|
|
||||||
|
http_methods = ['GET', 'POST']
|
||||||
|
|
||||||
|
auth_user = 'staff'
|
||||||
|
auth_forbidden = [None, 'user', 'member']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {'event_id': self.e.pk}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return '/event/{}/status'.format(self.e.pk)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.e = Event.objects.create()
|
||||||
|
|
||||||
|
self.cf1 = self.e.commentfields.create(name='Comment Field 1')
|
||||||
|
self.cf2 = self.e.commentfields.create(
|
||||||
|
name='Comment Field 2', fieldtype='char',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.o1 = self.e.options.create(name='Option 1')
|
||||||
|
self.o2 = self.e.options.create(name='Option 2', multi_choices=True)
|
||||||
|
|
||||||
|
self.oc1 = self.o1.choices.create(value='O1 - Choice 1')
|
||||||
|
self.oc2 = self.o1.choices.create(value='O1 - Choice 2')
|
||||||
|
self.oc3 = self.o2.choices.create(value='O2 - Choice 1')
|
||||||
|
self.oc4 = self.o2.choices.create(value='O2 - Choice 2')
|
||||||
|
|
||||||
|
self.er1 = self.e.eventregistration_set.create(user=self.users['user'])
|
||||||
|
self.er1.options.add(self.oc1)
|
||||||
|
self.er2 = self.e.eventregistration_set.create(
|
||||||
|
user=self.users['member'],
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_oc_filter_name(self, oc):
|
||||||
|
return 'option_{}_choice_{}'.format(oc.event_option.pk, oc.pk)
|
||||||
|
|
||||||
|
def _test_filters(self, filters, expected):
|
||||||
|
r = self.client.post(self.url, {
|
||||||
|
self._get_oc_filter_name(oc): v for oc, v in filters
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
r.context['user_choices'], map(repr, expected),
|
||||||
|
ordered=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_filter_none(self):
|
||||||
|
self._test_filters([(self.oc1, 'none')], [self.er1, self.er2])
|
||||||
|
|
||||||
|
def test_filter_yes(self):
|
||||||
|
self._test_filters([(self.oc1, 'yes')], [self.er1])
|
||||||
|
|
||||||
|
def test_filter_no(self):
|
||||||
|
self._test_filters([(self.oc1, 'no')], [self.er2])
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'survey.details'
|
||||||
|
http_methods = ['GET', 'POST']
|
||||||
|
|
||||||
|
auth_user = 'user'
|
||||||
|
auth_forbidden = [None]
|
||||||
|
|
||||||
|
post_expected_message = Message(messages.SUCCESS, (
|
||||||
|
"Votre réponse a bien été enregistrée ! Vous pouvez cependant la "
|
||||||
|
"modifier jusqu'à la fin du sondage."
|
||||||
|
))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {'survey_id': self.s.pk}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return '/survey/{}'.format(self.s.pk)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.s = Survey.objects.create(title='Title')
|
||||||
|
|
||||||
|
self.q1 = self.s.questions.create(question='Question 1 ?')
|
||||||
|
self.q2 = self.s.questions.create(
|
||||||
|
question='Question 2 ?',
|
||||||
|
multi_answers=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.qa1 = self.q1.answers.create(answer='Q1 - Answer 1')
|
||||||
|
self.qa2 = self.q1.answers.create(answer='Q1 - Answer 2')
|
||||||
|
self.qa3 = self.q2.answers.create(answer='Q2 - Answer 1')
|
||||||
|
self.qa4 = self.q2.answers.create(answer='Q2 - Answer 2')
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
def test_post_new(self):
|
||||||
|
r = self.client.post(self.url, {
|
||||||
|
'question_{}'.format(self.q1.pk): [str(self.qa1.pk)],
|
||||||
|
'question_{}'.format(self.q2.pk): [
|
||||||
|
str(self.qa3.pk), str(self.qa4.pk),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertIn(self.post_expected_message, get_messages(r.wsgi_request))
|
||||||
|
|
||||||
|
a = self.s.surveyanswer_set.get(user=self.users['user'])
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
a.answers.all(), map(repr, [self.qa1, self.qa3, self.qa4]),
|
||||||
|
ordered=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_edit(self):
|
||||||
|
a = self.s.surveyanswer_set.create(user=self.users['user'])
|
||||||
|
a.answers.add(self.qa1, self.qa1, self.qa4)
|
||||||
|
|
||||||
|
r = self.client.post(self.url, {
|
||||||
|
'question_{}'.format(self.q1.pk): [],
|
||||||
|
'question_{}'.format(self.q2.pk): [str(self.qa3.pk)],
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertIn(self.post_expected_message, get_messages(r.wsgi_request))
|
||||||
|
|
||||||
|
a.refresh_from_db()
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
a.answers.all(), map(repr, [self.qa3]),
|
||||||
|
ordered=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_delete(self):
|
||||||
|
a = self.s.surveyanswer_set.create(user=self.users['user'])
|
||||||
|
a.answers.add(self.qa1, self.qa4)
|
||||||
|
|
||||||
|
r = self.client.post(self.url, {'delete': '1'})
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
expected_message = Message(
|
||||||
|
messages.SUCCESS, "Votre réponse a bien été supprimée")
|
||||||
|
self.assertIn(expected_message, get_messages(r.wsgi_request))
|
||||||
|
|
||||||
|
with self.assertRaises(SurveyAnswer.DoesNotExist):
|
||||||
|
a.refresh_from_db()
|
||||||
|
|
||||||
|
def test_forbidden_closed(self):
|
||||||
|
self.s.survey_open = False
|
||||||
|
self.s.save()
|
||||||
|
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertNotEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
def test_forbidden_old(self):
|
||||||
|
self.s.old = True
|
||||||
|
self.s.save()
|
||||||
|
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertNotEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyStatusViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
url_name = 'survey.details.status'
|
||||||
|
|
||||||
|
http_methods = ['GET', 'POST']
|
||||||
|
|
||||||
|
auth_user = 'staff'
|
||||||
|
auth_forbidden = [None, 'user', 'member']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {'survey_id': self.s.pk}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return '/survey/{}/status'.format(self.s.pk)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.s = Survey.objects.create(title='Title')
|
||||||
|
|
||||||
|
self.q1 = self.s.questions.create(question='Question 1 ?')
|
||||||
|
self.q2 = self.s.questions.create(
|
||||||
|
question='Question 2 ?',
|
||||||
|
multi_answers=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.qa1 = self.q1.answers.create(answer='Q1 - Answer 1')
|
||||||
|
self.qa2 = self.q1.answers.create(answer='Q1 - Answer 2')
|
||||||
|
self.qa3 = self.q2.answers.create(answer='Q2 - Answer 1')
|
||||||
|
self.qa4 = self.q2.answers.create(answer='Q2 - Answer 2')
|
||||||
|
|
||||||
|
self.a1 = self.s.surveyanswer_set.create(user=self.users['user'])
|
||||||
|
self.a1.answers.add(self.qa1)
|
||||||
|
self.a2 = self.s.surveyanswer_set.create(user=self.users['member'])
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
def _get_qa_filter_name(self, qa):
|
||||||
|
return 'question_{}_answer_{}'.format(qa.survey_question.pk, qa.pk)
|
||||||
|
|
||||||
|
def _test_filters(self, filters, expected):
|
||||||
|
r = self.client.post(self.url, {
|
||||||
|
self._get_qa_filter_name(qa): v for qa, v in filters
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
r.context['user_answers'], map(repr, expected),
|
||||||
|
ordered=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_filter_none(self):
|
||||||
|
self._test_filters([(self.qa1, 'none')], [self.a1, self.a2])
|
||||||
|
|
||||||
|
def test_filter_yes(self):
|
||||||
|
self._test_filters([(self.qa1, 'yes')], [self.a1])
|
||||||
|
|
||||||
|
def test_filter_no(self):
|
||||||
|
self._test_filters([(self.qa1, 'no')], [self.a2])
|
24
gestioncof/tests/testcases.py
Normal file
24
gestioncof/tests/testcases.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
from shared.tests.testcases import ViewTestCaseMixin as BaseViewTestCaseMixin
|
||||||
|
|
||||||
|
from .utils import create_user, create_member, create_staff
|
||||||
|
|
||||||
|
|
||||||
|
class ViewTestCaseMixin(BaseViewTestCaseMixin):
|
||||||
|
"""
|
||||||
|
TestCase extension to ease testing of cof views.
|
||||||
|
|
||||||
|
Most information can be found in the base parent class doc.
|
||||||
|
This class performs some changes to users management, detailed below.
|
||||||
|
|
||||||
|
During setup, three users are created:
|
||||||
|
- 'user': a basic user without any permission,
|
||||||
|
- 'member': (profile.is_cof is True),
|
||||||
|
- 'staff': (profile.is_cof is True) && (profile.is_buro is True).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_users_base(self):
|
||||||
|
return {
|
||||||
|
'user': create_user('user'),
|
||||||
|
'member': create_member('member'),
|
||||||
|
'staff': create_staff('staff'),
|
||||||
|
}
|
61
gestioncof/tests/utils.py
Normal file
61
gestioncof/tests/utils.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
def _create_user(username, is_cof=False, is_staff=False, attrs=None):
|
||||||
|
if attrs is None:
|
||||||
|
attrs = {}
|
||||||
|
|
||||||
|
password = attrs.pop('password', username)
|
||||||
|
|
||||||
|
user_keys = [
|
||||||
|
'first_name', 'last_name', 'email', 'is_staff', 'is_superuser',
|
||||||
|
]
|
||||||
|
user_attrs = {k: v for k, v in attrs.items() if k in user_keys}
|
||||||
|
|
||||||
|
profile_keys = [
|
||||||
|
'is_cof', 'login_clipper', 'phone', 'occupation', 'departement',
|
||||||
|
'type_cotiz', 'mailing_cof', 'mailing_bda', 'mailing_bda_revente',
|
||||||
|
'comments', 'is_buro', 'petit_cours_accept',
|
||||||
|
'petit_cours_remarques',
|
||||||
|
]
|
||||||
|
profile_attrs = {k: v for k, v in attrs.items() if k in profile_keys}
|
||||||
|
|
||||||
|
if is_cof:
|
||||||
|
profile_attrs['is_cof'] = True
|
||||||
|
|
||||||
|
if is_staff:
|
||||||
|
# At the moment, admin is accessible by COF staff.
|
||||||
|
user_attrs['is_staff'] = True
|
||||||
|
profile_attrs['is_buro'] = True
|
||||||
|
|
||||||
|
user = User(username=username, **user_attrs)
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
for k, v in profile_attrs.items():
|
||||||
|
setattr(user.profile, k, v)
|
||||||
|
user.profile.save()
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def create_user(username, attrs=None):
|
||||||
|
return _create_user(username, attrs=attrs)
|
||||||
|
|
||||||
|
|
||||||
|
def create_member(username, attrs=None):
|
||||||
|
return _create_user(username, is_cof=True, attrs=attrs)
|
||||||
|
|
||||||
|
|
||||||
|
def create_staff(username, attrs=None):
|
||||||
|
return _create_user(username, is_cof=True, is_staff=True, attrs=attrs)
|
||||||
|
|
||||||
|
|
||||||
|
def create_root(username, attrs=None):
|
||||||
|
if attrs is None:
|
||||||
|
attrs = {}
|
||||||
|
attrs.setdefault('is_staff', True)
|
||||||
|
attrs.setdefault('is_superuser', True)
|
||||||
|
return _create_user(username, attrs=attrs)
|
|
@ -6,12 +6,17 @@ from gestioncof import views, petits_cours_views
|
||||||
from gestioncof.decorators import buro_required
|
from gestioncof.decorators import buro_required
|
||||||
|
|
||||||
export_patterns = [
|
export_patterns = [
|
||||||
url(r'^members$', views.export_members),
|
url(r'^members$', views.export_members,
|
||||||
url(r'^mega/avecremarques$', views.export_mega_remarksonly),
|
name='cof.membres_export'),
|
||||||
url(r'^mega/participants$', views.export_mega_participants),
|
url(r'^mega/avecremarques$', views.export_mega_remarksonly,
|
||||||
url(r'^mega/orgas$', views.export_mega_orgas),
|
name='cof.mega_export_remarks'),
|
||||||
|
url(r'^mega/participants$', views.export_mega_participants,
|
||||||
|
name='cof.mega_export_participants'),
|
||||||
|
url(r'^mega/orgas$', views.export_mega_orgas,
|
||||||
|
name='cof.mega_export_orgas'),
|
||||||
# url(r'^mega/(?P<type>.+)$', views.export_mega_bytype),
|
# url(r'^mega/(?P<type>.+)$', views.export_mega_bytype),
|
||||||
url(r'^mega$', views.export_mega),
|
url(r'^mega$', views.export_mega,
|
||||||
|
name='cof.mega_export'),
|
||||||
]
|
]
|
||||||
|
|
||||||
petitcours_patterns = [
|
petitcours_patterns = [
|
||||||
|
@ -36,19 +41,24 @@ petitcours_patterns = [
|
||||||
]
|
]
|
||||||
|
|
||||||
surveys_patterns = [
|
surveys_patterns = [
|
||||||
url(r'^(?P<survey_id>\d+)/status$', views.survey_status),
|
url(r'^(?P<survey_id>\d+)/status$', views.survey_status,
|
||||||
url(r'^(?P<survey_id>\d+)$', views.survey),
|
name='survey.details.status'),
|
||||||
|
url(r'^(?P<survey_id>\d+)$', views.survey,
|
||||||
|
name='survey.details'),
|
||||||
]
|
]
|
||||||
|
|
||||||
events_patterns = [
|
events_patterns = [
|
||||||
url(r'^(?P<event_id>\d+)$', views.event),
|
url(r'^(?P<event_id>\d+)$', views.event,
|
||||||
url(r'^(?P<event_id>\d+)/status$', views.event_status),
|
name='event.details'),
|
||||||
|
url(r'^(?P<event_id>\d+)/status$', views.event_status,
|
||||||
|
name='event.details.status'),
|
||||||
]
|
]
|
||||||
|
|
||||||
calendar_patterns = [
|
calendar_patterns = [
|
||||||
url(r'^subscription$', 'gestioncof.views.calendar'),
|
url(r'^subscription$', views.calendar,
|
||||||
url(r'^(?P<token>[a-z0-9-]+)/calendar.ics$',
|
name='calendar'),
|
||||||
'gestioncof.views.calendar_ics')
|
url(r'^(?P<token>[a-z0-9-]+)/calendar.ics$', views.calendar_ics,
|
||||||
|
name='calendar.ics'),
|
||||||
]
|
]
|
||||||
|
|
||||||
clubs_patterns = [
|
clubs_patterns = [
|
||||||
|
|
|
@ -9,6 +9,7 @@ from django.http import Http404, HttpResponse, HttpResponseForbidden
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.views import (
|
from django.contrib.auth.views import (
|
||||||
login as django_login_view, logout as django_logout_view,
|
login as django_login_view, logout as django_logout_view,
|
||||||
|
redirect_to_login,
|
||||||
)
|
)
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
|
@ -20,6 +21,8 @@ from django.contrib import messages
|
||||||
|
|
||||||
from django_cas_ng.views import logout as cas_logout_view
|
from django_cas_ng.views import logout as cas_logout_view
|
||||||
|
|
||||||
|
from utils.views.autocomplete import Select2QuerySetView
|
||||||
|
|
||||||
from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \
|
from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \
|
||||||
SurveyQuestionAnswer
|
SurveyQuestionAnswer
|
||||||
from gestioncof.models import Event, EventRegistration, EventOption, \
|
from gestioncof.models import Event, EventRegistration, EventOption, \
|
||||||
|
@ -54,8 +57,8 @@ def home(request):
|
||||||
|
|
||||||
|
|
||||||
def login(request):
|
def login(request):
|
||||||
if request.user.is_authenticated():
|
if request.user.is_authenticated:
|
||||||
return redirect("gestioncof.views.home")
|
return redirect("home")
|
||||||
context = {}
|
context = {}
|
||||||
if request.method == "GET" and 'next' in request.GET:
|
if request.method == "GET" and 'next' in request.GET:
|
||||||
context['next'] = request.GET['next']
|
context['next'] = request.GET['next']
|
||||||
|
@ -564,7 +567,7 @@ def liste_clubs(request):
|
||||||
if request.user.profile.is_buro:
|
if request.user.profile.is_buro:
|
||||||
data = {'owned_clubs': clubs.all()}
|
data = {'owned_clubs': clubs.all()}
|
||||||
else:
|
else:
|
||||||
data = {'owned_clubs': request.user.clubs_geres,
|
data = {'owned_clubs': request.user.clubs_geres.all(),
|
||||||
'other_clubs': clubs.exclude(respos=request.user)}
|
'other_clubs': clubs.exclude(respos=request.user)}
|
||||||
return render(request, 'liste_clubs.html', data)
|
return render(request, 'liste_clubs.html', data)
|
||||||
|
|
||||||
|
@ -577,7 +580,7 @@ def export_members(request):
|
||||||
writer = unicodecsv.writer(response)
|
writer = unicodecsv.writer(response)
|
||||||
for profile in CofProfile.objects.filter(is_cof=True).all():
|
for profile in CofProfile.objects.filter(is_cof=True).all():
|
||||||
user = profile.user
|
user = profile.user
|
||||||
bits = [profile.id, user.username, user.first_name, user.last_name,
|
bits = [user.id, user.username, user.first_name, user.last_name,
|
||||||
user.email, profile.phone, profile.occupation,
|
user.email, profile.phone, profile.occupation,
|
||||||
profile.departement, profile.type_cotiz]
|
profile.departement, profile.type_cotiz]
|
||||||
writer.writerow([str(bit) for bit in bits])
|
writer.writerow([str(bit) for bit in bits])
|
||||||
|
@ -596,7 +599,7 @@ def csv_export_mega(filename, qs):
|
||||||
comments = "---".join(
|
comments = "---".join(
|
||||||
[comment.content for comment in reg.comments.all()])
|
[comment.content for comment in reg.comments.all()])
|
||||||
bits = [user.username, user.first_name, user.last_name, user.email,
|
bits = [user.username, user.first_name, user.last_name, user.email,
|
||||||
profile.phone, profile.id,
|
profile.phone, user.id,
|
||||||
profile.comments if profile.comments else "", comments]
|
profile.comments if profile.comments else "", comments]
|
||||||
|
|
||||||
writer.writerow([str(bit) for bit in bits])
|
writer.writerow([str(bit) for bit in bits])
|
||||||
|
@ -780,9 +783,24 @@ class ConfigUpdate(FormView):
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if request.user is None or not request.user.is_superuser:
|
if request.user is None or not request.user.is_superuser:
|
||||||
raise Http404
|
return redirect_to_login(request.get_full_path())
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.save()
|
form.save()
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Autocomplete views
|
||||||
|
#
|
||||||
|
# https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#create-an-autocomplete-view
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
class UserAutocomplete(Select2QuerySetView):
|
||||||
|
model = User
|
||||||
|
search_fields = ('username', 'first_name', 'last_name')
|
||||||
|
|
||||||
|
|
||||||
|
user_autocomplete = buro_required(UserAutocomplete.as_view())
|
||||||
|
|
|
@ -20,6 +20,7 @@ class TriStateCheckbox(Widget):
|
||||||
def render(self, name, value, attrs=None, choices=()):
|
def render(self, name, value, attrs=None, choices=()):
|
||||||
if value is None:
|
if value is None:
|
||||||
value = 'none'
|
value = 'none'
|
||||||
final_attrs = self.build_attrs(attrs, value=value)
|
attrs['value'] = value
|
||||||
|
final_attrs = self.build_attrs(self.attrs, attrs)
|
||||||
output = ["<span class=\"tristate\"%s></span>" % flatatt(final_attrs)]
|
output = ["<span class=\"tristate\"%s></span>" % flatatt(final_attrs)]
|
||||||
return mark_safe('\n'.join(output))
|
return mark_safe('\n'.join(output))
|
||||||
|
|
|
@ -11,7 +11,6 @@ class KFetConfig(AppConfig):
|
||||||
verbose_name = "Application K-Fêt"
|
verbose_name = "Application K-Fêt"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
import kfet.signals
|
|
||||||
self.register_config()
|
self.register_config()
|
||||||
|
|
||||||
def register_config(self):
|
def register_config(self):
|
||||||
|
|
4
kfet/auth/__init__.py
Normal file
4
kfet/auth/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
default_app_config = 'kfet.auth.apps.KFetAuthConfig'
|
||||||
|
|
||||||
|
KFET_GENERIC_USERNAME = 'kfet_genericteam'
|
||||||
|
KFET_GENERIC_TRIGRAMME = 'GNR'
|
14
kfet/auth/apps.py
Normal file
14
kfet/auth/apps.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.db.models.signals import post_migrate
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class KFetAuthConfig(AppConfig):
|
||||||
|
name = 'kfet.auth'
|
||||||
|
label = 'kfetauth'
|
||||||
|
verbose_name = _("K-Fêt - Authentification et Autorisation")
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from . import signals # noqa
|
||||||
|
from .utils import setup_kfet_generic_user
|
||||||
|
post_migrate.connect(setup_kfet_generic_user, sender=self)
|
43
kfet/auth/backends.py
Normal file
43
kfet/auth/backends.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from kfet.models import Account, GenericTeamToken
|
||||||
|
|
||||||
|
from .utils import get_kfet_generic_user
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseKFetBackend:
|
||||||
|
def get_user(self, user_id):
|
||||||
|
"""
|
||||||
|
Add extra select related up to Account.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return (
|
||||||
|
User.objects
|
||||||
|
.select_related('profile__account_kfet')
|
||||||
|
.get(pk=user_id)
|
||||||
|
)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class AccountBackend(BaseKFetBackend):
|
||||||
|
def authenticate(self, request, kfet_password=None):
|
||||||
|
try:
|
||||||
|
return Account.objects.get_by_password(kfet_password).user
|
||||||
|
except Account.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class GenericBackend(BaseKFetBackend):
|
||||||
|
def authenticate(self, request, kfet_token=None):
|
||||||
|
try:
|
||||||
|
team_token = GenericTeamToken.objects.get(token=kfet_token)
|
||||||
|
except GenericTeamToken.DoesNotExist:
|
||||||
|
return
|
||||||
|
|
||||||
|
# No need to keep the token.
|
||||||
|
team_token.delete()
|
||||||
|
|
||||||
|
return get_kfet_generic_user()
|
10
kfet/auth/context_processors.py
Normal file
10
kfet/auth/context_processors.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
from django.contrib.auth.context_processors import PermWrapper
|
||||||
|
|
||||||
|
|
||||||
|
def temporary_auth(request):
|
||||||
|
if hasattr(request, 'real_user'):
|
||||||
|
return {
|
||||||
|
'user': request.real_user,
|
||||||
|
'perms': PermWrapper(request.real_user),
|
||||||
|
}
|
||||||
|
return {}
|
20
kfet/auth/fields.py
Normal file
20
kfet/auth/fields.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.models import Permission
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.forms import widgets
|
||||||
|
|
||||||
|
|
||||||
|
class KFetPermissionsField(forms.ModelMultipleChoiceField):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
queryset = Permission.objects.filter(
|
||||||
|
content_type__in=ContentType.objects.filter(app_label="kfet"),
|
||||||
|
)
|
||||||
|
super().__init__(
|
||||||
|
queryset=queryset,
|
||||||
|
widget=widgets.CheckboxSelectMultiple,
|
||||||
|
*args, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def label_from_instance(self, obj):
|
||||||
|
return obj.name
|
48
kfet/auth/forms.py
Normal file
48
kfet/auth/forms.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.models import Group, User
|
||||||
|
|
||||||
|
from .fields import KFetPermissionsField
|
||||||
|
|
||||||
|
|
||||||
|
class GroupForm(forms.ModelForm):
|
||||||
|
permissions = KFetPermissionsField()
|
||||||
|
|
||||||
|
def clean_name(self):
|
||||||
|
name = self.cleaned_data['name']
|
||||||
|
return 'K-Fêt %s' % name
|
||||||
|
|
||||||
|
def clean_permissions(self):
|
||||||
|
kfet_perms = self.cleaned_data['permissions']
|
||||||
|
# TODO: With Django >=1.11, the QuerySet method 'difference' can be
|
||||||
|
# used.
|
||||||
|
# other_groups = self.instance.permissions.difference(
|
||||||
|
# self.fields['permissions'].queryset
|
||||||
|
# )
|
||||||
|
if self.instance.pk is None:
|
||||||
|
return kfet_perms
|
||||||
|
other_perms = self.instance.permissions.exclude(
|
||||||
|
pk__in=[p.pk for p in self.fields['permissions'].queryset],
|
||||||
|
)
|
||||||
|
return list(kfet_perms) + list(other_perms)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Group
|
||||||
|
fields = ['name', 'permissions']
|
||||||
|
|
||||||
|
|
||||||
|
class UserGroupForm(forms.ModelForm):
|
||||||
|
groups = forms.ModelMultipleChoiceField(
|
||||||
|
Group.objects.filter(name__icontains='K-Fêt'),
|
||||||
|
label='Statut équipe',
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
def clean_groups(self):
|
||||||
|
kfet_groups = self.cleaned_data.get('groups')
|
||||||
|
if self.instance.pk is None:
|
||||||
|
return kfet_groups
|
||||||
|
other_groups = self.instance.groups.exclude(name__icontains='K-Fêt')
|
||||||
|
return list(kfet_groups) + list(other_groups)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ['groups']
|
43
kfet/auth/middleware.py
Normal file
43
kfet/auth/middleware.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
from .backends import AccountBackend
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class TemporaryAuthMiddleware:
|
||||||
|
"""Authenticate another user for this request if AccountBackend succeeds.
|
||||||
|
|
||||||
|
By the way, if a user is authenticated, we refresh its from db to add
|
||||||
|
values from CofProfile and Account of this user.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
# avoid multiple db accesses in views and templates
|
||||||
|
request.user = (
|
||||||
|
User.objects
|
||||||
|
.select_related('profile__account_kfet')
|
||||||
|
.get(pk=request.user.pk)
|
||||||
|
)
|
||||||
|
|
||||||
|
temp_request_user = AccountBackend().authenticate(
|
||||||
|
request,
|
||||||
|
kfet_password=self.get_kfet_password(request),
|
||||||
|
)
|
||||||
|
|
||||||
|
if temp_request_user:
|
||||||
|
request.real_user = request.user
|
||||||
|
request.user = temp_request_user
|
||||||
|
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
|
def get_kfet_password(self, request):
|
||||||
|
return (
|
||||||
|
request.META.get('HTTP_KFETPASSWORD') or
|
||||||
|
request.POST.get('KFETPASSWORD')
|
||||||
|
)
|
24
kfet/auth/migrations/0001_initial.py
Normal file
24
kfet/auth/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0006_require_contenttypes_0002'),
|
||||||
|
# Following dependency allows using Account model to set up the kfet
|
||||||
|
# generic user in post_migrate receiver.
|
||||||
|
('kfet', '0058_delete_genericteamtoken'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GenericTeamToken',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
|
||||||
|
('token', models.CharField(unique=True, max_length=50)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
0
kfet/auth/migrations/__init__.py
Normal file
0
kfet/auth/migrations/__init__.py
Normal file
17
kfet/auth/models.py
Normal file
17
kfet/auth/models.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.crypto import get_random_string
|
||||||
|
|
||||||
|
|
||||||
|
class GenericTeamTokenManager(models.Manager):
|
||||||
|
|
||||||
|
def create_token(self):
|
||||||
|
token = get_random_string(50)
|
||||||
|
while self.filter(token=token).exists():
|
||||||
|
token = get_random_string(50)
|
||||||
|
return self.create(token=token)
|
||||||
|
|
||||||
|
|
||||||
|
class GenericTeamToken(models.Model):
|
||||||
|
token = models.CharField(max_length=50, unique=True)
|
||||||
|
|
||||||
|
objects = GenericTeamTokenManager()
|
40
kfet/auth/signals.py
Normal file
40
kfet/auth/signals.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.signals import user_logged_in
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.dispatch import receiver
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
from .utils import get_kfet_generic_user
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(user_logged_in)
|
||||||
|
def suggest_auth_generic(sender, request, user, **kwargs):
|
||||||
|
"""
|
||||||
|
Suggest logged in user to continue as the kfet generic user.
|
||||||
|
|
||||||
|
Message is only added if the following conditions are met:
|
||||||
|
- the next page (where user is going to be redirected due to successful
|
||||||
|
authentication) is related to kfet, i.e. 'k-fet' is in its url.
|
||||||
|
- logged in user is a kfet staff member (except the generic user).
|
||||||
|
"""
|
||||||
|
# Filter against the next page.
|
||||||
|
if not(hasattr(request, 'GET') and 'next' in request.GET):
|
||||||
|
return
|
||||||
|
|
||||||
|
next_page = request.GET['next']
|
||||||
|
generic_url = reverse('kfet.login.generic')
|
||||||
|
|
||||||
|
if not('k-fet' in next_page and not next_page.startswith(generic_url)):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Filter against the logged in user.
|
||||||
|
if not(user.has_perm('kfet.is_team') and user != get_kfet_generic_user()):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Seems legit to add message.
|
||||||
|
text = _("K-Fêt — Ouvrir une session partagée ?")
|
||||||
|
messages.info(request, mark_safe(
|
||||||
|
'<a href="#" data-url="{}" onclick="submit_url(this)">{}</a>'
|
||||||
|
.format(generic_url, text)
|
||||||
|
))
|
369
kfet/auth/tests.py
Normal file
369
kfet/auth/tests.py
Normal file
|
@ -0,0 +1,369 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from django.core import signing
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.contrib.auth.models import AnonymousUser, Group, Permission, User
|
||||||
|
from django.test import RequestFactory, TestCase
|
||||||
|
|
||||||
|
from kfet.forms import UserGroupForm
|
||||||
|
from kfet.models import Account
|
||||||
|
|
||||||
|
from . import KFET_GENERIC_TRIGRAMME, KFET_GENERIC_USERNAME
|
||||||
|
from .backends import AccountBackend, GenericBackend
|
||||||
|
from .middleware import TemporaryAuthMiddleware
|
||||||
|
from .models import GenericTeamToken
|
||||||
|
from .utils import get_kfet_generic_user
|
||||||
|
from .views import GenericLoginView
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Forms
|
||||||
|
##
|
||||||
|
|
||||||
|
class UserGroupFormTests(TestCase):
|
||||||
|
"""Test suite for UserGroupForm."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# create user
|
||||||
|
self.user = User.objects.create(username="foo", password="foo")
|
||||||
|
|
||||||
|
# create some K-Fêt groups
|
||||||
|
prefix_name = "K-Fêt "
|
||||||
|
names = ["Group 1", "Group 2", "Group 3"]
|
||||||
|
self.kfet_groups = [
|
||||||
|
Group.objects.create(name=prefix_name+name)
|
||||||
|
for name in names
|
||||||
|
]
|
||||||
|
|
||||||
|
# create a non-K-Fêt group
|
||||||
|
self.other_group = Group.objects.create(name="Other group")
|
||||||
|
|
||||||
|
def test_choices(self):
|
||||||
|
"""Only K-Fêt groups are selectable."""
|
||||||
|
form = UserGroupForm(instance=self.user)
|
||||||
|
groups_field = form.fields['groups']
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
groups_field.queryset,
|
||||||
|
[repr(g) for g in self.kfet_groups],
|
||||||
|
ordered=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_keep_others(self):
|
||||||
|
"""User stays in its non-K-Fêt groups."""
|
||||||
|
user = self.user
|
||||||
|
|
||||||
|
# add user to a non-K-Fêt group
|
||||||
|
user.groups.add(self.other_group)
|
||||||
|
|
||||||
|
# add user to some K-Fêt groups through UserGroupForm
|
||||||
|
data = {
|
||||||
|
'groups': [group.pk for group in self.kfet_groups],
|
||||||
|
}
|
||||||
|
form = UserGroupForm(data, instance=user)
|
||||||
|
|
||||||
|
form.is_valid()
|
||||||
|
form.save()
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
user.groups.all(),
|
||||||
|
[repr(g) for g in [self.other_group] + self.kfet_groups],
|
||||||
|
ordered=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class KFetGenericUserTests(TestCase):
|
||||||
|
|
||||||
|
def test_exists(self):
|
||||||
|
"""
|
||||||
|
The account is set up when app is ready, so it should exist.
|
||||||
|
"""
|
||||||
|
generic = Account.objects.get_generic()
|
||||||
|
self.assertEqual(generic.trigramme, KFET_GENERIC_TRIGRAMME)
|
||||||
|
self.assertEqual(generic.user.username, KFET_GENERIC_USERNAME)
|
||||||
|
self.assertEqual(get_kfet_generic_user(), generic.user)
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Backends
|
||||||
|
##
|
||||||
|
|
||||||
|
class AccountBackendTests(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.request = RequestFactory().get('/')
|
||||||
|
|
||||||
|
def test_valid(self):
|
||||||
|
acc = Account(trigramme='000')
|
||||||
|
acc.change_pwd('valid')
|
||||||
|
acc.save({'username': 'user'})
|
||||||
|
|
||||||
|
auth = AccountBackend().authenticate(
|
||||||
|
self.request, kfet_password='valid')
|
||||||
|
|
||||||
|
self.assertEqual(auth, acc.user)
|
||||||
|
|
||||||
|
def test_invalid(self):
|
||||||
|
auth = AccountBackend().authenticate(
|
||||||
|
self.request, kfet_password='invalid')
|
||||||
|
self.assertIsNone(auth)
|
||||||
|
|
||||||
|
|
||||||
|
class GenericBackendTests(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.request = RequestFactory().get('/')
|
||||||
|
|
||||||
|
def test_valid(self):
|
||||||
|
token = GenericTeamToken.objects.create_token()
|
||||||
|
|
||||||
|
auth = GenericBackend().authenticate(
|
||||||
|
self.request, kfet_token=token.token)
|
||||||
|
|
||||||
|
self.assertEqual(auth, get_kfet_generic_user())
|
||||||
|
self.assertEqual(GenericTeamToken.objects.all().count(), 0)
|
||||||
|
|
||||||
|
def test_invalid(self):
|
||||||
|
auth = GenericBackend().authenticate(
|
||||||
|
self.request, kfet_token='invalid')
|
||||||
|
self.assertIsNone(auth)
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Views
|
||||||
|
##
|
||||||
|
|
||||||
|
class GenericLoginViewTests(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
patcher_messages = mock.patch('gestioncof.signals.messages')
|
||||||
|
patcher_messages.start()
|
||||||
|
self.addCleanup(patcher_messages.stop)
|
||||||
|
|
||||||
|
user_acc = Account(trigramme='000')
|
||||||
|
user_acc.save({'username': 'user'})
|
||||||
|
self.user = user_acc.user
|
||||||
|
self.user.set_password('user')
|
||||||
|
self.user.save()
|
||||||
|
|
||||||
|
team_acc = Account(trigramme='100')
|
||||||
|
team_acc.save({'username': 'team'})
|
||||||
|
self.team = team_acc.user
|
||||||
|
self.team.set_password('team')
|
||||||
|
self.team.save()
|
||||||
|
self.team.user_permissions.add(
|
||||||
|
Permission.objects.get(
|
||||||
|
content_type__app_label='kfet', codename='is_team'),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.url = reverse('kfet.login.generic')
|
||||||
|
self.generic_user = get_kfet_generic_user()
|
||||||
|
|
||||||
|
def test_url(self):
|
||||||
|
self.assertEqual(self.url, '/k-fet/login/generic')
|
||||||
|
|
||||||
|
def test_notoken_get(self):
|
||||||
|
"""
|
||||||
|
Send confirmation for user to emit POST request, instead of GET.
|
||||||
|
"""
|
||||||
|
self.client.login(username='team', password='team')
|
||||||
|
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertTemplateUsed(r, 'kfet/confirm_form.html')
|
||||||
|
|
||||||
|
def test_notoken_post(self):
|
||||||
|
"""
|
||||||
|
POST request without token in COOKIES sets a token and redirects to
|
||||||
|
logout url.
|
||||||
|
"""
|
||||||
|
self.client.login(username='team', password='team')
|
||||||
|
|
||||||
|
r = self.client.post(self.url)
|
||||||
|
|
||||||
|
self.assertRedirects(
|
||||||
|
r, '/logout?next={}'.format(self.url),
|
||||||
|
fetch_redirect_response=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_notoken_not_team(self):
|
||||||
|
"""
|
||||||
|
Logged in user must be a team user to initiate login as generic user.
|
||||||
|
"""
|
||||||
|
self.client.login(username='user', password='user')
|
||||||
|
|
||||||
|
# With GET.
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertRedirects(
|
||||||
|
r, '/login?next={}'.format(self.url),
|
||||||
|
fetch_redirect_response=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Also with POST.
|
||||||
|
r = self.client.post(self.url)
|
||||||
|
self.assertRedirects(
|
||||||
|
r, '/login?next={}'.format(self.url),
|
||||||
|
fetch_redirect_response=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _set_signed_cookie(self, client, key, value):
|
||||||
|
signed_value = signing.get_cookie_signer(salt=key).sign(value)
|
||||||
|
client.cookies.load({key: signed_value})
|
||||||
|
|
||||||
|
def _is_cookie_deleted(self, client, key):
|
||||||
|
try:
|
||||||
|
self.assertNotIn(key, client.cookies)
|
||||||
|
except AssertionError:
|
||||||
|
try:
|
||||||
|
cookie = client.cookies[key]
|
||||||
|
# It also can be emptied.
|
||||||
|
self.assertEqual(cookie.value, '')
|
||||||
|
self.assertEqual(
|
||||||
|
cookie['expires'], 'Thu, 01-Jan-1970 00:00:00 GMT')
|
||||||
|
self.assertEqual(cookie['max-age'], 0)
|
||||||
|
except AssertionError:
|
||||||
|
raise AssertionError("The cookie '%s' still exists." % key)
|
||||||
|
|
||||||
|
def test_withtoken_valid(self):
|
||||||
|
"""
|
||||||
|
The kfet generic user is logged in.
|
||||||
|
"""
|
||||||
|
token = GenericTeamToken.objects.create(token='valid')
|
||||||
|
self._set_signed_cookie(
|
||||||
|
self.client, GenericLoginView.TOKEN_COOKIE_NAME, 'valid')
|
||||||
|
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertRedirects(r, reverse('kfet.kpsul'))
|
||||||
|
self.assertEqual(r.wsgi_request.user, self.generic_user)
|
||||||
|
self._is_cookie_deleted(
|
||||||
|
self.client, GenericLoginView.TOKEN_COOKIE_NAME)
|
||||||
|
with self.assertRaises(GenericTeamToken.DoesNotExist):
|
||||||
|
token.refresh_from_db()
|
||||||
|
|
||||||
|
def test_withtoken_invalid(self):
|
||||||
|
"""
|
||||||
|
If token is invalid, delete it and try again.
|
||||||
|
"""
|
||||||
|
self._set_signed_cookie(
|
||||||
|
self.client, GenericLoginView.TOKEN_COOKIE_NAME, 'invalid')
|
||||||
|
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertRedirects(r, self.url, fetch_redirect_response=False)
|
||||||
|
self.assertEqual(r.wsgi_request.user, AnonymousUser())
|
||||||
|
self._is_cookie_deleted(
|
||||||
|
self.client, GenericLoginView.TOKEN_COOKIE_NAME)
|
||||||
|
|
||||||
|
def test_flow_ok(self):
|
||||||
|
"""
|
||||||
|
A team user is logged in as the kfet generic user.
|
||||||
|
"""
|
||||||
|
self.client.login(username='team', password='team')
|
||||||
|
next_url = '/k-fet/'
|
||||||
|
|
||||||
|
r = self.client.post(
|
||||||
|
'{}?next={}'.format(self.url, next_url), follow=True)
|
||||||
|
|
||||||
|
self.assertEqual(r.wsgi_request.user, self.generic_user)
|
||||||
|
self.assertEqual(r.wsgi_request.path, '/k-fet/')
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Temporary authentication
|
||||||
|
#
|
||||||
|
# Includes:
|
||||||
|
# - TemporaryAuthMiddleware
|
||||||
|
# - temporary_auth context processor
|
||||||
|
##
|
||||||
|
|
||||||
|
class TemporaryAuthTests(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
patcher_messages = mock.patch('gestioncof.signals.messages')
|
||||||
|
patcher_messages.start()
|
||||||
|
self.addCleanup(patcher_messages.stop)
|
||||||
|
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
|
||||||
|
self.middleware = TemporaryAuthMiddleware(mock.Mock())
|
||||||
|
|
||||||
|
user1_acc = Account(trigramme='000')
|
||||||
|
user1_acc.change_pwd('kfet_user1')
|
||||||
|
user1_acc.save({'username': 'user1'})
|
||||||
|
self.user1 = user1_acc.user
|
||||||
|
self.user1.set_password('user1')
|
||||||
|
self.user1.save()
|
||||||
|
|
||||||
|
user2_acc = Account(trigramme='100')
|
||||||
|
user2_acc.change_pwd('kfet_user2')
|
||||||
|
user2_acc.save({'username': 'user2'})
|
||||||
|
self.user2 = user2_acc.user
|
||||||
|
self.user2.set_password('user2')
|
||||||
|
self.user2.save()
|
||||||
|
|
||||||
|
self.perm = Permission.objects.get(
|
||||||
|
content_type__app_label='kfet', codename='is_team')
|
||||||
|
self.user2.user_permissions.add(self.perm)
|
||||||
|
|
||||||
|
def test_middleware_header(self):
|
||||||
|
"""
|
||||||
|
A user can be authenticated if ``HTTP_KFETPASSWORD`` header of a
|
||||||
|
request contains a valid kfet password.
|
||||||
|
"""
|
||||||
|
request = self.factory.get('/', HTTP_KFETPASSWORD='kfet_user2')
|
||||||
|
request.user = self.user1
|
||||||
|
|
||||||
|
self.middleware(request)
|
||||||
|
|
||||||
|
self.assertEqual(request.user, self.user2)
|
||||||
|
self.assertEqual(request.real_user, self.user1)
|
||||||
|
|
||||||
|
def test_middleware_post(self):
|
||||||
|
"""
|
||||||
|
A user can be authenticated if ``KFETPASSWORD`` of POST data contains
|
||||||
|
a valid kfet password.
|
||||||
|
"""
|
||||||
|
request = self.factory.post('/', {'KFETPASSWORD': 'kfet_user2'})
|
||||||
|
request.user = self.user1
|
||||||
|
|
||||||
|
self.middleware(request)
|
||||||
|
|
||||||
|
self.assertEqual(request.user, self.user2)
|
||||||
|
self.assertEqual(request.real_user, self.user1)
|
||||||
|
|
||||||
|
def test_middleware_invalid(self):
|
||||||
|
"""
|
||||||
|
The given password must be a password of an Account.
|
||||||
|
"""
|
||||||
|
request = self.factory.post('/', {'KFETPASSWORD': 'invalid'})
|
||||||
|
request.user = self.user1
|
||||||
|
|
||||||
|
self.middleware(request)
|
||||||
|
|
||||||
|
self.assertEqual(request.user, self.user1)
|
||||||
|
self.assertFalse(hasattr(request, 'real_user'))
|
||||||
|
|
||||||
|
def test_context_processor(self):
|
||||||
|
"""
|
||||||
|
Context variables give the real authenticated user and his permissions.
|
||||||
|
"""
|
||||||
|
self.client.login(username='user1', password='user1')
|
||||||
|
|
||||||
|
r = self.client.get('/k-fet/accounts/', HTTP_KFETPASSWORD='kfet_user2')
|
||||||
|
|
||||||
|
self.assertEqual(r.context['user'], self.user1)
|
||||||
|
self.assertNotIn('kfet.is_team', r.context['perms'])
|
||||||
|
|
||||||
|
def test_auth_not_persistent(self):
|
||||||
|
"""
|
||||||
|
The authentication is temporary, i.e. for one request.
|
||||||
|
"""
|
||||||
|
self.client.login(username='user1', password='user1')
|
||||||
|
|
||||||
|
r1 = self.client.get(
|
||||||
|
'/k-fet/accounts/', HTTP_KFETPASSWORD='kfet_user2')
|
||||||
|
self.assertEqual(r1.wsgi_request.user, self.user2)
|
||||||
|
|
||||||
|
r2 = self.client.get('/k-fet/accounts/')
|
||||||
|
self.assertEqual(r2.wsgi_request.user, self.user1)
|
34
kfet/auth/utils.py
Normal file
34
kfet/auth/utils.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.models import Permission
|
||||||
|
|
||||||
|
from kfet.models import Account
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
def get_kfet_generic_user():
|
||||||
|
"""
|
||||||
|
Return the user related to the kfet generic account.
|
||||||
|
"""
|
||||||
|
return Account.objects.get_generic().user
|
||||||
|
|
||||||
|
|
||||||
|
def setup_kfet_generic_user(**kwargs):
|
||||||
|
"""
|
||||||
|
First steps of setup of the kfet generic user are done in a migration, as
|
||||||
|
it is more robust against database schema changes.
|
||||||
|
Following steps cannot be done from migration.
|
||||||
|
"""
|
||||||
|
generic = get_kfet_generic_user()
|
||||||
|
generic.user_permissions.add(
|
||||||
|
Permission.objects.get(
|
||||||
|
content_type__app_label='kfet',
|
||||||
|
codename='is_team',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def hash_password(password):
|
||||||
|
return hashlib.sha256(password.encode('utf-8')).hexdigest()
|
136
kfet/auth/views.py
Normal file
136
kfet/auth/views.py
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
|
from django.contrib.auth import authenticate, login
|
||||||
|
from django.contrib.auth.decorators import permission_required
|
||||||
|
from django.contrib.auth.models import Group, User
|
||||||
|
from django.contrib.auth.views import redirect_to_login
|
||||||
|
from django.core.urlresolvers import reverse, reverse_lazy
|
||||||
|
from django.db.models import Prefetch
|
||||||
|
from django.http import QueryDict
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.views.generic import View
|
||||||
|
from django.views.decorators.http import require_http_methods
|
||||||
|
from django.views.generic.edit import CreateView, UpdateView
|
||||||
|
|
||||||
|
from .forms import GroupForm
|
||||||
|
from .models import GenericTeamToken
|
||||||
|
|
||||||
|
|
||||||
|
class GenericLoginView(View):
|
||||||
|
"""
|
||||||
|
View to authenticate as kfet generic user.
|
||||||
|
|
||||||
|
It is a 2-step view. First, issue a token if user is a team member and send
|
||||||
|
him to the logout view (for proper disconnect) with callback url to here.
|
||||||
|
Then authenticate the token to log in as the kfet generic user.
|
||||||
|
|
||||||
|
Token is stored in COOKIES to avoid share it with the authentication
|
||||||
|
provider, which can be external. Session is unusable as it will be cleared
|
||||||
|
on logout.
|
||||||
|
"""
|
||||||
|
TOKEN_COOKIE_NAME = 'kfettoken'
|
||||||
|
|
||||||
|
@method_decorator(require_http_methods(['GET', 'POST']))
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
token = request.get_signed_cookie(self.TOKEN_COOKIE_NAME, None)
|
||||||
|
if not token:
|
||||||
|
if not request.user.has_perm('kfet.is_team'):
|
||||||
|
return redirect_to_login(request.get_full_path())
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
# Step 1: set token and logout user.
|
||||||
|
return self.prepare_auth()
|
||||||
|
else:
|
||||||
|
# GET request should not change server/client states. Send a
|
||||||
|
# confirmation template to emit a POST request.
|
||||||
|
return render(request, 'kfet/confirm_form.html', {
|
||||||
|
'title': _("Ouvrir une session partagée"),
|
||||||
|
'text': _(
|
||||||
|
"Êtes-vous sûr·e de vouloir ouvrir une session "
|
||||||
|
"partagée ?"
|
||||||
|
),
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# Step 2: validate token.
|
||||||
|
return self.validate_auth(token)
|
||||||
|
|
||||||
|
def prepare_auth(self):
|
||||||
|
# Issue token.
|
||||||
|
token = GenericTeamToken.objects.create_token()
|
||||||
|
|
||||||
|
# Prepare callback of logout.
|
||||||
|
here_url = reverse(login_generic)
|
||||||
|
if 'next' in self.request.GET:
|
||||||
|
# Keep given next page.
|
||||||
|
here_qd = QueryDict(mutable=True)
|
||||||
|
here_qd['next'] = self.request.GET['next']
|
||||||
|
here_url += '?{}'.format(here_qd.urlencode())
|
||||||
|
|
||||||
|
logout_url = reverse('cof-logout')
|
||||||
|
logout_qd = QueryDict(mutable=True)
|
||||||
|
logout_qd['next'] = here_url
|
||||||
|
logout_url += '?{}'.format(logout_qd.urlencode(safe='/'))
|
||||||
|
|
||||||
|
resp = redirect(logout_url)
|
||||||
|
resp.set_signed_cookie(
|
||||||
|
self.TOKEN_COOKIE_NAME, token.token, httponly=True)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def validate_auth(self, token):
|
||||||
|
# Authenticate with GenericBackend.
|
||||||
|
user = authenticate(request=self.request, kfet_token=token)
|
||||||
|
|
||||||
|
if user:
|
||||||
|
# Log in generic user.
|
||||||
|
login(self.request, user)
|
||||||
|
messages.success(self.request, _(
|
||||||
|
"K-Fêt — Ouverture d'une session partagée."
|
||||||
|
))
|
||||||
|
resp = redirect(self.get_next_url())
|
||||||
|
else:
|
||||||
|
# Try again.
|
||||||
|
resp = redirect(self.request.get_full_path())
|
||||||
|
|
||||||
|
# Prevents blocking due to an invalid COOKIE.
|
||||||
|
resp.delete_cookie(self.TOKEN_COOKIE_NAME)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def get_next_url(self):
|
||||||
|
return self.request.GET.get('next', reverse('kfet.kpsul'))
|
||||||
|
|
||||||
|
|
||||||
|
login_generic = GenericLoginView.as_view()
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('kfet.manage_perms')
|
||||||
|
def account_group(request):
|
||||||
|
user_pre = Prefetch(
|
||||||
|
'user_set',
|
||||||
|
queryset=User.objects.select_related('profile__account_kfet'),
|
||||||
|
)
|
||||||
|
groups = (
|
||||||
|
Group.objects
|
||||||
|
.filter(name__icontains='K-Fêt')
|
||||||
|
.prefetch_related('permissions', user_pre)
|
||||||
|
)
|
||||||
|
return render(request, 'kfet/account_group.html', {
|
||||||
|
'groups': groups,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class AccountGroupCreate(SuccessMessageMixin, CreateView):
|
||||||
|
model = Group
|
||||||
|
template_name = 'kfet/account_group_form.html'
|
||||||
|
form_class = GroupForm
|
||||||
|
success_message = 'Nouveau groupe : %(name)s'
|
||||||
|
success_url = reverse_lazy('kfet.account.group')
|
||||||
|
|
||||||
|
|
||||||
|
class AccountGroupUpdate(SuccessMessageMixin, UpdateView):
|
||||||
|
queryset = Group.objects.filter(name__icontains='K-Fêt')
|
||||||
|
template_name = 'kfet/account_group_form.html'
|
||||||
|
form_class = GroupForm
|
||||||
|
success_message = 'Groupe modifié : %(name)s'
|
||||||
|
success_url = reverse_lazy('kfet.account.group')
|
|
@ -76,7 +76,7 @@ def account_create(request):
|
||||||
queries['users_notcof'].values_list('username', flat=True))
|
queries['users_notcof'].values_list('username', flat=True))
|
||||||
|
|
||||||
# Fetching data from the SPI
|
# Fetching data from the SPI
|
||||||
if hasattr(settings, 'LDAP_SERVER_URL'):
|
if getattr(settings, 'LDAP_SERVER_URL', None):
|
||||||
# Fetching
|
# Fetching
|
||||||
ldap_query = '(&{:s})'.format(''.join(
|
ldap_query = '(&{:s})'.format(''.join(
|
||||||
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=word)
|
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=word)
|
||||||
|
@ -106,6 +106,7 @@ def account_create(request):
|
||||||
return render(request, "kfet/account_create_autocomplete.html", data)
|
return render(request, "kfet/account_create_autocomplete.html", data)
|
||||||
|
|
||||||
|
|
||||||
|
@teamkfet_required
|
||||||
def account_search(request):
|
def account_search(request):
|
||||||
if "q" not in request.GET:
|
if "q" not in request.GET:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User, Permission
|
|
||||||
from gestioncof.models import CofProfile
|
|
||||||
from kfet.models import Account, GenericTeamToken
|
|
||||||
|
|
||||||
|
|
||||||
class KFetBackend(object):
|
|
||||||
def authenticate(self, request):
|
|
||||||
password = request.POST.get('KFETPASSWORD', '')
|
|
||||||
password = request.META.get('HTTP_KFETPASSWORD', password)
|
|
||||||
if not password:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
password_sha256 = (
|
|
||||||
hashlib.sha256(password.encode('utf-8'))
|
|
||||||
.hexdigest()
|
|
||||||
)
|
|
||||||
account = Account.objects.get(password=password_sha256)
|
|
||||||
return account.cofprofile.user
|
|
||||||
except Account.DoesNotExist:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class GenericTeamBackend(object):
|
|
||||||
def authenticate(self, username=None, token=None):
|
|
||||||
valid_token = GenericTeamToken.objects.get(token=token)
|
|
||||||
if username == 'kfet_genericteam' and valid_token:
|
|
||||||
# Création du user s'il n'existe pas déjà
|
|
||||||
user, _ = User.objects.get_or_create(username='kfet_genericteam')
|
|
||||||
profile, _ = CofProfile.objects.get_or_create(user=user)
|
|
||||||
account, _ = Account.objects.get_or_create(
|
|
||||||
cofprofile=profile,
|
|
||||||
trigramme='GNR')
|
|
||||||
|
|
||||||
# Ajoute la permission kfet.is_team à ce user
|
|
||||||
perm_is_team = Permission.objects.get(codename='is_team')
|
|
||||||
user.user_permissions.add(perm_is_team)
|
|
||||||
|
|
||||||
return user
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_user(self, user_id):
|
|
||||||
try:
|
|
||||||
return (
|
|
||||||
User.objects
|
|
||||||
.select_related('profile__account_kfet')
|
|
||||||
.get(pk=user_id)
|
|
||||||
)
|
|
||||||
except User.DoesNotExist:
|
|
||||||
return None
|
|
|
@ -53,7 +53,7 @@
|
||||||
"kfetpage"
|
"kfetpage"
|
||||||
],
|
],
|
||||||
"owner": [
|
"owner": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"expired": false,
|
"expired": false,
|
||||||
"first_published_at": "2017-05-28T04:20:00.000Z",
|
"first_published_at": "2017-05-28T04:20:00.000Z",
|
||||||
|
@ -83,7 +83,7 @@
|
||||||
"kfetpage"
|
"kfetpage"
|
||||||
],
|
],
|
||||||
"owner": [
|
"owner": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"expired": false,
|
"expired": false,
|
||||||
"first_published_at": "2017-05-28T04:20:00.000Z",
|
"first_published_at": "2017-05-28T04:20:00.000Z",
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
"kfetpage"
|
"kfetpage"
|
||||||
],
|
],
|
||||||
"owner": [
|
"owner": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"expired": false,
|
"expired": false,
|
||||||
"first_published_at": "2017-05-28T04:20:00.000Z",
|
"first_published_at": "2017-05-28T04:20:00.000Z",
|
||||||
|
@ -143,7 +143,7 @@
|
||||||
"kfetpage"
|
"kfetpage"
|
||||||
],
|
],
|
||||||
"owner": [
|
"owner": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"expired": false,
|
"expired": false,
|
||||||
"first_published_at": "2017-05-28T04:20:00.000Z",
|
"first_published_at": "2017-05-28T04:20:00.000Z",
|
||||||
|
@ -173,7 +173,7 @@
|
||||||
"kfetpage"
|
"kfetpage"
|
||||||
],
|
],
|
||||||
"owner": [
|
"owner": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"expired": false,
|
"expired": false,
|
||||||
"first_published_at": "2017-05-28T04:20:00.000Z",
|
"first_published_at": "2017-05-28T04:20:00.000Z",
|
||||||
|
@ -203,7 +203,7 @@
|
||||||
"kfetpage"
|
"kfetpage"
|
||||||
],
|
],
|
||||||
"owner": [
|
"owner": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"expired": false,
|
"expired": false,
|
||||||
"first_published_at": "2017-05-28T04:20:00.000Z",
|
"first_published_at": "2017-05-28T04:20:00.000Z",
|
||||||
|
@ -233,7 +233,7 @@
|
||||||
"page"
|
"page"
|
||||||
],
|
],
|
||||||
"owner": [
|
"owner": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"expired": false,
|
"expired": false,
|
||||||
"first_published_at": "2017-05-28T04:20:00.000Z",
|
"first_published_at": "2017-05-28T04:20:00.000Z",
|
||||||
|
@ -263,7 +263,7 @@
|
||||||
"kfetpage"
|
"kfetpage"
|
||||||
],
|
],
|
||||||
"owner": [
|
"owner": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"expired": false,
|
"expired": false,
|
||||||
"first_published_at": "2017-05-28T04:20:00.000Z",
|
"first_published_at": "2017-05-28T04:20:00.000Z",
|
||||||
|
@ -681,7 +681,7 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"created_at": "2017-05-30T04:20:00.000Z",
|
"created_at": "2017-05-30T04:20:00.000Z",
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"title": "K-F\u00eat - Plan d'acc\u00e8s",
|
"title": "K-F\u00eat - Plan d'acc\u00e8s",
|
||||||
|
@ -694,7 +694,7 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"created_at": "2017-05-30T04:20:00.000Z",
|
"created_at": "2017-05-30T04:20:00.000Z",
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"title": "K-F\u00eat - Demande d'autorisation",
|
"title": "K-F\u00eat - Demande d'autorisation",
|
||||||
|
@ -707,7 +707,7 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"created_at": "2017-05-30T04:20:00.000Z",
|
"created_at": "2017-05-30T04:20:00.000Z",
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"title": "K-F\u00eat - Trait\u00e9 de Flipper Th\u00e9orique",
|
"title": "K-F\u00eat - Trait\u00e9 de Flipper Th\u00e9orique",
|
||||||
|
@ -730,7 +730,7 @@
|
||||||
"title": "K-F\u00eat - Amazon Hunt",
|
"title": "K-F\u00eat - Amazon Hunt",
|
||||||
"width": 200,
|
"width": 200,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -750,7 +750,7 @@
|
||||||
"title": "K-F\u00eat - Fun Machine",
|
"title": "K-F\u00eat - Fun Machine",
|
||||||
"width": 200,
|
"width": 200,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -767,7 +767,7 @@
|
||||||
"title": "Hugo Manet",
|
"title": "Hugo Manet",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -787,7 +787,7 @@
|
||||||
"title": "Lisa Gourdon",
|
"title": "Lisa Gourdon",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -807,7 +807,7 @@
|
||||||
"title": "Pierre Quesselaire",
|
"title": "Pierre Quesselaire",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -827,7 +827,7 @@
|
||||||
"title": "Thibault Scoquard",
|
"title": "Thibault Scoquard",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -847,7 +847,7 @@
|
||||||
"title": "Arnaud Fanthomme",
|
"title": "Arnaud Fanthomme",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -867,7 +867,7 @@
|
||||||
"title": "Vincent Balerdi",
|
"title": "Vincent Balerdi",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -887,7 +887,7 @@
|
||||||
"title": "Nathana\u00ebl Willaime",
|
"title": "Nathana\u00ebl Willaime",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -907,7 +907,7 @@
|
||||||
"title": "\u00c9lisabeth Miller",
|
"title": "\u00c9lisabeth Miller",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -927,7 +927,7 @@
|
||||||
"title": "Arthur Lesage",
|
"title": "Arthur Lesage",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -947,7 +947,7 @@
|
||||||
"title": "Sarah Asset",
|
"title": "Sarah Asset",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -967,7 +967,7 @@
|
||||||
"title": "Alexandre Legrand",
|
"title": "Alexandre Legrand",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -987,7 +987,7 @@
|
||||||
"title": "\u00c9tienne Baudel",
|
"title": "\u00c9tienne Baudel",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1007,7 +1007,7 @@
|
||||||
"title": "Marine Snape",
|
"title": "Marine Snape",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1027,7 +1027,7 @@
|
||||||
"title": "Anatole Gosset",
|
"title": "Anatole Gosset",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1047,7 +1047,7 @@
|
||||||
"title": "Jacko Rastikian",
|
"title": "Jacko Rastikian",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1067,7 +1067,7 @@
|
||||||
"title": "Alexandre Jannaud",
|
"title": "Alexandre Jannaud",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1087,7 +1087,7 @@
|
||||||
"title": "Aur\u00e9lien Delobelle",
|
"title": "Aur\u00e9lien Delobelle",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1107,7 +1107,7 @@
|
||||||
"title": "Sylvain Douteau",
|
"title": "Sylvain Douteau",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1127,7 +1127,7 @@
|
||||||
"title": "Rapha\u00ebl Lescanne",
|
"title": "Rapha\u00ebl Lescanne",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1147,7 +1147,7 @@
|
||||||
"title": "Romain Gourvil",
|
"title": "Romain Gourvil",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1167,7 +1167,7 @@
|
||||||
"title": "Marie Labeye",
|
"title": "Marie Labeye",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1187,7 +1187,7 @@
|
||||||
"title": "Oscar Blumberg",
|
"title": "Oscar Blumberg",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1207,7 +1207,7 @@
|
||||||
"title": "Za\u00efd Allybokus",
|
"title": "Za\u00efd Allybokus",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1227,7 +1227,7 @@
|
||||||
"title": "Damien Garreau",
|
"title": "Damien Garreau",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1247,7 +1247,7 @@
|
||||||
"title": "Andr\u00e9a Londono-Lopez",
|
"title": "Andr\u00e9a Londono-Lopez",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1267,7 +1267,7 @@
|
||||||
"title": "Tristan Roussel",
|
"title": "Tristan Roussel",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1287,7 +1287,7 @@
|
||||||
"title": "Guillaume Vernade",
|
"title": "Guillaume Vernade",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1307,7 +1307,7 @@
|
||||||
"title": "Lucas Mercier",
|
"title": "Lucas Mercier",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1327,7 +1327,7 @@
|
||||||
"title": "Fran\u00e7ois Maillot",
|
"title": "Fran\u00e7ois Maillot",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
@ -1347,7 +1347,7 @@
|
||||||
"title": "Fabrice Catoire",
|
"title": "Fabrice Catoire",
|
||||||
"collection": 2,
|
"collection": 2,
|
||||||
"uploaded_by_user": [
|
"uploaded_by_user": [
|
||||||
"root"
|
"kfet_genericteam"
|
||||||
],
|
],
|
||||||
"created_at": "2017-05-30T04:20:00.000Z"
|
"created_at": "2017-05-30T04:20:00.000Z"
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,7 +20,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='KFetPage',
|
name='KFetPage',
|
||||||
fields=[
|
fields=[
|
||||||
('page_ptr', models.OneToOneField(serialize=False, primary_key=True, parent_link=True, auto_created=True, to='wagtailcore.Page')),
|
('page_ptr', models.OneToOneField(serialize=False, primary_key=True, parent_link=True, auto_created=True, to='wagtailcore.Page', on_delete=models.CASCADE)),
|
||||||
('no_header', models.BooleanField(verbose_name='Sans en-tête', help_text="Coché, l'en-tête (avec le titre) de la page n'est pas affiché.", default=False)),
|
('no_header', models.BooleanField(verbose_name='Sans en-tête', help_text="Coché, l'en-tête (avec le titre) de la page n'est pas affiché.", default=False)),
|
||||||
('content', wagtail.wagtailcore.fields.StreamField((('rich', wagtail.wagtailcore.blocks.RichTextBlock(label='Éditeur')), ('carte', kfet.cms.models.MenuBlock()), ('group_team', wagtail.wagtailcore.blocks.StructBlock((('show_only', wagtail.wagtailcore.blocks.IntegerBlock(help_text='Nombre initial de membres affichés. Laisser vide pour tou-te-s les afficher.', required=False, label='Montrer seulement')), ('members', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailsnippets.blocks.SnippetChooserBlock(kfet.cms.models.MemberTeam), classname='team-group', label='K-Fêt-eux-ses'))))), ('group', wagtail.wagtailcore.blocks.StreamBlock((('rich', wagtail.wagtailcore.blocks.RichTextBlock(label='Éditeur')), ('carte', kfet.cms.models.MenuBlock()), ('group_team', wagtail.wagtailcore.blocks.StructBlock((('show_only', wagtail.wagtailcore.blocks.IntegerBlock(help_text='Nombre initial de membres affichés. Laisser vide pour tou-te-s les afficher.', required=False, label='Montrer seulement')), ('members', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailsnippets.blocks.SnippetChooserBlock(kfet.cms.models.MemberTeam), classname='team-group', label='K-Fêt-eux-ses')))))), label='Contenu groupé'))), verbose_name='Contenu')),
|
('content', wagtail.wagtailcore.fields.StreamField((('rich', wagtail.wagtailcore.blocks.RichTextBlock(label='Éditeur')), ('carte', kfet.cms.models.MenuBlock()), ('group_team', wagtail.wagtailcore.blocks.StructBlock((('show_only', wagtail.wagtailcore.blocks.IntegerBlock(help_text='Nombre initial de membres affichés. Laisser vide pour tou-te-s les afficher.', required=False, label='Montrer seulement')), ('members', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailsnippets.blocks.SnippetChooserBlock(kfet.cms.models.MemberTeam), classname='team-group', label='K-Fêt-eux-ses'))))), ('group', wagtail.wagtailcore.blocks.StreamBlock((('rich', wagtail.wagtailcore.blocks.RichTextBlock(label='Éditeur')), ('carte', kfet.cms.models.MenuBlock()), ('group_team', wagtail.wagtailcore.blocks.StructBlock((('show_only', wagtail.wagtailcore.blocks.IntegerBlock(help_text='Nombre initial de membres affichés. Laisser vide pour tou-te-s les afficher.', required=False, label='Montrer seulement')), ('members', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailsnippets.blocks.SnippetChooserBlock(kfet.cms.models.MemberTeam), classname='team-group', label='K-Fêt-eux-ses')))))), label='Contenu groupé'))), verbose_name='Contenu')),
|
||||||
('layout', models.CharField(max_length=255, choices=[('kfet/base_col_1.html', 'Une colonne : centrée sur la page'), ('kfet/base_col_2.html', 'Deux colonnes : fixe à gauche, contenu à droite'), ('kfet/base_col_mult.html', 'Contenu scindé sur plusieurs colonnes')], help_text='Comment cette page devrait être affichée ?', verbose_name='Template', default='kfet/base_col_mult.html')),
|
('layout', models.CharField(max_length=255, choices=[('kfet/base_col_1.html', 'Une colonne : centrée sur la page'), ('kfet/base_col_2.html', 'Deux colonnes : fixe à gauche, contenu à droite'), ('kfet/base_col_mult.html', 'Contenu scindé sur plusieurs colonnes')], help_text='Comment cette page devrait être affichée ?', verbose_name='Template', default='kfet/base_col_mult.html')),
|
||||||
|
|
|
@ -1,18 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from django.contrib.auth.context_processors import PermWrapper
|
|
||||||
|
|
||||||
from kfet.config import kfet_config
|
from kfet.config import kfet_config
|
||||||
|
|
||||||
|
|
||||||
def auth(request):
|
|
||||||
if hasattr(request, 'real_user'):
|
|
||||||
return {
|
|
||||||
'user': request.real_user,
|
|
||||||
'perms': PermWrapper(request.real_user),
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
def config(request):
|
def config(request):
|
||||||
return {'kfet_config': kfet_config}
|
return {'kfet_config': kfet_config}
|
||||||
|
|
|
@ -5,9 +5,8 @@ from decimal import Decimal
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.contrib.auth.models import User, Group, Permission
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.forms import modelformset_factory
|
||||||
from django.forms import modelformset_factory, widgets
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from djconfig.forms import ConfigForm
|
from djconfig.forms import ConfigForm
|
||||||
|
@ -18,6 +17,8 @@ from kfet.models import (
|
||||||
TransferGroup, Supplier)
|
TransferGroup, Supplier)
|
||||||
from gestioncof.models import CofProfile
|
from gestioncof.models import CofProfile
|
||||||
|
|
||||||
|
from .auth.forms import UserGroupForm # noqa
|
||||||
|
|
||||||
|
|
||||||
# -----
|
# -----
|
||||||
# Widgets
|
# Widgets
|
||||||
|
@ -128,61 +129,6 @@ class UserRestrictTeamForm(UserForm):
|
||||||
fields = ['first_name', 'last_name', 'email']
|
fields = ['first_name', 'last_name', 'email']
|
||||||
|
|
||||||
|
|
||||||
class UserGroupForm(forms.ModelForm):
|
|
||||||
groups = forms.ModelMultipleChoiceField(
|
|
||||||
Group.objects.filter(name__icontains='K-Fêt'),
|
|
||||||
label='Statut équipe',
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
def clean_groups(self):
|
|
||||||
kfet_groups = self.cleaned_data.get('groups')
|
|
||||||
other_groups = self.instance.groups.exclude(name__icontains='K-Fêt')
|
|
||||||
return list(kfet_groups) + list(other_groups)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = User
|
|
||||||
fields = ['groups']
|
|
||||||
|
|
||||||
|
|
||||||
class KFetPermissionsField(forms.ModelMultipleChoiceField):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
queryset = Permission.objects.filter(
|
|
||||||
content_type__in=ContentType.objects.filter(app_label="kfet"),
|
|
||||||
)
|
|
||||||
super().__init__(
|
|
||||||
queryset=queryset,
|
|
||||||
widget=widgets.CheckboxSelectMultiple,
|
|
||||||
*args, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
def label_from_instance(self, obj):
|
|
||||||
return obj.name
|
|
||||||
|
|
||||||
|
|
||||||
class GroupForm(forms.ModelForm):
|
|
||||||
permissions = KFetPermissionsField()
|
|
||||||
|
|
||||||
def clean_name(self):
|
|
||||||
name = self.cleaned_data['name']
|
|
||||||
return 'K-Fêt %s' % name
|
|
||||||
|
|
||||||
def clean_permissions(self):
|
|
||||||
kfet_perms = self.cleaned_data['permissions']
|
|
||||||
# TODO: With Django >=1.11, the QuerySet method 'difference' can be used.
|
|
||||||
# other_groups = self.instance.permissions.difference(
|
|
||||||
# self.fields['permissions'].queryset
|
|
||||||
# )
|
|
||||||
other_perms = self.instance.permissions.exclude(
|
|
||||||
pk__in=[p.pk for p in self.fields['permissions'].queryset],
|
|
||||||
)
|
|
||||||
return list(kfet_perms) + list(other_perms)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Group
|
|
||||||
fields = ['name', 'permissions']
|
|
||||||
|
|
||||||
|
|
||||||
class AccountNegativeForm(forms.ModelForm):
|
class AccountNegativeForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AccountNegative
|
model = AccountNegative
|
||||||
|
@ -350,17 +296,17 @@ class KPsulAccountForm(forms.ModelForm):
|
||||||
|
|
||||||
class KPsulCheckoutForm(forms.Form):
|
class KPsulCheckoutForm(forms.Form):
|
||||||
checkout = forms.ModelChoiceField(
|
checkout = forms.ModelChoiceField(
|
||||||
queryset=(
|
queryset=None,
|
||||||
Checkout.objects
|
|
||||||
.filter(
|
|
||||||
is_protected=False,
|
|
||||||
valid_from__lte=timezone.now(),
|
|
||||||
valid_to__gte=timezone.now(),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
widget=forms.Select(attrs={'id': 'id_checkout_select'}),
|
widget=forms.Select(attrs={'id': 'id_checkout_select'}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Create the queryset on form instanciation to use the current time.
|
||||||
|
self.fields['checkout'].queryset = (
|
||||||
|
Checkout.objects.is_valid().filter(is_protected=False))
|
||||||
|
|
||||||
|
|
||||||
class KPsulOperationForm(forms.ModelForm):
|
class KPsulOperationForm(forms.ModelForm):
|
||||||
article = forms.ModelChoiceField(
|
article = forms.ModelChoiceField(
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
|
|
||||||
from kfet.backends import KFetBackend
|
|
||||||
|
|
||||||
|
|
||||||
class KFetAuthenticationMiddleware(object):
|
|
||||||
"""Authenticate another user for this request if KFetBackend succeeds.
|
|
||||||
|
|
||||||
By the way, if a user is authenticated, we refresh its from db to add
|
|
||||||
values from CofProfile and Account of this user.
|
|
||||||
|
|
||||||
"""
|
|
||||||
def process_request(self, request):
|
|
||||||
if request.user.is_authenticated():
|
|
||||||
# avoid multiple db accesses in views and templates
|
|
||||||
user_pk = request.user.pk
|
|
||||||
request.user = (
|
|
||||||
User.objects
|
|
||||||
.select_related('profile__account_kfet')
|
|
||||||
.get(pk=user_pk)
|
|
||||||
)
|
|
||||||
|
|
||||||
kfet_backend = KFetBackend()
|
|
||||||
temp_request_user = kfet_backend.authenticate(request)
|
|
||||||
if temp_request_user:
|
|
||||||
request.real_user = request.user
|
|
||||||
request.user = temp_request_user
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue