Merge branch 'master' into qwann/k-fet/graphs
This commit is contained in:
commit
3b23c28965
206 changed files with 12313 additions and 7830 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -10,6 +10,7 @@ venv/
|
||||||
media/
|
media/
|
||||||
*.log
|
*.log
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
.coverage
|
||||||
|
|
||||||
# PyCharm
|
# PyCharm
|
||||||
.idea
|
.idea
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
image: "python:3.5"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- postgres:latest
|
- postgres:latest
|
||||||
- redis:latest
|
- redis:latest
|
||||||
|
@ -10,7 +12,7 @@ variables:
|
||||||
REDIS_PASSWD: "dummy"
|
REDIS_PASSWD: "dummy"
|
||||||
|
|
||||||
# Cached packages
|
# Cached packages
|
||||||
PYTHONPATH: "$CI_PROJECT_DIR/vendor/python"
|
PIP_CACHE_DIR: "$CI_PROJECT_DIR/vendor/pip"
|
||||||
|
|
||||||
# postgres service configuration
|
# postgres service configuration
|
||||||
POSTGRES_PASSWORD: "4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
POSTGRES_PASSWORD: "4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
||||||
|
@ -20,22 +22,41 @@ variables:
|
||||||
# psql password authentication
|
# psql password authentication
|
||||||
PGPASSWORD: $POSTGRES_PASSWORD
|
PGPASSWORD: $POSTGRES_PASSWORD
|
||||||
|
|
||||||
cache:
|
|
||||||
paths:
|
|
||||||
- vendor/python
|
|
||||||
- vendor/pip
|
|
||||||
- vendor/apt
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- mkdir -p vendor/{python,pip,apt}
|
|
||||||
- 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 -i.bak -E 's;^REDIS_PASSWD = .*$;REDIS_PASSWD = "";' cof/settings/secret.py
|
|
||||||
# Remove the old test database if it has not been done yet
|
|
||||||
- psql --username=$POSTGRES_USER --host=$DBHOST -c "DROP DATABASE IF EXISTS test_$POSTGRES_DB"
|
|
||||||
- pip install --upgrade --cache-dir vendor/pip -t vendor/python -r requirements.txt
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
|
before_script:
|
||||||
|
- mkdir -p vendor/{pip,apt}
|
||||||
|
- 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 -i.bak -E 's;^REDIS_PASSWD = .*$;REDIS_PASSWD = "";' cof/settings/secret.py
|
||||||
|
# Remove the old test database if it has not been done yet
|
||||||
|
- psql --username=$POSTGRES_USER --host=$DBHOST -c "DROP DATABASE IF EXISTS test_$POSTGRES_DB"
|
||||||
|
- pip install --upgrade -r requirements.txt coverage
|
||||||
|
- python --version
|
||||||
script:
|
script:
|
||||||
- python manage.py test
|
- coverage run manage.py test
|
||||||
|
after_script:
|
||||||
|
- coverage report
|
||||||
|
cache:
|
||||||
|
key: test
|
||||||
|
paths:
|
||||||
|
- vendor/
|
||||||
|
# For GitLab CI to get coverage from build.
|
||||||
|
# Keep this disabled for now, as it may kill GitLab...
|
||||||
|
# coverage: '/TOTAL.*\s(\d+\.\d+)\%$/'
|
||||||
|
|
||||||
|
linters:
|
||||||
|
image: python:3.6
|
||||||
|
stage: test
|
||||||
|
before_script:
|
||||||
|
- mkdir -p vendor/pip
|
||||||
|
- pip install --upgrade black isort flake8
|
||||||
|
script:
|
||||||
|
- black --check .
|
||||||
|
- isort --recursive --check-only --diff bda cof gestioncof kfet provisioning shared utils
|
||||||
|
# Print errors only
|
||||||
|
- flake8 --exit-zero bda cof gestioncof kfet provisioning shared utils
|
||||||
|
cache:
|
||||||
|
key: linters
|
||||||
|
paths:
|
||||||
|
- vendor/
|
||||||
|
|
106
.pre-commit.sh
Executable file
106
.pre-commit.sh
Executable file
|
@ -0,0 +1,106 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# pre-commit hook for gestioCOF project.
|
||||||
|
#
|
||||||
|
# Run formatters first, then checkers.
|
||||||
|
# Formatters which changed a file must set the flag 'formatter_updated'.
|
||||||
|
|
||||||
|
exit_code=0
|
||||||
|
formatter_updated=0
|
||||||
|
checker_dirty=0
|
||||||
|
|
||||||
|
# TODO(AD): We should check only staged changes.
|
||||||
|
# Working? -> Stash unstaged changes, run it, pop stash
|
||||||
|
STAGED_PYTHON_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".py$")
|
||||||
|
|
||||||
|
|
||||||
|
# Formatter: black
|
||||||
|
|
||||||
|
printf "> black ... "
|
||||||
|
|
||||||
|
if type black &>/dev/null; then
|
||||||
|
if [ -z "$STAGED_PYTHON_FILES" ]; then
|
||||||
|
printf "OK\n"
|
||||||
|
else
|
||||||
|
BLACK_OUTPUT="/tmp/gc-black-output.log"
|
||||||
|
touch $BLACK_OUTPUT
|
||||||
|
|
||||||
|
if ! echo "$STAGED_PYTHON_FILES" | xargs -d'\n' black --check &>$BLACK_OUTPUT; then
|
||||||
|
echo "$STAGED_PYTHON_FILES" | xargs -d'\n' black &>$BLACK_OUTPUT
|
||||||
|
tail -1 $BLACK_OUTPUT
|
||||||
|
formatter_updated=1
|
||||||
|
else
|
||||||
|
printf "OK\n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "SKIP: program not found\n"
|
||||||
|
printf "HINT: Install black with 'pip3 install black' (black requires Python>=3.6)\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Formatter: isort
|
||||||
|
|
||||||
|
printf "> isort ... "
|
||||||
|
|
||||||
|
if type isort &>/dev/null; then
|
||||||
|
if [ -z "$STAGED_PYTHON_FILES" ]; then
|
||||||
|
printf "OK\n"
|
||||||
|
else
|
||||||
|
ISORT_OUTPUT="/tmp/gc-isort-output.log"
|
||||||
|
touch $ISORT_OUTPUT
|
||||||
|
|
||||||
|
if ! echo "$STAGED_PYTHON_FILES" | xargs -d'\n' isort --check-only &>$ISORT_OUTPUT; then
|
||||||
|
echo "$STAGED_PYTHON_FILES" | xargs -d'\n' isort &>$ISORT_OUTPUT
|
||||||
|
printf "Reformatted.\n"
|
||||||
|
formatter_updated=1
|
||||||
|
else
|
||||||
|
printf "OK\n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "SKIP: program not found\n"
|
||||||
|
printf "HINT: Install isort with 'pip install isort'\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Checker: flake8
|
||||||
|
|
||||||
|
printf "> flake8 ... "
|
||||||
|
|
||||||
|
if type flake8 &>/dev/null; then
|
||||||
|
if [ -z "$STAGED_PYTHON_FILES" ]; then
|
||||||
|
printf "OK\n"
|
||||||
|
else
|
||||||
|
FLAKE8_OUTPUT="/tmp/gc-flake8-output.log"
|
||||||
|
touch $FLAKE8_OUTPUT
|
||||||
|
|
||||||
|
if ! echo "$STAGED_PYTHON_FILES" | xargs -d'\n' flake8 &>$FLAKE8_OUTPUT; then
|
||||||
|
printf "FAIL\n"
|
||||||
|
cat $FLAKE8_OUTPUT
|
||||||
|
checker_dirty=1
|
||||||
|
else
|
||||||
|
printf "OK\n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "SKIP: program not found\n"
|
||||||
|
printf "HINT: Install flake8 with 'pip install flake8'\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# End
|
||||||
|
|
||||||
|
if [ $checker_dirty -ne 0 ]
|
||||||
|
then
|
||||||
|
printf ">>> Checker(s) detect(s) issue(s)\n"
|
||||||
|
printf " You can still commit and push :)\n"
|
||||||
|
printf " Be warned that our CI may cause you more trouble.\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $formatter_updated -ne 0 ]
|
||||||
|
then
|
||||||
|
printf ">>> Working tree updated by formatter(s)\n"
|
||||||
|
printf " Add changes to staging area and retry.\n"
|
||||||
|
exit_code=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "\n"
|
||||||
|
|
||||||
|
exit $exit_code
|
13
README.md
13
README.md
|
@ -1,6 +1,7 @@
|
||||||
# GestioCOF
|
# GestioCOF
|
||||||
|
|
||||||
![build_status](https://git.eleves.ens.fr/cof-geek/gestioCOF/badges/master/build.svg)
|
[![pipeline status](https://git.eleves.ens.fr/cof-geek/gestioCOF/badges/master/pipeline.svg)](https://git.eleves.ens.fr/cof-geek/gestioCOF/commits/master)
|
||||||
|
[![coverage report](https://git.eleves.ens.fr/cof-geek/gestioCOF/badges/master/coverage.svg)](https://git.eleves.ens.fr/cof-geek/gestioCOF/commits/master)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -43,6 +44,16 @@ pour profiter de façon transparente des mises à jour du fichier:
|
||||||
|
|
||||||
ln -s secret_example.py cof/settings/secret.py
|
ln -s secret_example.py cof/settings/secret.py
|
||||||
|
|
||||||
|
Nous avons un git hook de pre-commit pour formatter et vérifier que votre code
|
||||||
|
vérifie nos conventions. Pour bénéficier des mises à jour du hook, préférez
|
||||||
|
encore l'installation *via* un lien symbolique:
|
||||||
|
|
||||||
|
ln -s ../../.pre-commit.sh .git/hooks/pre-commit
|
||||||
|
|
||||||
|
Pour plus d'informations à ce sujet, consulter la
|
||||||
|
[page](https://git.eleves.ens.fr/cof-geek/gestioCOF/wikis/coding-style)
|
||||||
|
du wiki gestioCOF liée aux conventions.
|
||||||
|
|
||||||
|
|
||||||
#### Fin d'installation
|
#### Fin d'installation
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
179
bda/admin.py
179
bda/admin.py
|
@ -1,16 +1,24 @@
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from custommail.shortcuts import send_mass_custom_mail
|
|
||||||
|
|
||||||
|
from custommail.shortcuts import send_mass_custom_mail
|
||||||
|
from dal.autocomplete import ModelSelect2
|
||||||
|
from django import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db.models import Sum, Count
|
from django.db.models import Count, Sum
|
||||||
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 dal.autocomplete import ModelSelect2
|
from bda.models import (
|
||||||
|
Attribution,
|
||||||
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
CategorieSpectacle,
|
||||||
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
ChoixSpectacle,
|
||||||
|
Participant,
|
||||||
|
Quote,
|
||||||
|
Salle,
|
||||||
|
Spectacle,
|
||||||
|
SpectacleRevente,
|
||||||
|
Tirage,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReadOnlyMixin(object):
|
class ReadOnlyMixin(object):
|
||||||
|
@ -27,8 +35,8 @@ class ReadOnlyMixin(object):
|
||||||
class ChoixSpectacleAdminForm(forms.ModelForm):
|
class ChoixSpectacleAdminForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
widgets = {
|
widgets = {
|
||||||
'participant': ModelSelect2(url='bda-participant-autocomplete'),
|
"participant": ModelSelect2(url="bda-participant-autocomplete"),
|
||||||
'spectacle': ModelSelect2(url='bda-spectacle-autocomplete'),
|
"spectacle": ModelSelect2(url="bda-spectacle-autocomplete"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,10 +51,10 @@ class AttributionTabularAdminForm(forms.ModelForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
spectacles = Spectacle.objects.select_related('location')
|
spectacles = Spectacle.objects.select_related("location")
|
||||||
if self.listing is not None:
|
if self.listing is not None:
|
||||||
spectacles = spectacles.filter(listing=self.listing)
|
spectacles = spectacles.filter(listing=self.listing)
|
||||||
self.fields['spectacle'].queryset = spectacles
|
self.fields["spectacle"].queryset = spectacles
|
||||||
|
|
||||||
|
|
||||||
class WithoutListingAttributionTabularAdminForm(AttributionTabularAdminForm):
|
class WithoutListingAttributionTabularAdminForm(AttributionTabularAdminForm):
|
||||||
|
@ -70,7 +78,7 @@ class AttributionInline(admin.TabularInline):
|
||||||
|
|
||||||
|
|
||||||
class WithListingAttributionInline(AttributionInline):
|
class WithListingAttributionInline(AttributionInline):
|
||||||
exclude = ('given', )
|
exclude = ("given",)
|
||||||
form = WithListingAttributionTabularAdminForm
|
form = WithListingAttributionTabularAdminForm
|
||||||
listing = True
|
listing = True
|
||||||
|
|
||||||
|
@ -81,12 +89,10 @@ class WithoutListingAttributionInline(AttributionInline):
|
||||||
|
|
||||||
|
|
||||||
class ParticipantAdminForm(forms.ModelForm):
|
class ParticipantAdminForm(forms.ModelForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['choicesrevente'].queryset = (
|
self.fields["choicesrevente"].queryset = Spectacle.objects.select_related(
|
||||||
Spectacle.objects
|
"location"
|
||||||
.select_related('location')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,11 +100,13 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
||||||
inlines = [WithListingAttributionInline, WithoutListingAttributionInline]
|
inlines = [WithListingAttributionInline, WithoutListingAttributionInline]
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
return Participant.objects.annotate(nb_places=Count('attributions'),
|
return Participant.objects.annotate(
|
||||||
total=Sum('attributions__price'))
|
nb_places=Count("attributions"), total=Sum("attributions__price")
|
||||||
|
)
|
||||||
|
|
||||||
def nb_places(self, obj):
|
def nb_places(self, obj):
|
||||||
return obj.nb_places
|
return obj.nb_places
|
||||||
|
|
||||||
nb_places.admin_order_field = "nb_places"
|
nb_places.admin_order_field = "nb_places"
|
||||||
nb_places.short_description = "Nombre de places"
|
nb_places.short_description = "Nombre de places"
|
||||||
|
|
||||||
|
@ -108,33 +116,32 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
||||||
return "%.02f €" % tot
|
return "%.02f €" % tot
|
||||||
else:
|
else:
|
||||||
return "0 €"
|
return "0 €"
|
||||||
|
|
||||||
total.admin_order_field = "total"
|
total.admin_order_field = "total"
|
||||||
total.short_description = "Total à payer"
|
total.short_description = "Total à payer"
|
||||||
list_display = ("user", "nb_places", "total", "paid", "paymenttype",
|
list_display = ("user", "nb_places", "total", "paid", "paymenttype", "tirage")
|
||||||
"tirage")
|
|
||||||
list_filter = ("paid", "tirage")
|
list_filter = ("paid", "tirage")
|
||||||
search_fields = ('user__username', 'user__first_name', 'user__last_name')
|
search_fields = ("user__username", "user__first_name", "user__last_name")
|
||||||
actions = ['send_attribs', ]
|
actions = ["send_attribs"]
|
||||||
actions_on_bottom = True
|
actions_on_bottom = True
|
||||||
list_per_page = 400
|
list_per_page = 400
|
||||||
readonly_fields = ("total",)
|
readonly_fields = ("total",)
|
||||||
readonly_fields_update = ('user', 'tirage')
|
readonly_fields_update = ("user", "tirage")
|
||||||
form = ParticipantAdminForm
|
form = ParticipantAdminForm
|
||||||
|
|
||||||
def send_attribs(self, request, queryset):
|
def send_attribs(self, request, queryset):
|
||||||
datatuple = []
|
datatuple = []
|
||||||
for member in queryset.all():
|
for member in queryset.all():
|
||||||
attribs = member.attributions.all()
|
attribs = member.attributions.all()
|
||||||
context = {'member': member.user}
|
context = {"member": member.user}
|
||||||
shortname = ""
|
shortname = ""
|
||||||
if len(attribs) == 0:
|
if len(attribs) == 0:
|
||||||
shortname = "bda-attributions-decus"
|
shortname = "bda-attributions-decus"
|
||||||
else:
|
else:
|
||||||
shortname = "bda-attributions"
|
shortname = "bda-attributions"
|
||||||
context['places'] = attribs
|
context["places"] = attribs
|
||||||
print(context)
|
print(context)
|
||||||
datatuple.append((shortname, context, "bda@ens.fr",
|
datatuple.append((shortname, context, "bda@ens.fr", [member.user.email]))
|
||||||
[member.user.email]))
|
|
||||||
send_mass_custom_mail(datatuple)
|
send_mass_custom_mail(datatuple)
|
||||||
count = len(queryset.all())
|
count = len(queryset.all())
|
||||||
if count == 1:
|
if count == 1:
|
||||||
|
@ -143,24 +150,23 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
||||||
else:
|
else:
|
||||||
message_bit = "%d membres ont" % count
|
message_bit = "%d membres ont" % count
|
||||||
plural = "s"
|
plural = "s"
|
||||||
self.message_user(request, "%s été informé%s avec succès."
|
self.message_user(
|
||||||
% (message_bit, plural))
|
request, "%s été informé%s avec succès." % (message_bit, plural)
|
||||||
|
)
|
||||||
|
|
||||||
send_attribs.short_description = "Envoyer les résultats par mail"
|
send_attribs.short_description = "Envoyer les résultats par mail"
|
||||||
|
|
||||||
|
|
||||||
class AttributionAdminForm(forms.ModelForm):
|
class AttributionAdminForm(forms.ModelForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if 'spectacle' in self.fields:
|
if "spectacle" in self.fields:
|
||||||
self.fields['spectacle'].queryset = (
|
self.fields["spectacle"].queryset = Spectacle.objects.select_related(
|
||||||
Spectacle.objects
|
"location"
|
||||||
.select_related('location')
|
|
||||||
)
|
)
|
||||||
if 'participant' in self.fields:
|
if "participant" in self.fields:
|
||||||
self.fields['participant'].queryset = (
|
self.fields["participant"].queryset = Participant.objects.select_related(
|
||||||
Participant.objects
|
"user", "tirage"
|
||||||
.select_related('user', 'tirage')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
@ -171,21 +177,26 @@ class AttributionAdminForm(forms.ModelForm):
|
||||||
if participant.tirage != spectacle.tirage:
|
if participant.tirage != spectacle.tirage:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
"Erreur : le participant et le spectacle n'appartiennent"
|
"Erreur : le participant et le spectacle n'appartiennent"
|
||||||
"pas au même tirage")
|
"pas au même tirage"
|
||||||
|
)
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
||||||
def paid(self, obj):
|
def paid(self, obj):
|
||||||
return obj.participant.paid
|
return obj.participant.paid
|
||||||
paid.short_description = 'A payé'
|
|
||||||
|
paid.short_description = "A payé"
|
||||||
paid.boolean = True
|
paid.boolean = True
|
||||||
list_display = ("id", "spectacle", "participant", "given", "paid")
|
list_display = ("id", "spectacle", "participant", "given", "paid")
|
||||||
search_fields = ('spectacle__title', 'participant__user__username',
|
search_fields = (
|
||||||
'participant__user__first_name',
|
"spectacle__title",
|
||||||
'participant__user__last_name')
|
"participant__user__username",
|
||||||
|
"participant__user__first_name",
|
||||||
|
"participant__user__last_name",
|
||||||
|
)
|
||||||
form = AttributionAdminForm
|
form = AttributionAdminForm
|
||||||
readonly_fields_update = ('spectacle', 'participant')
|
readonly_fields_update = ("spectacle", "participant")
|
||||||
|
|
||||||
|
|
||||||
class ChoixSpectacleAdmin(admin.ModelAdmin):
|
class ChoixSpectacleAdmin(admin.ModelAdmin):
|
||||||
|
@ -193,13 +204,15 @@ class ChoixSpectacleAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
def tirage(self, obj):
|
def tirage(self, obj):
|
||||||
return obj.participant.tirage
|
return obj.participant.tirage
|
||||||
list_display = ("participant", "tirage", "spectacle", "priority",
|
|
||||||
"double_choice")
|
list_display = ("participant", "tirage", "spectacle", "priority", "double_choice")
|
||||||
list_filter = ("double_choice", "participant__tirage")
|
list_filter = ("double_choice", "participant__tirage")
|
||||||
search_fields = ('participant__user__username',
|
search_fields = (
|
||||||
'participant__user__first_name',
|
"participant__user__username",
|
||||||
'participant__user__last_name',
|
"participant__user__first_name",
|
||||||
'spectacle__title')
|
"participant__user__last_name",
|
||||||
|
"spectacle__title",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class QuoteInline(admin.TabularInline):
|
class QuoteInline(admin.TabularInline):
|
||||||
|
@ -209,42 +222,36 @@ class QuoteInline(admin.TabularInline):
|
||||||
class SpectacleAdmin(admin.ModelAdmin):
|
class SpectacleAdmin(admin.ModelAdmin):
|
||||||
inlines = [QuoteInline]
|
inlines = [QuoteInline]
|
||||||
model = Spectacle
|
model = Spectacle
|
||||||
list_display = ("title", "date", "tirage", "location", "slots", "price",
|
list_display = ("title", "date", "tirage", "location", "slots", "price", "listing")
|
||||||
"listing")
|
list_filter = ("location", "tirage")
|
||||||
list_filter = ("location", "tirage",)
|
|
||||||
search_fields = ("title", "location__name")
|
search_fields = ("title", "location__name")
|
||||||
readonly_fields = ("rappel_sent", )
|
readonly_fields = ("rappel_sent",)
|
||||||
|
|
||||||
|
|
||||||
class TirageAdmin(admin.ModelAdmin):
|
class TirageAdmin(admin.ModelAdmin):
|
||||||
model = Tirage
|
model = Tirage
|
||||||
list_display = ("title", "ouverture", "fermeture", "active",
|
list_display = ("title", "ouverture", "fermeture", "active", "enable_do_tirage")
|
||||||
"enable_do_tirage")
|
readonly_fields = ("tokens",)
|
||||||
readonly_fields = ("tokens", )
|
list_filter = ("active",)
|
||||||
list_filter = ("active", )
|
search_fields = ("title",)
|
||||||
search_fields = ("title", )
|
|
||||||
|
|
||||||
|
|
||||||
class SalleAdmin(admin.ModelAdmin):
|
class SalleAdmin(admin.ModelAdmin):
|
||||||
model = Salle
|
model = Salle
|
||||||
search_fields = ('name', 'address')
|
search_fields = ("name", "address")
|
||||||
|
|
||||||
|
|
||||||
class SpectacleReventeAdminForm(forms.ModelForm):
|
class SpectacleReventeAdminForm(forms.ModelForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['confirmed_entry'].queryset = (
|
self.fields["confirmed_entry"].queryset = Participant.objects.select_related(
|
||||||
Participant.objects
|
"user", "tirage"
|
||||||
.select_related('user', 'tirage')
|
|
||||||
)
|
)
|
||||||
self.fields['seller'].queryset = (
|
self.fields["seller"].queryset = Participant.objects.select_related(
|
||||||
Participant.objects
|
"user", "tirage"
|
||||||
.select_related('user', 'tirage')
|
|
||||||
)
|
)
|
||||||
self.fields['soldTo'].queryset = (
|
self.fields["soldTo"].queryset = Participant.objects.select_related(
|
||||||
Participant.objects
|
"user", "tirage"
|
||||||
.select_related('user', 'tirage')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -252,6 +259,7 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
Administration des reventes de spectacles
|
Administration des reventes de spectacles
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model = SpectacleRevente
|
model = SpectacleRevente
|
||||||
|
|
||||||
def spectacle(self, obj):
|
def spectacle(self, obj):
|
||||||
|
@ -263,12 +271,14 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||||
list_display = ("spectacle", "seller", "date", "soldTo")
|
list_display = ("spectacle", "seller", "date", "soldTo")
|
||||||
raw_id_fields = ("attribution",)
|
raw_id_fields = ("attribution",)
|
||||||
readonly_fields = ("date_tirage",)
|
readonly_fields = ("date_tirage",)
|
||||||
search_fields = ['attribution__spectacle__title',
|
search_fields = [
|
||||||
'seller__user__username',
|
"attribution__spectacle__title",
|
||||||
'seller__user__first_name',
|
"seller__user__username",
|
||||||
'seller__user__last_name']
|
"seller__user__first_name",
|
||||||
|
"seller__user__last_name",
|
||||||
|
]
|
||||||
|
|
||||||
actions = ['transfer', 'reinit']
|
actions = ["transfer", "reinit"]
|
||||||
actions_on_bottom = True
|
actions_on_bottom = True
|
||||||
form = SpectacleReventeAdminForm
|
form = SpectacleReventeAdminForm
|
||||||
|
|
||||||
|
@ -284,10 +294,10 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||||
attrib.save()
|
attrib.save()
|
||||||
self.message_user(
|
self.message_user(
|
||||||
request,
|
request,
|
||||||
"%d attribution%s %s été transférée%s avec succès." % (
|
"%d attribution%s %s été transférée%s avec succès."
|
||||||
count, pluralize(count),
|
% (count, pluralize(count), pluralize(count, "a,ont"), pluralize(count)),
|
||||||
pluralize(count, "a,ont"), pluralize(count))
|
)
|
||||||
)
|
|
||||||
transfer.short_description = "Transférer les reventes sélectionnées"
|
transfer.short_description = "Transférer les reventes sélectionnées"
|
||||||
|
|
||||||
def reinit(self, request, queryset):
|
def reinit(self, request, queryset):
|
||||||
|
@ -296,14 +306,15 @@ 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.reset(new_date=timezone.now() - timedelta(hours=1))
|
revente.reset(new_date=timezone.now() - timedelta(hours=1))
|
||||||
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."
|
||||||
count, pluralize(count),
|
% (count, pluralize(count), pluralize(count, "a,ont"), pluralize(count)),
|
||||||
pluralize(count, "a,ont"), pluralize(count))
|
)
|
||||||
)
|
|
||||||
reinit.short_description = "Réinitialiser les reventes sélectionnées"
|
reinit.short_description = "Réinitialiser les reventes sélectionnées"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from django.db.models import Max
|
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,7 +14,7 @@ class Algorithm(object):
|
||||||
show.requests
|
show.requests
|
||||||
- on crée des tables de demandes pour chaque personne, afin de
|
- on crée des tables de demandes pour chaque personne, afin de
|
||||||
pouvoir modifier les rankings"""
|
pouvoir modifier les rankings"""
|
||||||
self.max_group = 2*max(choice.priority for choice in choices)
|
self.max_group = 2 * max(choice.priority for choice in choices)
|
||||||
self.shows = []
|
self.shows = []
|
||||||
showdict = {}
|
showdict = {}
|
||||||
for show in shows:
|
for show in shows:
|
||||||
|
@ -54,16 +52,19 @@ class Algorithm(object):
|
||||||
self.ranks[member][show] -= increment
|
self.ranks[member][show] -= increment
|
||||||
|
|
||||||
def appendResult(self, l, member, show):
|
def appendResult(self, l, member, show):
|
||||||
l.append((member,
|
l.append(
|
||||||
self.ranks[member][show],
|
(
|
||||||
self.origranks[member][show],
|
member,
|
||||||
self.choices[member][show].double))
|
self.ranks[member][show],
|
||||||
|
self.origranks[member][show],
|
||||||
|
self.choices[member][show].double,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def __call__(self, seed):
|
def __call__(self, seed):
|
||||||
random.seed(seed)
|
random.seed(seed)
|
||||||
results = []
|
results = []
|
||||||
shows = sorted(self.shows, key=lambda x: x.nrequests / x.slots,
|
shows = sorted(self.shows, key=lambda x: x.nrequests / x.slots, reverse=True)
|
||||||
reverse=True)
|
|
||||||
for show in shows:
|
for show in shows:
|
||||||
# On regroupe tous les gens ayant le même rang
|
# On regroupe tous les gens ayant le même rang
|
||||||
groups = dict([(i, []) for i in range(1, self.max_group + 1)])
|
groups = dict([(i, []) for i in range(1, self.max_group + 1)])
|
||||||
|
@ -82,8 +83,10 @@ class Algorithm(object):
|
||||||
if len(winners) + 1 < show.slots:
|
if len(winners) + 1 < show.slots:
|
||||||
self.appendResult(winners, member, show)
|
self.appendResult(winners, member, show)
|
||||||
self.appendResult(winners, member, show)
|
self.appendResult(winners, member, show)
|
||||||
elif not self.choices[member][show].autoquit \
|
elif (
|
||||||
and len(winners) < show.slots:
|
not self.choices[member][show].autoquit
|
||||||
|
and len(winners) < show.slots
|
||||||
|
):
|
||||||
self.appendResult(winners, member, show)
|
self.appendResult(winners, member, show)
|
||||||
self.appendResult(losers, member, show)
|
self.appendResult(losers, member, show)
|
||||||
else:
|
else:
|
||||||
|
|
147
bda/forms.py
147
bda/forms.py
|
@ -6,7 +6,6 @@ from bda.models import Attribution, Spectacle, SpectacleRevente
|
||||||
|
|
||||||
|
|
||||||
class InscriptionInlineFormSet(BaseInlineFormSet):
|
class InscriptionInlineFormSet(BaseInlineFormSet):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -16,9 +15,9 @@ class InscriptionInlineFormSet(BaseInlineFormSet):
|
||||||
# set once for all "spectacle" field choices
|
# set once for all "spectacle" field choices
|
||||||
# - restrict choices to the spectacles of this tirage
|
# - restrict choices to the spectacles of this tirage
|
||||||
# - force_choices avoid many db requests
|
# - force_choices avoid many db requests
|
||||||
spectacles = tirage.spectacle_set.select_related('location')
|
spectacles = tirage.spectacle_set.select_related("location")
|
||||||
choices = [(sp.pk, str(sp)) for sp in spectacles]
|
choices = [(sp.pk, str(sp)) for sp in spectacles]
|
||||||
self.force_choices('spectacle', choices)
|
self.force_choices("spectacle", choices)
|
||||||
|
|
||||||
def force_choices(self, name, choices):
|
def force_choices(self, name, choices):
|
||||||
"""Set choices of a field.
|
"""Set choices of a field.
|
||||||
|
@ -30,7 +29,7 @@ class InscriptionInlineFormSet(BaseInlineFormSet):
|
||||||
for form in self.forms:
|
for form in self.forms:
|
||||||
field = form.fields[name]
|
field = form.fields[name]
|
||||||
if field.empty_label is not None:
|
if field.empty_label is not None:
|
||||||
field.choices = [('', field.empty_label)] + choices
|
field.choices = [("", field.empty_label)] + choices
|
||||||
else:
|
else:
|
||||||
field.choices = choices
|
field.choices = choices
|
||||||
|
|
||||||
|
@ -53,128 +52,128 @@ class ReventeModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
||||||
label = "{show}{suffix}"
|
label = "{show}{suffix}"
|
||||||
suffix = ""
|
suffix = ""
|
||||||
if self.own:
|
if self.own:
|
||||||
# C'est notre propre revente : pas besoin de spécifier le vendeur
|
# C'est notre propre revente : informations sur le statut
|
||||||
if obj.soldTo is not None:
|
if obj.soldTo is not None:
|
||||||
suffix = " -- Vendue à {firstname} {lastname}".format(
|
suffix = " -- Vendue à {firstname} {lastname}".format(
|
||||||
firstname=obj.soldTo.user.first_name,
|
firstname=obj.soldTo.user.first_name,
|
||||||
lastname=obj.soldTo.user.last_name,
|
lastname=obj.soldTo.user.last_name,
|
||||||
)
|
)
|
||||||
|
elif obj.shotgun:
|
||||||
|
suffix = " -- Tirage infructueux"
|
||||||
|
elif obj.notif_sent:
|
||||||
|
suffix = " -- Inscriptions au tirage en cours"
|
||||||
else:
|
else:
|
||||||
# Ce n'est pas à nous : on ne voit jamais l'acheteur
|
# Ce n'est pas à nous : on ne voit jamais l'acheteur
|
||||||
suffix = " -- Vendue par {firstname} {lastname}".format(
|
suffix = " -- Vendue par {firstname} {lastname}".format(
|
||||||
firstname=obj.seller.user.first_name,
|
firstname=obj.seller.user.first_name, lastname=obj.seller.user.last_name
|
||||||
lastname=obj.seller.user.last_name,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
return label.format(show=str(obj.attribution.spectacle),
|
return label.format(show=str(obj.attribution.spectacle), suffix=suffix)
|
||||||
suffix=suffix)
|
|
||||||
|
|
||||||
|
|
||||||
class ResellForm(forms.Form):
|
class ResellForm(forms.Form):
|
||||||
attributions = AttributionModelMultipleChoiceField(
|
attributions = AttributionModelMultipleChoiceField(
|
||||||
label='',
|
label="",
|
||||||
queryset=Attribution.objects.none(),
|
queryset=Attribution.objects.none(),
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple,
|
||||||
required=False)
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
def __init__(self, participant, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['attributions'].queryset = (
|
self.fields["attributions"].queryset = (
|
||||||
participant.attribution_set
|
participant.attribution_set.filter(spectacle__date__gte=timezone.now())
|
||||||
.filter(spectacle__date__gte=timezone.now())
|
|
||||||
.exclude(revente__seller=participant)
|
.exclude(revente__seller=participant)
|
||||||
.select_related('spectacle', 'spectacle__location',
|
.select_related("spectacle", "spectacle__location", "participant__user")
|
||||||
'participant__user')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AnnulForm(forms.Form):
|
class AnnulForm(forms.Form):
|
||||||
reventes = ReventeModelMultipleChoiceField(
|
reventes = ReventeModelMultipleChoiceField(
|
||||||
own=True,
|
own=True,
|
||||||
label='',
|
label="",
|
||||||
queryset=Attribution.objects.none(),
|
queryset=Attribution.objects.none(),
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple,
|
||||||
required=False)
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
def __init__(self, participant, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['reventes'].queryset = (
|
self.fields["reventes"].queryset = (
|
||||||
participant.original_shows
|
participant.original_shows.filter(
|
||||||
.filter(attribution__spectacle__date__gte=timezone.now(),
|
attribution__spectacle__date__gte=timezone.now(), soldTo__isnull=True
|
||||||
notif_sent=False,
|
)
|
||||||
soldTo__isnull=True)
|
.select_related(
|
||||||
.select_related('attribution__spectacle',
|
"attribution__spectacle", "attribution__spectacle__location"
|
||||||
'attribution__spectacle__location')
|
)
|
||||||
|
.order_by("-date")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class InscriptionReventeForm(forms.Form):
|
class InscriptionReventeForm(forms.Form):
|
||||||
spectacles = forms.ModelMultipleChoiceField(
|
spectacles = forms.ModelMultipleChoiceField(
|
||||||
queryset=Spectacle.objects.none(),
|
queryset=Spectacle.objects.none(),
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple,
|
||||||
required=False)
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, tirage, *args, **kwargs):
|
def __init__(self, tirage, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['spectacles'].queryset = (
|
self.fields["spectacles"].queryset = tirage.spectacle_set.select_related(
|
||||||
tirage.spectacle_set
|
"location"
|
||||||
.select_related('location')
|
).filter(date__gte=timezone.now())
|
||||||
.filter(date__gte=timezone.now())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ReventeTirageAnnulForm(forms.Form):
|
class ReventeTirageAnnulForm(forms.Form):
|
||||||
reventes = ReventeModelMultipleChoiceField(
|
reventes = ReventeModelMultipleChoiceField(
|
||||||
own=False,
|
own=False,
|
||||||
label='',
|
label="",
|
||||||
queryset=SpectacleRevente.objects.none(),
|
queryset=SpectacleRevente.objects.none(),
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple,
|
||||||
required=False
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
def __init__(self, participant, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['reventes'].queryset = (
|
self.fields["reventes"].queryset = participant.entered.filter(
|
||||||
participant.entered.filter(soldTo__isnull=True)
|
soldTo__isnull=True
|
||||||
.select_related('attribution__spectacle',
|
).select_related("attribution__spectacle", "seller__user")
|
||||||
'seller__user')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ReventeTirageForm(forms.Form):
|
class ReventeTirageForm(forms.Form):
|
||||||
reventes = ReventeModelMultipleChoiceField(
|
reventes = ReventeModelMultipleChoiceField(
|
||||||
own=False,
|
own=False,
|
||||||
label='',
|
label="",
|
||||||
queryset=SpectacleRevente.objects.none(),
|
queryset=SpectacleRevente.objects.none(),
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple,
|
||||||
required=False
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
def __init__(self, participant, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['reventes'].queryset = (
|
self.fields["reventes"].queryset = (
|
||||||
SpectacleRevente.objects.filter(
|
SpectacleRevente.objects.filter(
|
||||||
notif_sent=True,
|
notif_sent=True, shotgun=False, tirage_done=False
|
||||||
shotgun=False,
|
)
|
||||||
tirage_done=False
|
.exclude(confirmed_entry=participant)
|
||||||
).exclude(confirmed_entry=participant)
|
.select_related("attribution__spectacle")
|
||||||
.select_related('attribution__spectacle')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SoldForm(forms.Form):
|
class SoldForm(forms.Form):
|
||||||
reventes = ReventeModelMultipleChoiceField(
|
reventes = ReventeModelMultipleChoiceField(
|
||||||
own=True,
|
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().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['reventes'].queryset = (
|
self.fields["reventes"].queryset = (
|
||||||
participant.original_shows
|
participant.original_shows.filter(soldTo__isnull=False)
|
||||||
.filter(soldTo__isnull=False)
|
|
||||||
.exclude(soldTo=participant)
|
.exclude(soldTo=participant)
|
||||||
.select_related('attribution__spectacle',
|
.select_related(
|
||||||
'attribution__spectacle__location')
|
"attribution__spectacle", "attribution__spectacle__location"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,17 +5,15 @@ Crée deux tirages de test et y inscrit les utilisateurs
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from gestioncof.management.base import MyBaseCommand
|
from bda.models import ChoixSpectacle, Participant, Salle, Spectacle, Tirage
|
||||||
from bda.models import Tirage, Spectacle, Salle, Participant, ChoixSpectacle
|
|
||||||
from bda.views import do_tirage
|
from bda.views import do_tirage
|
||||||
|
from gestioncof.management.base import MyBaseCommand
|
||||||
|
|
||||||
# Où sont stockés les fichiers json
|
# Où sont stockés les fichiers json
|
||||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data")
|
||||||
'data')
|
|
||||||
|
|
||||||
|
|
||||||
class Command(MyBaseCommand):
|
class Command(MyBaseCommand):
|
||||||
|
@ -27,27 +25,29 @@ class Command(MyBaseCommand):
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
Tirage.objects.all().delete()
|
Tirage.objects.all().delete()
|
||||||
Tirage.objects.bulk_create([
|
Tirage.objects.bulk_create(
|
||||||
Tirage(
|
[
|
||||||
title="Tirage de test 1",
|
Tirage(
|
||||||
ouverture=timezone.now()-timezone.timedelta(days=7),
|
title="Tirage de test 1",
|
||||||
fermeture=timezone.now(),
|
ouverture=timezone.now() - timezone.timedelta(days=7),
|
||||||
active=True
|
fermeture=timezone.now(),
|
||||||
),
|
active=True,
|
||||||
Tirage(
|
),
|
||||||
title="Tirage de test 2",
|
Tirage(
|
||||||
ouverture=timezone.now(),
|
title="Tirage de test 2",
|
||||||
fermeture=timezone.now()+timezone.timedelta(days=60),
|
ouverture=timezone.now(),
|
||||||
active=True
|
fermeture=timezone.now() + timezone.timedelta(days=60),
|
||||||
)
|
active=True,
|
||||||
])
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
tirages = Tirage.objects.all()
|
tirages = Tirage.objects.all()
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Salles
|
# Salles
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
locations = self.from_json('locations.json', DATA_DIR, Salle)
|
locations = self.from_json("locations.json", DATA_DIR, Salle)
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Spectacles
|
# Spectacles
|
||||||
|
@ -60,15 +60,13 @@ class Command(MyBaseCommand):
|
||||||
"""
|
"""
|
||||||
show.tirage = random.choice(tirages)
|
show.tirage = random.choice(tirages)
|
||||||
show.listing = bool(random.randint(0, 1))
|
show.listing = bool(random.randint(0, 1))
|
||||||
show.date = (
|
show.date = show.tirage.fermeture + timezone.timedelta(
|
||||||
show.tirage.fermeture
|
days=random.randint(60, 90)
|
||||||
+ timezone.timedelta(days=random.randint(60, 90))
|
|
||||||
)
|
)
|
||||||
show.location = random.choice(locations)
|
show.location = random.choice(locations)
|
||||||
return show
|
return show
|
||||||
shows = self.from_json(
|
|
||||||
'shows.json', DATA_DIR, Spectacle, show_callback
|
shows = self.from_json("shows.json", DATA_DIR, Spectacle, show_callback)
|
||||||
)
|
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Inscriptions
|
# Inscriptions
|
||||||
|
@ -79,23 +77,19 @@ class Command(MyBaseCommand):
|
||||||
choices = []
|
choices = []
|
||||||
for user in User.objects.filter(profile__is_cof=True):
|
for user in User.objects.filter(profile__is_cof=True):
|
||||||
for tirage in tirages:
|
for tirage in tirages:
|
||||||
part, _ = Participant.objects.get_or_create(
|
part, _ = Participant.objects.get_or_create(user=user, tirage=tirage)
|
||||||
user=user,
|
|
||||||
tirage=tirage
|
|
||||||
)
|
|
||||||
shows = random.sample(
|
shows = random.sample(
|
||||||
list(tirage.spectacle_set.all()),
|
list(tirage.spectacle_set.all()), tirage.spectacle_set.count() // 2
|
||||||
tirage.spectacle_set.count() // 2
|
|
||||||
)
|
)
|
||||||
for (rank, show) in enumerate(shows):
|
for (rank, show) in enumerate(shows):
|
||||||
choices.append(ChoixSpectacle(
|
choices.append(
|
||||||
participant=part,
|
ChoixSpectacle(
|
||||||
spectacle=show,
|
participant=part,
|
||||||
priority=rank + 1,
|
spectacle=show,
|
||||||
double_choice=random.choice(
|
priority=rank + 1,
|
||||||
['1', 'double', 'autoquit']
|
double_choice=random.choice(["1", "double", "autoquit"]),
|
||||||
)
|
)
|
||||||
))
|
)
|
||||||
ChoixSpectacle.objects.bulk_create(choices)
|
ChoixSpectacle.objects.bulk_create(choices)
|
||||||
self.stdout.write("- {:d} inscriptions générées".format(len(choices)))
|
self.stdout.write("- {:d} inscriptions générées".format(len(choices)))
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,14 @@ Gestion en ligne de commande des reventes.
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Envoie les mails de notification et effectue " \
|
help = (
|
||||||
"les tirages au sort des reventes"
|
"Envoie les mails de notification et effectue les tirages au sort des reventes"
|
||||||
|
)
|
||||||
leave_locale_alone = True
|
leave_locale_alone = True
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
@ -28,22 +30,18 @@ class Command(BaseCommand):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Le spectacle est dans plus longtemps : on prévient
|
# Le spectacle est dans plus longtemps : on prévient
|
||||||
elif (revente.can_notif and not revente.notif_sent):
|
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(
|
self.stdout.write(
|
||||||
"Mails d'inscription à la revente [%s] envoyés"
|
"Mails d'inscription à la revente [%s] envoyés" % revente
|
||||||
% revente
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# On fait le tirage
|
# On fait le tirage
|
||||||
elif (now >= revente.date_tirage and not revente.tirage_done):
|
elif now >= revente.date_tirage and not revente.tirage_done:
|
||||||
self.stdout.write(str(now))
|
self.stdout.write(str(now))
|
||||||
winner = revente.tirage()
|
winner = revente.tirage()
|
||||||
self.stdout.write(
|
self.stdout.write("Tirage effectué pour la revente [%s]" % revente)
|
||||||
"Tirage effectué pour la revente [%s]"
|
|
||||||
% revente
|
|
||||||
)
|
|
||||||
|
|
||||||
if winner:
|
if winner:
|
||||||
self.stdout.write("Gagnant : %s" % winner.user)
|
self.stdout.write("Gagnant : %s" % winner.user)
|
||||||
|
|
|
@ -3,27 +3,31 @@ Gestion en ligne de commande des mails de rappel.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from bda.models import Spectacle
|
from bda.models import Spectacle
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Envoie les mails de rappel des spectacles dont la date ' \
|
help = (
|
||||||
'approche.\nNe renvoie pas les mails déjà envoyés.'
|
"Envoie les mails de rappel des spectacles dont la date approche.\n"
|
||||||
|
"Ne renvoie pas les mails déjà envoyés."
|
||||||
|
)
|
||||||
leave_locale_alone = True
|
leave_locale_alone = True
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
delay = timedelta(days=4)
|
delay = timedelta(days=4)
|
||||||
shows = Spectacle.objects \
|
shows = (
|
||||||
.filter(date__range=(now, now+delay)) \
|
Spectacle.objects.filter(date__range=(now, now + delay))
|
||||||
.filter(tirage__active=True) \
|
.filter(tirage__active=True)
|
||||||
.filter(rappel_sent__isnull=True) \
|
.filter(rappel_sent__isnull=True)
|
||||||
.all()
|
.all()
|
||||||
|
)
|
||||||
for show in shows:
|
for show in shows:
|
||||||
show.send_rappel()
|
show.send_rappel()
|
||||||
self.stdout.write(
|
self.stdout.write("Mails de rappels pour %s envoyés avec succès." % show)
|
||||||
'Mails de rappels pour %s envoyés avec succès.' % show)
|
|
||||||
if not shows:
|
if not shows:
|
||||||
self.stdout.write('Aucun mail à envoyer.')
|
self.stdout.write("Aucun mail à envoyer.")
|
||||||
|
|
|
@ -1,108 +1,206 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Attribution',
|
name="Attribution",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('given', models.BooleanField(default=False, verbose_name='Donn\xe9e')),
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("given", models.BooleanField(default=False, verbose_name="Donn\xe9e")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ChoixSpectacle',
|
name="ChoixSpectacle",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('priority', models.PositiveIntegerField(verbose_name=b'Priorit\xc3\xa9')),
|
"id",
|
||||||
('double_choice', models.CharField(default=b'1', max_length=10, verbose_name=b'Nombre de places', choices=[(b'1', b'1 place'), (b'autoquit', b'2 places si possible, 1 sinon'), (b'double', b'2 places sinon rien')])),
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"priority",
|
||||||
|
models.PositiveIntegerField(verbose_name=b"Priorit\xc3\xa9"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"double_choice",
|
||||||
|
models.CharField(
|
||||||
|
default=b"1",
|
||||||
|
max_length=10,
|
||||||
|
verbose_name=b"Nombre de places",
|
||||||
|
choices=[
|
||||||
|
(b"1", b"1 place"),
|
||||||
|
(b"autoquit", b"2 places si possible, 1 sinon"),
|
||||||
|
(b"double", b"2 places sinon rien"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('priority',),
|
"ordering": ("priority",),
|
||||||
'verbose_name': 'voeu',
|
"verbose_name": "voeu",
|
||||||
'verbose_name_plural': 'voeux',
|
"verbose_name_plural": "voeux",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Participant',
|
name="Participant",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('paid', models.BooleanField(default=False, verbose_name='A pay\xe9')),
|
"id",
|
||||||
('paymenttype', models.CharField(blank=True, max_length=6, verbose_name='Moyen de paiement', choices=[(b'cash', 'Cash'), (b'cb', b'CB'), (b'cheque', 'Ch\xe8que'), (b'autre', 'Autre')])),
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("paid", models.BooleanField(default=False, verbose_name="A pay\xe9")),
|
||||||
|
(
|
||||||
|
"paymenttype",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=6,
|
||||||
|
verbose_name="Moyen de paiement",
|
||||||
|
choices=[
|
||||||
|
(b"cash", "Cash"),
|
||||||
|
(b"cb", b"CB"),
|
||||||
|
(b"cheque", "Ch\xe8que"),
|
||||||
|
(b"autre", "Autre"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Salle',
|
name="Salle",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('name', models.CharField(max_length=300, verbose_name=b'Nom')),
|
"id",
|
||||||
('address', models.TextField(verbose_name=b'Adresse')),
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=300, verbose_name=b"Nom")),
|
||||||
|
("address", models.TextField(verbose_name=b"Adresse")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Spectacle',
|
name="Spectacle",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('title', models.CharField(max_length=300, verbose_name=b'Titre')),
|
"id",
|
||||||
('date', models.DateTimeField(verbose_name=b'Date & heure')),
|
models.AutoField(
|
||||||
('description', models.TextField(verbose_name=b'Description', blank=True)),
|
verbose_name="ID",
|
||||||
('slots_description', models.TextField(verbose_name=b'Description des places', blank=True)),
|
serialize=False,
|
||||||
('price', models.FloatField(verbose_name=b"Prix d'une place", blank=True)),
|
auto_created=True,
|
||||||
('slots', models.IntegerField(verbose_name=b'Places')),
|
primary_key=True,
|
||||||
('priority', models.IntegerField(default=1000, verbose_name=b'Priorit\xc3\xa9')),
|
),
|
||||||
('location', models.ForeignKey(to='bda.Salle', on_delete=models.CASCADE)),
|
),
|
||||||
|
("title", models.CharField(max_length=300, verbose_name=b"Titre")),
|
||||||
|
("date", models.DateTimeField(verbose_name=b"Date & heure")),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.TextField(verbose_name=b"Description", blank=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slots_description",
|
||||||
|
models.TextField(
|
||||||
|
verbose_name=b"Description des places", blank=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"price",
|
||||||
|
models.FloatField(verbose_name=b"Prix d'une place", blank=True),
|
||||||
|
),
|
||||||
|
("slots", models.IntegerField(verbose_name=b"Places")),
|
||||||
|
(
|
||||||
|
"priority",
|
||||||
|
models.IntegerField(default=1000, verbose_name=b"Priorit\xc3\xa9"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"location",
|
||||||
|
models.ForeignKey(to="bda.Salle", on_delete=models.CASCADE),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('priority', 'date', 'title'),
|
"ordering": ("priority", "date", "title"),
|
||||||
'verbose_name': 'Spectacle',
|
"verbose_name": "Spectacle",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='participant',
|
model_name="participant",
|
||||||
name='attributions',
|
name="attributions",
|
||||||
field=models.ManyToManyField(related_name='attributed_to', through='bda.Attribution', to='bda.Spectacle'),
|
field=models.ManyToManyField(
|
||||||
|
related_name="attributed_to",
|
||||||
|
through="bda.Attribution",
|
||||||
|
to="bda.Spectacle",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='participant',
|
model_name="participant",
|
||||||
name='choices',
|
name="choices",
|
||||||
field=models.ManyToManyField(related_name='chosen_by', through='bda.ChoixSpectacle', to='bda.Spectacle'),
|
field=models.ManyToManyField(
|
||||||
|
related_name="chosen_by",
|
||||||
|
through="bda.ChoixSpectacle",
|
||||||
|
to="bda.Spectacle",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='participant',
|
model_name="participant",
|
||||||
name='user',
|
name="user",
|
||||||
field=models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
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', on_delete=models.CASCADE),
|
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', on_delete=models.CASCADE),
|
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', on_delete=models.CASCADE),
|
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', on_delete=models.CASCADE),
|
field=models.ForeignKey(
|
||||||
|
related_name="attribues", to="bda.Spectacle", on_delete=models.CASCADE
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='choixspectacle',
|
name="choixspectacle", unique_together=set([("participant", "spectacle")])
|
||||||
unique_together=set([('participant', 'spectacle')]),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,49 +36,77 @@ def fill_tirage_fields(apps, schema_editor):
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0001_initial")]
|
||||||
('bda', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Tirage',
|
name="Tirage",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('title', models.CharField(max_length=300, verbose_name=b'Titre')),
|
"id",
|
||||||
('ouverture', models.DateTimeField(verbose_name=b"Date et heure d'ouverture du tirage")),
|
models.AutoField(
|
||||||
('fermeture', models.DateTimeField(verbose_name=b'Date et heure de fermerture du tirage')),
|
verbose_name="ID",
|
||||||
('token', models.TextField(verbose_name=b'Graine du tirage', blank=True)),
|
serialize=False,
|
||||||
('active', models.BooleanField(default=True, verbose_name=b'Tirage actif')),
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("title", models.CharField(max_length=300, verbose_name=b"Titre")),
|
||||||
|
(
|
||||||
|
"ouverture",
|
||||||
|
models.DateTimeField(
|
||||||
|
verbose_name=b"Date et heure d'ouverture du tirage"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"fermeture",
|
||||||
|
models.DateTimeField(
|
||||||
|
verbose_name=b"Date et heure de fermerture du tirage"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"token",
|
||||||
|
models.TextField(verbose_name=b"Graine du tirage", blank=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"active",
|
||||||
|
models.BooleanField(default=True, verbose_name=b"Tirage actif"),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='participant',
|
model_name="participant",
|
||||||
name='user',
|
name="user",
|
||||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
field=models.ForeignKey(
|
||||||
|
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
|
||||||
|
),
|
||||||
),
|
),
|
||||||
# Create fields `spectacle` for `Participant` and `Spectacle` models.
|
# Create fields `spectacle` for `Participant` and `Spectacle` models.
|
||||||
# These fields are not nullable, but we first create them as nullable
|
# These fields are not nullable, but we first create them as nullable
|
||||||
# to give a default value for existing instances of these models.
|
# 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(to='bda.Tirage', null=True, on_delete=models.CASCADE),
|
field=models.ForeignKey(
|
||||||
|
to="bda.Tirage", null=True, on_delete=models.CASCADE
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='tirage',
|
name="tirage",
|
||||||
field=models.ForeignKey(to='bda.Tirage', null=True, on_delete=models.CASCADE),
|
field=models.ForeignKey(
|
||||||
|
to="bda.Tirage", null=True, on_delete=models.CASCADE
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.RunPython(fill_tirage_fields, migrations.RunPython.noop),
|
migrations.RunPython(fill_tirage_fields, migrations.RunPython.noop),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='participant',
|
model_name="participant",
|
||||||
name='tirage',
|
name="tirage",
|
||||||
field=models.ForeignKey(to='bda.Tirage', on_delete=models.CASCADE),
|
field=models.ForeignKey(to="bda.Tirage", on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='tirage',
|
name="tirage",
|
||||||
field=models.ForeignKey(to='bda.Tirage', on_delete=models.CASCADE),
|
field=models.ForeignKey(to="bda.Tirage", on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,19 +6,17 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0002_add_tirage")]
|
||||||
('bda', '0002_add_tirage'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='price',
|
name="price",
|
||||||
field=models.FloatField(verbose_name=b"Prix d'une place"),
|
field=models.FloatField(verbose_name=b"Prix d'une place"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='active',
|
name="active",
|
||||||
field=models.BooleanField(default=False, verbose_name=b'Tirage actif'),
|
field=models.BooleanField(default=False, verbose_name=b"Tirage actif"),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,20 +6,22 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0003_update_tirage_and_spectacle")]
|
||||||
('bda', '0003_update_tirage_and_spectacle'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='listing',
|
name="listing",
|
||||||
field=models.BooleanField(default=False, verbose_name=b'Les places sont sur listing'),
|
field=models.BooleanField(
|
||||||
|
default=False, verbose_name=b"Les places sont sur listing"
|
||||||
|
),
|
||||||
preserve_default=False,
|
preserve_default=False,
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='rappel_sent',
|
name="rappel_sent",
|
||||||
field=models.DateTimeField(null=True, verbose_name=b'Mail de rappel envoy\xc3\xa9', blank=True),
|
field=models.DateTimeField(
|
||||||
|
null=True, verbose_name=b"Mail de rappel envoy\xc3\xa9", blank=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,24 +6,24 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0004_mails-rappel")]
|
||||||
('bda', '0004_mails-rappel'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='choixspectacle',
|
model_name="choixspectacle",
|
||||||
name='priority',
|
name="priority",
|
||||||
field=models.PositiveIntegerField(verbose_name='Priorit\xe9'),
|
field=models.PositiveIntegerField(verbose_name="Priorit\xe9"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='priority',
|
name="priority",
|
||||||
field=models.IntegerField(default=1000, verbose_name='Priorit\xe9'),
|
field=models.IntegerField(default=1000, verbose_name="Priorit\xe9"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='rappel_sent',
|
name="rappel_sent",
|
||||||
field=models.DateTimeField(null=True, verbose_name='Mail de rappel envoy\xe9', blank=True),
|
field=models.DateTimeField(
|
||||||
|
null=True, verbose_name="Mail de rappel envoy\xe9", blank=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -10,26 +10,25 @@ def forwards_func(apps, schema_editor):
|
||||||
db_alias = schema_editor.connection.alias
|
db_alias = schema_editor.connection.alias
|
||||||
for tirage in Tirage.objects.using(db_alias).all():
|
for tirage in Tirage.objects.using(db_alias).all():
|
||||||
if tirage.tokens:
|
if tirage.tokens:
|
||||||
tirage.tokens = "Before %s\n\"\"\"%s\"\"\"\n" % (
|
tirage.tokens = 'Before %s\n"""%s"""\n' % (
|
||||||
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
|
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
|
||||||
tirage.tokens)
|
tirage.tokens,
|
||||||
|
)
|
||||||
tirage.save()
|
tirage.save()
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0005_encoding")]
|
||||||
('bda', '0005_encoding'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RenameField('tirage', 'token', 'tokens'),
|
migrations.RenameField("tirage", "token", "tokens"),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='enable_do_tirage',
|
name="enable_do_tirage",
|
||||||
field=models.BooleanField(
|
field=models.BooleanField(
|
||||||
default=False,
|
default=False, verbose_name=b"Le tirage peut \xc3\xaatre lanc\xc3\xa9"
|
||||||
verbose_name=b'Le tirage peut \xc3\xaatre lanc\xc3\xa9'),
|
),
|
||||||
),
|
),
|
||||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,91 +1,100 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0006_add_tirage_switch")]
|
||||||
('bda', '0006_add_tirage_switch'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='CategorieSpectacle',
|
name="CategorieSpectacle",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False,
|
(
|
||||||
auto_created=True, primary_key=True)),
|
"id",
|
||||||
('name', models.CharField(max_length=100, verbose_name='Nom',
|
models.AutoField(
|
||||||
unique=True)),
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"name",
|
||||||
|
models.CharField(max_length=100, verbose_name="Nom", unique=True),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={"verbose_name": "Cat\xe9gorie"},
|
||||||
'verbose_name': 'Cat\xe9gorie',
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Quote',
|
name="Quote",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False,
|
(
|
||||||
auto_created=True, primary_key=True)),
|
"id",
|
||||||
('text', models.TextField(verbose_name='Citation')),
|
models.AutoField(
|
||||||
('author', models.CharField(max_length=200,
|
verbose_name="ID",
|
||||||
verbose_name='Auteur')),
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("text", models.TextField(verbose_name="Citation")),
|
||||||
|
("author", models.CharField(max_length=200, verbose_name="Auteur")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='spectacle',
|
name="spectacle",
|
||||||
options={'ordering': ('date', 'title'),
|
options={"ordering": ("date", "title"), "verbose_name": "Spectacle"},
|
||||||
'verbose_name': 'Spectacle'},
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='priority',
|
|
||||||
),
|
),
|
||||||
|
migrations.RemoveField(model_name="spectacle", name="priority"),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='ext_link',
|
name="ext_link",
|
||||||
field=models.CharField(
|
field=models.CharField(
|
||||||
max_length=500,
|
max_length=500,
|
||||||
verbose_name='Lien vers le site du spectacle',
|
verbose_name="Lien vers le site du spectacle",
|
||||||
blank=True),
|
blank=True,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='image',
|
name="image",
|
||||||
field=models.ImageField(upload_to='imgs/shows/', null=True,
|
field=models.ImageField(
|
||||||
verbose_name='Image', blank=True),
|
upload_to="imgs/shows/", null=True, verbose_name="Image", blank=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='enable_do_tirage',
|
name="enable_do_tirage",
|
||||||
field=models.BooleanField(
|
field=models.BooleanField(
|
||||||
default=False,
|
default=False, verbose_name="Le tirage peut \xeatre lanc\xe9"
|
||||||
verbose_name='Le tirage peut \xeatre lanc\xe9'),
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='tokens',
|
name="tokens",
|
||||||
field=models.TextField(verbose_name='Graine(s) du tirage',
|
field=models.TextField(verbose_name="Graine(s) du tirage", blank=True),
|
||||||
blank=True),
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='category',
|
name="category",
|
||||||
field=models.ForeignKey(blank=True, to='bda.CategorieSpectacle',
|
field=models.ForeignKey(
|
||||||
on_delete=models.CASCADE,
|
blank=True,
|
||||||
null=True),
|
to="bda.CategorieSpectacle",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='vips',
|
name="vips",
|
||||||
field=models.TextField(verbose_name='Personnalit\xe9s',
|
field=models.TextField(verbose_name="Personnalit\xe9s", blank=True),
|
||||||
blank=True),
|
|
||||||
),
|
),
|
||||||
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),
|
||||||
on_delete=models.CASCADE),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,103 +1,110 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0007_extends_spectacle")]
|
||||||
('bda', '0007_extends_spectacle'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='choixspectacle',
|
model_name="choixspectacle",
|
||||||
name='double_choice',
|
name="double_choice",
|
||||||
field=models.CharField(
|
field=models.CharField(
|
||||||
verbose_name='Nombre de places',
|
verbose_name="Nombre de places",
|
||||||
choices=[('1', '1 place'),
|
choices=[
|
||||||
('autoquit', '2 places si possible, 1 sinon'),
|
("1", "1 place"),
|
||||||
('double', '2 places sinon rien')],
|
("autoquit", "2 places si possible, 1 sinon"),
|
||||||
max_length=10, default='1'),
|
("double", "2 places sinon rien"),
|
||||||
|
],
|
||||||
|
max_length=10,
|
||||||
|
default="1",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='participant',
|
model_name="participant",
|
||||||
name='paymenttype',
|
name="paymenttype",
|
||||||
field=models.CharField(
|
field=models.CharField(
|
||||||
blank=True,
|
blank=True,
|
||||||
choices=[('cash', 'Cash'), ('cb', 'CB'),
|
choices=[
|
||||||
('cheque', 'Chèque'), ('autre', 'Autre')],
|
("cash", "Cash"),
|
||||||
max_length=6, verbose_name='Moyen de paiement'),
|
("cb", "CB"),
|
||||||
|
("cheque", "Chèque"),
|
||||||
|
("autre", "Autre"),
|
||||||
|
],
|
||||||
|
max_length=6,
|
||||||
|
verbose_name="Moyen de paiement",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='salle',
|
model_name="salle",
|
||||||
name='address',
|
name="address",
|
||||||
field=models.TextField(verbose_name='Adresse'),
|
field=models.TextField(verbose_name="Adresse"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='salle',
|
model_name="salle",
|
||||||
name='name',
|
name="name",
|
||||||
field=models.CharField(verbose_name='Nom', max_length=300),
|
field=models.CharField(verbose_name="Nom", max_length=300),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='date',
|
name="date",
|
||||||
field=models.DateTimeField(verbose_name='Date & heure'),
|
field=models.DateTimeField(verbose_name="Date & heure"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='description',
|
name="description",
|
||||||
field=models.TextField(verbose_name='Description', blank=True),
|
field=models.TextField(verbose_name="Description", blank=True),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='listing',
|
name="listing",
|
||||||
field=models.BooleanField(
|
field=models.BooleanField(verbose_name="Les places sont sur listing"),
|
||||||
verbose_name='Les places sont sur listing'),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='price',
|
name="price",
|
||||||
field=models.FloatField(verbose_name="Prix d'une place"),
|
field=models.FloatField(verbose_name="Prix d'une place"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='slots',
|
name="slots",
|
||||||
field=models.IntegerField(verbose_name='Places'),
|
field=models.IntegerField(verbose_name="Places"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='slots_description',
|
name="slots_description",
|
||||||
field=models.TextField(verbose_name='Description des places',
|
field=models.TextField(verbose_name="Description des places", blank=True),
|
||||||
blank=True),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='title',
|
name="title",
|
||||||
field=models.CharField(verbose_name='Titre', max_length=300),
|
field=models.CharField(verbose_name="Titre", max_length=300),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='active',
|
name="active",
|
||||||
field=models.BooleanField(verbose_name='Tirage actif',
|
field=models.BooleanField(verbose_name="Tirage actif", default=False),
|
||||||
default=False),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='fermeture',
|
name="fermeture",
|
||||||
field=models.DateTimeField(
|
field=models.DateTimeField(
|
||||||
verbose_name='Date et heure de fermerture du tirage'),
|
verbose_name="Date et heure de fermerture du tirage"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='ouverture',
|
name="ouverture",
|
||||||
field=models.DateTimeField(
|
field=models.DateTimeField(
|
||||||
verbose_name="Date et heure d'ouverture du tirage"),
|
verbose_name="Date et heure d'ouverture du tirage"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='title',
|
name="title",
|
||||||
field=models.CharField(verbose_name='Titre', max_length=300),
|
field=models.CharField(verbose_name="Titre", max_length=300),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,69 +1,87 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0008_py3")]
|
||||||
('bda', '0008_py3'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='SpectacleRevente',
|
name="SpectacleRevente",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(serialize=False, primary_key=True,
|
(
|
||||||
auto_created=True, verbose_name='ID')),
|
"id",
|
||||||
('date', models.DateTimeField(
|
models.AutoField(
|
||||||
verbose_name='Date de mise en vente',
|
serialize=False,
|
||||||
default=django.utils.timezone.now)),
|
primary_key=True,
|
||||||
('notif_sent', models.BooleanField(
|
auto_created=True,
|
||||||
verbose_name='Notification envoyée', default=False)),
|
verbose_name="ID",
|
||||||
('tirage_done', models.BooleanField(
|
),
|
||||||
verbose_name='Tirage effectué', default=False)),
|
),
|
||||||
|
(
|
||||||
|
"date",
|
||||||
|
models.DateTimeField(
|
||||||
|
verbose_name="Date de mise en vente",
|
||||||
|
default=django.utils.timezone.now,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"notif_sent",
|
||||||
|
models.BooleanField(
|
||||||
|
verbose_name="Notification envoyée", default=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tirage_done",
|
||||||
|
models.BooleanField(verbose_name="Tirage effectué", default=False),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={"verbose_name": "Revente"},
|
||||||
'verbose_name': 'Revente',
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='participant',
|
model_name="participant",
|
||||||
name='choicesrevente',
|
name="choicesrevente",
|
||||||
field=models.ManyToManyField(to='bda.Spectacle',
|
field=models.ManyToManyField(
|
||||||
related_name='subscribed',
|
to="bda.Spectacle", related_name="subscribed", blank=True
|
||||||
blank=True),
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectaclerevente',
|
model_name="spectaclerevente",
|
||||||
name='answered_mail',
|
name="answered_mail",
|
||||||
field=models.ManyToManyField(to='bda.Participant',
|
field=models.ManyToManyField(
|
||||||
related_name='wanted',
|
to="bda.Participant", related_name="wanted", blank=True
|
||||||
blank=True),
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectaclerevente',
|
model_name="spectaclerevente",
|
||||||
name='attribution',
|
name="attribution",
|
||||||
field=models.OneToOneField(to='bda.Attribution',
|
field=models.OneToOneField(
|
||||||
on_delete=models.CASCADE,
|
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(
|
||||||
on_delete=models.CASCADE,
|
to="bda.Participant",
|
||||||
verbose_name='Vendeur',
|
on_delete=models.CASCADE,
|
||||||
related_name='original_shows'),
|
verbose_name="Vendeur",
|
||||||
|
related_name="original_shows",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectaclerevente',
|
model_name="spectaclerevente",
|
||||||
name='soldTo',
|
name="soldTo",
|
||||||
field=models.ForeignKey(to='bda.Participant',
|
field=models.ForeignKey(
|
||||||
on_delete=models.CASCADE,
|
to="bda.Participant",
|
||||||
verbose_name='Vendue à', null=True,
|
on_delete=models.CASCADE,
|
||||||
blank=True),
|
verbose_name="Vendue à",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,33 +1,35 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
from django.utils import timezone
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
def forwards_func(apps, schema_editor):
|
def forwards_func(apps, schema_editor):
|
||||||
SpectacleRevente = apps.get_model("bda", "SpectacleRevente")
|
SpectacleRevente = apps.get_model("bda", "SpectacleRevente")
|
||||||
|
|
||||||
for revente in SpectacleRevente.objects.all():
|
for revente in SpectacleRevente.objects.all():
|
||||||
is_expired = timezone.now() > revente.date_tirage()
|
is_expired = timezone.now() > revente.date_tirage()
|
||||||
is_direct = (revente.attribution.spectacle.date >= revente.date and
|
is_direct = revente.attribution.spectacle.date >= revente.date and timezone.now() > revente.date + timedelta(
|
||||||
timezone.now() > revente.date + timedelta(minutes=15))
|
minutes=15
|
||||||
|
)
|
||||||
revente.shotgun = is_expired or is_direct
|
revente.shotgun = is_expired or is_direct
|
||||||
revente.save()
|
revente.save()
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0009_revente")]
|
||||||
('bda', '0009_revente'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectaclerevente',
|
model_name="spectaclerevente",
|
||||||
name='shotgun',
|
name="shotgun",
|
||||||
field=models.BooleanField(default=False, verbose_name='Disponible imm\xe9diatement'),
|
field=models.BooleanField(
|
||||||
|
default=False, verbose_name="Disponible imm\xe9diatement"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,17 +6,14 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0010_spectaclerevente_shotgun")]
|
||||||
('bda', '0010_spectaclerevente_shotgun'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='appear_catalogue',
|
name="appear_catalogue",
|
||||||
field=models.BooleanField(
|
field=models.BooleanField(
|
||||||
default=False,
|
default=False, verbose_name="Tirage à afficher dans le catalogue"
|
||||||
verbose_name='Tirage à afficher dans le catalogue'
|
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,24 +6,26 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0011_tirage_appear_catalogue")]
|
||||||
('bda', '0011_tirage_appear_catalogue'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RenameField(
|
migrations.RenameField(
|
||||||
model_name='spectaclerevente',
|
model_name="spectaclerevente",
|
||||||
old_name='answered_mail',
|
old_name="answered_mail",
|
||||||
new_name='confirmed_entry',
|
new_name="confirmed_entry",
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectaclerevente',
|
model_name="spectaclerevente",
|
||||||
name='confirmed_entry',
|
name="confirmed_entry",
|
||||||
field=models.ManyToManyField(blank=True, related_name='entered', to='bda.Participant'),
|
field=models.ManyToManyField(
|
||||||
|
blank=True, related_name="entered", to="bda.Participant"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectaclerevente',
|
model_name="spectaclerevente",
|
||||||
name='notif_time',
|
name="notif_time",
|
||||||
field=models.DateTimeField(blank=True, verbose_name="Moment d'envoi de la notification", null=True),
|
field=models.DateTimeField(
|
||||||
|
blank=True, verbose_name="Moment d'envoi de la notification", null=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -14,40 +14,38 @@ def swap_double_choice(apps, schema_editor):
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0011_tirage_appear_catalogue")]
|
||||||
('bda', '0011_tirage_appear_catalogue'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
# Temporarily allow an extra "tmp" value for the `double_choice` field
|
# Temporarily allow an extra "tmp" value for the `double_choice` field
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='choixspectacle',
|
model_name="choixspectacle",
|
||||||
name='double_choice',
|
name="double_choice",
|
||||||
field=models.CharField(
|
field=models.CharField(
|
||||||
verbose_name='Nombre de places',
|
verbose_name="Nombre de places",
|
||||||
max_length=10,
|
max_length=10,
|
||||||
default='1',
|
default="1",
|
||||||
choices=[
|
choices=[
|
||||||
('tmp', 'tmp'),
|
("tmp", "tmp"),
|
||||||
('1', '1 place'),
|
("1", "1 place"),
|
||||||
('double', '2 places si possible, 1 sinon'),
|
("double", "2 places si possible, 1 sinon"),
|
||||||
('autoquit', '2 places sinon rien')
|
("autoquit", "2 places sinon rien"),
|
||||||
]
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
migrations.RunPython(swap_double_choice, migrations.RunPython.noop),
|
migrations.RunPython(swap_double_choice, migrations.RunPython.noop),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='choixspectacle',
|
model_name="choixspectacle",
|
||||||
name='double_choice',
|
name="double_choice",
|
||||||
field=models.CharField(
|
field=models.CharField(
|
||||||
verbose_name='Nombre de places',
|
verbose_name="Nombre de places",
|
||||||
max_length=10,
|
max_length=10,
|
||||||
default='1',
|
default="1",
|
||||||
choices=[
|
choices=[
|
||||||
('1', '1 place'),
|
("1", "1 place"),
|
||||||
('double', '2 places si possible, 1 sinon'),
|
("double", "2 places si possible, 1 sinon"),
|
||||||
('autoquit', '2 places sinon rien')
|
("autoquit", "2 places sinon rien"),
|
||||||
]
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -7,10 +7,6 @@ from django.db import migrations
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0012_notif_time"), ("bda", "0012_swap_double_choice")]
|
||||||
('bda', '0012_notif_time'),
|
|
||||||
('bda', '0012_swap_double_choice'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = []
|
||||||
]
|
|
||||||
|
|
248
bda/models.py
248
bda/models.py
|
@ -1,23 +1,22 @@
|
||||||
import calendar
|
import calendar
|
||||||
import random
|
import random
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from custommail.shortcuts import send_mass_custom_mail
|
|
||||||
|
|
||||||
|
from custommail.models import CustomMail
|
||||||
|
from custommail.shortcuts import send_mass_custom_mail
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.core import mail
|
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.utils import formats, timezone
|
||||||
from django.conf import settings
|
|
||||||
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(
|
||||||
username="bda_generic",
|
username="bda_generic",
|
||||||
defaults={"email": "bda@ens.fr", "first_name": "Bureau des arts"}
|
defaults={"email": "bda@ens.fr", "first_name": "Bureau des arts"},
|
||||||
)
|
)
|
||||||
return generic
|
return generic
|
||||||
|
|
||||||
|
@ -29,15 +28,15 @@ class Tirage(models.Model):
|
||||||
tokens = models.TextField("Graine(s) du tirage", blank=True)
|
tokens = models.TextField("Graine(s) du tirage", blank=True)
|
||||||
active = models.BooleanField("Tirage actif", default=False)
|
active = models.BooleanField("Tirage actif", default=False)
|
||||||
appear_catalogue = models.BooleanField(
|
appear_catalogue = models.BooleanField(
|
||||||
"Tirage à afficher dans le catalogue",
|
"Tirage à afficher dans le catalogue", default=False
|
||||||
default=False
|
|
||||||
)
|
)
|
||||||
enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
|
enable_do_tirage = models.BooleanField("Le tirage peut être lancé", default=False)
|
||||||
default=False)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s - %s" % (self.title, formats.localize(
|
return "%s - %s" % (
|
||||||
timezone.template_localtime(self.fermeture)))
|
self.title,
|
||||||
|
formats.localize(timezone.template_localtime(self.fermeture)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Salle(models.Model):
|
class Salle(models.Model):
|
||||||
|
@ -49,7 +48,7 @@ class Salle(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class CategorieSpectacle(models.Model):
|
class CategorieSpectacle(models.Model):
|
||||||
name = models.CharField('Nom', max_length=100, unique=True)
|
name = models.CharField("Nom", max_length=100, unique=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -61,28 +60,26 @@ 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(
|
category = models.ForeignKey(
|
||||||
CategorieSpectacle, on_delete=models.CASCADE,
|
CategorieSpectacle, on_delete=models.CASCADE, blank=True, null=True
|
||||||
blank=True, null=True,
|
|
||||||
)
|
)
|
||||||
date = models.DateTimeField("Date & heure")
|
date = models.DateTimeField("Date & heure")
|
||||||
location = models.ForeignKey(Salle, on_delete=models.CASCADE)
|
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)
|
||||||
image = models.ImageField('Image', blank=True, null=True,
|
image = models.ImageField("Image", blank=True, null=True, upload_to="imgs/shows/")
|
||||||
upload_to='imgs/shows/')
|
ext_link = models.CharField(
|
||||||
ext_link = models.CharField('Lien vers le site du spectacle', blank=True,
|
"Lien vers le site du spectacle", blank=True, 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, on_delete=models.CASCADE)
|
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)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Spectacle"
|
verbose_name = "Spectacle"
|
||||||
ordering = ("date", "title",)
|
ordering = ("date", "title")
|
||||||
|
|
||||||
def timestamp(self):
|
def timestamp(self):
|
||||||
return "%d" % calendar.timegm(self.date.utctimetuple())
|
return "%d" % calendar.timegm(self.date.utctimetuple())
|
||||||
|
@ -92,7 +89,7 @@ class Spectacle(models.Model):
|
||||||
self.title,
|
self.title,
|
||||||
formats.localize(timezone.template_localtime(self.date)),
|
formats.localize(timezone.template_localtime(self.date)),
|
||||||
self.location,
|
self.location,
|
||||||
self.price
|
self.price,
|
||||||
)
|
)
|
||||||
|
|
||||||
def getImgUrl(self):
|
def getImgUrl(self):
|
||||||
|
@ -101,7 +98,7 @@ class Spectacle(models.Model):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self.image.url
|
return self.image.url
|
||||||
except:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def send_rappel(self):
|
def send_rappel(self):
|
||||||
|
@ -111,19 +108,21 @@ class Spectacle(models.Model):
|
||||||
"""
|
"""
|
||||||
# On récupère la liste des participants + le BdA
|
# On récupère la liste des participants + le BdA
|
||||||
members = list(
|
members = list(
|
||||||
User.objects
|
User.objects.filter(participant__attributions=self)
|
||||||
.filter(participant__attributions=self)
|
.annotate(nb_attr=Count("id"))
|
||||||
.annotate(nb_attr=Count("id")).order_by()
|
.order_by()
|
||||||
)
|
)
|
||||||
bda_generic = get_generic_user()
|
bda_generic = get_generic_user()
|
||||||
bda_generic.nb_attr = 1
|
bda_generic.nb_attr = 1
|
||||||
members.append(bda_generic)
|
members.append(bda_generic)
|
||||||
# On écrit un mail personnalisé à chaque participant
|
# On écrit un mail personnalisé à chaque participant
|
||||||
datatuple = [(
|
datatuple = [
|
||||||
'bda-rappel',
|
(
|
||||||
{'member': member, "nb_attr": member.nb_attr, 'show': self},
|
"bda-rappel",
|
||||||
settings.MAIL_DATA['rappels']['FROM'],
|
{"member": member, "nb_attr": member.nb_attr, "show": self},
|
||||||
[member.email])
|
settings.MAIL_DATA["rappels"]["FROM"],
|
||||||
|
[member.email],
|
||||||
|
)
|
||||||
for member in members
|
for member in members
|
||||||
]
|
]
|
||||||
send_mass_custom_mail(datatuple)
|
send_mass_custom_mail(datatuple)
|
||||||
|
@ -140,8 +139,8 @@ class Spectacle(models.Model):
|
||||||
|
|
||||||
class Quote(models.Model):
|
class Quote(models.Model):
|
||||||
spectacle = models.ForeignKey(Spectacle, on_delete=models.CASCADE)
|
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)
|
||||||
|
|
||||||
|
|
||||||
PAYMENT_TYPES = (
|
PAYMENT_TYPES = (
|
||||||
|
@ -154,20 +153,20 @@ PAYMENT_TYPES = (
|
||||||
|
|
||||||
class Participant(models.Model):
|
class Participant(models.Model):
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
choices = models.ManyToManyField(Spectacle,
|
choices = models.ManyToManyField(
|
||||||
through="ChoixSpectacle",
|
Spectacle, through="ChoixSpectacle", related_name="chosen_by"
|
||||||
related_name="chosen_by")
|
)
|
||||||
attributions = models.ManyToManyField(Spectacle,
|
attributions = models.ManyToManyField(
|
||||||
through="Attribution",
|
Spectacle, through="Attribution", related_name="attributed_to"
|
||||||
related_name="attributed_to")
|
)
|
||||||
paid = models.BooleanField("A payé", default=False)
|
paid = models.BooleanField("A payé", default=False)
|
||||||
paymenttype = models.CharField("Moyen de paiement",
|
paymenttype = models.CharField(
|
||||||
max_length=6, choices=PAYMENT_TYPES,
|
"Moyen de paiement", max_length=6, choices=PAYMENT_TYPES, blank=True
|
||||||
blank=True)
|
)
|
||||||
tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
|
tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
|
||||||
choicesrevente = models.ManyToManyField(Spectacle,
|
choicesrevente = models.ManyToManyField(
|
||||||
related_name="subscribed",
|
Spectacle, related_name="subscribed", blank=True
|
||||||
blank=True)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s - %s" % (self.user, self.tirage.title)
|
return "%s - %s" % (self.user, self.tirage.title)
|
||||||
|
@ -183,30 +182,32 @@ DOUBLE_CHOICES = (
|
||||||
class ChoixSpectacle(models.Model):
|
class ChoixSpectacle(models.Model):
|
||||||
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
|
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
|
||||||
spectacle = models.ForeignKey(
|
spectacle = models.ForeignKey(
|
||||||
Spectacle, on_delete=models.CASCADE,
|
Spectacle, on_delete=models.CASCADE, related_name="participants"
|
||||||
related_name="participants",
|
|
||||||
)
|
)
|
||||||
priority = models.PositiveIntegerField("Priorité")
|
priority = models.PositiveIntegerField("Priorité")
|
||||||
double_choice = models.CharField("Nombre de places",
|
double_choice = models.CharField(
|
||||||
default="1", choices=DOUBLE_CHOICES,
|
"Nombre de places", default="1", choices=DOUBLE_CHOICES, max_length=10
|
||||||
max_length=10)
|
)
|
||||||
|
|
||||||
def get_double(self):
|
def get_double(self):
|
||||||
return self.double_choice != "1"
|
return self.double_choice != "1"
|
||||||
|
|
||||||
double = property(get_double)
|
double = property(get_double)
|
||||||
|
|
||||||
def get_autoquit(self):
|
def get_autoquit(self):
|
||||||
return self.double_choice == "autoquit"
|
return self.double_choice == "autoquit"
|
||||||
|
|
||||||
autoquit = property(get_autoquit)
|
autoquit = property(get_autoquit)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Vœux de %s pour %s" % (
|
return "Vœux de %s pour %s" % (
|
||||||
self.participant.user.get_full_name(),
|
self.participant.user.get_full_name(),
|
||||||
self.spectacle.title)
|
self.spectacle.title,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ("priority",)
|
ordering = ("priority",)
|
||||||
unique_together = (("participant", "spectacle",),)
|
unique_together = (("participant", "spectacle"),)
|
||||||
verbose_name = "voeu"
|
verbose_name = "voeu"
|
||||||
verbose_name_plural = "voeux"
|
verbose_name_plural = "voeux"
|
||||||
|
|
||||||
|
@ -214,48 +215,49 @@ class ChoixSpectacle(models.Model):
|
||||||
class Attribution(models.Model):
|
class Attribution(models.Model):
|
||||||
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
|
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
|
||||||
spectacle = models.ForeignKey(
|
spectacle = models.ForeignKey(
|
||||||
Spectacle, on_delete=models.CASCADE,
|
Spectacle, on_delete=models.CASCADE, related_name="attribues"
|
||||||
related_name="attribues",
|
|
||||||
)
|
)
|
||||||
given = models.BooleanField("Donnée", default=False)
|
given = models.BooleanField("Donnée", default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s -- %s, %s" % (self.participant.user, self.spectacle.title,
|
return "%s -- %s, %s" % (
|
||||||
self.spectacle.date)
|
self.participant.user,
|
||||||
|
self.spectacle.title,
|
||||||
|
self.spectacle.date,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SpectacleRevente(models.Model):
|
class SpectacleRevente(models.Model):
|
||||||
attribution = models.OneToOneField(
|
attribution = models.OneToOneField(
|
||||||
Attribution, on_delete=models.CASCADE,
|
Attribution, on_delete=models.CASCADE, related_name="revente"
|
||||||
related_name="revente",
|
)
|
||||||
|
date = models.DateTimeField("Date de mise en vente", default=timezone.now)
|
||||||
|
confirmed_entry = models.ManyToManyField(
|
||||||
|
Participant, related_name="entered", blank=True
|
||||||
)
|
)
|
||||||
date = models.DateTimeField("Date de mise en vente",
|
|
||||||
default=timezone.now)
|
|
||||||
confirmed_entry = models.ManyToManyField(Participant,
|
|
||||||
related_name="entered",
|
|
||||||
blank=True)
|
|
||||||
seller = models.ForeignKey(
|
seller = models.ForeignKey(
|
||||||
Participant, on_delete=models.CASCADE,
|
Participant,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
verbose_name="Vendeur",
|
verbose_name="Vendeur",
|
||||||
related_name="original_shows",
|
related_name="original_shows",
|
||||||
)
|
)
|
||||||
soldTo = models.ForeignKey(
|
soldTo = models.ForeignKey(
|
||||||
Participant, on_delete=models.CASCADE,
|
Participant,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
verbose_name="Vendue à",
|
verbose_name="Vendue à",
|
||||||
blank=True, null=True,
|
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",
|
notif_time = models.DateTimeField(
|
||||||
blank=True, null=True)
|
"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
|
# Some class attributes
|
||||||
###
|
###
|
||||||
|
@ -282,8 +284,9 @@ class SpectacleRevente(models.Model):
|
||||||
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."""
|
||||||
|
|
||||||
remaining_time = (self.attribution.spectacle.date
|
remaining_time = (
|
||||||
- self.real_notif_time - self.min_margin)
|
self.attribution.spectacle.date - self.real_notif_time - self.min_margin
|
||||||
|
)
|
||||||
|
|
||||||
delay = min(remaining_time, self.max_wait_time)
|
delay = min(remaining_time, self.max_wait_time)
|
||||||
|
|
||||||
|
@ -296,16 +299,14 @@ class SpectacleRevente(models.Model):
|
||||||
Plus précisément, on doit avoir min_margin + min_wait_time de marge.
|
Plus précisément, on doit avoir min_margin + min_wait_time de marge.
|
||||||
"""
|
"""
|
||||||
spectacle_date = self.attribution.spectacle.date
|
spectacle_date = self.attribution.spectacle.date
|
||||||
return (spectacle_date <= timezone.now() + self.min_margin
|
return spectacle_date <= timezone.now() + self.min_margin + self.min_wait_time
|
||||||
+ self.min_wait_time)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def can_notif(self):
|
def can_notif(self):
|
||||||
return (timezone.now() >= self.date + self.remorse_time)
|
return timezone.now() >= self.date + self.remorse_time
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s -- %s" % (self.seller,
|
return "%s -- %s" % (self.seller, self.attribution.spectacle.title)
|
||||||
self.attribution.spectacle.title)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Revente"
|
verbose_name = "Revente"
|
||||||
|
@ -327,17 +328,19 @@ class SpectacleRevente(models.Model):
|
||||||
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
|
||||||
BdA-Revente à tous les intéressés.
|
BdA-Revente à tous les intéressés.
|
||||||
"""
|
"""
|
||||||
inscrits = self.attribution.spectacle.subscribed.select_related('user')
|
inscrits = self.attribution.spectacle.subscribed.select_related("user")
|
||||||
datatuple = [(
|
datatuple = [
|
||||||
'bda-revente',
|
(
|
||||||
{
|
"bda-revente",
|
||||||
'member': participant.user,
|
{
|
||||||
'show': self.attribution.spectacle,
|
"member": participant.user,
|
||||||
'revente': self,
|
"show": self.attribution.spectacle,
|
||||||
'site': Site.objects.get_current()
|
"revente": self,
|
||||||
},
|
"site": Site.objects.get_current(),
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
},
|
||||||
[participant.user.email])
|
settings.MAIL_DATA["revente"]["FROM"],
|
||||||
|
[participant.user.email],
|
||||||
|
)
|
||||||
for participant in inscrits
|
for participant in inscrits
|
||||||
]
|
]
|
||||||
send_mass_custom_mail(datatuple)
|
send_mass_custom_mail(datatuple)
|
||||||
|
@ -350,16 +353,18 @@ class SpectacleRevente(models.Model):
|
||||||
Envoie un mail à toutes les personnes intéréssées par le spectacle pour
|
Envoie un mail à toutes les personnes intéréssées par le spectacle pour
|
||||||
leur indiquer qu'il est désormais disponible au shotgun.
|
leur indiquer qu'il est désormais disponible au shotgun.
|
||||||
"""
|
"""
|
||||||
inscrits = self.attribution.spectacle.subscribed.select_related('user')
|
inscrits = self.attribution.spectacle.subscribed.select_related("user")
|
||||||
datatuple = [(
|
datatuple = [
|
||||||
'bda-shotgun',
|
(
|
||||||
{
|
"bda-shotgun",
|
||||||
'member': participant.user,
|
{
|
||||||
'show': self.attribution.spectacle,
|
"member": participant.user,
|
||||||
'site': Site.objects.get_current(),
|
"show": self.attribution.spectacle,
|
||||||
},
|
"site": Site.objects.get_current(),
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
},
|
||||||
[participant.user.email])
|
settings.MAIL_DATA["revente"]["FROM"],
|
||||||
|
[participant.user.email],
|
||||||
|
)
|
||||||
for participant in inscrits
|
for participant in inscrits
|
||||||
]
|
]
|
||||||
send_mass_custom_mail(datatuple)
|
send_mass_custom_mail(datatuple)
|
||||||
|
@ -389,30 +394,33 @@ class SpectacleRevente(models.Model):
|
||||||
mails = []
|
mails = []
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'acheteur': winner.user,
|
"acheteur": winner.user,
|
||||||
'vendeur': seller.user,
|
"vendeur": seller.user,
|
||||||
'show': spectacle,
|
"show": spectacle,
|
||||||
}
|
}
|
||||||
|
|
||||||
c_mails_qs = CustomMail.objects.filter(shortname__in=[
|
c_mails_qs = CustomMail.objects.filter(
|
||||||
'bda-revente-winner', 'bda-revente-loser',
|
shortname__in=[
|
||||||
'bda-revente-seller',
|
"bda-revente-winner",
|
||||||
])
|
"bda-revente-loser",
|
||||||
|
"bda-revente-seller",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
c_mails = {cm.shortname: cm for cm in c_mails_qs}
|
c_mails = {cm.shortname: cm for cm in c_mails_qs}
|
||||||
|
|
||||||
mails.append(
|
mails.append(
|
||||||
c_mails['bda-revente-winner'].get_message(
|
c_mails["bda-revente-winner"].get_message(
|
||||||
context,
|
context,
|
||||||
from_email=settings.MAIL_DATA['revente']['FROM'],
|
from_email=settings.MAIL_DATA["revente"]["FROM"],
|
||||||
to=[winner.user.email],
|
to=[winner.user.email],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
mails.append(
|
mails.append(
|
||||||
c_mails['bda-revente-seller'].get_message(
|
c_mails["bda-revente-seller"].get_message(
|
||||||
context,
|
context,
|
||||||
from_email=settings.MAIL_DATA['revente']['FROM'],
|
from_email=settings.MAIL_DATA["revente"]["FROM"],
|
||||||
to=[seller.user.email],
|
to=[seller.user.email],
|
||||||
reply_to=[winner.user.email],
|
reply_to=[winner.user.email],
|
||||||
)
|
)
|
||||||
|
@ -422,12 +430,12 @@ class SpectacleRevente(models.Model):
|
||||||
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
|
||||||
|
|
||||||
mails.append(
|
mails.append(
|
||||||
c_mails['bda-revente-loser'].get_message(
|
c_mails["bda-revente-loser"].get_message(
|
||||||
new_context,
|
new_context,
|
||||||
from_email=settings.MAIL_DATA['revente']['FROM'],
|
from_email=settings.MAIL_DATA["revente"]["FROM"],
|
||||||
to=[inscrit.user.email],
|
to=[inscrit.user.email],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<h2>Gestion des places que je revends</h2>
|
<h2>Gestion des places que je revends</h2>
|
||||||
{% with resell_attributions=resellform.attributions annul_reventes=annulform.reventes sold_reventes=soldform.reventes %}
|
{% with resell_attributions=resellform.attributions annul_reventes=annulform.reventes sold_reventes=soldform.reventes %}
|
||||||
|
|
||||||
{% if resellform.attributions %}
|
{% if resell_attributions %}
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<h3>Places non revendues</h3>
|
<h3>Places non revendues</h3>
|
||||||
|
@ -29,34 +29,24 @@
|
||||||
<hr />
|
<hr />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if annul_reventes or overdue %}
|
{% if annul_reventes %}
|
||||||
<h3>Places en cours de revente</h3>
|
<h3>Places en cours de revente</h3>
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
{% if annul_reventes %}
|
<div class="bg-info text-info center-block">
|
||||||
<div class="bg-info text-info center-block">
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
Vous pouvez annuler les reventes qui n'ont pas encore trouvé preneur·se.
|
||||||
Vous pouvez annuler les places mises en vente il y a moins d'une heure.
|
</div>
|
||||||
</div>
|
{% csrf_token %}
|
||||||
{% endif %}
|
<div class='form-group'>
|
||||||
{% csrf_token %}
|
<div class='multiple-checkbox'>
|
||||||
<div class='form-group'>
|
<ul>
|
||||||
<div class='multiple-checkbox'>
|
{% for revente in annul_reventes %}
|
||||||
<ul>
|
<li>{{ revente.tag }} {{ revente.choice_label }}</li>
|
||||||
{% for revente in annul_reventes %}
|
{% endfor %}
|
||||||
<li>{{ revente.tag }} {{ revente.choice_label }}</li>
|
</ul>
|
||||||
{% endfor %}
|
</div>
|
||||||
{% for attrib in overdue %}
|
</div>
|
||||||
<li>
|
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
||||||
<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>
|
</form>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -82,7 +72,7 @@
|
||||||
<button type="submit" class="btn btn-primary" name="reinit">Réinitialiser</button>
|
<button type="submit" class="btn btn-primary" name="reinit">Réinitialiser</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not resell_attributions and not annul_attributions and not overdue and not sold_reventes %}
|
{% if not resell_attributions and not annul_reventes and not sold_reventes %}
|
||||||
<p>Plus de reventes possibles !</p>
|
<p>Plus de reventes possibles !</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -7,28 +7,33 @@ from django.test import TestCase
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from bda.models import (
|
from bda.models import (
|
||||||
Attribution, Participant, Salle, Spectacle, SpectacleRevente, Tirage,
|
Attribution,
|
||||||
|
Participant,
|
||||||
|
Salle,
|
||||||
|
Spectacle,
|
||||||
|
SpectacleRevente,
|
||||||
|
Tirage,
|
||||||
)
|
)
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class SpectacleReventeTests(TestCase):
|
class SpectacleReventeTests(TestCase):
|
||||||
fixtures = ['gestioncof/management/data/custommail.json']
|
fixtures = ["gestioncof/management/data/custommail.json"]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
|
|
||||||
self.t = Tirage.objects.create(
|
self.t = Tirage.objects.create(
|
||||||
title='Tirage',
|
title="Tirage",
|
||||||
ouverture=now - timedelta(days=7),
|
ouverture=now - timedelta(days=7),
|
||||||
fermeture=now - timedelta(days=3),
|
fermeture=now - timedelta(days=3),
|
||||||
active=True,
|
active=True,
|
||||||
)
|
)
|
||||||
self.s = Spectacle.objects.create(
|
self.s = Spectacle.objects.create(
|
||||||
title='Spectacle',
|
title="Spectacle",
|
||||||
date=now + timedelta(days=20),
|
date=now + timedelta(days=20),
|
||||||
location=Salle.objects.create(name='Salle', address='Address'),
|
location=Salle.objects.create(name="Salle", address="Address"),
|
||||||
price=10.5,
|
price=10.5,
|
||||||
slots=5,
|
slots=5,
|
||||||
tirage=self.t,
|
tirage=self.t,
|
||||||
|
@ -36,31 +41,28 @@ class SpectacleReventeTests(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.seller = Participant.objects.create(
|
self.seller = Participant.objects.create(
|
||||||
user=User.objects.create(
|
user=User.objects.create(username="seller", email="seller@mail.net"),
|
||||||
username='seller', email='seller@mail.net'),
|
|
||||||
tirage=self.t,
|
tirage=self.t,
|
||||||
)
|
)
|
||||||
self.p1 = Participant.objects.create(
|
self.p1 = Participant.objects.create(
|
||||||
user=User.objects.create(username='part1', email='part1@mail.net'),
|
user=User.objects.create(username="part1", email="part1@mail.net"),
|
||||||
tirage=self.t,
|
tirage=self.t,
|
||||||
)
|
)
|
||||||
self.p2 = Participant.objects.create(
|
self.p2 = Participant.objects.create(
|
||||||
user=User.objects.create(username='part2', email='part2@mail.net'),
|
user=User.objects.create(username="part2", email="part2@mail.net"),
|
||||||
tirage=self.t,
|
tirage=self.t,
|
||||||
)
|
)
|
||||||
self.p3 = Participant.objects.create(
|
self.p3 = Participant.objects.create(
|
||||||
user=User.objects.create(username='part3', email='part3@mail.net'),
|
user=User.objects.create(username="part3", email="part3@mail.net"),
|
||||||
tirage=self.t,
|
tirage=self.t,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.attr = Attribution.objects.create(
|
self.attr = Attribution.objects.create(
|
||||||
participant=self.seller,
|
participant=self.seller, spectacle=self.s
|
||||||
spectacle=self.s,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.rev = SpectacleRevente.objects.create(
|
self.rev = SpectacleRevente.objects.create(
|
||||||
attribution=self.attr,
|
attribution=self.attr, seller=self.seller
|
||||||
seller=self.seller,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_tirage(self):
|
def test_tirage(self):
|
||||||
|
@ -69,7 +71,7 @@ class SpectacleReventeTests(TestCase):
|
||||||
wanted_by = [self.p1, self.p2, self.p3]
|
wanted_by = [self.p1, self.p2, self.p3]
|
||||||
revente.confirmed_entry = wanted_by
|
revente.confirmed_entry = wanted_by
|
||||||
|
|
||||||
with mock.patch('bda.models.random.choice') as mc:
|
with mock.patch("bda.models.random.choice") as mc:
|
||||||
# Set winner to self.p1.
|
# Set winner to self.p1.
|
||||||
mc.return_value = self.p1
|
mc.return_value = self.p1
|
||||||
|
|
||||||
|
@ -87,14 +89,14 @@ class SpectacleReventeTests(TestCase):
|
||||||
|
|
||||||
self.assertEqual(len(mails), 4)
|
self.assertEqual(len(mails), 4)
|
||||||
|
|
||||||
m_seller = mails['seller@mail.net']
|
m_seller = mails["seller@mail.net"]
|
||||||
self.assertListEqual(m_seller.to, ['seller@mail.net'])
|
self.assertListEqual(m_seller.to, ["seller@mail.net"])
|
||||||
self.assertListEqual(m_seller.reply_to, ['part1@mail.net'])
|
self.assertListEqual(m_seller.reply_to, ["part1@mail.net"])
|
||||||
|
|
||||||
m_winner = mails['part1@mail.net']
|
m_winner = mails["part1@mail.net"]
|
||||||
self.assertListEqual(m_winner.to, ['part1@mail.net'])
|
self.assertListEqual(m_winner.to, ["part1@mail.net"])
|
||||||
|
|
||||||
self.assertCountEqual(
|
self.assertCountEqual(
|
||||||
[mails['part2@mail.net'].to, mails['part3@mail.net'].to],
|
[mails["part2@mail.net"].to, mails["part3@mail.net"].to],
|
||||||
[['part2@mail.net'], ['part3@mail.net']],
|
[["part2@mail.net"], ["part3@mail.net"]],
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,60 +1,71 @@
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.test import TestCase, Client
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from bda.models import (Tirage, Spectacle, Salle, CategorieSpectacle,
|
from django.contrib.auth.models import User
|
||||||
SpectacleRevente, Attribution, Participant)
|
from django.test import TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from bda.models import (
|
||||||
|
Attribution,
|
||||||
|
CategorieSpectacle,
|
||||||
|
Participant,
|
||||||
|
Salle,
|
||||||
|
Spectacle,
|
||||||
|
SpectacleRevente,
|
||||||
|
Tirage,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestModels(TestCase):
|
class TestModels(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.tirage = Tirage.objects.create(
|
self.tirage = Tirage.objects.create(
|
||||||
title="Tirage test",
|
title="Tirage test",
|
||||||
appear_catalogue=True,
|
appear_catalogue=True,
|
||||||
ouverture=timezone.now(),
|
ouverture=timezone.now(),
|
||||||
fermeture=timezone.now()
|
fermeture=timezone.now(),
|
||||||
)
|
)
|
||||||
self.category = CategorieSpectacle.objects.create(name="Category")
|
self.category = CategorieSpectacle.objects.create(name="Category")
|
||||||
self.location = Salle.objects.create(name="here")
|
self.location = Salle.objects.create(name="here")
|
||||||
self.spectacle_soon = Spectacle.objects.create(
|
self.spectacle_soon = Spectacle.objects.create(
|
||||||
title="foo", date=timezone.now()+timedelta(days=1),
|
title="foo",
|
||||||
location=self.location, price=0, slots=42,
|
date=timezone.now() + timedelta(days=1),
|
||||||
tirage=self.tirage, listing=False, category=self.category
|
location=self.location,
|
||||||
|
price=0,
|
||||||
|
slots=42,
|
||||||
|
tirage=self.tirage,
|
||||||
|
listing=False,
|
||||||
|
category=self.category,
|
||||||
)
|
)
|
||||||
self.spectacle_later = Spectacle.objects.create(
|
self.spectacle_later = Spectacle.objects.create(
|
||||||
title="bar", date=timezone.now()+timedelta(days=30),
|
title="bar",
|
||||||
location=self.location, price=0, slots=42,
|
date=timezone.now() + timedelta(days=30),
|
||||||
tirage=self.tirage, listing=False, category=self.category
|
location=self.location,
|
||||||
|
price=0,
|
||||||
|
slots=42,
|
||||||
|
tirage=self.tirage,
|
||||||
|
listing=False,
|
||||||
|
category=self.category,
|
||||||
)
|
)
|
||||||
|
|
||||||
user_buyer = User.objects.create_user(
|
user_buyer = User.objects.create_user(
|
||||||
username="bda_buyer", password="testbuyer"
|
username="bda_buyer", password="testbuyer"
|
||||||
)
|
)
|
||||||
user_seller = User.objects.create_user(
|
user_seller = User.objects.create_user(
|
||||||
username="bda_seller", password="testseller"
|
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.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(
|
self.attr_soon = Attribution.objects.create(
|
||||||
participant=self.seller, spectacle=self.spectacle_soon
|
participant=self.seller, spectacle=self.spectacle_soon
|
||||||
)
|
)
|
||||||
self.attr_later = Attribution.objects.create(
|
self.attr_later = Attribution.objects.create(
|
||||||
participant=self.seller, spectacle=self.spectacle_later
|
participant=self.seller, spectacle=self.spectacle_later
|
||||||
)
|
)
|
||||||
self.revente_soon = SpectacleRevente.objects.create(
|
self.revente_soon = SpectacleRevente.objects.create(
|
||||||
seller=self.seller,
|
seller=self.seller, attribution=self.attr_soon
|
||||||
attribution=self.attr_soon
|
|
||||||
)
|
)
|
||||||
self.revente_later = SpectacleRevente.objects.create(
|
self.revente_later = SpectacleRevente.objects.create(
|
||||||
seller=self.seller,
|
seller=self.seller, attribution=self.attr_later
|
||||||
attribution=self.attr_later
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_urgent(self):
|
def test_urgent(self):
|
||||||
|
@ -64,6 +75,5 @@ class TestModels(TestCase):
|
||||||
def test_tirage(self):
|
def test_tirage(self):
|
||||||
self.revente_soon.confirmed_entry.add(self.buyer)
|
self.revente_soon.confirmed_entry.add(self.buyer)
|
||||||
|
|
||||||
self.assertEqual(self.revente_soon.tirage(send_mails=False),
|
self.assertEqual(self.revente_soon.tirage(send_mails=False), self.buyer)
|
||||||
self.buyer)
|
|
||||||
self.assertIsNone(self.revente_later.tirage(send_mails=False))
|
self.assertIsNone(self.revente_later.tirage(send_mails=False))
|
||||||
|
|
|
@ -1,14 +1,92 @@
|
||||||
import json
|
import json
|
||||||
|
from datetime import timedelta
|
||||||
|
from unittest import mock
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.test import TestCase, Client
|
from django.test import Client, TestCase
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from bda.models import Tirage, Spectacle, Salle, CategorieSpectacle
|
from ..models import CategorieSpectacle, Salle, Spectacle, Tirage
|
||||||
|
|
||||||
|
|
||||||
class TestBdAViews(TestCase):
|
def create_user(username, is_cof=False, is_buro=False):
|
||||||
|
user = User.objects.create_user(username=username, password=username)
|
||||||
|
user.profile.is_cof = is_cof
|
||||||
|
user.profile.is_buro = is_buro
|
||||||
|
user.profile.save()
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def user_is_cof(user):
|
||||||
|
return (user is not None) and user.profile.is_cof
|
||||||
|
|
||||||
|
|
||||||
|
def user_is_staff(user):
|
||||||
|
return (user is not None) and user.profile.is_buro
|
||||||
|
|
||||||
|
|
||||||
|
class BdATestHelpers:
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
# Some user with different access privileges
|
||||||
|
staff = create_user(username="bda_staff", is_cof=True, is_buro=True)
|
||||||
|
staff_c = Client()
|
||||||
|
staff_c.force_login(staff)
|
||||||
|
|
||||||
|
member = create_user(username="bda_member", is_cof=True)
|
||||||
|
member_c = Client()
|
||||||
|
member_c.force_login(member)
|
||||||
|
|
||||||
|
other = create_user(username="bda_other")
|
||||||
|
other_c = Client()
|
||||||
|
other_c.force_login(other)
|
||||||
|
|
||||||
|
self.client_matrix = [
|
||||||
|
(staff, staff_c),
|
||||||
|
(member, member_c),
|
||||||
|
(other, other_c),
|
||||||
|
(None, Client()),
|
||||||
|
]
|
||||||
|
|
||||||
|
def require_custommails(self):
|
||||||
|
from django.core.management import call_command
|
||||||
|
|
||||||
|
call_command("syncmails", verbosity=0)
|
||||||
|
|
||||||
|
def check_restricted_access(
|
||||||
|
self, url, validate_user=user_is_cof, redirect_url=None
|
||||||
|
):
|
||||||
|
def craft_redirect_url(user):
|
||||||
|
if redirect_url:
|
||||||
|
return redirect_url
|
||||||
|
elif user is None:
|
||||||
|
# client is not logged in
|
||||||
|
login_url = "/login"
|
||||||
|
if url:
|
||||||
|
login_url += "?{}".format(urlencode({"next": url}, safe="/"))
|
||||||
|
return login_url
|
||||||
|
else:
|
||||||
|
return "/"
|
||||||
|
|
||||||
|
for (user, client) in self.client_matrix:
|
||||||
|
resp = client.get(url, follow=True)
|
||||||
|
if validate_user(user):
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
else:
|
||||||
|
self.assertRedirects(resp, craft_redirect_url(user))
|
||||||
|
|
||||||
|
|
||||||
|
class TestBdAViews(BdATestHelpers, TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# Signals handlers on login/logout send messages.
|
||||||
|
# Due to the way the Django' test Client performs login, this raise an
|
||||||
|
# error. As workaround, we mock the Django' messages module.
|
||||||
|
patcher_messages = mock.patch("gestioncof.signals.messages")
|
||||||
|
patcher_messages.start()
|
||||||
|
self.addCleanup(patcher_messages.stop)
|
||||||
|
# Set up the helpers
|
||||||
|
super().setUp()
|
||||||
|
# Some BdA stuff
|
||||||
self.tirage = Tirage.objects.create(
|
self.tirage = Tirage.objects.create(
|
||||||
title="Test tirage",
|
title="Test tirage",
|
||||||
appear_catalogue=True,
|
appear_catalogue=True,
|
||||||
|
@ -17,82 +95,137 @@ class TestBdAViews(TestCase):
|
||||||
)
|
)
|
||||||
self.category = CategorieSpectacle.objects.create(name="Category")
|
self.category = CategorieSpectacle.objects.create(name="Category")
|
||||||
self.location = Salle.objects.create(name="here")
|
self.location = Salle.objects.create(name="here")
|
||||||
Spectacle.objects.bulk_create([
|
Spectacle.objects.bulk_create(
|
||||||
Spectacle(
|
[
|
||||||
title="foo", date=timezone.now(), location=self.location,
|
Spectacle(
|
||||||
price=0, slots=42, tirage=self.tirage, listing=False,
|
title="foo",
|
||||||
category=self.category
|
date=timezone.now(),
|
||||||
),
|
location=self.location,
|
||||||
Spectacle(
|
price=0,
|
||||||
title="bar", date=timezone.now(), location=self.location,
|
slots=42,
|
||||||
price=1, slots=142, tirage=self.tirage, listing=False,
|
tirage=self.tirage,
|
||||||
category=self.category
|
listing=False,
|
||||||
),
|
category=self.category,
|
||||||
Spectacle(
|
),
|
||||||
title="baz", date=timezone.now(), location=self.location,
|
Spectacle(
|
||||||
price=2, slots=242, tirage=self.tirage, listing=False,
|
title="bar",
|
||||||
category=self.category
|
date=timezone.now(),
|
||||||
),
|
location=self.location,
|
||||||
])
|
price=1,
|
||||||
|
slots=142,
|
||||||
self.bda_user = User.objects.create_user(
|
tirage=self.tirage,
|
||||||
username="bda_user", password="bda4ever"
|
listing=False,
|
||||||
|
category=self.category,
|
||||||
|
),
|
||||||
|
Spectacle(
|
||||||
|
title="baz",
|
||||||
|
date=timezone.now(),
|
||||||
|
location=self.location,
|
||||||
|
price=2,
|
||||||
|
slots=242,
|
||||||
|
tirage=self.tirage,
|
||||||
|
listing=False,
|
||||||
|
category=self.category,
|
||||||
|
),
|
||||||
|
]
|
||||||
)
|
)
|
||||||
self.bda_user.profile.is_cof = True
|
|
||||||
self.bda_user.profile.is_buro = True
|
|
||||||
self.bda_user.profile.save()
|
|
||||||
|
|
||||||
def bda_participants(self):
|
def test_bda_inscriptions(self):
|
||||||
"""The BdA participants views can be queried"""
|
# TODO: test the form
|
||||||
client = Client()
|
url = "/bda/inscription/{}".format(self.tirage.id)
|
||||||
|
self.check_restricted_access(url)
|
||||||
|
|
||||||
|
def test_bda_places(self):
|
||||||
|
url = "/bda/places/{}".format(self.tirage.id)
|
||||||
|
self.check_restricted_access(url)
|
||||||
|
|
||||||
|
def test_etat_places(self):
|
||||||
|
url = "/bda/etat-places/{}".format(self.tirage.id)
|
||||||
|
self.check_restricted_access(url)
|
||||||
|
|
||||||
|
def test_perform_tirage(self):
|
||||||
|
# Only staff member can perform a tirage
|
||||||
|
url = "/bda/tirage/{}".format(self.tirage.id)
|
||||||
|
self.check_restricted_access(url, validate_user=user_is_staff)
|
||||||
|
|
||||||
|
_, staff_c = self.client_matrix[0]
|
||||||
|
# Cannot be performed if disabled
|
||||||
|
self.tirage.enable_do_tirage = False
|
||||||
|
self.tirage.save()
|
||||||
|
resp = staff_c.get(url)
|
||||||
|
self.assertTemplateUsed(resp, "tirage-failed.html")
|
||||||
|
# Cannot be performed if registrations are still open
|
||||||
|
self.tirage.enable_do_tirage = True
|
||||||
|
self.tirage.fermeture = timezone.now() + timedelta(seconds=3600)
|
||||||
|
self.tirage.save()
|
||||||
|
resp = staff_c.get(url)
|
||||||
|
self.assertTemplateUsed(resp, "tirage-failed.html")
|
||||||
|
# Otherwise, perform the tirage
|
||||||
|
self.tirage.fermeture = timezone.now()
|
||||||
|
self.tirage.save()
|
||||||
|
resp = staff_c.get(url)
|
||||||
|
self.assertTemplateNotUsed(resp, "tirage-failed.html")
|
||||||
|
|
||||||
|
def test_spectacles_list(self):
|
||||||
|
url = "/bda/spectacles/{}".format(self.tirage.id)
|
||||||
|
self.check_restricted_access(url, validate_user=user_is_staff)
|
||||||
|
|
||||||
|
def test_spectacle_detail(self):
|
||||||
show = self.tirage.spectacle_set.first()
|
show = self.tirage.spectacle_set.first()
|
||||||
|
url = "/bda/spectacles/{}/{}".format(self.tirage.id, show.id)
|
||||||
|
self.check_restricted_access(url, validate_user=user_is_staff)
|
||||||
|
|
||||||
client.login(self.bda_user.username, "bda4ever")
|
def test_tirage_unpaid(self):
|
||||||
tirage_resp = client.get("/bda/spectacles/{}".format(self.tirage.id))
|
url = "/bda/spectacles/unpaid/{}".format(self.tirage.id)
|
||||||
show_resp = client.get(
|
self.check_restricted_access(url, validate_user=user_is_staff)
|
||||||
"/bda/spectacles/{}/{}".format(self.tirage.id, show.id)
|
|
||||||
)
|
|
||||||
reminder_url = "/bda/mails-rappel/{}".format(show.id)
|
|
||||||
reminder_get_resp = client.get(reminder_url)
|
|
||||||
reminder_post_resp = client.post(reminder_url)
|
|
||||||
self.assertEqual(200, tirage_resp.status_code)
|
|
||||||
self.assertEqual(200, show_resp.status_code)
|
|
||||||
self.assertEqual(200, reminder_get_resp.status_code)
|
|
||||||
self.assertEqual(200, reminder_post_resp.status_code)
|
|
||||||
|
|
||||||
def test_catalogue(self):
|
def test_send_reminders(self):
|
||||||
"""Test the catalogue JSON API"""
|
self.require_custommails()
|
||||||
client = Client()
|
# Just get the page
|
||||||
|
show = self.tirage.spectacle_set.first()
|
||||||
|
url = "/bda/mails-rappel/{}".format(show.id)
|
||||||
|
self.check_restricted_access(url, validate_user=user_is_staff)
|
||||||
|
# Actually send the reminder emails
|
||||||
|
_, staff_c = self.client_matrix[0]
|
||||||
|
resp = staff_c.post(url)
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
# TODO: check that emails are sent
|
||||||
|
|
||||||
# The `list` hook
|
def test_catalogue_api(self):
|
||||||
resp = client.get("/bda/catalogue/list")
|
url_list = "/bda/catalogue/list"
|
||||||
|
url_details = "/bda/catalogue/details?id={}".format(self.tirage.id)
|
||||||
|
url_descriptions = "/bda/catalogue/descriptions?id={}".format(self.tirage.id)
|
||||||
|
|
||||||
|
# Anyone can get
|
||||||
|
def anyone_can_get(url):
|
||||||
|
self.check_restricted_access(url, validate_user=lambda user: True)
|
||||||
|
|
||||||
|
anyone_can_get(url_list)
|
||||||
|
anyone_can_get(url_details)
|
||||||
|
anyone_can_get(url_descriptions)
|
||||||
|
|
||||||
|
# The resulting JSON contains the information
|
||||||
|
_, client = self.client_matrix[0]
|
||||||
|
|
||||||
|
# List
|
||||||
|
resp = client.get(url_list)
|
||||||
self.assertJSONEqual(
|
self.assertJSONEqual(
|
||||||
resp.content.decode("utf-8"),
|
resp.content.decode("utf-8"),
|
||||||
[{"id": self.tirage.id, "title": self.tirage.title}]
|
[{"id": self.tirage.id, "title": self.tirage.title}],
|
||||||
)
|
)
|
||||||
|
|
||||||
# The `details` hook
|
# Details
|
||||||
resp = client.get(
|
resp = client.get(url_details)
|
||||||
"/bda/catalogue/details?id={}".format(self.tirage.id)
|
|
||||||
)
|
|
||||||
self.assertJSONEqual(
|
self.assertJSONEqual(
|
||||||
resp.content.decode("utf-8"),
|
resp.content.decode("utf-8"),
|
||||||
{
|
{
|
||||||
"categories": [{
|
"categories": [{"id": self.category.id, "name": self.category.name}],
|
||||||
"id": self.category.id,
|
"locations": [{"id": self.location.id, "name": self.location.name}],
|
||||||
"name": self.category.name
|
},
|
||||||
}],
|
|
||||||
"locations": [{
|
|
||||||
"id": self.location.id,
|
|
||||||
"name": self.location.name
|
|
||||||
}],
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# The `descriptions` hook
|
# Descriptions
|
||||||
resp = client.get(
|
resp = client.get(url_descriptions)
|
||||||
"/bda/catalogue/descriptions?id={}".format(self.tirage.id)
|
|
||||||
)
|
|
||||||
raw = resp.content.decode("utf-8")
|
raw = resp.content.decode("utf-8")
|
||||||
try:
|
try:
|
||||||
results = json.loads(raw)
|
results = json.loads(raw)
|
||||||
|
@ -101,5 +234,10 @@ class TestBdAViews(TestCase):
|
||||||
self.assertEqual(len(results), 3)
|
self.assertEqual(len(results), 3)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
{(s["title"], s["price"], s["slots"]) for s in results},
|
{(s["title"], s["price"], s["slots"]) for s in results},
|
||||||
{("foo", 0, 42), ("bar", 1, 142), ("baz", 2, 242)}
|
{("foo", 0, 42), ("bar", 1, 142), ("baz", 2, 242)},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBdaRevente:
|
||||||
|
pass
|
||||||
|
# TODO
|
||||||
|
|
103
bda/urls.py
103
bda/urls.py
|
@ -1,62 +1,75 @@
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from gestioncof.decorators import buro_required
|
|
||||||
from bda.views import SpectacleListView
|
|
||||||
from bda import views
|
from bda import views
|
||||||
|
from bda.views import SpectacleListView
|
||||||
|
from gestioncof.decorators import buro_required
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^inscription/(?P<tirage_id>\d+)$',
|
url(
|
||||||
|
r"^inscription/(?P<tirage_id>\d+)$",
|
||||||
views.inscription,
|
views.inscription,
|
||||||
name='bda-tirage-inscription'),
|
name="bda-tirage-inscription",
|
||||||
url(r'^places/(?P<tirage_id>\d+)$',
|
),
|
||||||
views.places,
|
url(r"^places/(?P<tirage_id>\d+)$", views.places, name="bda-places-attribuees"),
|
||||||
name="bda-places-attribuees"),
|
url(r"^etat-places/(?P<tirage_id>\d+)$", views.etat_places, name="bda-etat-places"),
|
||||||
url(r'^etat-places/(?P<tirage_id>\d+)$',
|
url(r"^tirage/(?P<tirage_id>\d+)$", views.tirage),
|
||||||
views.etat_places,
|
url(
|
||||||
name='bda-etat-places'),
|
r"^spectacles/(?P<tirage_id>\d+)$",
|
||||||
url(r'^tirage/(?P<tirage_id>\d+)$', views.tirage),
|
|
||||||
url(r'^spectacles/(?P<tirage_id>\d+)$',
|
|
||||||
buro_required(SpectacleListView.as_view()),
|
buro_required(SpectacleListView.as_view()),
|
||||||
name="bda-liste-spectacles"),
|
name="bda-liste-spectacles",
|
||||||
url(r'^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$',
|
),
|
||||||
|
url(
|
||||||
|
r"^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$",
|
||||||
views.spectacle,
|
views.spectacle,
|
||||||
name="bda-spectacle"),
|
name="bda-spectacle",
|
||||||
url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$',
|
),
|
||||||
views.unpaid,
|
url(r"^spectacles/unpaid/(?P<tirage_id>\d+)$", views.unpaid, name="bda-unpaid"),
|
||||||
name="bda-unpaid"),
|
url(
|
||||||
url(r'^spectacles/autocomplete$',
|
r"^spectacles/autocomplete$",
|
||||||
views.spectacle_autocomplete,
|
views.spectacle_autocomplete,
|
||||||
name="bda-spectacle-autocomplete"),
|
name="bda-spectacle-autocomplete",
|
||||||
url(r'^participants/autocomplete$',
|
),
|
||||||
|
url(
|
||||||
|
r"^participants/autocomplete$",
|
||||||
views.participant_autocomplete,
|
views.participant_autocomplete,
|
||||||
name="bda-participant-autocomplete"),
|
name="bda-participant-autocomplete",
|
||||||
|
),
|
||||||
# Urls BdA-Revente
|
# Urls BdA-Revente
|
||||||
|
url(
|
||||||
url(r'^revente/(?P<tirage_id>\d+)/manage$',
|
r"^revente/(?P<tirage_id>\d+)/manage$",
|
||||||
views.revente_manage,
|
views.revente_manage,
|
||||||
name='bda-revente-manage'),
|
name="bda-revente-manage",
|
||||||
url(r'^revente/(?P<tirage_id>\d+)/subscribe$',
|
),
|
||||||
|
url(
|
||||||
|
r"^revente/(?P<tirage_id>\d+)/subscribe$",
|
||||||
views.revente_subscribe,
|
views.revente_subscribe,
|
||||||
name="bda-revente-subscribe"),
|
name="bda-revente-subscribe",
|
||||||
url(r'^revente/(?P<tirage_id>\d+)/tirages$',
|
),
|
||||||
|
url(
|
||||||
|
r"^revente/(?P<tirage_id>\d+)/tirages$",
|
||||||
views.revente_tirages,
|
views.revente_tirages,
|
||||||
name="bda-revente-tirages"),
|
name="bda-revente-tirages",
|
||||||
url(r'^revente/(?P<spectacle_id>\d+)/buy$',
|
),
|
||||||
|
url(
|
||||||
|
r"^revente/(?P<spectacle_id>\d+)/buy$",
|
||||||
views.revente_buy,
|
views.revente_buy,
|
||||||
name="bda-revente-buy"),
|
name="bda-revente-buy",
|
||||||
url(r'^revente/(?P<revente_id>\d+)/confirm$',
|
),
|
||||||
|
url(
|
||||||
|
r"^revente/(?P<revente_id>\d+)/confirm$",
|
||||||
views.revente_confirm,
|
views.revente_confirm,
|
||||||
name='bda-revente-confirm'),
|
name="bda-revente-confirm",
|
||||||
url(r'^revente/(?P<tirage_id>\d+)/shotgun$',
|
),
|
||||||
|
url(
|
||||||
|
r"^revente/(?P<tirage_id>\d+)/shotgun$",
|
||||||
views.revente_shotgun,
|
views.revente_shotgun,
|
||||||
name="bda-revente-shotgun"),
|
name="bda-revente-shotgun",
|
||||||
|
),
|
||||||
url(r'^mails-rappel/(?P<spectacle_id>\d+)$',
|
url(r"^mails-rappel/(?P<spectacle_id>\d+)$", views.send_rappel, name="bda-rappels"),
|
||||||
views.send_rappel,
|
url(
|
||||||
name="bda-rappels"
|
r"^descriptions/(?P<tirage_id>\d+)$",
|
||||||
),
|
views.descriptions_spectacles,
|
||||||
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
|
name="bda-descriptions",
|
||||||
name='bda-descriptions'),
|
),
|
||||||
url(r'^catalogue/(?P<request_type>[a-z]+)$', views.catalogue,
|
url(r"^catalogue/(?P<request_type>[a-z]+)$", views.catalogue, name="bda-catalogue"),
|
||||||
name='bda-catalogue'),
|
|
||||||
]
|
]
|
||||||
|
|
661
bda/views.py
661
bda/views.py
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from channels.asgi import get_channel_layer
|
from channels.asgi import get_channel_layer
|
||||||
|
|
||||||
if "DJANGO_SETTINGS_MODULE" not in os.environ:
|
if "DJANGO_SETTINGS_MODULE" not in os.environ:
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
Formats français.
|
Formats français.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DATETIME_FORMAT = r'l j F Y \à H:i'
|
DATETIME_FORMAT = r"l j F Y \à H:i"
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
from channels.routing import include
|
from channels.routing import include
|
||||||
|
|
||||||
|
routing = [include("kfet.routing.routing", path=r"^/ws/k-fet")]
|
||||||
routing = [
|
|
||||||
include('kfet.routing.routing', path=r'^/ws/k-fet'),
|
|
||||||
]
|
|
||||||
|
|
|
@ -46,112 +46,106 @@ KFETOPEN_TOKEN = import_secret("KFETOPEN_TOKEN")
|
||||||
LDAP_SERVER_URL = import_secret("LDAP_SERVER_URL")
|
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'
|
TESTING = sys.argv[1] == "test"
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'shared',
|
"shared",
|
||||||
|
"gestioncof",
|
||||||
'gestioncof',
|
|
||||||
|
|
||||||
# Must be before 'django.contrib.admin'.
|
# Must be before 'django.contrib.admin'.
|
||||||
# https://django-autocomplete-light.readthedocs.io/en/master/install.html
|
# https://django-autocomplete-light.readthedocs.io/en/master/install.html
|
||||||
'dal',
|
"dal",
|
||||||
'dal_select2',
|
"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',
|
"django.contrib.admin",
|
||||||
'django.contrib.admin',
|
"django.contrib.admindocs",
|
||||||
'django.contrib.admindocs',
|
"bda",
|
||||||
|
"captcha",
|
||||||
'bda',
|
"django_cas_ng",
|
||||||
'captcha',
|
"bootstrapform",
|
||||||
'django_cas_ng',
|
"kfet",
|
||||||
'bootstrapform',
|
"kfet.open",
|
||||||
'kfet',
|
"channels",
|
||||||
'kfet.open',
|
"widget_tweaks",
|
||||||
'channels',
|
"custommail",
|
||||||
'widget_tweaks',
|
"djconfig",
|
||||||
'custommail',
|
"wagtail.wagtailforms",
|
||||||
'djconfig',
|
"wagtail.wagtailredirects",
|
||||||
'wagtail.wagtailforms',
|
"wagtail.wagtailembeds",
|
||||||
'wagtail.wagtailredirects',
|
"wagtail.wagtailsites",
|
||||||
'wagtail.wagtailembeds',
|
"wagtail.wagtailusers",
|
||||||
'wagtail.wagtailsites',
|
"wagtail.wagtailsnippets",
|
||||||
'wagtail.wagtailusers',
|
"wagtail.wagtaildocs",
|
||||||
'wagtail.wagtailsnippets',
|
"wagtail.wagtailimages",
|
||||||
'wagtail.wagtaildocs',
|
"wagtail.wagtailsearch",
|
||||||
'wagtail.wagtailimages',
|
"wagtail.wagtailadmin",
|
||||||
'wagtail.wagtailsearch',
|
"wagtail.wagtailcore",
|
||||||
'wagtail.wagtailadmin',
|
"wagtail.contrib.modeladmin",
|
||||||
'wagtail.wagtailcore',
|
"wagtailmenus",
|
||||||
'wagtail.contrib.modeladmin',
|
"modelcluster",
|
||||||
'wagtailmenus',
|
"taggit",
|
||||||
'modelcluster',
|
"kfet.auth",
|
||||||
'taggit',
|
"kfet.cms",
|
||||||
'kfet.auth',
|
"corsheaders",
|
||||||
'kfet.cms',
|
|
||||||
'corsheaders',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
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.auth.middleware.TemporaryAuthMiddleware',
|
"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",
|
||||||
'djconfig.middleware.DjConfigMiddleware',
|
"djconfig.middleware.DjConfigMiddleware",
|
||||||
'wagtail.wagtailcore.middleware.SiteMiddleware',
|
"wagtail.wagtailcore.middleware.SiteMiddleware",
|
||||||
'wagtail.wagtailredirects.middleware.RedirectMiddleware',
|
"wagtail.wagtailredirects.middleware.RedirectMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'cof.urls'
|
ROOT_URLCONF = "cof.urls"
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
'DIRS': [],
|
"DIRS": [],
|
||||||
'APP_DIRS': True,
|
"APP_DIRS": True,
|
||||||
'OPTIONS': {
|
"OPTIONS": {
|
||||||
'context_processors': [
|
"context_processors": [
|
||||||
'django.template.context_processors.debug',
|
"django.template.context_processors.debug",
|
||||||
'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.template.context_processors.i18n',
|
"django.template.context_processors.i18n",
|
||||||
'django.template.context_processors.media',
|
"django.template.context_processors.media",
|
||||||
'django.template.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.auth.context_processors.temporary_auth',
|
"kfet.auth.context_processors.temporary_auth",
|
||||||
'kfet.context_processors.config',
|
"kfet.context_processors.config",
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
"default": {
|
||||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
"ENGINE": "django.db.backends.postgresql_psycopg2",
|
||||||
'NAME': DBNAME,
|
"NAME": DBNAME,
|
||||||
'USER': DBUSER,
|
"USER": DBUSER,
|
||||||
'PASSWORD': DBPASSWD,
|
"PASSWORD": DBPASSWD,
|
||||||
'HOST': os.environ.get('DBHOST', 'localhost'),
|
"HOST": os.environ.get("DBHOST", "localhost"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,9 +153,9 @@ DATABASES = {
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = 'fr-fr'
|
LANGUAGE_CODE = "fr-fr"
|
||||||
|
|
||||||
TIME_ZONE = 'Europe/Paris'
|
TIME_ZONE = "Europe/Paris"
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
|
@ -173,35 +167,35 @@ USE_TZ = True
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
GRAPPELLI_ADMIN_HEADLINE = "GestioCOF"
|
GRAPPELLI_ADMIN_HEADLINE = "GestioCOF"
|
||||||
GRAPPELLI_ADMIN_TITLE = "<a href=\"/\">GestioCOF</a>"
|
GRAPPELLI_ADMIN_TITLE = '<a href="/">GestioCOF</a>'
|
||||||
|
|
||||||
MAIL_DATA = {
|
MAIL_DATA = {
|
||||||
'petits_cours': {
|
"petits_cours": {
|
||||||
'FROM': "Le COF <cof@ens.fr>",
|
"FROM": "Le COF <cof@ens.fr>",
|
||||||
'BCC': "archivescof@gmail.com",
|
"BCC": "archivescof@gmail.com",
|
||||||
'REPLYTO': "cof@ens.fr"},
|
"REPLYTO": "cof@ens.fr",
|
||||||
'rappels': {
|
},
|
||||||
'FROM': 'Le BdA <bda@ens.fr>',
|
"rappels": {"FROM": "Le BdA <bda@ens.fr>", "REPLYTO": "Le BdA <bda@ens.fr>"},
|
||||||
'REPLYTO': 'Le BdA <bda@ens.fr>'},
|
"revente": {
|
||||||
'revente': {
|
"FROM": "BdA-Revente <bda-revente@ens.fr>",
|
||||||
'FROM': 'BdA-Revente <bda-revente@ens.fr>',
|
"REPLYTO": "BdA-Revente <bda-revente@ens.fr>",
|
||||||
'REPLYTO': 'BdA-Revente <bda-revente@ens.fr>'},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGIN_URL = "cof-login"
|
LOGIN_URL = "cof-login"
|
||||||
LOGIN_REDIRECT_URL = "home"
|
LOGIN_REDIRECT_URL = "home"
|
||||||
|
|
||||||
CAS_SERVER_URL = 'https://cas.eleves.ens.fr/'
|
CAS_SERVER_URL = "https://cas.eleves.ens.fr/"
|
||||||
CAS_VERSION = '3'
|
CAS_VERSION = "3"
|
||||||
CAS_LOGIN_MSG = None
|
CAS_LOGIN_MSG = None
|
||||||
CAS_IGNORE_REFERER = True
|
CAS_IGNORE_REFERER = True
|
||||||
CAS_REDIRECT_URL = '/'
|
CAS_REDIRECT_URL = "/"
|
||||||
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
|
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.auth.backends.GenericBackend',
|
"kfet.auth.backends.GenericBackend",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -214,21 +208,16 @@ AUTHENTICATION_BACKENDS = (
|
||||||
NOCAPTCHA = True
|
NOCAPTCHA = True
|
||||||
RECAPTCHA_USE_SSL = True
|
RECAPTCHA_USE_SSL = True
|
||||||
|
|
||||||
CORS_ORIGIN_WHITELIST = (
|
CORS_ORIGIN_WHITELIST = ("bda.ens.fr", "www.bda.ens.fr" "cof.ens.fr", "www.cof.ens.fr")
|
||||||
'bda.ens.fr',
|
|
||||||
'www.bda.ens.fr'
|
|
||||||
'cof.ens.fr',
|
|
||||||
'www.cof.ens.fr',
|
|
||||||
)
|
|
||||||
|
|
||||||
# Cache settings
|
# Cache settings
|
||||||
|
|
||||||
CACHES = {
|
CACHES = {
|
||||||
'default': {
|
"default": {
|
||||||
'BACKEND': 'redis_cache.RedisCache',
|
"BACKEND": "redis_cache.RedisCache",
|
||||||
'LOCATION': 'redis://:{passwd}@{host}:{port}/db'
|
"LOCATION": "redis://:{passwd}@{host}:{port}/db".format(
|
||||||
.format(passwd=REDIS_PASSWD, host=REDIS_HOST,
|
passwd=REDIS_PASSWD, host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB
|
||||||
port=REDIS_PORT, db=REDIS_DB),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,20 +228,25 @@ CHANNEL_LAYERS = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "asgi_redis.RedisChannelLayer",
|
"BACKEND": "asgi_redis.RedisChannelLayer",
|
||||||
"CONFIG": {
|
"CONFIG": {
|
||||||
"hosts": [(
|
"hosts": [
|
||||||
"redis://:{passwd}@{host}:{port}/{db}"
|
(
|
||||||
.format(passwd=REDIS_PASSWD, host=REDIS_HOST,
|
"redis://:{passwd}@{host}:{port}/{db}".format(
|
||||||
port=REDIS_PORT, db=REDIS_DB)
|
passwd=REDIS_PASSWD,
|
||||||
)],
|
host=REDIS_HOST,
|
||||||
|
port=REDIS_PORT,
|
||||||
|
db=REDIS_DB,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"ROUTING": "cof.routing.routing",
|
"ROUTING": "cof.routing.routing",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FORMAT_MODULE_PATH = 'cof.locale'
|
FORMAT_MODULE_PATH = "cof.locale"
|
||||||
|
|
||||||
# Wagtail settings
|
# Wagtail settings
|
||||||
|
|
||||||
WAGTAIL_SITE_NAME = 'GestioCOF'
|
WAGTAIL_SITE_NAME = "GestioCOF"
|
||||||
WAGTAIL_ENABLE_UPDATE_CHECK = False
|
WAGTAIL_ENABLE_UPDATE_CHECK = False
|
||||||
TAGGIT_CASE_INSENSITIVE = True
|
TAGGIT_CASE_INSENSITIVE = True
|
||||||
|
|
|
@ -6,32 +6,30 @@ 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, TESTING
|
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:
|
if TESTING:
|
||||||
PASSWORD_HASHERS = [
|
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
|
||||||
'django.contrib.auth.hashers.MD5PasswordHasher',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Apache static/media config
|
# Apache static/media config
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = "/static/"
|
||||||
STATIC_ROOT = '/srv/gestiocof/static/'
|
STATIC_ROOT = "/srv/gestiocof/static/"
|
||||||
|
|
||||||
MEDIA_ROOT = '/srv/gestiocof/media/'
|
MEDIA_ROOT = "/srv/gestiocof/media/"
|
||||||
MEDIA_URL = '/media/'
|
MEDIA_URL = "/media/"
|
||||||
|
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Debug tool bar
|
# Debug tool bar
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
|
|
||||||
def show_toolbar(request):
|
def show_toolbar(request):
|
||||||
"""
|
"""
|
||||||
On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar
|
On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar
|
||||||
|
@ -41,13 +39,10 @@ def show_toolbar(request):
|
||||||
"""
|
"""
|
||||||
return DEBUG
|
return DEBUG
|
||||||
|
|
||||||
|
|
||||||
if not TESTING:
|
if not TESTING:
|
||||||
INSTALLED_APPS += ["debug_toolbar", "debug_panel"]
|
INSTALLED_APPS += ["debug_toolbar", "debug_panel"]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = ["debug_panel.middleware.DebugPanelMiddleware"] + MIDDLEWARE
|
||||||
"debug_panel.middleware.DebugPanelMiddleware"
|
|
||||||
] + MIDDLEWARE
|
|
||||||
|
|
||||||
DEBUG_TOOLBAR_CONFIG = {
|
DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": show_toolbar}
|
||||||
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,21 +8,16 @@ import os
|
||||||
from .dev import * # NOQA
|
from .dev import * # NOQA
|
||||||
from .dev import BASE_DIR
|
from .dev import BASE_DIR
|
||||||
|
|
||||||
|
|
||||||
# Use sqlite for local development
|
# Use sqlite for local development
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
"NAME": os.path.join(BASE_DIR, "db.sqlite3")
|
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Use the default cache backend for local development
|
# Use the default cache backend for local development
|
||||||
CACHES = {
|
CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}}
|
||||||
"default": {
|
|
||||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Use the default in memory asgi backend for local development
|
# Use the default in memory asgi backend for local development
|
||||||
CHANNEL_LAYERS = {
|
CHANNEL_LAYERS = {
|
||||||
|
|
|
@ -8,21 +8,13 @@ import os
|
||||||
from .common import * # NOQA
|
from .common import * # NOQA
|
||||||
from .common import BASE_DIR, import_secret
|
from .common import BASE_DIR, import_secret
|
||||||
|
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
|
||||||
ALLOWED_HOSTS = [
|
ALLOWED_HOSTS = ["cof.ens.fr", "www.cof.ens.fr", "dev.cof.ens.fr"]
|
||||||
"cof.ens.fr",
|
|
||||||
"www.cof.ens.fr",
|
|
||||||
"dev.cof.ens.fr"
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
STATIC_ROOT = os.path.join(
|
STATIC_ROOT = os.path.join(
|
||||||
os.path.dirname(os.path.dirname(BASE_DIR)),
|
os.path.dirname(os.path.dirname(BASE_DIR)), "public", "gestion", "static"
|
||||||
"public",
|
|
||||||
"gestion",
|
|
||||||
"static",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
STATIC_URL = "/gestion/static/"
|
STATIC_URL = "/gestion/static/"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
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"
|
EMAIL_HOST = "localhost"
|
||||||
|
|
161
cof/urls.py
161
cof/urls.py
|
@ -6,111 +6,130 @@ 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
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.views.generic.base import TemplateView
|
|
||||||
from django.contrib.auth import views as django_views
|
from django.contrib.auth import views as django_views
|
||||||
|
from django.views.generic.base import TemplateView
|
||||||
from django_cas_ng import views as django_cas_views
|
from django_cas_ng import views as django_cas_views
|
||||||
|
|
||||||
from wagtail.wagtailadmin import urls as wagtailadmin_urls
|
from wagtail.wagtailadmin import urls as wagtailadmin_urls
|
||||||
from wagtail.wagtailcore import urls as wagtail_urls
|
from wagtail.wagtailcore import urls as wagtail_urls
|
||||||
from wagtail.wagtaildocs import urls as wagtaildocs_urls
|
from wagtail.wagtaildocs import urls as wagtaildocs_urls
|
||||||
|
|
||||||
from gestioncof import views as gestioncof_views, csv_views
|
from gestioncof import csv_views, views as gestioncof_views
|
||||||
from gestioncof.urls import export_patterns, petitcours_patterns, \
|
|
||||||
surveys_patterns, events_patterns, calendar_patterns, \
|
|
||||||
clubs_patterns
|
|
||||||
from gestioncof.autocomplete import autocomplete
|
from gestioncof.autocomplete import autocomplete
|
||||||
|
from gestioncof.urls import (
|
||||||
|
calendar_patterns,
|
||||||
|
clubs_patterns,
|
||||||
|
events_patterns,
|
||||||
|
export_patterns,
|
||||||
|
petitcours_patterns,
|
||||||
|
surveys_patterns,
|
||||||
|
)
|
||||||
|
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Page d'accueil
|
# Page d'accueil
|
||||||
url(r'^$', gestioncof_views.home, name='home'),
|
url(r"^$", gestioncof_views.home, name="home"),
|
||||||
# Le BdA
|
# Le BdA
|
||||||
url(r'^bda/', include('bda.urls')),
|
url(r"^bda/", include("bda.urls")),
|
||||||
# Les exports
|
# Les exports
|
||||||
url(r'^export/', include(export_patterns)),
|
url(r"^export/", include(export_patterns)),
|
||||||
# Les petits cours
|
# Les petits cours
|
||||||
url(r'^petitcours/', include(petitcours_patterns)),
|
url(r"^petitcours/", include(petitcours_patterns)),
|
||||||
# Les sondages
|
# Les sondages
|
||||||
url(r'^survey/', include(surveys_patterns)),
|
url(r"^survey/", include(surveys_patterns)),
|
||||||
# Evenements
|
# Evenements
|
||||||
url(r'^event/', include(events_patterns)),
|
url(r"^event/", include(events_patterns)),
|
||||||
# Calendrier
|
# Calendrier
|
||||||
url(r'^calendar/', include(calendar_patterns)),
|
url(r"^calendar/", include(calendar_patterns)),
|
||||||
# Clubs
|
# Clubs
|
||||||
url(r'^clubs/', include(clubs_patterns)),
|
url(r"^clubs/", include(clubs_patterns)),
|
||||||
# Authentification
|
# Authentification
|
||||||
url(r'^cof/denied$', TemplateView.as_view(template_name='cof-denied.html'),
|
url(
|
||||||
name="cof-denied"),
|
r"^cof/denied$",
|
||||||
url(r'^cas/login$', django_cas_views.login, name="cas_login_view"),
|
TemplateView.as_view(template_name="cof-denied.html"),
|
||||||
url(r'^cas/logout$', django_cas_views.logout),
|
name="cof-denied",
|
||||||
url(r'^outsider/login$', gestioncof_views.login_ext,
|
),
|
||||||
name="ext_login_view"),
|
url(r"^cas/login$", django_cas_views.login, name="cas_login_view"),
|
||||||
url(r'^outsider/logout$', django_views.logout, {'next_page': 'home'}),
|
url(r"^cas/logout$", django_cas_views.logout),
|
||||||
url(r'^login$', gestioncof_views.login, name="cof-login"),
|
url(r"^outsider/login$", gestioncof_views.login_ext, name="ext_login_view"),
|
||||||
url(r'^logout$', gestioncof_views.logout, name="cof-logout"),
|
url(r"^outsider/logout$", django_views.logout, {"next_page": "home"}),
|
||||||
|
url(r"^login$", gestioncof_views.login, name="cof-login"),
|
||||||
|
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, name="profile"),
|
||||||
name='profile'),
|
url(
|
||||||
url(r'^outsider/password-change$', django_views.password_change,
|
r"^outsider/password-change$",
|
||||||
name='password_change'),
|
django_views.password_change,
|
||||||
url(r'^outsider/password-change-done$',
|
name="password_change",
|
||||||
|
),
|
||||||
|
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"),
|
||||||
name='registration'),
|
url(
|
||||||
url(r'^registration/clipper/(?P<login_clipper>[\w-]+)/'
|
r"^registration/clipper/(?P<login_clipper>[\w-]+)/" r"(?P<fullname>.*)$",
|
||||||
r'(?P<fullname>.*)$',
|
gestioncof_views.registration_form2,
|
||||||
gestioncof_views.registration_form2, name="clipper-registration"),
|
name="clipper-registration",
|
||||||
url(r'^registration/user/(?P<username>.+)$',
|
),
|
||||||
gestioncof_views.registration_form2, name="user-registration"),
|
url(
|
||||||
url(r'^registration/empty$', gestioncof_views.registration_form2,
|
r"^registration/user/(?P<username>.+)$",
|
||||||
name="empty-registration"),
|
gestioncof_views.registration_form2,
|
||||||
|
name="user-registration",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^registration/empty$",
|
||||||
|
gestioncof_views.registration_form2,
|
||||||
|
name="empty-registration",
|
||||||
|
),
|
||||||
# Autocompletion
|
# Autocompletion
|
||||||
url(r'^autocomplete/registration$', autocomplete,
|
url(
|
||||||
name="cof.registration.autocomplete"),
|
r"^autocomplete/registration$",
|
||||||
url(r'^user/autocomplete$', gestioncof_views.user_autocomplete,
|
autocomplete,
|
||||||
name='cof-user-autocomplete'),
|
name="cof.registration.autocomplete",
|
||||||
|
),
|
||||||
|
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")),
|
||||||
url(r'^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/',
|
url(
|
||||||
|
r"^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/",
|
||||||
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)),
|
||||||
# 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, name="utile_cof"),
|
||||||
name='utile_cof'),
|
url(r"^utile_bda$", gestioncof_views.utile_bda, name="utile_bda"),
|
||||||
url(r'^utile_bda$', gestioncof_views.utile_bda,
|
url(r"^utile_bda/bda_diff$", gestioncof_views.liste_bdadiff, name="ml_diffbda"),
|
||||||
name='utile_bda'),
|
url(r"^utile_cof/diff_cof$", gestioncof_views.liste_diffcof, name="ml_diffcof"),
|
||||||
url(r'^utile_bda/bda_diff$', gestioncof_views.liste_bdadiff,
|
url(
|
||||||
name="ml_diffbda"),
|
r"^utile_bda/bda_revente$",
|
||||||
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof,
|
gestioncof_views.liste_bdarevente,
|
||||||
name='ml_diffcof'),
|
name="ml_bda_revente",
|
||||||
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente,
|
),
|
||||||
name="ml_bda_revente"),
|
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"),
|
||||||
name='config.edit'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if 'debug_toolbar' in settings.INSTALLED_APPS:
|
if "debug_toolbar" in settings.INSTALLED_APPS:
|
||||||
import debug_toolbar
|
import debug_toolbar
|
||||||
urlpatterns += [
|
|
||||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
urlpatterns += [url(r"^__debug__/", include(debug_toolbar.urls))]
|
||||||
]
|
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
# Si on est en production, MEDIA_ROOT est servi par Apache.
|
# Si on est en production, MEDIA_ROOT est servi par Apache.
|
||||||
# Il faut dire à Django de servir MEDIA_ROOT lui-même en développement.
|
# Il faut dire à Django de servir MEDIA_ROOT lui-même en développement.
|
||||||
urlpatterns += static(settings.MEDIA_URL,
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
document_root=settings.MEDIA_ROOT)
|
|
||||||
|
|
||||||
# Wagtail for uncatched
|
# Wagtail for uncatched
|
||||||
urlpatterns += [
|
urlpatterns += [url(r"", include(wagtail_urls))]
|
||||||
url(r'', include(wagtail_urls)),
|
|
||||||
]
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
default_app_config = 'gestioncof.apps.GestioncofConfig'
|
default_app_config = "gestioncof.apps.GestioncofConfig"
|
||||||
|
|
|
@ -1,23 +1,35 @@
|
||||||
|
from dal.autocomplete import ModelSelect2
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from gestioncof.models import SurveyQuestionAnswer, SurveyQuestion, \
|
|
||||||
CofProfile, EventOption, EventOptionChoice, Event, Club, \
|
|
||||||
Survey, EventCommentField, EventRegistration
|
|
||||||
from gestioncof.petits_cours_models import PetitCoursDemande, \
|
|
||||||
PetitCoursSubject, PetitCoursAbility, PetitCoursAttribution, \
|
|
||||||
PetitCoursAttributionCounter
|
|
||||||
from django.contrib.auth.models import User, Group, Permission
|
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
|
from django.contrib.auth.models import Group, Permission, User
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from dal.autocomplete import ModelSelect2
|
from gestioncof.models import (
|
||||||
|
Club,
|
||||||
|
CofProfile,
|
||||||
|
Event,
|
||||||
|
EventCommentField,
|
||||||
|
EventOption,
|
||||||
|
EventOptionChoice,
|
||||||
|
EventRegistration,
|
||||||
|
Survey,
|
||||||
|
SurveyQuestion,
|
||||||
|
SurveyQuestionAnswer,
|
||||||
|
)
|
||||||
|
from gestioncof.petits_cours_models import (
|
||||||
|
PetitCoursAbility,
|
||||||
|
PetitCoursAttribution,
|
||||||
|
PetitCoursAttributionCounter,
|
||||||
|
PetitCoursDemande,
|
||||||
|
PetitCoursSubject,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_link_field(target_model='', field='', link_text=str,
|
def add_link_field(target_model="", field="", link_text=str, desc_text=str):
|
||||||
desc_text=str):
|
|
||||||
def add_link(cls):
|
def add_link(cls):
|
||||||
reverse_name = target_model or cls.model.__name__.lower()
|
reverse_name = target_model or cls.model.__name__.lower()
|
||||||
|
|
||||||
|
@ -28,14 +40,14 @@ def add_link_field(target_model='', field='', link_text=str,
|
||||||
if not link_obj.id:
|
if not link_obj.id:
|
||||||
return ""
|
return ""
|
||||||
url = reverse(reverse_path, args=(link_obj.id,))
|
url = reverse(reverse_path, args=(link_obj.id,))
|
||||||
return mark_safe("<a href='%s'>%s</a>"
|
return mark_safe("<a href='%s'>%s</a>" % (url, link_text(link_obj)))
|
||||||
% (url, link_text(link_obj)))
|
|
||||||
link.allow_tags = True
|
link.allow_tags = True
|
||||||
link.short_description = desc_text(reverse_name + ' link')
|
link.short_description = desc_text(reverse_name + " link")
|
||||||
cls.link = link
|
cls.link = link
|
||||||
cls.readonly_fields =\
|
cls.readonly_fields = list(getattr(cls, "readonly_fields", [])) + ["link"]
|
||||||
list(getattr(cls, 'readonly_fields', [])) + ['link']
|
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
return add_link
|
return add_link
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,32 +55,28 @@ class SurveyQuestionAnswerInline(admin.TabularInline):
|
||||||
model = SurveyQuestionAnswer
|
model = SurveyQuestionAnswer
|
||||||
|
|
||||||
|
|
||||||
@add_link_field(desc_text=lambda x: "Réponses",
|
@add_link_field(
|
||||||
link_text=lambda x: "Éditer les réponses")
|
desc_text=lambda x: "Réponses", link_text=lambda x: "Éditer les réponses"
|
||||||
|
)
|
||||||
class SurveyQuestionInline(admin.TabularInline):
|
class SurveyQuestionInline(admin.TabularInline):
|
||||||
model = SurveyQuestion
|
model = SurveyQuestion
|
||||||
|
|
||||||
|
|
||||||
class SurveyQuestionAdmin(admin.ModelAdmin):
|
class SurveyQuestionAdmin(admin.ModelAdmin):
|
||||||
search_fields = ('survey__title', 'answer')
|
search_fields = ("survey__title", "answer")
|
||||||
inlines = [
|
inlines = [SurveyQuestionAnswerInline]
|
||||||
SurveyQuestionAnswerInline,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class SurveyAdmin(admin.ModelAdmin):
|
class SurveyAdmin(admin.ModelAdmin):
|
||||||
search_fields = ('title', 'details')
|
search_fields = ("title", "details")
|
||||||
inlines = [
|
inlines = [SurveyQuestionInline]
|
||||||
SurveyQuestionInline,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class EventOptionChoiceInline(admin.TabularInline):
|
class EventOptionChoiceInline(admin.TabularInline):
|
||||||
model = EventOptionChoice
|
model = EventOptionChoice
|
||||||
|
|
||||||
|
|
||||||
@add_link_field(desc_text=lambda x: "Choix",
|
@add_link_field(desc_text=lambda x: "Choix", link_text=lambda x: "Éditer les choix")
|
||||||
link_text=lambda x: "Éditer les choix")
|
|
||||||
class EventOptionInline(admin.TabularInline):
|
class EventOptionInline(admin.TabularInline):
|
||||||
model = EventOption
|
model = EventOption
|
||||||
|
|
||||||
|
@ -78,18 +86,13 @@ class EventCommentFieldInline(admin.TabularInline):
|
||||||
|
|
||||||
|
|
||||||
class EventOptionAdmin(admin.ModelAdmin):
|
class EventOptionAdmin(admin.ModelAdmin):
|
||||||
search_fields = ('event__title', 'name')
|
search_fields = ("event__title", "name")
|
||||||
inlines = [
|
inlines = [EventOptionChoiceInline]
|
||||||
EventOptionChoiceInline,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class EventAdmin(admin.ModelAdmin):
|
class EventAdmin(admin.ModelAdmin):
|
||||||
search_fields = ('title', 'location', 'description')
|
search_fields = ("title", "location", "description")
|
||||||
inlines = [
|
inlines = [EventOptionInline, EventCommentFieldInline]
|
||||||
EventOptionInline,
|
|
||||||
EventCommentFieldInline,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class CofProfileInline(admin.StackedInline):
|
class CofProfileInline(admin.StackedInline):
|
||||||
|
@ -98,10 +101,9 @@ class CofProfileInline(admin.StackedInline):
|
||||||
|
|
||||||
|
|
||||||
class FkeyLookup(object):
|
class FkeyLookup(object):
|
||||||
def __init__(self, fkeydecl, short_description=None,
|
def __init__(self, fkeydecl, short_description=None, admin_order_field=None):
|
||||||
admin_order_field=None):
|
self.fk, fkattrs = fkeydecl.split("__", 1)
|
||||||
self.fk, fkattrs = fkeydecl.split('__', 1)
|
self.fkattrs = fkattrs.split("__")
|
||||||
self.fkattrs = fkattrs.split('__')
|
|
||||||
|
|
||||||
self.short_description = short_description or self.fkattrs[-1]
|
self.short_description = short_description or self.fkattrs[-1]
|
||||||
self.admin_order_field = admin_order_field or fkeydecl
|
self.admin_order_field = admin_order_field or fkeydecl
|
||||||
|
@ -126,19 +128,19 @@ def ProfileInfo(field, short_description, boolean=False):
|
||||||
return getattr(self.profile, field)
|
return getattr(self.profile, field)
|
||||||
except CofProfile.DoesNotExist:
|
except CofProfile.DoesNotExist:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
getter.short_description = short_description
|
getter.short_description = short_description
|
||||||
getter.boolean = boolean
|
getter.boolean = boolean
|
||||||
return getter
|
return getter
|
||||||
|
|
||||||
User.profile_login_clipper = FkeyLookup("profile__login_clipper",
|
|
||||||
"Login clipper")
|
User.profile_login_clipper = FkeyLookup("profile__login_clipper", "Login clipper")
|
||||||
User.profile_phone = ProfileInfo("phone", "Téléphone")
|
User.profile_phone = ProfileInfo("phone", "Téléphone")
|
||||||
User.profile_occupation = ProfileInfo("occupation", "Occupation")
|
User.profile_occupation = ProfileInfo("occupation", "Occupation")
|
||||||
User.profile_departement = ProfileInfo("departement", "Departement")
|
User.profile_departement = ProfileInfo("departement", "Departement")
|
||||||
User.profile_mailing_cof = ProfileInfo("mailing_cof", "ML COF", True)
|
User.profile_mailing_cof = ProfileInfo("mailing_cof", "ML COF", True)
|
||||||
User.profile_mailing_bda = ProfileInfo("mailing_bda", "ML BdA", True)
|
User.profile_mailing_bda = ProfileInfo("mailing_bda", "ML BdA", True)
|
||||||
User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente",
|
User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente", "ML BdA-R", True)
|
||||||
"ML BdA-R", True)
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfileAdmin(UserAdmin):
|
class UserProfileAdmin(UserAdmin):
|
||||||
|
@ -147,7 +149,8 @@ class UserProfileAdmin(UserAdmin):
|
||||||
return obj.profile.is_buro
|
return obj.profile.is_buro
|
||||||
except CofProfile.DoesNotExist:
|
except CofProfile.DoesNotExist:
|
||||||
return False
|
return False
|
||||||
is_buro.short_description = 'Membre du Buro'
|
|
||||||
|
is_buro.short_description = "Membre du Buro"
|
||||||
is_buro.boolean = True
|
is_buro.boolean = True
|
||||||
|
|
||||||
def is_cof(self, obj):
|
def is_cof(self, obj):
|
||||||
|
@ -155,27 +158,33 @@ class UserProfileAdmin(UserAdmin):
|
||||||
return obj.profile.is_cof
|
return obj.profile.is_cof
|
||||||
except CofProfile.DoesNotExist:
|
except CofProfile.DoesNotExist:
|
||||||
return False
|
return False
|
||||||
is_cof.short_description = 'Membre du COF'
|
|
||||||
|
is_cof.short_description = "Membre du COF"
|
||||||
is_cof.boolean = True
|
is_cof.boolean = True
|
||||||
|
|
||||||
list_display = (
|
list_display = UserAdmin.list_display + (
|
||||||
UserAdmin.list_display
|
"profile_login_clipper",
|
||||||
+ ('profile_login_clipper', 'profile_phone', 'profile_occupation',
|
"profile_phone",
|
||||||
'profile_mailing_cof', 'profile_mailing_bda',
|
"profile_occupation",
|
||||||
'profile_mailing_bda_revente', 'is_cof', 'is_buro', )
|
"profile_mailing_cof",
|
||||||
|
"profile_mailing_bda",
|
||||||
|
"profile_mailing_bda_revente",
|
||||||
|
"is_cof",
|
||||||
|
"is_buro",
|
||||||
)
|
)
|
||||||
list_display_links = ('username', 'email', 'first_name', 'last_name')
|
list_display_links = ("username", "email", "first_name", "last_name")
|
||||||
list_filter = UserAdmin.list_filter \
|
list_filter = UserAdmin.list_filter + (
|
||||||
+ ('profile__is_cof', 'profile__is_buro', 'profile__mailing_cof',
|
"profile__is_cof",
|
||||||
'profile__mailing_bda')
|
"profile__is_buro",
|
||||||
search_fields = UserAdmin.search_fields + ('profile__phone',)
|
"profile__mailing_cof",
|
||||||
inlines = [
|
"profile__mailing_bda",
|
||||||
CofProfileInline,
|
)
|
||||||
]
|
search_fields = UserAdmin.search_fields + ("profile__phone",)
|
||||||
|
inlines = [CofProfileInline]
|
||||||
|
|
||||||
staff_fieldsets = [
|
staff_fieldsets = [
|
||||||
(None, {'fields': ['username', 'password']}),
|
(None, {"fields": ["username", "password"]}),
|
||||||
(_('Personal info'), {'fields': ['first_name', 'last_name', 'email']}),
|
(_("Personal info"), {"fields": ["first_name", "last_name", "email"]}),
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_fieldsets(self, request, user=None):
|
def get_fieldsets(self, request, user=None):
|
||||||
|
@ -184,15 +193,15 @@ class UserProfileAdmin(UserAdmin):
|
||||||
return super().get_fieldsets(request, user)
|
return super().get_fieldsets(request, user)
|
||||||
|
|
||||||
def save_model(self, request, user, form, change):
|
def save_model(self, request, user, form, change):
|
||||||
cof_group, created = Group.objects.get_or_create(name='COF')
|
cof_group, created = Group.objects.get_or_create(name="COF")
|
||||||
if created:
|
if created:
|
||||||
# Si le groupe COF n'était pas déjà dans la bdd
|
# Si le groupe COF n'était pas déjà dans la bdd
|
||||||
# On lui assigne les bonnes permissions
|
# On lui assigne les bonnes permissions
|
||||||
perms = Permission.objects.filter(
|
perms = Permission.objects.filter(
|
||||||
Q(content_type__app_label='gestioncof')
|
Q(content_type__app_label="gestioncof")
|
||||||
| Q(content_type__app_label='bda')
|
| Q(content_type__app_label="bda")
|
||||||
| (Q(content_type__app_label='auth')
|
| (Q(content_type__app_label="auth") & Q(content_type__model="user"))
|
||||||
& Q(content_type__model='user')))
|
)
|
||||||
cof_group.permissions = perms
|
cof_group.permissions = perms
|
||||||
# On y associe les membres du Burô
|
# On y associe les membres du Burô
|
||||||
cof_group.user_set = User.objects.filter(profile__is_buro=True)
|
cof_group.user_set = User.objects.filter(profile__is_buro=True)
|
||||||
|
@ -214,72 +223,97 @@ def user_str(self):
|
||||||
return "{} ({})".format(self.get_full_name(), self.username)
|
return "{} ({})".format(self.get_full_name(), self.username)
|
||||||
else:
|
else:
|
||||||
return self.username
|
return self.username
|
||||||
|
|
||||||
|
|
||||||
User.__str__ = user_str
|
User.__str__ = user_str
|
||||||
|
|
||||||
|
|
||||||
class EventRegistrationAdminForm(forms.ModelForm):
|
class EventRegistrationAdminForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
widgets = {
|
widgets = {"user": ModelSelect2(url="cof-user-autocomplete")}
|
||||||
'user': ModelSelect2(url='cof-user-autocomplete'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class EventRegistrationAdmin(admin.ModelAdmin):
|
class EventRegistrationAdmin(admin.ModelAdmin):
|
||||||
form = EventRegistrationAdminForm
|
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__email', 'event__title')
|
"user__username",
|
||||||
|
"user__first_name",
|
||||||
|
"user__last_name",
|
||||||
|
"user__email",
|
||||||
|
"event__title",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAbilityAdmin(admin.ModelAdmin):
|
class PetitCoursAbilityAdmin(admin.ModelAdmin):
|
||||||
list_display = ('user', 'matiere', 'niveau', 'agrege')
|
list_display = ("user", "matiere", "niveau", "agrege")
|
||||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
search_fields = (
|
||||||
'user__email', 'matiere__name', 'niveau')
|
"user__username",
|
||||||
list_filter = ('matiere', 'niveau', 'agrege')
|
"user__first_name",
|
||||||
|
"user__last_name",
|
||||||
|
"user__email",
|
||||||
|
"matiere__name",
|
||||||
|
"niveau",
|
||||||
|
)
|
||||||
|
list_filter = ("matiere", "niveau", "agrege")
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAttributionAdmin(admin.ModelAdmin):
|
class PetitCoursAttributionAdmin(admin.ModelAdmin):
|
||||||
list_display = ('user', 'demande', 'matiere', 'rank', )
|
list_display = ("user", "demande", "matiere", "rank")
|
||||||
search_fields = ('user__username', 'matiere__name')
|
search_fields = ("user__username", "matiere__name")
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAttributionCounterAdmin(admin.ModelAdmin):
|
class PetitCoursAttributionCounterAdmin(admin.ModelAdmin):
|
||||||
list_display = ('user', 'matiere', 'count', )
|
list_display = ("user", "matiere", "count")
|
||||||
list_filter = ('matiere',)
|
list_filter = ("matiere",)
|
||||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
search_fields = (
|
||||||
'user__email', 'matiere__name')
|
"user__username",
|
||||||
actions = ['reset', ]
|
"user__first_name",
|
||||||
|
"user__last_name",
|
||||||
|
"user__email",
|
||||||
|
"matiere__name",
|
||||||
|
)
|
||||||
|
actions = ["reset"]
|
||||||
actions_on_bottom = True
|
actions_on_bottom = True
|
||||||
|
|
||||||
def reset(self, request, queryset):
|
def reset(self, request, queryset):
|
||||||
queryset.update(count=0)
|
queryset.update(count=0)
|
||||||
|
|
||||||
reset.short_description = "Remise à zéro du compteur"
|
reset.short_description = "Remise à zéro du compteur"
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursDemandeAdmin(admin.ModelAdmin):
|
class PetitCoursDemandeAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'email', 'agrege_requis', 'niveau', 'created',
|
list_display = (
|
||||||
'traitee', 'processed')
|
"name",
|
||||||
list_filter = ('traitee', 'niveau')
|
"email",
|
||||||
search_fields = ('name', 'email', 'phone', 'lieu', 'remarques')
|
"agrege_requis",
|
||||||
|
"niveau",
|
||||||
|
"created",
|
||||||
|
"traitee",
|
||||||
|
"processed",
|
||||||
|
)
|
||||||
|
list_filter = ("traitee", "niveau")
|
||||||
|
search_fields = ("name", "email", "phone", "lieu", "remarques")
|
||||||
|
|
||||||
|
|
||||||
class ClubAdminForm(forms.ModelForm):
|
class ClubAdminForm(forms.ModelForm):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
respos = cleaned_data.get('respos')
|
respos = cleaned_data.get("respos")
|
||||||
members = cleaned_data.get('membres')
|
members = cleaned_data.get("membres")
|
||||||
for respo in respos.all():
|
for respo in respos.all():
|
||||||
if respo not in members:
|
if respo not in members:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
"Erreur : le respo %s n'est pas membre du club."
|
"Erreur : le respo %s n'est pas membre du club."
|
||||||
% respo.get_full_name())
|
% respo.get_full_name()
|
||||||
|
)
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
class ClubAdmin(admin.ModelAdmin):
|
class ClubAdmin(admin.ModelAdmin):
|
||||||
list_display = ['name']
|
list_display = ["name"]
|
||||||
form = ClubAdminForm
|
form = ClubAdminForm
|
||||||
|
|
||||||
|
|
||||||
|
@ -294,7 +328,6 @@ admin.site.register(Club, ClubAdmin)
|
||||||
admin.site.register(PetitCoursSubject)
|
admin.site.register(PetitCoursSubject)
|
||||||
admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin)
|
admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin)
|
||||||
admin.site.register(PetitCoursAttribution, PetitCoursAttributionAdmin)
|
admin.site.register(PetitCoursAttribution, PetitCoursAttributionAdmin)
|
||||||
admin.site.register(PetitCoursAttributionCounter,
|
admin.site.register(PetitCoursAttributionCounter, PetitCoursAttributionCounterAdmin)
|
||||||
PetitCoursAttributionCounterAdmin)
|
|
||||||
admin.site.register(PetitCoursDemande, PetitCoursDemandeAdmin)
|
admin.site.register(PetitCoursDemande, PetitCoursDemandeAdmin)
|
||||||
admin.site.register(EventRegistration, EventRegistrationAdmin)
|
admin.site.register(EventRegistration, EventRegistrationAdmin)
|
||||||
|
|
|
@ -2,14 +2,16 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class GestioncofConfig(AppConfig):
|
class GestioncofConfig(AppConfig):
|
||||||
name = 'gestioncof'
|
name = "gestioncof"
|
||||||
verbose_name = "Gestion des adhérents du COF"
|
verbose_name = "Gestion des adhérents du COF"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from . import signals
|
from . import signals # noqa
|
||||||
|
|
||||||
self.register_config()
|
self.register_config()
|
||||||
|
|
||||||
def register_config(self):
|
def register_config(self):
|
||||||
import djconfig
|
import djconfig
|
||||||
from .forms import GestioncofConfigForm
|
from .forms import GestioncofConfigForm
|
||||||
|
|
||||||
djconfig.register(GestioncofConfigForm)
|
djconfig.register(GestioncofConfigForm)
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
|
from django import shortcuts
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.http import Http404
|
||||||
from ldap3 import Connection
|
from ldap3 import Connection
|
||||||
|
|
||||||
from django import shortcuts
|
|
||||||
from django.http import Http404
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from gestioncof.models import CofProfile
|
|
||||||
from gestioncof.decorators import buro_required
|
from gestioncof.decorators import buro_required
|
||||||
|
from gestioncof.models import CofProfile
|
||||||
|
|
||||||
|
|
||||||
class Clipper(object):
|
class Clipper(object):
|
||||||
|
@ -19,68 +18,71 @@ class Clipper(object):
|
||||||
self.clipper = clipper
|
self.clipper = clipper
|
||||||
self.fullname = fullname
|
self.fullname = fullname
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{} ({})".format(self.clipper, self.fullname)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.clipper == other.clipper and self.fullname == other.fullname
|
||||||
|
|
||||||
|
|
||||||
@buro_required
|
@buro_required
|
||||||
def autocomplete(request):
|
def autocomplete(request):
|
||||||
if "q" not in request.GET:
|
if "q" not in request.GET:
|
||||||
raise Http404
|
raise Http404
|
||||||
q = request.GET['q']
|
q = request.GET["q"]
|
||||||
data = {
|
data = {"q": q}
|
||||||
'q': q,
|
|
||||||
}
|
|
||||||
|
|
||||||
queries = {}
|
queries = {}
|
||||||
bits = q.split()
|
bits = q.split()
|
||||||
|
|
||||||
# Fetching data from User and CofProfile tables
|
# Fetching data from User and CofProfile tables
|
||||||
queries['members'] = CofProfile.objects.filter(is_cof=True)
|
queries["members"] = CofProfile.objects.filter(is_cof=True)
|
||||||
queries['users'] = User.objects.filter(profile__is_cof=False)
|
queries["users"] = User.objects.filter(profile__is_cof=False)
|
||||||
for bit in bits:
|
for bit in bits:
|
||||||
queries['members'] = queries['members'].filter(
|
queries["members"] = queries["members"].filter(
|
||||||
Q(user__first_name__icontains=bit)
|
Q(user__first_name__icontains=bit)
|
||||||
| Q(user__last_name__icontains=bit)
|
| Q(user__last_name__icontains=bit)
|
||||||
| Q(user__username__icontains=bit)
|
| Q(user__username__icontains=bit)
|
||||||
| Q(login_clipper__icontains=bit))
|
| Q(login_clipper__icontains=bit)
|
||||||
queries['users'] = queries['users'].filter(
|
)
|
||||||
|
queries["users"] = queries["users"].filter(
|
||||||
Q(first_name__icontains=bit)
|
Q(first_name__icontains=bit)
|
||||||
| Q(last_name__icontains=bit)
|
| Q(last_name__icontains=bit)
|
||||||
| Q(username__icontains=bit))
|
| Q(username__icontains=bit)
|
||||||
queries['members'] = queries['members'].distinct()
|
)
|
||||||
queries['users'] = queries['users'].distinct()
|
queries["members"] = queries["members"].distinct()
|
||||||
|
queries["users"] = queries["users"].distinct()
|
||||||
|
|
||||||
# Clearing redundancies
|
# Clearing redundancies
|
||||||
usernames = (
|
usernames = set(queries["members"].values_list("login_clipper", flat="True")) | set(
|
||||||
set(queries['members'].values_list('login_clipper', flat='True'))
|
queries["users"].values_list("profile__login_clipper", flat="True")
|
||||||
| set(queries['users'].values_list('profile__login_clipper',
|
|
||||||
flat='True'))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fetching data from the SPI
|
# Fetching data from the SPI
|
||||||
if getattr(settings, 'LDAP_SERVER_URL', None):
|
if getattr(settings, "LDAP_SERVER_URL", None):
|
||||||
# Fetching
|
# Fetching
|
||||||
ldap_query = '(&{:s})'.format(''.join(
|
ldap_query = "(&{:s})".format(
|
||||||
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=bit)
|
"".join(
|
||||||
for bit in bits if bit.isalnum()
|
"(|(cn=*{bit:s}*)(uid=*{bit:s}*))".format(bit=bit)
|
||||||
))
|
for bit in bits
|
||||||
|
if bit.isalnum()
|
||||||
|
)
|
||||||
|
)
|
||||||
if ldap_query != "(&)":
|
if ldap_query != "(&)":
|
||||||
# If none of the bits were legal, we do not perform the query
|
# If none of the bits were legal, we do not perform the query
|
||||||
entries = None
|
entries = None
|
||||||
with Connection(settings.LDAP_SERVER_URL) as conn:
|
with Connection(settings.LDAP_SERVER_URL) as conn:
|
||||||
conn.search(
|
conn.search("dc=spi,dc=ens,dc=fr", ldap_query, attributes=["uid", "cn"])
|
||||||
'dc=spi,dc=ens,dc=fr', ldap_query,
|
|
||||||
attributes=['uid', 'cn']
|
|
||||||
)
|
|
||||||
entries = conn.entries
|
entries = conn.entries
|
||||||
# Clearing redundancies
|
# Clearing redundancies
|
||||||
queries['clippers'] = [
|
queries["clippers"] = [
|
||||||
Clipper(entry.uid.value, entry.cn.value)
|
Clipper(entry.uid.value, entry.cn.value)
|
||||||
for entry in entries
|
for entry in entries
|
||||||
if entry.uid.value
|
if entry.uid.value and entry.uid.value not in usernames
|
||||||
and entry.uid.value not in usernames
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Resulting data
|
# Resulting data
|
||||||
data.update(queries)
|
data.update(queries)
|
||||||
data['options'] = sum(len(query) for query in queries)
|
data["options"] = sum(len(query) for query in queries)
|
||||||
|
|
||||||
return shortcuts.render(request, "autocomplete_user.html", data)
|
return shortcuts.render(request, "autocomplete_user.html", data)
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import csv
|
import csv
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
from django.http import HttpResponse, HttpResponseForbidden
|
from django.http import HttpResponse, HttpResponseForbidden
|
||||||
from django.template.defaultfilters import slugify
|
from django.template.defaultfilters import slugify
|
||||||
from django.apps import apps
|
|
||||||
|
|
||||||
|
|
||||||
def export(qs, fields=None):
|
def export(qs, fields=None):
|
||||||
model = qs.model
|
model = qs.model
|
||||||
response = HttpResponse(content_type='text/csv')
|
response = HttpResponse(content_type="text/csv")
|
||||||
response['Content-Disposition'] = 'attachment; filename=%s.csv' \
|
response["Content-Disposition"] = "attachment; filename=%s.csv" % slugify(
|
||||||
% slugify(model.__name__)
|
model.__name__
|
||||||
|
)
|
||||||
writer = csv.writer(response)
|
writer = csv.writer(response)
|
||||||
# Write headers to CSV file
|
# Write headers to CSV file
|
||||||
if fields:
|
if fields:
|
||||||
|
@ -32,8 +34,9 @@ def export(qs, fields=None):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def admin_list_export(request, model_name, app_label, queryset=None,
|
def admin_list_export(
|
||||||
fields=None, list_display=True):
|
request, model_name, app_label, queryset=None, fields=None, list_display=True
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Put the following line in your urls.py BEFORE your admin include
|
Put the following line in your urls.py BEFORE your admin include
|
||||||
(r'^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/',
|
(r'^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/',
|
||||||
|
|
|
@ -5,9 +5,10 @@ def is_cof(user):
|
||||||
try:
|
try:
|
||||||
profile = user.profile
|
profile = user.profile
|
||||||
return profile.is_cof
|
return profile.is_cof
|
||||||
except:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
cof_required = user_passes_test(is_cof)
|
cof_required = user_passes_test(is_cof)
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,7 +16,8 @@ def is_buro(user):
|
||||||
try:
|
try:
|
||||||
profile = user.profile
|
profile = user.profile
|
||||||
return profile.is_buro
|
return profile.is_buro
|
||||||
except:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
buro_required = user_passes_test(is_buro)
|
buro_required = user_passes_test(is_buro)
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.forms.widgets import RadioSelect, CheckboxSelectMultiple
|
|
||||||
from django.forms.formsets import BaseFormSet, formset_factory
|
from django.forms.formsets import BaseFormSet, formset_factory
|
||||||
|
from django.forms.widgets import CheckboxSelectMultiple, RadioSelect
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from djconfig.forms import ConfigForm
|
from djconfig.forms import ConfigForm
|
||||||
|
|
||||||
from gestioncof.models import CofProfile, EventCommentValue, \
|
|
||||||
CalendarSubscription, Club
|
|
||||||
from gestioncof.widgets import TriStateCheckbox
|
|
||||||
|
|
||||||
from bda.models import Spectacle
|
from bda.models import Spectacle
|
||||||
|
from gestioncof.models import CalendarSubscription, Club, CofProfile, EventCommentValue
|
||||||
|
from gestioncof.widgets import TriStateCheckbox
|
||||||
|
|
||||||
|
|
||||||
class EventForm(forms.Form):
|
class EventForm(forms.Form):
|
||||||
|
@ -28,31 +25,33 @@ class EventForm(forms.Form):
|
||||||
choices[choice.event_option.id].append(choice.id)
|
choices[choice.event_option.id].append(choice.id)
|
||||||
all_choices = choices
|
all_choices = choices
|
||||||
for option in event.options.all():
|
for option in event.options.all():
|
||||||
choices = [(choice.id, choice.value)
|
choices = [(choice.id, choice.value) for choice in option.choices.all()]
|
||||||
for choice in option.choices.all()]
|
|
||||||
if option.multi_choices:
|
if option.multi_choices:
|
||||||
initial = [] if option.id not in all_choices \
|
initial = [] if option.id not in all_choices else all_choices[option.id]
|
||||||
else all_choices[option.id]
|
|
||||||
field = forms.MultipleChoiceField(
|
field = forms.MultipleChoiceField(
|
||||||
label=option.name,
|
label=option.name,
|
||||||
choices=choices,
|
choices=choices,
|
||||||
widget=CheckboxSelectMultiple,
|
widget=CheckboxSelectMultiple,
|
||||||
required=False,
|
required=False,
|
||||||
initial=initial)
|
initial=initial,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
initial = None if option.id not in all_choices \
|
initial = (
|
||||||
else all_choices[option.id][0]
|
None if option.id not in all_choices else all_choices[option.id][0]
|
||||||
field = forms.ChoiceField(label=option.name,
|
)
|
||||||
choices=choices,
|
field = forms.ChoiceField(
|
||||||
widget=RadioSelect,
|
label=option.name,
|
||||||
required=False,
|
choices=choices,
|
||||||
initial=initial)
|
widget=RadioSelect,
|
||||||
|
required=False,
|
||||||
|
initial=initial,
|
||||||
|
)
|
||||||
field.option_id = option.id
|
field.option_id = option.id
|
||||||
self.fields["option_%d" % option.id] = field
|
self.fields["option_%d" % option.id] = field
|
||||||
|
|
||||||
def choices(self):
|
def choices(self):
|
||||||
for name, value in self.cleaned_data.items():
|
for name, value in self.cleaned_data.items():
|
||||||
if name.startswith('option_'):
|
if name.startswith("option_"):
|
||||||
yield (self.fields[name].option_id, value)
|
yield (self.fields[name].option_id, value)
|
||||||
|
|
||||||
|
|
||||||
|
@ -69,31 +68,33 @@ class SurveyForm(forms.Form):
|
||||||
else:
|
else:
|
||||||
answers[answer.survey_question.id].append(answer.id)
|
answers[answer.survey_question.id].append(answer.id)
|
||||||
for question in survey.questions.all():
|
for question in survey.questions.all():
|
||||||
choices = [(answer.id, answer.answer)
|
choices = [(answer.id, answer.answer) for answer in question.answers.all()]
|
||||||
for answer in question.answers.all()]
|
|
||||||
if question.multi_answers:
|
if question.multi_answers:
|
||||||
initial = [] if question.id not in answers\
|
initial = [] if question.id not in answers else answers[question.id]
|
||||||
else answers[question.id]
|
|
||||||
field = forms.MultipleChoiceField(
|
field = forms.MultipleChoiceField(
|
||||||
label=question.question,
|
label=question.question,
|
||||||
choices=choices,
|
choices=choices,
|
||||||
widget=CheckboxSelectMultiple,
|
widget=CheckboxSelectMultiple,
|
||||||
required=False,
|
required=False,
|
||||||
initial=initial)
|
initial=initial,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
initial = None if question.id not in answers\
|
initial = (
|
||||||
else answers[question.id][0]
|
None if question.id not in answers else answers[question.id][0]
|
||||||
field = forms.ChoiceField(label=question.question,
|
)
|
||||||
choices=choices,
|
field = forms.ChoiceField(
|
||||||
widget=RadioSelect,
|
label=question.question,
|
||||||
required=False,
|
choices=choices,
|
||||||
initial=initial)
|
widget=RadioSelect,
|
||||||
|
required=False,
|
||||||
|
initial=initial,
|
||||||
|
)
|
||||||
field.question_id = question.id
|
field.question_id = question.id
|
||||||
self.fields["question_%d" % question.id] = field
|
self.fields["question_%d" % question.id] = field
|
||||||
|
|
||||||
def answers(self):
|
def answers(self):
|
||||||
for name, value in self.cleaned_data.items():
|
for name, value in self.cleaned_data.items():
|
||||||
if name.startswith('question_'):
|
if name.startswith("question_"):
|
||||||
yield (self.fields[name].question_id, value)
|
yield (self.fields[name].question_id, value)
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,8 +105,7 @@ class SurveyStatusFilterForm(forms.Form):
|
||||||
for question in survey.questions.all():
|
for question in survey.questions.all():
|
||||||
for answer in question.answers.all():
|
for answer in question.answers.all():
|
||||||
name = "question_%d_answer_%d" % (question.id, answer.id)
|
name = "question_%d_answer_%d" % (question.id, answer.id)
|
||||||
if self.is_bound \
|
if self.is_bound and self.data.get(self.add_prefix(name), None):
|
||||||
and self.data.get(self.add_prefix(name), None):
|
|
||||||
initial = self.data.get(self.add_prefix(name), None)
|
initial = self.data.get(self.add_prefix(name), None)
|
||||||
else:
|
else:
|
||||||
initial = "none"
|
initial = "none"
|
||||||
|
@ -114,16 +114,20 @@ class SurveyStatusFilterForm(forms.Form):
|
||||||
choices=[("yes", "yes"), ("no", "no"), ("none", "none")],
|
choices=[("yes", "yes"), ("no", "no"), ("none", "none")],
|
||||||
widget=TriStateCheckbox,
|
widget=TriStateCheckbox,
|
||||||
required=False,
|
required=False,
|
||||||
initial=initial)
|
initial=initial,
|
||||||
|
)
|
||||||
field.question_id = question.id
|
field.question_id = question.id
|
||||||
field.answer_id = answer.id
|
field.answer_id = answer.id
|
||||||
self.fields[name] = field
|
self.fields[name] = field
|
||||||
|
|
||||||
def filters(self):
|
def filters(self):
|
||||||
for name, value in self.cleaned_data.items():
|
for name, value in self.cleaned_data.items():
|
||||||
if name.startswith('question_'):
|
if name.startswith("question_"):
|
||||||
yield (self.fields[name].question_id,
|
yield (
|
||||||
self.fields[name].answer_id, value)
|
self.fields[name].question_id,
|
||||||
|
self.fields[name].answer_id,
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EventStatusFilterForm(forms.Form):
|
class EventStatusFilterForm(forms.Form):
|
||||||
|
@ -133,8 +137,7 @@ class EventStatusFilterForm(forms.Form):
|
||||||
for option in event.options.all():
|
for option in event.options.all():
|
||||||
for choice in option.choices.all():
|
for choice in option.choices.all():
|
||||||
name = "option_%d_choice_%d" % (option.id, choice.id)
|
name = "option_%d_choice_%d" % (option.id, choice.id)
|
||||||
if self.is_bound \
|
if self.is_bound and self.data.get(self.add_prefix(name), None):
|
||||||
and self.data.get(self.add_prefix(name), None):
|
|
||||||
initial = self.data.get(self.add_prefix(name), None)
|
initial = self.data.get(self.add_prefix(name), None)
|
||||||
else:
|
else:
|
||||||
initial = "none"
|
initial = "none"
|
||||||
|
@ -143,7 +146,8 @@ class EventStatusFilterForm(forms.Form):
|
||||||
choices=[("yes", "yes"), ("no", "no"), ("none", "none")],
|
choices=[("yes", "yes"), ("no", "no"), ("none", "none")],
|
||||||
widget=TriStateCheckbox,
|
widget=TriStateCheckbox,
|
||||||
required=False,
|
required=False,
|
||||||
initial=initial)
|
initial=initial,
|
||||||
|
)
|
||||||
field.option_id = option.id
|
field.option_id = option.id
|
||||||
field.choice_id = choice.id
|
field.choice_id = choice.id
|
||||||
self.fields[name] = field
|
self.fields[name] = field
|
||||||
|
@ -153,48 +157,45 @@ class EventStatusFilterForm(forms.Form):
|
||||||
initial = self.data.get(self.add_prefix(name), None)
|
initial = self.data.get(self.add_prefix(name), None)
|
||||||
else:
|
else:
|
||||||
initial = "none"
|
initial = "none"
|
||||||
field = forms.ChoiceField(label="Événement payé",
|
field = forms.ChoiceField(
|
||||||
choices=[("yes", "yes"), ("no", "no"),
|
label="Événement payé",
|
||||||
("none", "none")],
|
choices=[("yes", "yes"), ("no", "no"), ("none", "none")],
|
||||||
widget=TriStateCheckbox,
|
widget=TriStateCheckbox,
|
||||||
required=False,
|
required=False,
|
||||||
initial=initial)
|
initial=initial,
|
||||||
|
)
|
||||||
self.fields[name] = field
|
self.fields[name] = field
|
||||||
|
|
||||||
def filters(self):
|
def filters(self):
|
||||||
for name, value in self.cleaned_data.items():
|
for name, value in self.cleaned_data.items():
|
||||||
if name.startswith('option_'):
|
if name.startswith("option_"):
|
||||||
yield (self.fields[name].option_id,
|
yield (self.fields[name].option_id, self.fields[name].choice_id, value)
|
||||||
self.fields[name].choice_id, value)
|
|
||||||
elif name == "event_has_paid":
|
elif name == "event_has_paid":
|
||||||
yield ("has_paid", None, value)
|
yield ("has_paid", None, value)
|
||||||
|
|
||||||
|
|
||||||
class UserProfileForm(forms.ModelForm):
|
class UserForm(forms.ModelForm):
|
||||||
first_name = forms.CharField(label=_('Prénom'), max_length=30)
|
class Meta:
|
||||||
last_name = forms.CharField(label=_('Nom'), max_length=30)
|
model = User
|
||||||
|
fields = ["first_name", "last_name", "email"]
|
||||||
|
|
||||||
def __init__(self, *args, **kw):
|
|
||||||
super().__init__(*args, **kw)
|
|
||||||
self.fields['first_name'].initial = self.instance.user.first_name
|
|
||||||
self.fields['last_name'].initial = self.instance.user.last_name
|
|
||||||
|
|
||||||
def save(self, *args, **kw):
|
|
||||||
super().save(*args, **kw)
|
|
||||||
self.instance.user.first_name = self.cleaned_data.get('first_name')
|
|
||||||
self.instance.user.last_name = self.cleaned_data.get('last_name')
|
|
||||||
self.instance.user.save()
|
|
||||||
|
|
||||||
|
class ProfileForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CofProfile
|
model = CofProfile
|
||||||
fields = ["first_name", "last_name", "phone", "mailing_cof",
|
fields = [
|
||||||
"mailing_bda", "mailing_bda_revente"]
|
"phone",
|
||||||
|
"mailing_cof",
|
||||||
|
"mailing_bda",
|
||||||
|
"mailing_bda_revente",
|
||||||
|
"mailing_unernestaparis",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class RegistrationUserForm(forms.ModelForm):
|
class RegistrationUserForm(forms.ModelForm):
|
||||||
def __init__(self, *args, **kw):
|
def __init__(self, *args, **kw):
|
||||||
super().__init__(*args, **kw)
|
super().__init__(*args, **kw)
|
||||||
self.fields['username'].help_text = ""
|
self.fields["username"].help_text = ""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
|
@ -205,22 +206,23 @@ class RegistrationPassUserForm(RegistrationUserForm):
|
||||||
"""
|
"""
|
||||||
Formulaire pour changer le mot de passe d'un utilisateur.
|
Formulaire pour changer le mot de passe d'un utilisateur.
|
||||||
"""
|
"""
|
||||||
password1 = forms.CharField(label=_('Mot de passe'),
|
|
||||||
widget=forms.PasswordInput)
|
password1 = forms.CharField(label=_("Mot de passe"), widget=forms.PasswordInput)
|
||||||
password2 = forms.CharField(label=_('Confirmation du mot de passe'),
|
password2 = forms.CharField(
|
||||||
widget=forms.PasswordInput)
|
label=_("Confirmation du mot de passe"), widget=forms.PasswordInput
|
||||||
|
)
|
||||||
|
|
||||||
def clean_password2(self):
|
def clean_password2(self):
|
||||||
pass1 = self.cleaned_data['password1']
|
pass1 = self.cleaned_data["password1"]
|
||||||
pass2 = self.cleaned_data['password2']
|
pass2 = self.cleaned_data["password2"]
|
||||||
if pass1 and pass2:
|
if pass1 and pass2:
|
||||||
if pass1 != pass2:
|
if pass1 != pass2:
|
||||||
raise forms.ValidationError(_('Mots de passe non identiques.'))
|
raise forms.ValidationError(_("Mots de passe non identiques."))
|
||||||
return pass2
|
return pass2
|
||||||
|
|
||||||
def save(self, commit=True, *args, **kwargs):
|
def save(self, commit=True, *args, **kwargs):
|
||||||
user = super().save(commit, *args, **kwargs)
|
user = super().save(commit, *args, **kwargs)
|
||||||
user.set_password(self.cleaned_data['password2'])
|
user.set_password(self.cleaned_data["password2"])
|
||||||
if commit:
|
if commit:
|
||||||
user.save()
|
user.save()
|
||||||
return user
|
return user
|
||||||
|
@ -229,44 +231,62 @@ class RegistrationPassUserForm(RegistrationUserForm):
|
||||||
class RegistrationProfileForm(forms.ModelForm):
|
class RegistrationProfileForm(forms.ModelForm):
|
||||||
def __init__(self, *args, **kw):
|
def __init__(self, *args, **kw):
|
||||||
super().__init__(*args, **kw)
|
super().__init__(*args, **kw)
|
||||||
self.fields['mailing_cof'].initial = True
|
self.fields["mailing_cof"].initial = True
|
||||||
self.fields['mailing_bda'].initial = True
|
self.fields["mailing_bda"].initial = True
|
||||||
self.fields['mailing_bda_revente'].initial = True
|
self.fields["mailing_bda_revente"].initial = True
|
||||||
|
self.fields["mailing_unernestaparis"].initial = True
|
||||||
|
|
||||||
self.fields.keyOrder = [
|
self.fields.keyOrder = [
|
||||||
'login_clipper',
|
"login_clipper",
|
||||||
'phone',
|
"phone",
|
||||||
'occupation',
|
"occupation",
|
||||||
'departement',
|
"departement",
|
||||||
'is_cof',
|
"is_cof",
|
||||||
'type_cotiz',
|
"type_cotiz",
|
||||||
'mailing_cof',
|
"mailing_cof",
|
||||||
'mailing_bda',
|
"mailing_bda",
|
||||||
'mailing_bda_revente',
|
"mailing_bda_revente",
|
||||||
'comments'
|
"mailing_unernestaparis",
|
||||||
]
|
"comments",
|
||||||
|
]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CofProfile
|
model = CofProfile
|
||||||
fields = ("login_clipper", "phone", "occupation",
|
fields = (
|
||||||
"departement", "is_cof", "type_cotiz", "mailing_cof",
|
"login_clipper",
|
||||||
"mailing_bda", "mailing_bda_revente", "comments")
|
"phone",
|
||||||
|
"occupation",
|
||||||
|
"departement",
|
||||||
|
"is_cof",
|
||||||
|
"type_cotiz",
|
||||||
|
"mailing_cof",
|
||||||
|
"mailing_bda",
|
||||||
|
"mailing_bda_revente",
|
||||||
|
"mailing_unernestaparis",
|
||||||
|
"comments",
|
||||||
|
)
|
||||||
|
|
||||||
STATUS_CHOICES = (('no', 'Non'),
|
|
||||||
('wait', 'Oui mais attente paiement'),
|
STATUS_CHOICES = (
|
||||||
('paid', 'Oui payé'),)
|
("no", "Non"),
|
||||||
|
("wait", "Oui mais attente paiement"),
|
||||||
|
("paid", "Oui payé"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AdminEventForm(forms.Form):
|
class AdminEventForm(forms.Form):
|
||||||
status = forms.ChoiceField(label="Inscription", initial="no",
|
status = forms.ChoiceField(
|
||||||
choices=STATUS_CHOICES, widget=RadioSelect)
|
label="Inscription", initial="no", choices=STATUS_CHOICES, widget=RadioSelect
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.event = kwargs.pop("event")
|
self.event = kwargs.pop("event")
|
||||||
registration = kwargs.pop("current_registration", None)
|
registration = kwargs.pop("current_registration", None)
|
||||||
current_choices, paid = \
|
current_choices, paid = (
|
||||||
(registration.options.all(), registration.paid) \
|
(registration.options.all(), registration.paid)
|
||||||
if registration is not None else ([], None)
|
if registration is not None
|
||||||
|
else ([], None)
|
||||||
|
)
|
||||||
if paid is True:
|
if paid is True:
|
||||||
kwargs["initial"] = {"status": "paid"}
|
kwargs["initial"] = {"status": "paid"}
|
||||||
elif paid is False:
|
elif paid is False:
|
||||||
|
@ -282,66 +302,69 @@ class AdminEventForm(forms.Form):
|
||||||
choices[choice.event_option.id].append(choice.id)
|
choices[choice.event_option.id].append(choice.id)
|
||||||
all_choices = choices
|
all_choices = choices
|
||||||
for option in self.event.options.all():
|
for option in self.event.options.all():
|
||||||
choices = [(choice.id, choice.value)
|
choices = [(choice.id, choice.value) for choice in option.choices.all()]
|
||||||
for choice in option.choices.all()]
|
|
||||||
if option.multi_choices:
|
if option.multi_choices:
|
||||||
initial = [] if option.id not in all_choices\
|
initial = [] if option.id not in all_choices else all_choices[option.id]
|
||||||
else all_choices[option.id]
|
|
||||||
field = forms.MultipleChoiceField(
|
field = forms.MultipleChoiceField(
|
||||||
label=option.name,
|
label=option.name,
|
||||||
choices=choices,
|
choices=choices,
|
||||||
widget=CheckboxSelectMultiple,
|
widget=CheckboxSelectMultiple,
|
||||||
required=False,
|
required=False,
|
||||||
initial=initial)
|
initial=initial,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
initial = None if option.id not in all_choices\
|
initial = (
|
||||||
else all_choices[option.id][0]
|
None if option.id not in all_choices else all_choices[option.id][0]
|
||||||
field = forms.ChoiceField(label=option.name,
|
)
|
||||||
choices=choices,
|
field = forms.ChoiceField(
|
||||||
widget=RadioSelect,
|
label=option.name,
|
||||||
required=False,
|
choices=choices,
|
||||||
initial=initial)
|
widget=RadioSelect,
|
||||||
|
required=False,
|
||||||
|
initial=initial,
|
||||||
|
)
|
||||||
field.option_id = option.id
|
field.option_id = option.id
|
||||||
self.fields["option_%d" % option.id] = field
|
self.fields["option_%d" % option.id] = field
|
||||||
for commentfield in self.event.commentfields.all():
|
for commentfield in self.event.commentfields.all():
|
||||||
initial = commentfield.default
|
initial = commentfield.default
|
||||||
if registration is not None:
|
if registration is not None:
|
||||||
try:
|
try:
|
||||||
initial = registration.comments \
|
initial = registration.comments.get(
|
||||||
.get(commentfield=commentfield).content
|
commentfield=commentfield
|
||||||
|
).content
|
||||||
except EventCommentValue.DoesNotExist:
|
except EventCommentValue.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
widget = forms.Textarea if commentfield.fieldtype == "text" \
|
widget = (
|
||||||
else forms.TextInput
|
forms.Textarea if commentfield.fieldtype == "text" else forms.TextInput
|
||||||
field = forms.CharField(label=commentfield.name,
|
)
|
||||||
widget=widget,
|
field = forms.CharField(
|
||||||
required=False,
|
label=commentfield.name, widget=widget, required=False, initial=initial
|
||||||
initial=initial)
|
)
|
||||||
field.comment_id = commentfield.id
|
field.comment_id = commentfield.id
|
||||||
self.fields["comment_%d" % commentfield.id] = field
|
self.fields["comment_%d" % commentfield.id] = field
|
||||||
|
|
||||||
def choices(self):
|
def choices(self):
|
||||||
for name, value in self.cleaned_data.items():
|
for name, value in self.cleaned_data.items():
|
||||||
if name.startswith('option_'):
|
if name.startswith("option_"):
|
||||||
yield (self.fields[name].option_id, value)
|
yield (self.fields[name].option_id, value)
|
||||||
|
|
||||||
def comments(self):
|
def comments(self):
|
||||||
for name, value in self.cleaned_data.items():
|
for name, value in self.cleaned_data.items():
|
||||||
if name.startswith('comment_'):
|
if name.startswith("comment_"):
|
||||||
yield (self.fields[name].comment_id, value)
|
yield (self.fields[name].comment_id, value)
|
||||||
|
|
||||||
|
|
||||||
class BaseEventRegistrationFormset(BaseFormSet):
|
class BaseEventRegistrationFormset(BaseFormSet):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.events = kwargs.pop('events')
|
self.events = kwargs.pop("events")
|
||||||
self.current_registrations = kwargs.pop('current_registrations', None)
|
self.current_registrations = kwargs.pop("current_registrations", None)
|
||||||
self.extra = len(self.events)
|
self.extra = len(self.events)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def _construct_form(self, index, **kwargs):
|
def _construct_form(self, index, **kwargs):
|
||||||
kwargs['event'] = self.events[index]
|
kwargs["event"] = self.events[index]
|
||||||
if self.current_registrations is not None:
|
if self.current_registrations is not None:
|
||||||
kwargs['current_registration'] = self.current_registrations[index]
|
kwargs["current_registration"] = self.current_registrations[index]
|
||||||
return super()._construct_form(index, **kwargs)
|
return super()._construct_form(index, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@ -350,34 +373,36 @@ 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", required=False
|
||||||
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)
|
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),
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple,
|
||||||
required=False)
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CalendarSubscription
|
model = CalendarSubscription
|
||||||
fields = ['subscribe_to_events', 'subscribe_to_my_shows',
|
fields = ["subscribe_to_events", "subscribe_to_my_shows", "other_shows"]
|
||||||
'other_shows']
|
|
||||||
|
|
||||||
|
|
||||||
class ClubsForm(forms.Form):
|
class ClubsForm(forms.Form):
|
||||||
"""
|
"""
|
||||||
Formulaire d'inscription d'un membre à plusieurs clubs du COF.
|
Formulaire d'inscription d'un membre à plusieurs clubs du COF.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
clubs = forms.ModelMultipleChoiceField(
|
clubs = forms.ModelMultipleChoiceField(
|
||||||
label="Inscriptions aux clubs du COF",
|
label="Inscriptions aux clubs du COF",
|
||||||
queryset=Club.objects.all(),
|
queryset=Club.objects.all(),
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple,
|
||||||
required=False)
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
|
@ -385,9 +410,10 @@ class ClubsForm(forms.Form):
|
||||||
# TODO: move this to the `gestion` app once the supportBDS branch is merged
|
# TODO: move this to the `gestion` app once the supportBDS branch is merged
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
|
|
||||||
class GestioncofConfigForm(ConfigForm):
|
class GestioncofConfigForm(ConfigForm):
|
||||||
gestion_banner = forms.CharField(
|
gestion_banner = forms.CharField(
|
||||||
label=_("Announcements banner"),
|
label=_("Announcements banner"),
|
||||||
help_text=_("An empty banner disables annoucements"),
|
help_text=_("An empty banner disables annoucements"),
|
||||||
max_length=2048
|
max_length=2048,
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
Un mixin à utiliser avec BaseCommand pour charger des objets depuis un json
|
Un mixin à utiliser avec BaseCommand pour charger des objets depuis un json
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
@ -13,15 +13,14 @@ class MyBaseCommand(BaseCommand):
|
||||||
Ajoute une méthode ``from_json`` qui charge des objets à partir d'un json.
|
Ajoute une méthode ``from_json`` qui charge des objets à partir d'un json.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def from_json(self, filename, data_dir, klass,
|
def from_json(self, filename, data_dir, klass, callback=lambda obj: obj):
|
||||||
callback=lambda obj: obj):
|
|
||||||
"""
|
"""
|
||||||
Charge les objets contenus dans le fichier json référencé par
|
Charge les objets contenus dans le fichier json référencé par
|
||||||
``filename`` dans la base de donnée. La fonction callback est appelées
|
``filename`` dans la base de donnée. La fonction callback est appelées
|
||||||
sur chaque objet avant enregistrement.
|
sur chaque objet avant enregistrement.
|
||||||
"""
|
"""
|
||||||
self.stdout.write("Chargement de {:s}".format(filename))
|
self.stdout.write("Chargement de {:s}".format(filename))
|
||||||
with open(os.path.join(data_dir, filename), 'r') as file:
|
with open(os.path.join(data_dir, filename), "r") as file:
|
||||||
descriptions = json.load(file)
|
descriptions = json.load(file)
|
||||||
objects = []
|
objects = []
|
||||||
nb_new = 0
|
nb_new = 0
|
||||||
|
@ -36,6 +35,7 @@ class MyBaseCommand(BaseCommand):
|
||||||
objects.append(obj)
|
objects.append(obj)
|
||||||
nb_new += 1
|
nb_new += 1
|
||||||
self.stdout.write("- {:d} objets créés".format(nb_new))
|
self.stdout.write("- {:d} objets créés".format(nb_new))
|
||||||
self.stdout.write("- {:d} objets gardés en l'état"
|
self.stdout.write(
|
||||||
.format(len(objects)-nb_new))
|
"- {:d} objets gardés en l'état".format(len(objects) - nb_new)
|
||||||
|
)
|
||||||
return objects
|
return objects
|
||||||
|
|
|
@ -15,13 +15,14 @@ from django.core.management import call_command
|
||||||
|
|
||||||
from gestioncof.management.base import MyBaseCommand
|
from gestioncof.management.base import MyBaseCommand
|
||||||
from gestioncof.petits_cours_models import (
|
from gestioncof.petits_cours_models import (
|
||||||
PetitCoursAbility, PetitCoursSubject, LEVELS_CHOICES,
|
LEVELS_CHOICES,
|
||||||
PetitCoursAttributionCounter
|
PetitCoursAbility,
|
||||||
|
PetitCoursAttributionCounter,
|
||||||
|
PetitCoursSubject,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Où sont stockés les fichiers json
|
# Où sont stockés les fichiers json
|
||||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data")
|
||||||
'data')
|
|
||||||
|
|
||||||
|
|
||||||
class Command(MyBaseCommand):
|
class Command(MyBaseCommand):
|
||||||
|
@ -32,11 +33,11 @@ class Command(MyBaseCommand):
|
||||||
Permet de ne pas créer l'utilisateur "root".
|
Permet de ne pas créer l'utilisateur "root".
|
||||||
"""
|
"""
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--no-root',
|
"--no-root",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
dest='no-root',
|
dest="no-root",
|
||||||
default=False,
|
default=False,
|
||||||
help='Ne crée pas l\'utilisateur "root"'
|
help='Ne crée pas l\'utilisateur "root"',
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
@ -45,24 +46,25 @@ class Command(MyBaseCommand):
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
# Gaulois
|
# Gaulois
|
||||||
gaulois = self.from_json('gaulois.json', DATA_DIR, User)
|
gaulois = self.from_json("gaulois.json", DATA_DIR, User)
|
||||||
for user in gaulois:
|
for user in gaulois:
|
||||||
user.profile.is_cof = True
|
user.profile.is_cof = True
|
||||||
user.profile.save()
|
user.profile.save()
|
||||||
|
|
||||||
# Romains
|
# Romains
|
||||||
self.from_json('romains.json', DATA_DIR, User)
|
self.from_json("romains.json", DATA_DIR, User)
|
||||||
|
|
||||||
# Root
|
# Root
|
||||||
no_root = options.get('no-root', False)
|
no_root = options.get("no-root", False)
|
||||||
if not no_root:
|
if not no_root:
|
||||||
self.stdout.write("Création de l'utilisateur root")
|
self.stdout.write("Création de l'utilisateur root")
|
||||||
root, _ = User.objects.get_or_create(
|
root, _ = User.objects.get_or_create(
|
||||||
username='root',
|
username="root",
|
||||||
first_name='super',
|
first_name="super",
|
||||||
last_name='user',
|
last_name="user",
|
||||||
email='root@localhost')
|
email="root@localhost",
|
||||||
root.set_password('root')
|
)
|
||||||
|
root.set_password("root")
|
||||||
root.is_staff = True
|
root.is_staff = True
|
||||||
root.is_superuser = True
|
root.is_superuser = True
|
||||||
root.profile.is_cof = True
|
root.profile.is_cof = True
|
||||||
|
@ -87,18 +89,17 @@ class Command(MyBaseCommand):
|
||||||
# L'utilisateur est compétent dans une matière
|
# L'utilisateur est compétent dans une matière
|
||||||
subject = random.choice(subjects)
|
subject = random.choice(subjects)
|
||||||
if not PetitCoursAbility.objects.filter(
|
if not PetitCoursAbility.objects.filter(
|
||||||
user=user,
|
user=user, matiere=subject
|
||||||
matiere=subject).exists():
|
).exists():
|
||||||
PetitCoursAbility.objects.create(
|
PetitCoursAbility.objects.create(
|
||||||
user=user,
|
user=user,
|
||||||
matiere=subject,
|
matiere=subject,
|
||||||
niveau=random.choice(levels),
|
niveau=random.choice(levels),
|
||||||
agrege=bool(random.randint(0, 1))
|
agrege=bool(random.randint(0, 1)),
|
||||||
)
|
)
|
||||||
# On initialise son compteur d'attributions
|
# On initialise son compteur d'attributions
|
||||||
PetitCoursAttributionCounter.objects.get_or_create(
|
PetitCoursAttributionCounter.objects.get_or_create(
|
||||||
user=user,
|
user=user, matiere=subject
|
||||||
matiere=subject
|
|
||||||
)
|
)
|
||||||
self.stdout.write("- {:d} inscriptions".format(nb_of_teachers))
|
self.stdout.write("- {:d} inscriptions".format(nb_of_teachers))
|
||||||
|
|
||||||
|
@ -106,10 +107,10 @@ class Command(MyBaseCommand):
|
||||||
# Le BdA
|
# Le BdA
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
call_command('loadbdadevdata')
|
call_command("loadbdadevdata")
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# La K-Fêt
|
# La K-Fêt
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
call_command('loadkfetdevdata')
|
call_command("loadkfetdevdata")
|
||||||
|
|
|
@ -4,84 +4,86 @@ Import des mails de GestioCOF dans la base de donnée
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from custommail.models import Type, CustomMail, Variable
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from custommail.models import CustomMail, Type, Variable
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
DATA_LOCATION = os.path.join(os.path.dirname(__file__), "..", "data", "custommail.json")
|
||||||
|
|
||||||
|
|
||||||
|
def dummy_log(__):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# XXX. this should probably be in the custommail package
|
||||||
|
def load_from_file(log=dummy_log, verbosity=1):
|
||||||
|
with open(DATA_LOCATION, "r") as jsonfile:
|
||||||
|
mail_data = json.load(jsonfile)
|
||||||
|
|
||||||
|
# On se souvient à quel objet correspond quel pk du json
|
||||||
|
assoc = {"types": {}, "mails": {}}
|
||||||
|
status = {"synced": 0, "unchanged": 0}
|
||||||
|
|
||||||
|
for obj in mail_data:
|
||||||
|
fields = obj["fields"]
|
||||||
|
|
||||||
|
# Pour les trois types d'objets :
|
||||||
|
# - On récupère les objets référencés par les clefs étrangères
|
||||||
|
# - On crée l'objet si nécessaire
|
||||||
|
# - On le stocke éventuellement dans les deux dictionnaires définis
|
||||||
|
# plus haut
|
||||||
|
|
||||||
|
# Variable types
|
||||||
|
if obj["model"] == "custommail.variabletype":
|
||||||
|
fields["inner1"] = assoc["types"].get(fields["inner1"])
|
||||||
|
fields["inner2"] = assoc["types"].get(fields["inner2"])
|
||||||
|
if fields["kind"] == "model":
|
||||||
|
fields["content_type"] = ContentType.objects.get_by_natural_key(
|
||||||
|
*fields["content_type"]
|
||||||
|
)
|
||||||
|
var_type, _ = Type.objects.get_or_create(**fields)
|
||||||
|
assoc["types"][obj["pk"]] = var_type
|
||||||
|
|
||||||
|
# Custom mails
|
||||||
|
if obj["model"] == "custommail.custommail":
|
||||||
|
mail = None
|
||||||
|
try:
|
||||||
|
mail = CustomMail.objects.get(shortname=fields["shortname"])
|
||||||
|
status["unchanged"] += 1
|
||||||
|
except CustomMail.DoesNotExist:
|
||||||
|
mail = CustomMail.objects.create(**fields)
|
||||||
|
status["synced"] += 1
|
||||||
|
if verbosity:
|
||||||
|
log("SYNCED {:s}".format(fields["shortname"]))
|
||||||
|
assoc["mails"][obj["pk"]] = mail
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
if obj["model"] == "custommail.custommailvariable":
|
||||||
|
fields["custommail"] = assoc["mails"].get(fields["custommail"])
|
||||||
|
fields["type"] = assoc["types"].get(fields["type"])
|
||||||
|
try:
|
||||||
|
Variable.objects.get(
|
||||||
|
custommail=fields["custommail"], name=fields["name"]
|
||||||
|
)
|
||||||
|
except Variable.DoesNotExist:
|
||||||
|
Variable.objects.create(**fields)
|
||||||
|
|
||||||
|
if verbosity:
|
||||||
|
log("{synced:d} mails synchronized {unchanged:d} unchanged".format(**status))
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = ("Va chercher les données mails de GestioCOF stocké au format json "
|
help = (
|
||||||
"dans /gestioncof/management/data/custommails.json. Le format des "
|
"Va chercher les données mails de GestioCOF stocké au format json "
|
||||||
"données est celui donné par la commande :"
|
"dans /gestioncof/management/data/custommails.json. Le format des "
|
||||||
" `python manage.py dumpdata custommail --natural-foreign` "
|
"données est celui donné par la commande :"
|
||||||
"La bonne façon de mettre à jour ce fichier est donc de le "
|
" `python manage.py dumpdata custommail --natural-foreign` "
|
||||||
"charger à l'aide de syncmails, le faire les modifications à "
|
"La bonne façon de mettre à jour ce fichier est donc de le "
|
||||||
"l'aide de l'interface administration et/ou du shell puis de le "
|
"charger à l'aide de syncmails, le faire les modifications à "
|
||||||
"remplacer par le nouveau résultat de la commande précédente.")
|
"l'aide de l'interface administration et/ou du shell puis de le "
|
||||||
|
"remplacer par le nouveau résultat de la commande précédente."
|
||||||
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
path = os.path.join(
|
load_from_file(log=self.stdout.write)
|
||||||
os.path.dirname(os.path.dirname(__file__)),
|
|
||||||
'data', 'custommail.json')
|
|
||||||
with open(path, 'r') as jsonfile:
|
|
||||||
mail_data = json.load(jsonfile)
|
|
||||||
|
|
||||||
# On se souvient à quel objet correspond quel pk du json
|
|
||||||
assoc = {'types': {}, 'mails': {}}
|
|
||||||
status = {'synced': 0, 'unchanged': 0}
|
|
||||||
|
|
||||||
for obj in mail_data:
|
|
||||||
fields = obj['fields']
|
|
||||||
|
|
||||||
# Pour les trois types d'objets :
|
|
||||||
# - On récupère les objets référencés par les clefs étrangères
|
|
||||||
# - On crée l'objet si nécessaire
|
|
||||||
# - On le stocke éventuellement dans les deux dictionnaires définis
|
|
||||||
# plus haut
|
|
||||||
|
|
||||||
# Variable types
|
|
||||||
if obj['model'] == 'custommail.variabletype':
|
|
||||||
fields['inner1'] = assoc['types'].get(fields['inner1'])
|
|
||||||
fields['inner2'] = assoc['types'].get(fields['inner2'])
|
|
||||||
if fields['kind'] == 'model':
|
|
||||||
fields['content_type'] = (
|
|
||||||
ContentType.objects
|
|
||||||
.get_by_natural_key(*fields['content_type'])
|
|
||||||
)
|
|
||||||
var_type, _ = Type.objects.get_or_create(**fields)
|
|
||||||
assoc['types'][obj['pk']] = var_type
|
|
||||||
|
|
||||||
# Custom mails
|
|
||||||
if obj['model'] == 'custommail.custommail':
|
|
||||||
mail = None
|
|
||||||
try:
|
|
||||||
mail = CustomMail.objects.get(
|
|
||||||
shortname=fields['shortname'])
|
|
||||||
status['unchanged'] += 1
|
|
||||||
except CustomMail.DoesNotExist:
|
|
||||||
mail = CustomMail.objects.create(**fields)
|
|
||||||
status['synced'] += 1
|
|
||||||
if options['verbosity']:
|
|
||||||
self.stdout.write(
|
|
||||||
'SYNCED {:s}'.format(fields['shortname']))
|
|
||||||
assoc['mails'][obj['pk']] = mail
|
|
||||||
|
|
||||||
# Variables
|
|
||||||
if obj['model'] == 'custommail.custommailvariable':
|
|
||||||
fields['custommail'] = assoc['mails'].get(fields['custommail'])
|
|
||||||
fields['type'] = assoc['types'].get(fields['type'])
|
|
||||||
try:
|
|
||||||
Variable.objects.get(
|
|
||||||
custommail=fields['custommail'],
|
|
||||||
name=fields['name']
|
|
||||||
)
|
|
||||||
except Variable.DoesNotExist:
|
|
||||||
Variable.objects.create(**fields)
|
|
||||||
|
|
||||||
if options['verbosity']:
|
|
||||||
# C'est agréable d'avoir le résultat affiché
|
|
||||||
self.stdout.write(
|
|
||||||
'{synced:d} mails synchronized {unchanged:d} unchanged'
|
|
||||||
.format(**status)
|
|
||||||
)
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -6,14 +6,14 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("gestioncof", "0001_initial")]
|
||||||
('gestioncof', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='petitcoursdemande',
|
model_name="petitcoursdemande",
|
||||||
name='processed',
|
name="processed",
|
||||||
field=models.DateTimeField(null=True, verbose_name='Date de traitement', blank=True),
|
field=models.DateTimeField(
|
||||||
),
|
null=True, verbose_name="Date de traitement", blank=True
|
||||||
|
),
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,14 +6,14 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("gestioncof", "0002_enable_unprocessed_demandes")]
|
||||||
('gestioncof', '0002_enable_unprocessed_demandes'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='event',
|
model_name="event",
|
||||||
name='image',
|
name="image",
|
||||||
field=models.ImageField(upload_to=b'imgs/events/', null=True, verbose_name=b'Image', blank=True),
|
field=models.ImageField(
|
||||||
),
|
upload_to=b"imgs/events/", null=True, verbose_name=b"Image", blank=True
|
||||||
|
),
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,27 +8,28 @@ def create_mail(apps, schema_editor):
|
||||||
CustomMail = apps.get_model("gestioncof", "CustomMail")
|
CustomMail = apps.get_model("gestioncof", "CustomMail")
|
||||||
db_alias = schema_editor.connection.alias
|
db_alias = schema_editor.connection.alias
|
||||||
if CustomMail.objects.filter(shortname="bienvenue").count() == 0:
|
if CustomMail.objects.filter(shortname="bienvenue").count() == 0:
|
||||||
CustomMail.objects.using(db_alias).bulk_create([
|
CustomMail.objects.using(db_alias).bulk_create(
|
||||||
CustomMail(
|
[
|
||||||
shortname="bienvenue",
|
CustomMail(
|
||||||
title="Bienvenue au COF",
|
shortname="bienvenue",
|
||||||
content="Mail de bienvenue au COF, envoyé automatiquement à " \
|
title="Bienvenue au COF",
|
||||||
+ "l'inscription.\n\n" \
|
content="Mail de bienvenue au COF, envoyé automatiquement à "
|
||||||
+ "Les balises {{ ... }} sont interprétées comme expliqué " \
|
+ "l'inscription.\n\n"
|
||||||
|
+ "Les balises {{ ... }} sont interprétées comme expliqué "
|
||||||
+ "ci-dessous à l'envoi.",
|
+ "ci-dessous à l'envoi.",
|
||||||
comments="{{ nom }} \t fullname de la personne.\n"\
|
comments="{{ nom }} \t fullname de la personne.\n"
|
||||||
+ "{{ prenom }} \t prénom de la personne.")
|
+ "{{ prenom }} \t prénom de la personne.",
|
||||||
])
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("gestioncof", "0003_event_image")]
|
||||||
('gestioncof', '0003_event_image'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
# Pas besoin de supprimer le mail lors de la migration dans l'autre
|
# Pas besoin de supprimer le mail lors de la migration dans l'autre
|
||||||
# sens.
|
# sens.
|
||||||
migrations.RunPython(create_mail, migrations.RunPython.noop),
|
migrations.RunPython(create_mail, migrations.RunPython.noop)
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,62 +6,71 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("gestioncof", "0004_registration_mail")]
|
||||||
('gestioncof', '0004_registration_mail'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='custommail',
|
name="custommail",
|
||||||
options={'verbose_name': 'Mail personnalisable', 'verbose_name_plural': 'Mails personnalisables'},
|
options={
|
||||||
|
"verbose_name": "Mail personnalisable",
|
||||||
|
"verbose_name_plural": "Mails personnalisables",
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='eventoptionchoice',
|
name="eventoptionchoice",
|
||||||
options={'verbose_name': 'Choix', 'verbose_name_plural': 'Choix'},
|
options={"verbose_name": "Choix", "verbose_name_plural": "Choix"},
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='is_buro',
|
name="is_buro",
|
||||||
field=models.BooleanField(default=False, verbose_name='Membre du Bur\xf4'),
|
field=models.BooleanField(default=False, verbose_name="Membre du Bur\xf4"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='num',
|
name="num",
|
||||||
field=models.IntegerField(default=0, verbose_name="Num\xe9ro d'adh\xe9rent", blank=True),
|
field=models.IntegerField(
|
||||||
|
default=0, verbose_name="Num\xe9ro d'adh\xe9rent", blank=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='phone',
|
name="phone",
|
||||||
field=models.CharField(max_length=20, verbose_name='T\xe9l\xe9phone', blank=True),
|
field=models.CharField(
|
||||||
|
max_length=20, verbose_name="T\xe9l\xe9phone", blank=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='event',
|
model_name="event",
|
||||||
name='old',
|
name="old",
|
||||||
field=models.BooleanField(default=False, verbose_name='Archiver (\xe9v\xe9nement fini)'),
|
field=models.BooleanField(
|
||||||
|
default=False, verbose_name="Archiver (\xe9v\xe9nement fini)"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='event',
|
model_name="event",
|
||||||
name='start_date',
|
name="start_date",
|
||||||
field=models.DateField(null=True, verbose_name='Date de d\xe9but', blank=True),
|
field=models.DateField(
|
||||||
|
null=True, verbose_name="Date de d\xe9but", blank=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='eventcommentfield',
|
model_name="eventcommentfield",
|
||||||
name='default',
|
name="default",
|
||||||
field=models.TextField(verbose_name='Valeur par d\xe9faut', blank=True),
|
field=models.TextField(verbose_name="Valeur par d\xe9faut", blank=True),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='eventregistration',
|
model_name="eventregistration",
|
||||||
name='paid',
|
name="paid",
|
||||||
field=models.BooleanField(default=False, verbose_name='A pay\xe9'),
|
field=models.BooleanField(default=False, verbose_name="A pay\xe9"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='survey',
|
model_name="survey",
|
||||||
name='details',
|
name="details",
|
||||||
field=models.TextField(verbose_name='D\xe9tails', blank=True),
|
field=models.TextField(verbose_name="D\xe9tails", blank=True),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='surveyquestionanswer',
|
model_name="surveyquestionanswer",
|
||||||
name='answer',
|
name="answer",
|
||||||
field=models.CharField(max_length=200, verbose_name='R\xe9ponse'),
|
field=models.CharField(max_length=200, verbose_name="R\xe9ponse"),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,51 +1,66 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('bda', '0004_mails-rappel'),
|
("bda", "0004_mails-rappel"),
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('gestioncof', '0005_encoding'),
|
("gestioncof", "0005_encoding"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='CalendarSubscription',
|
name="CalendarSubscription",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False,
|
(
|
||||||
auto_created=True, primary_key=True)),
|
"id",
|
||||||
('token', models.UUIDField()),
|
models.AutoField(
|
||||||
('subscribe_to_events', models.BooleanField(default=True)),
|
verbose_name="ID",
|
||||||
('subscribe_to_my_shows', models.BooleanField(default=True)),
|
serialize=False,
|
||||||
('other_shows', models.ManyToManyField(to='bda.Spectacle')),
|
auto_created=True,
|
||||||
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL,
|
primary_key=True,
|
||||||
on_delete=models.CASCADE)),
|
),
|
||||||
|
),
|
||||||
|
("token", models.UUIDField()),
|
||||||
|
("subscribe_to_events", models.BooleanField(default=True)),
|
||||||
|
("subscribe_to_my_shows", models.BooleanField(default=True)),
|
||||||
|
("other_shows", models.ManyToManyField(to="bda.Spectacle")),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.OneToOneField(
|
||||||
|
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='custommail',
|
name="custommail",
|
||||||
options={'verbose_name': 'Mail personnalisable',
|
options={
|
||||||
'verbose_name_plural': 'Mails personnalisables'},
|
"verbose_name": "Mail personnalisable",
|
||||||
|
"verbose_name_plural": "Mails personnalisables",
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='eventoptionchoice',
|
name="eventoptionchoice",
|
||||||
options={'verbose_name': 'Choix', 'verbose_name_plural': 'Choix'},
|
options={"verbose_name": "Choix", "verbose_name_plural": "Choix"},
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='event',
|
model_name="event",
|
||||||
name='end_date',
|
name="end_date",
|
||||||
field=models.DateTimeField(null=True, verbose_name=b'Date de fin',
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='start_date',
|
|
||||||
field=models.DateTimeField(
|
field=models.DateTimeField(
|
||||||
null=True, verbose_name=b'Date de d\xc3\xa9but', blank=True),
|
null=True, verbose_name=b"Date de fin", blank=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="event",
|
||||||
|
name="start_date",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
null=True, verbose_name=b"Date de d\xc3\xa9but", blank=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,47 +1,44 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("gestioncof", "0006_add_calendar")]
|
||||||
('gestioncof', '0006_add_calendar'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='club',
|
model_name="club",
|
||||||
name='name',
|
name="name",
|
||||||
field=models.CharField(unique=True, max_length=200,
|
field=models.CharField(unique=True, max_length=200, verbose_name="Nom"),
|
||||||
verbose_name='Nom')
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='club',
|
model_name="club",
|
||||||
name='description',
|
name="description",
|
||||||
field=models.TextField(verbose_name='Description', blank=True)
|
field=models.TextField(verbose_name="Description", blank=True),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='club',
|
model_name="club",
|
||||||
name='membres',
|
name="membres",
|
||||||
field=models.ManyToManyField(related_name='clubs',
|
field=models.ManyToManyField(
|
||||||
to=settings.AUTH_USER_MODEL,
|
related_name="clubs", to=settings.AUTH_USER_MODEL, blank=True
|
||||||
blank=True),
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='club',
|
model_name="club",
|
||||||
name='respos',
|
name="respos",
|
||||||
field=models.ManyToManyField(related_name='clubs_geres',
|
field=models.ManyToManyField(
|
||||||
to=settings.AUTH_USER_MODEL,
|
related_name="clubs_geres", to=settings.AUTH_USER_MODEL, blank=True
|
||||||
blank=True),
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='event',
|
model_name="event",
|
||||||
name='start_date',
|
name="start_date",
|
||||||
field=models.DateTimeField(null=True,
|
field=models.DateTimeField(
|
||||||
verbose_name='Date de d\xe9but',
|
null=True, verbose_name="Date de d\xe9but", blank=True
|
||||||
blank=True),
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
def forwards(apps, schema_editor):
|
def forwards(apps, schema_editor):
|
||||||
|
@ -11,243 +11,266 @@ def forwards(apps, schema_editor):
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("gestioncof", "0007_alter_club")]
|
||||||
('gestioncof', '0007_alter_club'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='clipper',
|
model_name="clipper",
|
||||||
name='fullname',
|
name="fullname",
|
||||||
field=models.CharField(verbose_name='Nom complet', max_length=200),
|
field=models.CharField(verbose_name="Nom complet", max_length=200),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='clipper',
|
model_name="clipper",
|
||||||
name='username',
|
name="username",
|
||||||
field=models.CharField(verbose_name='Identifiant', max_length=20),
|
field=models.CharField(verbose_name="Identifiant", max_length=20),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='comments',
|
name="comments",
|
||||||
field=models.TextField(
|
field=models.TextField(
|
||||||
verbose_name="Commentaires visibles par l'utilisateur",
|
verbose_name="Commentaires visibles par l'utilisateur", blank=True
|
||||||
blank=True),
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='is_cof',
|
name="is_cof",
|
||||||
field=models.BooleanField(verbose_name='Membre du COF',
|
field=models.BooleanField(verbose_name="Membre du COF", default=False),
|
||||||
default=False),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='login_clipper',
|
name="login_clipper",
|
||||||
field=models.CharField(verbose_name='Login clipper', max_length=8,
|
field=models.CharField(
|
||||||
blank=True),
|
verbose_name="Login clipper", max_length=8, blank=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='mailing_bda',
|
name="mailing_bda",
|
||||||
field=models.BooleanField(verbose_name='Recevoir les mails BdA',
|
|
||||||
default=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='mailing_bda_revente',
|
|
||||||
field=models.BooleanField(
|
field=models.BooleanField(
|
||||||
verbose_name='Recevoir les mails de revente de places BdA',
|
verbose_name="Recevoir les mails BdA", default=False
|
||||||
default=False),
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='mailing_cof',
|
name="mailing_bda_revente",
|
||||||
field=models.BooleanField(verbose_name='Recevoir les mails COF',
|
field=models.BooleanField(
|
||||||
default=False),
|
verbose_name="Recevoir les mails de revente de places BdA",
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='occupation',
|
name="mailing_cof",
|
||||||
field=models.CharField(verbose_name='Occupation',
|
field=models.BooleanField(
|
||||||
choices=[('exterieur', 'Extérieur'),
|
verbose_name="Recevoir les mails COF", default=False
|
||||||
('1A', '1A'),
|
),
|
||||||
('2A', '2A'),
|
|
||||||
('3A', '3A'),
|
|
||||||
('4A', '4A'),
|
|
||||||
('archicube', 'Archicube'),
|
|
||||||
('doctorant', 'Doctorant'),
|
|
||||||
('CST', 'CST')],
|
|
||||||
max_length=9, default='1A'),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='petits_cours_accept',
|
name="occupation",
|
||||||
field=models.BooleanField(verbose_name='Recevoir des petits cours',
|
field=models.CharField(
|
||||||
default=False),
|
verbose_name="Occupation",
|
||||||
|
choices=[
|
||||||
|
("exterieur", "Extérieur"),
|
||||||
|
("1A", "1A"),
|
||||||
|
("2A", "2A"),
|
||||||
|
("3A", "3A"),
|
||||||
|
("4A", "4A"),
|
||||||
|
("archicube", "Archicube"),
|
||||||
|
("doctorant", "Doctorant"),
|
||||||
|
("CST", "CST"),
|
||||||
|
],
|
||||||
|
max_length=9,
|
||||||
|
default="1A",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='petits_cours_remarques',
|
name="petits_cours_accept",
|
||||||
|
field=models.BooleanField(
|
||||||
|
verbose_name="Recevoir des petits cours", default=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="cofprofile",
|
||||||
|
name="petits_cours_remarques",
|
||||||
field=models.TextField(
|
field=models.TextField(
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name='Remarques et précisions pour les petits cours',
|
verbose_name="Remarques et précisions pour les petits cours",
|
||||||
default=''),
|
default="",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='type_cotiz',
|
name="type_cotiz",
|
||||||
field=models.CharField(
|
field=models.CharField(
|
||||||
verbose_name='Type de cotisation',
|
verbose_name="Type de cotisation",
|
||||||
choices=[('etudiant', 'Normalien étudiant'),
|
choices=[
|
||||||
('normalien', 'Normalien élève'),
|
("etudiant", "Normalien étudiant"),
|
||||||
('exterieur', 'Extérieur')],
|
("normalien", "Normalien élève"),
|
||||||
max_length=9, default='normalien'),
|
("exterieur", "Extérieur"),
|
||||||
|
],
|
||||||
|
max_length=9,
|
||||||
|
default="normalien",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='custommail',
|
model_name="custommail",
|
||||||
name='comments',
|
name="comments",
|
||||||
field=models.TextField(
|
field=models.TextField(
|
||||||
verbose_name='Informations contextuelles sur le mail',
|
verbose_name="Informations contextuelles sur le mail", blank=True
|
||||||
blank=True),
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='custommail',
|
model_name="custommail",
|
||||||
name='content',
|
name="content",
|
||||||
field=models.TextField(verbose_name='Contenu'),
|
field=models.TextField(verbose_name="Contenu"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='custommail',
|
model_name="custommail",
|
||||||
name='title',
|
name="title",
|
||||||
field=models.CharField(verbose_name='Titre', max_length=200),
|
field=models.CharField(verbose_name="Titre", max_length=200),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='event',
|
model_name="event",
|
||||||
name='description',
|
name="description",
|
||||||
field=models.TextField(verbose_name='Description', blank=True),
|
field=models.TextField(verbose_name="Description", blank=True),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='event',
|
model_name="event",
|
||||||
name='end_date',
|
name="end_date",
|
||||||
field=models.DateTimeField(null=True, verbose_name='Date de fin',
|
field=models.DateTimeField(
|
||||||
blank=True),
|
null=True, verbose_name="Date de fin", blank=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='event',
|
model_name="event",
|
||||||
name='image',
|
name="image",
|
||||||
field=models.ImageField(upload_to='imgs/events/', null=True,
|
field=models.ImageField(
|
||||||
verbose_name='Image', blank=True),
|
upload_to="imgs/events/", null=True, verbose_name="Image", blank=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='event',
|
model_name="event",
|
||||||
name='location',
|
name="location",
|
||||||
field=models.CharField(verbose_name='Lieu', max_length=200),
|
field=models.CharField(verbose_name="Lieu", max_length=200),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='event',
|
model_name="event",
|
||||||
name='registration_open',
|
name="registration_open",
|
||||||
field=models.BooleanField(verbose_name='Inscriptions ouvertes',
|
field=models.BooleanField(
|
||||||
default=True),
|
verbose_name="Inscriptions ouvertes", default=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='event',
|
model_name="event",
|
||||||
name='title',
|
name="title",
|
||||||
field=models.CharField(verbose_name='Titre', max_length=200),
|
field=models.CharField(verbose_name="Titre", max_length=200),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='eventcommentfield',
|
model_name="eventcommentfield",
|
||||||
name='fieldtype',
|
name="fieldtype",
|
||||||
field=models.CharField(verbose_name='Type',
|
|
||||||
choices=[('text', 'Texte long'),
|
|
||||||
('char', 'Texte court')],
|
|
||||||
max_length=10, default='text'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventcommentfield',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(verbose_name='Champ', max_length=200),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventcommentvalue',
|
|
||||||
name='content',
|
|
||||||
field=models.TextField(null=True, verbose_name='Contenu',
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventoption',
|
|
||||||
name='multi_choices',
|
|
||||||
field=models.BooleanField(verbose_name='Choix multiples',
|
|
||||||
default=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventoption',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(verbose_name='Option', max_length=200),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventoptionchoice',
|
|
||||||
name='value',
|
|
||||||
field=models.CharField(verbose_name='Valeur', max_length=200),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='petitcoursability',
|
|
||||||
name='niveau',
|
|
||||||
field=models.CharField(
|
field=models.CharField(
|
||||||
choices=[('college', 'Collège'), ('lycee', 'Lycée'),
|
verbose_name="Type",
|
||||||
('prepa1styear', 'Prépa 1ère année / L1'),
|
choices=[("text", "Texte long"), ("char", "Texte court")],
|
||||||
('prepa2ndyear', 'Prépa 2ème année / L2'),
|
max_length=10,
|
||||||
('licence3', 'Licence 3'),
|
default="text",
|
||||||
('other', 'Autre (préciser dans les commentaires)')],
|
),
|
||||||
max_length=12, verbose_name='Niveau'),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='petitcoursattribution',
|
model_name="eventcommentfield",
|
||||||
name='rank',
|
name="name",
|
||||||
|
field=models.CharField(verbose_name="Champ", max_length=200),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="eventcommentvalue",
|
||||||
|
name="content",
|
||||||
|
field=models.TextField(null=True, verbose_name="Contenu", blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="eventoption",
|
||||||
|
name="multi_choices",
|
||||||
|
field=models.BooleanField(verbose_name="Choix multiples", default=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="eventoption",
|
||||||
|
name="name",
|
||||||
|
field=models.CharField(verbose_name="Option", max_length=200),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="eventoptionchoice",
|
||||||
|
name="value",
|
||||||
|
field=models.CharField(verbose_name="Valeur", max_length=200),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="petitcoursability",
|
||||||
|
name="niveau",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("college", "Collège"),
|
||||||
|
("lycee", "Lycée"),
|
||||||
|
("prepa1styear", "Prépa 1ère année / L1"),
|
||||||
|
("prepa2ndyear", "Prépa 2ème année / L2"),
|
||||||
|
("licence3", "Licence 3"),
|
||||||
|
("other", "Autre (préciser dans les commentaires)"),
|
||||||
|
],
|
||||||
|
max_length=12,
|
||||||
|
verbose_name="Niveau",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="petitcoursattribution",
|
||||||
|
name="rank",
|
||||||
field=models.IntegerField(verbose_name="Rang dans l'email"),
|
field=models.IntegerField(verbose_name="Rang dans l'email"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='petitcoursattributioncounter',
|
model_name="petitcoursattributioncounter",
|
||||||
name='count',
|
name="count",
|
||||||
field=models.IntegerField(verbose_name="Nombre d'envois",
|
field=models.IntegerField(verbose_name="Nombre d'envois", default=0),
|
||||||
default=0),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='petitcoursdemande',
|
model_name="petitcoursdemande",
|
||||||
name='niveau',
|
name="niveau",
|
||||||
field=models.CharField(
|
field=models.CharField(
|
||||||
verbose_name='Niveau',
|
verbose_name="Niveau",
|
||||||
choices=[('college', 'Collège'), ('lycee', 'Lycée'),
|
choices=[
|
||||||
('prepa1styear', 'Prépa 1ère année / L1'),
|
("college", "Collège"),
|
||||||
('prepa2ndyear', 'Prépa 2ème année / L2'),
|
("lycee", "Lycée"),
|
||||||
('licence3', 'Licence 3'),
|
("prepa1styear", "Prépa 1ère année / L1"),
|
||||||
('other', 'Autre (préciser dans les commentaires)')],
|
("prepa2ndyear", "Prépa 2ème année / L2"),
|
||||||
max_length=12, default=''),
|
("licence3", "Licence 3"),
|
||||||
|
("other", "Autre (préciser dans les commentaires)"),
|
||||||
|
],
|
||||||
|
max_length=12,
|
||||||
|
default="",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='survey',
|
model_name="survey",
|
||||||
name='old',
|
name="old",
|
||||||
field=models.BooleanField(verbose_name='Archiver (sondage fini)',
|
field=models.BooleanField(
|
||||||
default=False),
|
verbose_name="Archiver (sondage fini)", default=False
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='survey',
|
model_name="survey",
|
||||||
name='survey_open',
|
name="survey_open",
|
||||||
field=models.BooleanField(verbose_name='Sondage ouvert',
|
field=models.BooleanField(verbose_name="Sondage ouvert", default=True),
|
||||||
default=True),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='survey',
|
model_name="survey",
|
||||||
name='title',
|
name="title",
|
||||||
field=models.CharField(verbose_name='Titre', max_length=200),
|
field=models.CharField(verbose_name="Titre", max_length=200),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='surveyquestion',
|
model_name="surveyquestion",
|
||||||
name='multi_answers',
|
name="multi_answers",
|
||||||
field=models.BooleanField(verbose_name='Choix multiples',
|
field=models.BooleanField(verbose_name="Choix multiples", default=False),
|
||||||
default=False),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='surveyquestion',
|
model_name="surveyquestion",
|
||||||
name='question',
|
name="question",
|
||||||
field=models.CharField(verbose_name='Question', max_length=200),
|
field=models.CharField(verbose_name="Question", max_length=200),
|
||||||
),
|
),
|
||||||
migrations.RunPython(forwards, migrations.RunPython.noop),
|
migrations.RunPython(forwards, migrations.RunPython.noop),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,12 +6,6 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("gestioncof", "0008_py3")]
|
||||||
('gestioncof', '0008_py3'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [migrations.DeleteModel(name="Clipper")]
|
||||||
migrations.DeleteModel(
|
|
||||||
name='Clipper',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
|
@ -5,12 +5,6 @@ from django.db import migrations
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("gestioncof", "0009_delete_clipper")]
|
||||||
('gestioncof', '0009_delete_clipper'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [migrations.DeleteModel(name="CustomMail")]
|
||||||
migrations.DeleteModel(
|
|
||||||
name='CustomMail',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
|
@ -6,14 +6,14 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("gestioncof", "0010_delete_custommail")]
|
||||||
('gestioncof', '0010_delete_custommail'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='login_clipper',
|
name="login_clipper",
|
||||||
field=models.CharField(verbose_name='Login clipper', blank=True, max_length=32),
|
field=models.CharField(
|
||||||
),
|
verbose_name="Login clipper", blank=True, max_length=32
|
||||||
|
),
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,13 +6,6 @@ from django.db import migrations
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("gestioncof", "0010_delete_custommail")]
|
||||||
('gestioncof', '0010_delete_custommail'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [migrations.RemoveField(model_name="cofprofile", name="num")]
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='num',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
|
@ -7,9 +7,8 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('gestioncof', '0011_remove_cofprofile_num'),
|
("gestioncof", "0011_remove_cofprofile_num"),
|
||||||
('gestioncof', '0011_longer_clippers'),
|
("gestioncof", "0011_longer_clippers"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = []
|
||||||
]
|
|
||||||
|
|
|
@ -6,42 +6,42 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("gestioncof", "0012_merge")]
|
||||||
('gestioncof', '0012_merge'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='occupation',
|
name="occupation",
|
||||||
field=models.CharField(
|
field=models.CharField(
|
||||||
verbose_name='Occupation',
|
verbose_name="Occupation",
|
||||||
max_length=9,
|
max_length=9,
|
||||||
default='1A',
|
default="1A",
|
||||||
choices=[
|
choices=[
|
||||||
('exterieur', 'Extérieur'),
|
("exterieur", "Extérieur"),
|
||||||
('1A', '1A'),
|
("1A", "1A"),
|
||||||
('2A', '2A'),
|
("2A", "2A"),
|
||||||
('3A', '3A'),
|
("3A", "3A"),
|
||||||
('4A', '4A'),
|
("4A", "4A"),
|
||||||
('archicube', 'Archicube'),
|
("archicube", "Archicube"),
|
||||||
('doctorant', 'Doctorant'),
|
("doctorant", "Doctorant"),
|
||||||
('CST', 'CST'),
|
("CST", "CST"),
|
||||||
('PEI', 'PEI')
|
("PEI", "PEI"),
|
||||||
]),
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='cofprofile',
|
model_name="cofprofile",
|
||||||
name='type_cotiz',
|
name="type_cotiz",
|
||||||
field=models.CharField(
|
field=models.CharField(
|
||||||
verbose_name='Type de cotisation',
|
verbose_name="Type de cotisation",
|
||||||
max_length=9,
|
max_length=9,
|
||||||
default='normalien',
|
default="normalien",
|
||||||
choices=[
|
choices=[
|
||||||
('etudiant', 'Normalien étudiant'),
|
("etudiant", "Normalien étudiant"),
|
||||||
('normalien', 'Normalien élève'),
|
("normalien", "Normalien élève"),
|
||||||
('exterieur', 'Extérieur'),
|
("exterieur", "Extérieur"),
|
||||||
('gratis', 'Gratuit')
|
("gratis", "Gratuit"),
|
||||||
]),
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.15 on 2018-09-02 21:13
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [("gestioncof", "0013_pei")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="cofprofile",
|
||||||
|
name="mailing_unernestaparis",
|
||||||
|
field=models.BooleanField(
|
||||||
|
default=False, verbose_name="Recevoir les mails unErnestAParis"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
|
@ -1,17 +1,13 @@
|
||||||
from django.db import models
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models.signals import post_delete, post_save
|
||||||
|
from django.dispatch import receiver
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db.models.signals import post_save, post_delete
|
|
||||||
|
|
||||||
from gestioncof.petits_cours_models import choices_length
|
|
||||||
|
|
||||||
from bda.models import Spectacle
|
from bda.models import Spectacle
|
||||||
|
from gestioncof.petits_cours_models import choices_length
|
||||||
|
|
||||||
TYPE_COMMENT_FIELD = (
|
TYPE_COMMENT_FIELD = (("text", _("Texte long")), ("char", _("Texte court")))
|
||||||
('text', _("Texte long")),
|
|
||||||
('char', _("Texte court")),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CofProfile(models.Model):
|
class CofProfile(models.Model):
|
||||||
|
@ -49,39 +45,39 @@ class CofProfile(models.Model):
|
||||||
(COTIZ_GRATIS, _("Gratuit")),
|
(COTIZ_GRATIS, _("Gratuit")),
|
||||||
)
|
)
|
||||||
|
|
||||||
user = models.OneToOneField(
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
|
||||||
User, on_delete=models.CASCADE,
|
login_clipper = models.CharField("Login clipper", max_length=32, blank=True)
|
||||||
related_name="profile",
|
|
||||||
)
|
|
||||||
login_clipper = models.CharField(
|
|
||||||
"Login clipper", max_length=32, blank=True
|
|
||||||
)
|
|
||||||
is_cof = models.BooleanField("Membre du COF", default=False)
|
is_cof = models.BooleanField("Membre du COF", default=False)
|
||||||
phone = models.CharField("Téléphone", max_length=20, blank=True)
|
phone = models.CharField("Téléphone", max_length=20, blank=True)
|
||||||
occupation = models.CharField(_("Occupation"),
|
occupation = models.CharField(
|
||||||
default="1A",
|
_("Occupation"),
|
||||||
choices=OCCUPATION_CHOICES,
|
default="1A",
|
||||||
max_length=choices_length(
|
choices=OCCUPATION_CHOICES,
|
||||||
OCCUPATION_CHOICES))
|
max_length=choices_length(OCCUPATION_CHOICES),
|
||||||
departement = models.CharField(_("Département"), max_length=50,
|
)
|
||||||
blank=True)
|
departement = models.CharField(_("Département"), max_length=50, blank=True)
|
||||||
type_cotiz = models.CharField(_("Type de cotisation"),
|
type_cotiz = models.CharField(
|
||||||
default="normalien",
|
_("Type de cotisation"),
|
||||||
choices=TYPE_COTIZ_CHOICES,
|
default="normalien",
|
||||||
max_length=choices_length(
|
choices=TYPE_COTIZ_CHOICES,
|
||||||
TYPE_COTIZ_CHOICES))
|
max_length=choices_length(TYPE_COTIZ_CHOICES),
|
||||||
|
)
|
||||||
mailing_cof = models.BooleanField("Recevoir les mails COF", default=False)
|
mailing_cof = models.BooleanField("Recevoir les mails COF", default=False)
|
||||||
mailing_bda = models.BooleanField("Recevoir les mails BdA", default=False)
|
mailing_bda = models.BooleanField("Recevoir les mails BdA", default=False)
|
||||||
|
mailing_unernestaparis = models.BooleanField(
|
||||||
|
"Recevoir les mails unErnestAParis", default=False
|
||||||
|
)
|
||||||
mailing_bda_revente = models.BooleanField(
|
mailing_bda_revente = models.BooleanField(
|
||||||
"Recevoir les mails de revente de places BdA", default=False)
|
"Recevoir les mails de revente de places BdA", default=False
|
||||||
comments = models.TextField(
|
)
|
||||||
"Commentaires visibles par l'utilisateur", blank=True)
|
comments = models.TextField("Commentaires visibles par l'utilisateur", blank=True)
|
||||||
is_buro = models.BooleanField("Membre du Burô", default=False)
|
is_buro = models.BooleanField("Membre du Burô", default=False)
|
||||||
petits_cours_accept = models.BooleanField(
|
petits_cours_accept = models.BooleanField(
|
||||||
"Recevoir des petits cours", default=False)
|
"Recevoir des petits cours", default=False
|
||||||
|
)
|
||||||
petits_cours_remarques = models.TextField(
|
petits_cours_remarques = models.TextField(
|
||||||
_("Remarques et précisions pour les petits cours"),
|
_("Remarques et précisions pour les petits cours"), blank=True, default=""
|
||||||
blank=True, default="")
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Profil COF"
|
verbose_name = "Profil COF"
|
||||||
|
@ -105,8 +101,7 @@ def post_delete_user(sender, instance, *args, **kwargs):
|
||||||
class Club(models.Model):
|
class Club(models.Model):
|
||||||
name = models.CharField("Nom", max_length=200, unique=True)
|
name = models.CharField("Nom", max_length=200, unique=True)
|
||||||
description = models.TextField("Description", blank=True)
|
description = models.TextField("Description", blank=True)
|
||||||
respos = models.ManyToManyField(User, related_name="clubs_geres",
|
respos = models.ManyToManyField(User, related_name="clubs_geres", blank=True)
|
||||||
blank=True)
|
|
||||||
membres = models.ManyToManyField(User, related_name="clubs", blank=True)
|
membres = models.ManyToManyField(User, related_name="clubs", blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -119,10 +114,8 @@ class Event(models.Model):
|
||||||
start_date = models.DateTimeField("Date de début", blank=True, null=True)
|
start_date = models.DateTimeField("Date de début", blank=True, null=True)
|
||||||
end_date = models.DateTimeField("Date de fin", blank=True, null=True)
|
end_date = models.DateTimeField("Date de fin", blank=True, null=True)
|
||||||
description = models.TextField("Description", blank=True)
|
description = models.TextField("Description", blank=True)
|
||||||
image = models.ImageField("Image", blank=True, null=True,
|
image = models.ImageField("Image", blank=True, null=True, upload_to="imgs/events/")
|
||||||
upload_to="imgs/events/")
|
registration_open = models.BooleanField("Inscriptions ouvertes", default=True)
|
||||||
registration_open = models.BooleanField("Inscriptions ouvertes",
|
|
||||||
default=True)
|
|
||||||
old = models.BooleanField("Archiver (événement fini)", default=False)
|
old = models.BooleanField("Archiver (événement fini)", default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -134,12 +127,12 @@ class Event(models.Model):
|
||||||
|
|
||||||
class EventCommentField(models.Model):
|
class EventCommentField(models.Model):
|
||||||
event = models.ForeignKey(
|
event = models.ForeignKey(
|
||||||
Event, on_delete=models.CASCADE,
|
Event, on_delete=models.CASCADE, related_name="commentfields"
|
||||||
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(
|
||||||
choices=TYPE_COMMENT_FIELD, default="text")
|
"Type", max_length=10, choices=TYPE_COMMENT_FIELD, default="text"
|
||||||
|
)
|
||||||
default = models.TextField("Valeur par défaut", blank=True)
|
default = models.TextField("Valeur par défaut", blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -151,12 +144,10 @@ class EventCommentField(models.Model):
|
||||||
|
|
||||||
class EventCommentValue(models.Model):
|
class EventCommentValue(models.Model):
|
||||||
commentfield = models.ForeignKey(
|
commentfield = models.ForeignKey(
|
||||||
EventCommentField, on_delete=models.CASCADE,
|
EventCommentField, on_delete=models.CASCADE, related_name="values"
|
||||||
related_name="values",
|
|
||||||
)
|
)
|
||||||
registration = models.ForeignKey(
|
registration = models.ForeignKey(
|
||||||
"EventRegistration", on_delete=models.CASCADE,
|
"EventRegistration", on_delete=models.CASCADE, related_name="comments"
|
||||||
related_name="comments",
|
|
||||||
)
|
)
|
||||||
content = models.TextField("Contenu", blank=True, null=True)
|
content = models.TextField("Contenu", blank=True, null=True)
|
||||||
|
|
||||||
|
@ -165,10 +156,7 @@ class EventCommentValue(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class EventOption(models.Model):
|
class EventOption(models.Model):
|
||||||
event = models.ForeignKey(
|
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="options")
|
||||||
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)
|
||||||
|
|
||||||
|
@ -181,8 +169,7 @@ class EventOption(models.Model):
|
||||||
|
|
||||||
class EventOptionChoice(models.Model):
|
class EventOptionChoice(models.Model):
|
||||||
event_option = models.ForeignKey(
|
event_option = models.ForeignKey(
|
||||||
EventOption, on_delete=models.CASCADE,
|
EventOption, on_delete=models.CASCADE, related_name="choices"
|
||||||
related_name="choices",
|
|
||||||
)
|
)
|
||||||
value = models.CharField("Valeur", max_length=200)
|
value = models.CharField("Valeur", max_length=200)
|
||||||
|
|
||||||
|
@ -198,8 +185,9 @@ class EventRegistration(models.Model):
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||||
options = models.ManyToManyField(EventOptionChoice)
|
options = models.ManyToManyField(EventOptionChoice)
|
||||||
filledcomments = models.ManyToManyField(EventCommentField,
|
filledcomments = models.ManyToManyField(
|
||||||
through=EventCommentValue)
|
EventCommentField, through=EventCommentValue
|
||||||
|
)
|
||||||
paid = models.BooleanField("A payé", default=False)
|
paid = models.BooleanField("A payé", default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -225,8 +213,7 @@ class Survey(models.Model):
|
||||||
|
|
||||||
class SurveyQuestion(models.Model):
|
class SurveyQuestion(models.Model):
|
||||||
survey = models.ForeignKey(
|
survey = models.ForeignKey(
|
||||||
Survey, on_delete=models.CASCADE,
|
Survey, on_delete=models.CASCADE, related_name="questions"
|
||||||
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)
|
||||||
|
@ -240,8 +227,7 @@ class SurveyQuestion(models.Model):
|
||||||
|
|
||||||
class SurveyQuestionAnswer(models.Model):
|
class SurveyQuestionAnswer(models.Model):
|
||||||
survey_question = models.ForeignKey(
|
survey_question = models.ForeignKey(
|
||||||
SurveyQuestion, on_delete=models.CASCADE,
|
SurveyQuestion, on_delete=models.CASCADE, related_name="answers"
|
||||||
related_name="answers",
|
|
||||||
)
|
)
|
||||||
answer = models.CharField("Réponse", max_length=200)
|
answer = models.CharField("Réponse", max_length=200)
|
||||||
|
|
||||||
|
@ -255,8 +241,7 @@ class SurveyQuestionAnswer(models.Model):
|
||||||
class SurveyAnswer(models.Model):
|
class SurveyAnswer(models.Model):
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
survey = models.ForeignKey(Survey, on_delete=models.CASCADE)
|
survey = models.ForeignKey(Survey, on_delete=models.CASCADE)
|
||||||
answers = models.ManyToManyField(SurveyQuestionAnswer,
|
answers = models.ManyToManyField(SurveyQuestionAnswer, related_name="selected_by")
|
||||||
related_name="selected_by")
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Réponses"
|
verbose_name = "Réponses"
|
||||||
|
@ -264,8 +249,9 @@ class SurveyAnswer(models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Réponse de %s sondage %s" % (
|
return "Réponse de %s sondage %s" % (
|
||||||
self.user.get_full_name(),
|
self.user.get_full_name(),
|
||||||
self.survey.title)
|
self.survey.title,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CalendarSubscription(models.Model):
|
class CalendarSubscription(models.Model):
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
from captcha.fields import ReCaptchaField
|
from captcha.fields import ReCaptchaField
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms import ModelForm
|
|
||||||
from django.forms.models import inlineformset_factory, BaseInlineFormSet
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.forms import ModelForm
|
||||||
|
from django.forms.models import BaseInlineFormSet, inlineformset_factory
|
||||||
|
|
||||||
from gestioncof.petits_cours_models import PetitCoursDemande, PetitCoursAbility
|
from gestioncof.petits_cours_models import PetitCoursAbility, PetitCoursDemande
|
||||||
|
|
||||||
|
|
||||||
class BaseMatieresFormSet(BaseInlineFormSet):
|
class BaseMatieresFormSet(BaseInlineFormSet):
|
||||||
|
@ -20,33 +19,44 @@ class BaseMatieresFormSet(BaseInlineFormSet):
|
||||||
form = self.forms[i]
|
form = self.forms[i]
|
||||||
if not form.cleaned_data:
|
if not form.cleaned_data:
|
||||||
continue
|
continue
|
||||||
matiere = form.cleaned_data['matiere']
|
matiere = form.cleaned_data["matiere"]
|
||||||
niveau = form.cleaned_data['niveau']
|
niveau = form.cleaned_data["niveau"]
|
||||||
delete = form.cleaned_data['DELETE']
|
delete = form.cleaned_data["DELETE"]
|
||||||
if not delete and (matiere, niveau) in matieres:
|
if not delete and (matiere, niveau) in matieres:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
"Vous ne pouvez pas vous inscrire deux fois pour la "
|
"Vous ne pouvez pas vous inscrire deux fois pour la "
|
||||||
"même matiere avec le même niveau.")
|
"même matiere avec le même niveau."
|
||||||
|
)
|
||||||
matieres.append((matiere, niveau))
|
matieres.append((matiere, niveau))
|
||||||
|
|
||||||
|
|
||||||
class DemandeForm(ModelForm):
|
class DemandeForm(ModelForm):
|
||||||
captcha = ReCaptchaField(attrs={'theme': 'clean', 'lang': 'fr'})
|
captcha = ReCaptchaField(attrs={"theme": "clean", "lang": "fr"})
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['matieres'].help_text = ''
|
self.fields["matieres"].help_text = ""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PetitCoursDemande
|
model = PetitCoursDemande
|
||||||
fields = ('name', 'email', 'phone', 'quand', 'freq', 'lieu',
|
fields = (
|
||||||
'matieres', 'agrege_requis', 'niveau', 'remarques')
|
"name",
|
||||||
widgets = {'matieres': forms.CheckboxSelectMultiple}
|
"email",
|
||||||
|
"phone",
|
||||||
|
"quand",
|
||||||
|
"freq",
|
||||||
|
"lieu",
|
||||||
|
"matieres",
|
||||||
|
"agrege_requis",
|
||||||
|
"niveau",
|
||||||
|
"remarques",
|
||||||
|
)
|
||||||
|
widgets = {"matieres": forms.CheckboxSelectMultiple}
|
||||||
|
|
||||||
|
|
||||||
MatieresFormSet = inlineformset_factory(
|
MatieresFormSet = inlineformset_factory(
|
||||||
User,
|
User,
|
||||||
PetitCoursAbility,
|
PetitCoursAbility,
|
||||||
fields=("matiere", "niveau", "agrege"),
|
fields=("matiere", "niveau", "agrege"),
|
||||||
formset=BaseMatieresFormSet
|
formset=BaseMatieresFormSet,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,28 +1,30 @@
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Min
|
from django.db.models import Min
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
def choices_length(choices):
|
def choices_length(choices):
|
||||||
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
|
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
|
||||||
|
|
||||||
|
|
||||||
LEVELS_CHOICES = (
|
LEVELS_CHOICES = (
|
||||||
('college', _("Collège")),
|
("college", _("Collège")),
|
||||||
('lycee', _("Lycée")),
|
("lycee", _("Lycée")),
|
||||||
('prepa1styear', _("Prépa 1ère année / L1")),
|
("prepa1styear", _("Prépa 1ère année / L1")),
|
||||||
('prepa2ndyear', _("Prépa 2ème année / L2")),
|
("prepa2ndyear", _("Prépa 2ème année / L2")),
|
||||||
('licence3', _("Licence 3")),
|
("licence3", _("Licence 3")),
|
||||||
('other', _("Autre (préciser dans les commentaires)")),
|
("other", _("Autre (préciser dans les commentaires)")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursSubject(models.Model):
|
class PetitCoursSubject(models.Model):
|
||||||
name = models.CharField(_("Matière"), max_length=30)
|
name = models.CharField(_("Matière"), max_length=30)
|
||||||
users = models.ManyToManyField(User, related_name="petits_cours_matieres",
|
users = models.ManyToManyField(
|
||||||
through="PetitCoursAbility")
|
User, related_name="petits_cours_matieres", through="PetitCoursAbility"
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Matière de petits cours"
|
verbose_name = "Matière de petits cours"
|
||||||
|
@ -35,12 +37,11 @@ class PetitCoursSubject(models.Model):
|
||||||
class PetitCoursAbility(models.Model):
|
class PetitCoursAbility(models.Model):
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
matiere = models.ForeignKey(
|
matiere = models.ForeignKey(
|
||||||
PetitCoursSubject, on_delete=models.CASCADE,
|
PetitCoursSubject, on_delete=models.CASCADE, verbose_name=_("Matière")
|
||||||
verbose_name=_("Matière"),
|
)
|
||||||
|
niveau = models.CharField(
|
||||||
|
_("Niveau"), choices=LEVELS_CHOICES, max_length=choices_length(LEVELS_CHOICES)
|
||||||
)
|
)
|
||||||
niveau = models.CharField(_("Niveau"),
|
|
||||||
choices=LEVELS_CHOICES,
|
|
||||||
max_length=choices_length(LEVELS_CHOICES))
|
|
||||||
agrege = models.BooleanField(_("Agrégé"), default=False)
|
agrege = models.BooleanField(_("Agrégé"), default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -56,41 +57,50 @@ class PetitCoursAbility(models.Model):
|
||||||
class PetitCoursDemande(models.Model):
|
class PetitCoursDemande(models.Model):
|
||||||
name = models.CharField(_("Nom/prénom"), max_length=200)
|
name = models.CharField(_("Nom/prénom"), max_length=200)
|
||||||
email = models.CharField(_("Adresse email"), max_length=300)
|
email = models.CharField(_("Adresse email"), max_length=300)
|
||||||
phone = models.CharField(_("Téléphone (facultatif)"),
|
phone = models.CharField(_("Téléphone (facultatif)"), max_length=20, blank=True)
|
||||||
max_length=20, blank=True)
|
|
||||||
quand = models.CharField(
|
quand = models.CharField(
|
||||||
_("Quand ?"),
|
_("Quand ?"),
|
||||||
help_text=_("Indiquez ici la période désirée pour les petits"
|
help_text=_(
|
||||||
" cours (vacances scolaires, semaine, week-end)."),
|
"Indiquez ici la période désirée pour les petits"
|
||||||
max_length=300, blank=True)
|
" cours (vacances scolaires, semaine, week-end)."
|
||||||
|
),
|
||||||
|
max_length=300,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
freq = models.CharField(
|
freq = models.CharField(
|
||||||
_("Fréquence"),
|
_("Fréquence"),
|
||||||
help_text=_("Indiquez ici la fréquence envisagée "
|
help_text=_(
|
||||||
"(hebdomadaire, 2 fois par semaine, ...)"),
|
"Indiquez ici la fréquence envisagée "
|
||||||
max_length=300, blank=True)
|
"(hebdomadaire, 2 fois par semaine, ...)"
|
||||||
|
),
|
||||||
|
max_length=300,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
lieu = models.CharField(
|
lieu = models.CharField(
|
||||||
_("Lieu (si préférence)"),
|
_("Lieu (si préférence)"),
|
||||||
help_text=_("Si vous avez avez une préférence sur le lieu."),
|
help_text=_("Si vous avez avez une préférence sur le lieu."),
|
||||||
max_length=300, blank=True)
|
max_length=300,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
|
||||||
matieres = models.ManyToManyField(
|
matieres = models.ManyToManyField(
|
||||||
PetitCoursSubject, verbose_name=_("Matières"),
|
PetitCoursSubject, verbose_name=_("Matières"), related_name="demandes"
|
||||||
related_name="demandes")
|
)
|
||||||
agrege_requis = models.BooleanField(_("Agrégé requis"), default=False)
|
agrege_requis = models.BooleanField(_("Agrégé requis"), default=False)
|
||||||
niveau = models.CharField(_("Niveau"),
|
niveau = models.CharField(
|
||||||
default="",
|
_("Niveau"),
|
||||||
choices=LEVELS_CHOICES,
|
default="",
|
||||||
max_length=choices_length(LEVELS_CHOICES))
|
choices=LEVELS_CHOICES,
|
||||||
|
max_length=choices_length(LEVELS_CHOICES),
|
||||||
|
)
|
||||||
|
|
||||||
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(
|
traitee_par = models.ForeignKey(
|
||||||
User, on_delete=models.CASCADE,
|
User, on_delete=models.CASCADE, blank=True, null=True
|
||||||
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)
|
||||||
|
|
||||||
def get_candidates(self, redo=False):
|
def get_candidates(self, redo=False):
|
||||||
|
@ -105,18 +115,15 @@ class PetitCoursDemande(models.Model):
|
||||||
matiere=matiere,
|
matiere=matiere,
|
||||||
niveau=self.niveau,
|
niveau=self.niveau,
|
||||||
user__profile__is_cof=True,
|
user__profile__is_cof=True,
|
||||||
user__profile__petits_cours_accept=True
|
user__profile__petits_cours_accept=True,
|
||||||
)
|
)
|
||||||
if self.agrege_requis:
|
if self.agrege_requis:
|
||||||
candidates = candidates.filter(agrege=True)
|
candidates = candidates.filter(agrege=True)
|
||||||
if redo:
|
if redo:
|
||||||
attrs = self.petitcoursattribution_set.filter(matiere=matiere)
|
attrs = self.petitcoursattribution_set.filter(matiere=matiere)
|
||||||
already_proposed = [
|
already_proposed = [attr.user for attr in attrs]
|
||||||
attr.user
|
|
||||||
for attr in attrs
|
|
||||||
]
|
|
||||||
candidates = candidates.exclude(user__in=already_proposed)
|
candidates = candidates.exclude(user__in=already_proposed)
|
||||||
candidates = candidates.order_by('?').select_related().all()
|
candidates = candidates.order_by("?").select_related().all()
|
||||||
yield (matiere, candidates)
|
yield (matiere, candidates)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -124,25 +131,20 @@ class PetitCoursDemande(models.Model):
|
||||||
verbose_name_plural = "Demandes de petits cours"
|
verbose_name_plural = "Demandes de petits cours"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Demande {:d} du {:s}".format(
|
return "Demande {:d} du {:s}".format(self.id, self.created.strftime("%d %b %Y"))
|
||||||
self.id, self.created.strftime("%d %b %Y")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAttribution(models.Model):
|
class PetitCoursAttribution(models.Model):
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
demande = models.ForeignKey(
|
demande = models.ForeignKey(
|
||||||
PetitCoursDemande, on_delete=models.CASCADE,
|
PetitCoursDemande, on_delete=models.CASCADE, verbose_name=_("Demande")
|
||||||
verbose_name=_("Demande"),
|
|
||||||
)
|
)
|
||||||
matiere = models.ForeignKey(
|
matiere = models.ForeignKey(
|
||||||
PetitCoursSubject, on_delete=models.CASCADE,
|
PetitCoursSubject, on_delete=models.CASCADE, verbose_name=_("Matière")
|
||||||
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"), default=False)
|
||||||
default=False)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Attribution de petits cours"
|
verbose_name = "Attribution de petits cours"
|
||||||
|
@ -157,8 +159,7 @@ class PetitCoursAttribution(models.Model):
|
||||||
class PetitCoursAttributionCounter(models.Model):
|
class PetitCoursAttributionCounter(models.Model):
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
matiere = models.ForeignKey(
|
matiere = models.ForeignKey(
|
||||||
PetitCoursSubject, on_delete=models.CASCADE,
|
PetitCoursSubject, on_delete=models.CASCADE, verbose_name=_("Matiere")
|
||||||
verbose_name=_("Matiere"),
|
|
||||||
)
|
)
|
||||||
count = models.IntegerField("Nombre d'envois", default=0)
|
count = models.IntegerField("Nombre d'envois", default=0)
|
||||||
|
|
||||||
|
@ -169,15 +170,12 @@ class PetitCoursAttributionCounter(models.Model):
|
||||||
n'existe pas encore, il est initialisé avec le minimum des valeurs des
|
n'existe pas encore, il est initialisé avec le minimum des valeurs des
|
||||||
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)
|
||||||
.aggregate(Min('count'))
|
.exclude(user=user)
|
||||||
['count__min']
|
.aggregate(Min("count"))["count__min"]
|
||||||
)
|
)
|
||||||
counter.count = mincount or 0
|
counter.count = mincount or 0
|
||||||
counter.save()
|
counter.save()
|
||||||
|
|
|
@ -1,31 +1,31 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
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.core import mail
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.views.generic import ListView, DetailView
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.core import mail
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.views.generic import DetailView, ListView
|
||||||
|
|
||||||
from gestioncof.models import CofProfile
|
|
||||||
from gestioncof.petits_cours_models import (
|
|
||||||
PetitCoursDemande, PetitCoursAttribution, PetitCoursAttributionCounter,
|
|
||||||
PetitCoursAbility
|
|
||||||
)
|
|
||||||
from gestioncof.petits_cours_forms import DemandeForm, MatieresFormSet
|
|
||||||
from gestioncof.decorators import buro_required
|
from gestioncof.decorators import buro_required
|
||||||
|
from gestioncof.models import CofProfile
|
||||||
|
from gestioncof.petits_cours_forms import DemandeForm, MatieresFormSet
|
||||||
|
from gestioncof.petits_cours_models import (
|
||||||
|
PetitCoursAbility,
|
||||||
|
PetitCoursAttribution,
|
||||||
|
PetitCoursAttributionCounter,
|
||||||
|
PetitCoursDemande,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DemandeListView(ListView):
|
class DemandeListView(ListView):
|
||||||
queryset = (
|
queryset = PetitCoursDemande.objects.prefetch_related("matieres").order_by(
|
||||||
PetitCoursDemande.objects
|
"traitee", "-id"
|
||||||
.prefetch_related('matieres')
|
|
||||||
.order_by('traitee', '-id')
|
|
||||||
)
|
)
|
||||||
template_name = "petits_cours_demandes_list.html"
|
template_name = "petits_cours_demandes_list.html"
|
||||||
paginate_by = 20
|
paginate_by = 20
|
||||||
|
@ -33,10 +33,8 @@ class DemandeListView(ListView):
|
||||||
|
|
||||||
class DemandeDetailView(DetailView):
|
class DemandeDetailView(DetailView):
|
||||||
model = PetitCoursDemande
|
model = PetitCoursDemande
|
||||||
queryset = (
|
queryset = PetitCoursDemande.objects.prefetch_related(
|
||||||
PetitCoursDemande.objects
|
"petitcoursattribution_set", "matieres"
|
||||||
.prefetch_related('petitcoursattribution_set',
|
|
||||||
'matieres')
|
|
||||||
)
|
)
|
||||||
template_name = "gestioncof/details_demande_petit_cours.html"
|
template_name = "gestioncof/details_demande_petit_cours.html"
|
||||||
context_object_name = "demande"
|
context_object_name = "demande"
|
||||||
|
@ -44,7 +42,7 @@ class DemandeDetailView(DetailView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
obj = self.object
|
obj = self.object
|
||||||
context['attributions'] = obj.petitcoursattribution_set.all()
|
context["attributions"] = obj.petitcoursattribution_set.all()
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,13 +62,15 @@ def traitement(request, demande_id, redo=False):
|
||||||
tuples = []
|
tuples = []
|
||||||
for candidate in candidates:
|
for candidate in candidates:
|
||||||
user = candidate.user
|
user = candidate.user
|
||||||
tuples.append((
|
tuples.append(
|
||||||
candidate,
|
(
|
||||||
PetitCoursAttributionCounter.get_uptodate(user, matiere)
|
candidate,
|
||||||
))
|
PetitCoursAttributionCounter.get_uptodate(user, matiere),
|
||||||
|
)
|
||||||
|
)
|
||||||
tuples = sorted(tuples, key=lambda c: c[1].count)
|
tuples = sorted(tuples, key=lambda c: c[1].count)
|
||||||
candidates, _ = zip(*tuples)
|
candidates, _ = zip(*tuples)
|
||||||
candidates = candidates[0:min(3, len(candidates))]
|
candidates = candidates[0 : min(3, len(candidates))]
|
||||||
attribdata[matiere.id] = []
|
attribdata[matiere.id] = []
|
||||||
proposals[matiere] = []
|
proposals[matiere] = []
|
||||||
for candidate in candidates:
|
for candidate in candidates:
|
||||||
|
@ -83,8 +83,9 @@ def traitement(request, demande_id, redo=False):
|
||||||
proposed_for[user].append(matiere)
|
proposed_for[user].append(matiere)
|
||||||
else:
|
else:
|
||||||
unsatisfied.append(matiere)
|
unsatisfied.append(matiere)
|
||||||
return _finalize_traitement(request, demande, proposals,
|
return _finalize_traitement(
|
||||||
proposed_for, unsatisfied, attribdata, redo)
|
request, demande, proposals, proposed_for, unsatisfied, attribdata, redo
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@buro_required
|
@buro_required
|
||||||
|
@ -92,43 +93,56 @@ def retraitement(request, demande_id):
|
||||||
return traitement(request, demande_id, redo=True)
|
return traitement(request, demande_id, redo=True)
|
||||||
|
|
||||||
|
|
||||||
def _finalize_traitement(request, demande, proposals, proposed_for,
|
def _finalize_traitement(
|
||||||
unsatisfied, attribdata, redo=False, errors=None):
|
request,
|
||||||
|
demande,
|
||||||
|
proposals,
|
||||||
|
proposed_for,
|
||||||
|
unsatisfied,
|
||||||
|
attribdata,
|
||||||
|
redo=False,
|
||||||
|
errors=None,
|
||||||
|
):
|
||||||
proposals = proposals.items()
|
proposals = proposals.items()
|
||||||
proposed_for = proposed_for.items()
|
proposed_for = proposed_for.items()
|
||||||
attribdata = list(attribdata.items())
|
attribdata = list(attribdata.items())
|
||||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||||
mainmail = render_custom_mail("petits-cours-mail-demandeur", {
|
mainmail = render_custom_mail(
|
||||||
"proposals": proposals,
|
"petits-cours-mail-demandeur",
|
||||||
"unsatisfied": unsatisfied,
|
{
|
||||||
"extra":
|
"proposals": proposals,
|
||||||
'<textarea name="extra" '
|
"unsatisfied": unsatisfied,
|
||||||
|
"extra": '<textarea name="extra" '
|
||||||
'style="width:99%; height: 90px;">'
|
'style="width:99%; height: 90px;">'
|
||||||
'</textarea>'
|
"</textarea>",
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if errors is not None:
|
if errors is not None:
|
||||||
for error in errors:
|
for error in errors:
|
||||||
messages.error(request, error)
|
messages.error(request, error)
|
||||||
return render(request, "gestioncof/traitement_demande_petit_cours.html",
|
return render(
|
||||||
{"demande": demande,
|
request,
|
||||||
"unsatisfied": unsatisfied,
|
"gestioncof/traitement_demande_petit_cours.html",
|
||||||
"proposals": proposals,
|
{
|
||||||
"proposed_for": proposed_for,
|
"demande": demande,
|
||||||
"proposed_mails": proposed_mails,
|
"unsatisfied": unsatisfied,
|
||||||
"mainmail": mainmail,
|
"proposals": proposals,
|
||||||
"attribdata": json.dumps(attribdata),
|
"proposed_for": proposed_for,
|
||||||
"redo": redo,
|
"proposed_mails": proposed_mails,
|
||||||
})
|
"mainmail": mainmail,
|
||||||
|
"attribdata": json.dumps(attribdata),
|
||||||
|
"redo": redo,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _generate_eleve_email(demande, proposed_for):
|
def _generate_eleve_email(demande, proposed_for):
|
||||||
return [
|
return [
|
||||||
(
|
(
|
||||||
user,
|
user,
|
||||||
render_custom_mail('petit-cours-mail-eleve', {
|
render_custom_mail(
|
||||||
"demande": demande,
|
"petit-cours-mail-eleve", {"demande": demande, "matieres": matieres}
|
||||||
"matieres": matieres
|
),
|
||||||
})
|
|
||||||
)
|
)
|
||||||
for user, matieres in proposed_for
|
for user, matieres in proposed_for
|
||||||
]
|
]
|
||||||
|
@ -143,25 +157,30 @@ def _traitement_other_preparing(request, demande):
|
||||||
errors = []
|
errors = []
|
||||||
for matiere, candidates in demande.get_candidates(redo):
|
for matiere, candidates in demande.get_candidates(redo):
|
||||||
if candidates:
|
if candidates:
|
||||||
candidates = dict([(candidate.user.id, candidate.user)
|
candidates = dict(
|
||||||
for candidate in candidates])
|
[(candidate.user.id, candidate.user) for candidate in candidates]
|
||||||
|
)
|
||||||
attribdata[matiere.id] = []
|
attribdata[matiere.id] = []
|
||||||
proposals[matiere] = []
|
proposals[matiere] = []
|
||||||
for choice_id in range(min(3, len(candidates))):
|
for choice_id in range(min(3, len(candidates))):
|
||||||
choice = int(
|
choice = int(
|
||||||
request.POST["proposal-{:d}-{:d}"
|
request.POST["proposal-{:d}-{:d}".format(matiere.id, choice_id)]
|
||||||
.format(matiere.id, choice_id)]
|
|
||||||
)
|
)
|
||||||
if choice == -1:
|
if choice == -1:
|
||||||
continue
|
continue
|
||||||
if choice not in candidates:
|
if choice not in candidates:
|
||||||
errors.append("Choix invalide pour la proposition {:d}"
|
errors.append(
|
||||||
"en {!s}".format(choice_id + 1, matiere))
|
"Choix invalide pour la proposition {:d}"
|
||||||
|
"en {!s}".format(choice_id + 1, matiere)
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
user = candidates[choice]
|
user = candidates[choice]
|
||||||
if user in proposals[matiere]:
|
if user in proposals[matiere]:
|
||||||
errors.append("La proposition {:d} en {!s} est un doublon"
|
errors.append(
|
||||||
.format(choice_id + 1, matiere))
|
"La proposition {:d} en {!s} est un doublon".format(
|
||||||
|
choice_id + 1, matiere
|
||||||
|
)
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
proposals[matiere].append(user)
|
proposals[matiere].append(user)
|
||||||
attribdata[matiere.id].append(user.id)
|
attribdata[matiere.id].append(user.id)
|
||||||
|
@ -172,15 +191,24 @@ def _traitement_other_preparing(request, demande):
|
||||||
if not proposals[matiere]:
|
if not proposals[matiere]:
|
||||||
errors.append("Aucune proposition pour {!s}".format(matiere))
|
errors.append("Aucune proposition pour {!s}".format(matiere))
|
||||||
elif len(proposals[matiere]) < 3:
|
elif len(proposals[matiere]) < 3:
|
||||||
errors.append("Seulement {:d} proposition{:s} pour {!s}"
|
errors.append(
|
||||||
.format(
|
"Seulement {:d} proposition{:s} pour {!s}".format(
|
||||||
len(proposals[matiere]),
|
len(proposals[matiere]),
|
||||||
"s" if len(proposals[matiere]) > 1 else "",
|
"s" if len(proposals[matiere]) > 1 else "",
|
||||||
matiere))
|
matiere,
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
unsatisfied.append(matiere)
|
unsatisfied.append(matiere)
|
||||||
return _finalize_traitement(request, demande, proposals, proposed_for,
|
return _finalize_traitement(
|
||||||
unsatisfied, attribdata, errors=errors)
|
request,
|
||||||
|
demande,
|
||||||
|
proposals,
|
||||||
|
proposed_for,
|
||||||
|
unsatisfied,
|
||||||
|
attribdata,
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _traitement_other(request, demande, redo):
|
def _traitement_other(request, demande, redo):
|
||||||
|
@ -198,10 +226,12 @@ def _traitement_other(request, demande, redo):
|
||||||
tuples = []
|
tuples = []
|
||||||
for candidate in candidates:
|
for candidate in candidates:
|
||||||
user = candidate.user
|
user = candidate.user
|
||||||
tuples.append((
|
tuples.append(
|
||||||
candidate,
|
(
|
||||||
PetitCoursAttributionCounter.get_uptodate(user, matiere)
|
candidate,
|
||||||
))
|
PetitCoursAttributionCounter.get_uptodate(user, matiere),
|
||||||
|
)
|
||||||
|
)
|
||||||
tuples = sorted(tuples, key=lambda c: c[1].count)
|
tuples = sorted(tuples, key=lambda c: c[1].count)
|
||||||
candidates, _ = zip(*tuples)
|
candidates, _ = zip(*tuples)
|
||||||
attribdata[matiere.id] = []
|
attribdata[matiere.id] = []
|
||||||
|
@ -218,13 +248,16 @@ def _traitement_other(request, demande, redo):
|
||||||
unsatisfied.append(matiere)
|
unsatisfied.append(matiere)
|
||||||
proposals = proposals.items()
|
proposals = proposals.items()
|
||||||
proposed_for = proposed_for.items()
|
proposed_for = proposed_for.items()
|
||||||
return render(request,
|
return render(
|
||||||
"gestioncof/traitement_demande_petit_cours_autre_niveau.html",
|
request,
|
||||||
{"demande": demande,
|
"gestioncof/traitement_demande_petit_cours_autre_niveau.html",
|
||||||
"unsatisfied": unsatisfied,
|
{
|
||||||
"proposals": proposals,
|
"demande": demande,
|
||||||
"proposed_for": proposed_for,
|
"unsatisfied": unsatisfied,
|
||||||
})
|
"proposals": proposals,
|
||||||
|
"proposed_for": proposed_for,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _traitement_post(request, demande):
|
def _traitement_post(request, demande):
|
||||||
|
@ -252,24 +285,32 @@ def _traitement_post(request, demande):
|
||||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||||
mainmail_object, mainmail_body = render_custom_mail(
|
mainmail_object, mainmail_body = render_custom_mail(
|
||||||
"petits-cours-mail-demandeur",
|
"petits-cours-mail-demandeur",
|
||||||
{
|
{"proposals": proposals_list, "unsatisfied": unsatisfied, "extra": extra},
|
||||||
"proposals": proposals_list,
|
|
||||||
"unsatisfied": unsatisfied,
|
|
||||||
"extra": extra
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
frommail = settings.MAIL_DATA['petits_cours']['FROM']
|
frommail = settings.MAIL_DATA["petits_cours"]["FROM"]
|
||||||
bccaddress = settings.MAIL_DATA['petits_cours']['BCC']
|
bccaddress = settings.MAIL_DATA["petits_cours"]["BCC"]
|
||||||
replyto = settings.MAIL_DATA['petits_cours']['REPLYTO']
|
replyto = settings.MAIL_DATA["petits_cours"]["REPLYTO"]
|
||||||
mails_to_send = []
|
mails_to_send = []
|
||||||
for (user, (mail_object, body)) in proposed_mails:
|
for (user, (mail_object, body)) in proposed_mails:
|
||||||
msg = mail.EmailMessage(mail_object, body, frommail, [user.email],
|
msg = mail.EmailMessage(
|
||||||
[bccaddress], headers={'Reply-To': replyto})
|
mail_object,
|
||||||
|
body,
|
||||||
|
frommail,
|
||||||
|
[user.email],
|
||||||
|
[bccaddress],
|
||||||
|
headers={"Reply-To": replyto},
|
||||||
|
)
|
||||||
mails_to_send.append(msg)
|
mails_to_send.append(msg)
|
||||||
mails_to_send.append(mail.EmailMessage(mainmail_object, mainmail_body,
|
mails_to_send.append(
|
||||||
frommail, [demande.email],
|
mail.EmailMessage(
|
||||||
[bccaddress],
|
mainmail_object,
|
||||||
headers={'Reply-To': replyto}))
|
mainmail_body,
|
||||||
|
frommail,
|
||||||
|
[demande.email],
|
||||||
|
[bccaddress],
|
||||||
|
headers={"Reply-To": replyto},
|
||||||
|
)
|
||||||
|
)
|
||||||
connection = mail.get_connection(fail_silently=False)
|
connection = mail.get_connection(fail_silently=False)
|
||||||
connection.send_messages(mails_to_send)
|
connection.send_messages(mails_to_send)
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
@ -280,18 +321,19 @@ def _traitement_post(request, demande):
|
||||||
)
|
)
|
||||||
counter.count += 1
|
counter.count += 1
|
||||||
counter.save()
|
counter.save()
|
||||||
attrib = PetitCoursAttribution(user=user, matiere=matiere,
|
attrib = PetitCoursAttribution(
|
||||||
demande=demande, rank=rank + 1)
|
user=user, matiere=matiere, demande=demande, rank=rank + 1
|
||||||
|
)
|
||||||
attrib.save()
|
attrib.save()
|
||||||
demande.traitee = True
|
demande.traitee = True
|
||||||
demande.traitee_par = request.user
|
demande.traitee_par = request.user
|
||||||
demande.processed = timezone.now()
|
demande.processed = timezone.now()
|
||||||
demande.save()
|
demande.save()
|
||||||
return render(request,
|
return render(
|
||||||
"gestioncof/traitement_demande_petit_cours_success.html",
|
request,
|
||||||
{"demande": demande,
|
"gestioncof/traitement_demande_petit_cours_success.html",
|
||||||
"redo": redo,
|
{"demande": demande, "redo": redo},
|
||||||
})
|
)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -308,22 +350,25 @@ def inscription(request):
|
||||||
profile.petits_cours_remarques = request.POST["remarques"]
|
profile.petits_cours_remarques = request.POST["remarques"]
|
||||||
profile.save()
|
profile.save()
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
abilities = (
|
abilities = PetitCoursAbility.objects.filter(user=request.user).all()
|
||||||
PetitCoursAbility.objects.filter(user=request.user).all()
|
|
||||||
)
|
|
||||||
for ability in abilities:
|
for ability in abilities:
|
||||||
PetitCoursAttributionCounter.get_uptodate(
|
PetitCoursAttributionCounter.get_uptodate(
|
||||||
ability.user,
|
ability.user, ability.matiere
|
||||||
ability.matiere
|
|
||||||
)
|
)
|
||||||
success = True
|
success = True
|
||||||
formset = MatieresFormSet(instance=request.user)
|
formset = MatieresFormSet(instance=request.user)
|
||||||
else:
|
else:
|
||||||
formset = MatieresFormSet(instance=request.user)
|
formset = MatieresFormSet(instance=request.user)
|
||||||
return render(request, "inscription-petit-cours.html",
|
return render(
|
||||||
{"formset": formset, "success": success,
|
request,
|
||||||
"receive_proposals": profile.petits_cours_accept,
|
"inscription-petit-cours.html",
|
||||||
"remarques": profile.petits_cours_remarques})
|
{
|
||||||
|
"formset": formset,
|
||||||
|
"success": success,
|
||||||
|
"receive_proposals": profile.petits_cours_accept,
|
||||||
|
"remarques": profile.petits_cours_remarques,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
|
@ -336,8 +381,9 @@ def demande(request):
|
||||||
success = True
|
success = True
|
||||||
else:
|
else:
|
||||||
form = DemandeForm()
|
form = DemandeForm()
|
||||||
return render(request, "demande-petit-cours.html", {"form": form,
|
return render(
|
||||||
"success": success})
|
request, "demande-petit-cours.html", {"form": form, "success": success}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
|
@ -350,5 +396,6 @@ def demande_raw(request):
|
||||||
success = True
|
success = True
|
||||||
else:
|
else:
|
||||||
form = DemandeForm()
|
form = DemandeForm()
|
||||||
return render(request, "demande-petit-cours-raw.html",
|
return render(
|
||||||
{"form": form, "success": success})
|
request, "demande-petit-cours-raw.html", {"form": form, "success": success}
|
||||||
|
)
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
|
|
||||||
from django_cas_ng.backends import CASBackend
|
from django_cas_ng.backends import CASBackend
|
||||||
|
|
||||||
from gestioncof.models import CofProfile
|
|
||||||
|
|
||||||
|
|
||||||
class COFCASBackend(CASBackend):
|
class COFCASBackend(CASBackend):
|
||||||
|
|
||||||
def clean_username(self, username):
|
def clean_username(self, username):
|
||||||
# Le CAS de l'ENS accepte les logins avec des espaces au début
|
# Le CAS de l'ENS accepte les logins avec des espaces au début
|
||||||
# et à la fin, ainsi qu’avec une casse variable. On normalise pour
|
# et à la fin, ainsi qu’avec une casse variable. On normalise pour
|
||||||
|
@ -24,9 +20,6 @@ class COFCASBackend(CASBackend):
|
||||||
|
|
||||||
|
|
||||||
def context_processor(request):
|
def context_processor(request):
|
||||||
'''Append extra data to the context of the given request'''
|
"""Append extra data to the context of the given request"""
|
||||||
data = {
|
data = {"user": request.user, "site": Site.objects.get_current()}
|
||||||
"user": request.user,
|
|
||||||
"site": Site.objects.get_current(),
|
|
||||||
}
|
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -2,22 +2,21 @@ from django.contrib import messages
|
||||||
from django.contrib.auth.signals import user_logged_in
|
from django.contrib.auth.signals import user_logged_in
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from django_cas_ng.signals import cas_user_authenticated
|
from django_cas_ng.signals import cas_user_authenticated
|
||||||
|
|
||||||
|
|
||||||
@receiver(user_logged_in)
|
@receiver(user_logged_in)
|
||||||
def messages_on_out_login(request, user, **kwargs):
|
def messages_on_out_login(request, user, **kwargs):
|
||||||
if user.backend.startswith('django.contrib.auth'):
|
if user.backend.startswith("django.contrib.auth"):
|
||||||
msg = _('Connexion à GestioCOF réussie. Bienvenue {}.').format(
|
msg = _("Connexion à GestioCOF réussie. Bienvenue {}.").format(
|
||||||
user.get_short_name(),
|
user.get_short_name()
|
||||||
)
|
)
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
|
|
||||||
|
|
||||||
@receiver(cas_user_authenticated)
|
@receiver(cas_user_authenticated)
|
||||||
def mesagges_on_cas_login(request, user, **kwargs):
|
def mesagges_on_cas_login(request, user, **kwargs):
|
||||||
msg = _('Connexion à GestioCOF par CAS réussie. Bienvenue {}.').format(
|
msg = _("Connexion à GestioCOF par CAS réussie. Bienvenue {}.").format(
|
||||||
user.get_short_name(),
|
user.get_short_name()
|
||||||
)
|
)
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
|
|
|
@ -4,26 +4,23 @@
|
||||||
{% block page_size %}col-sm-8{%endblock%}
|
{% block page_size %}col-sm-8{%endblock%}
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
<h2>Modifier mon profil</h2>
|
<h2>Modifier mon profil</h2>
|
||||||
<form id="profile form-horizontal" method="post" action="{% url 'profile' %}">
|
<form id="profile form-horizontal" method="post" action="">
|
||||||
<div class="row" style="margin: 0 15%;">
|
<div class="row" style="margin: 0 15%;">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<fieldset"center-block">
|
{{ user_form | bootstrap }}
|
||||||
{% for field in form %}
|
{{ profile_form | bootstrap }}
|
||||||
{{ field | bootstrap }}
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</fieldset>
|
{% if user.profile.comments %}
|
||||||
</div>
|
<div class="row" style="margin: 0 15%;">
|
||||||
{% if user.profile.comments %}
|
<h4>Commentaires</h4>
|
||||||
<div class="row" style="margin: 0 15%;">
|
<p>{{ user.profile.comments }}</p>
|
||||||
<h4>Commentaires</h4>
|
</div>
|
||||||
<p>
|
{% endif %}
|
||||||
{{ user.profile.comments }}
|
|
||||||
</p>
|
<div class="form-actions">
|
||||||
</div>
|
<input type="submit" class="btn btn-primary pull-right" value="Enregistrer" />
|
||||||
{% endif %}
|
</div>
|
||||||
<div class="form-actions">
|
</form>
|
||||||
<input type="submit" class="btn btn-primary pull-right" value="Enregistrer" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
import re
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ def key(d, key_name):
|
||||||
value = d[key_name]
|
value = d[key_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
value = settings.TEMPLATE_STRING_IF_INVALID
|
value = settings.TEMPLATE_STRING_IF_INVALID
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -19,16 +20,15 @@ def key(d, key_name):
|
||||||
def highlight_text(text, q):
|
def highlight_text(text, q):
|
||||||
q2 = "|".join(re.escape(word) for word in q.split())
|
q2 = "|".join(re.escape(word) for word in q.split())
|
||||||
pattern = re.compile(r"(?P<filter>%s)" % q2, re.IGNORECASE)
|
pattern = re.compile(r"(?P<filter>%s)" % q2, re.IGNORECASE)
|
||||||
return mark_safe(re.sub(pattern,
|
return mark_safe(
|
||||||
r"<span class='highlight'>\g<filter></span>",
|
re.sub(pattern, r"<span class='highlight'>\g<filter></span>", text)
|
||||||
text))
|
)
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def highlight_user(user, q):
|
def highlight_user(user, q):
|
||||||
if user.first_name and user.last_name:
|
if user.first_name and user.last_name:
|
||||||
text = "%s %s (<tt>%s</tt>)" % (user.first_name, user.last_name,
|
text = "%s %s (<tt>%s</tt>)" % (user.first_name, user.last_name, user.username)
|
||||||
user.username)
|
|
||||||
else:
|
else:
|
||||||
text = user.username
|
text = user.username
|
||||||
return highlight_text(text, q)
|
return highlight_text(text, q)
|
||||||
|
|
|
@ -12,17 +12,17 @@ from gestioncof.models import CofProfile, User
|
||||||
|
|
||||||
class SimpleTest(TestCase):
|
class SimpleTest(TestCase):
|
||||||
def test_delete_user(self):
|
def test_delete_user(self):
|
||||||
u = User(username='foo', first_name='foo', last_name='bar')
|
u = User(username="foo", first_name="foo", last_name="bar")
|
||||||
|
|
||||||
# to each user there's a cofprofile associated
|
# to each user there's a cofprofile associated
|
||||||
u.save()
|
u.save()
|
||||||
self.assertTrue(CofProfile.objects.filter(user__username='foo').exists())
|
self.assertTrue(CofProfile.objects.filter(user__username="foo").exists())
|
||||||
|
|
||||||
# there's no point in having a cofprofile without a user associated.
|
# there's no point in having a cofprofile without a user associated.
|
||||||
u.delete()
|
u.delete()
|
||||||
self.assertFalse(CofProfile.objects.filter(user__username='foo').exists())
|
self.assertFalse(CofProfile.objects.filter(user__username="foo").exists())
|
||||||
|
|
||||||
# there's no point in having a user without a cofprofile associated.
|
# there's no point in having a user without a cofprofile associated.
|
||||||
u.save()
|
u.save()
|
||||||
CofProfile.objects.get(user__username='foo').delete()
|
CofProfile.objects.get(user__username="foo").delete()
|
||||||
self.assertFalse(User.objects.filter(username='foo').exists())
|
self.assertFalse(User.objects.filter(username="foo").exists())
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
from shared.tests.testcases import ViewTestCaseMixin as BaseViewTestCaseMixin
|
from shared.tests.testcases import ViewTestCaseMixin as BaseViewTestCaseMixin
|
||||||
|
|
||||||
from .utils import create_user, create_member, create_staff
|
from .utils import create_member, create_staff, create_user
|
||||||
|
|
||||||
|
|
||||||
class ViewTestCaseMixin(BaseViewTestCaseMixin):
|
class ViewTestCaseMixin(BaseViewTestCaseMixin):
|
||||||
|
@ -18,7 +18,7 @@ class ViewTestCaseMixin(BaseViewTestCaseMixin):
|
||||||
|
|
||||||
def get_users_base(self):
|
def get_users_base(self):
|
||||||
return {
|
return {
|
||||||
'user': create_user('user'),
|
"user": create_user("user"),
|
||||||
'member': create_member('member'),
|
"member": create_member("member"),
|
||||||
'staff': create_staff('staff'),
|
"staff": create_staff("staff"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,28 +7,35 @@ def _create_user(username, is_cof=False, is_staff=False, attrs=None):
|
||||||
if attrs is None:
|
if attrs is None:
|
||||||
attrs = {}
|
attrs = {}
|
||||||
|
|
||||||
password = attrs.pop('password', username)
|
password = attrs.pop("password", username)
|
||||||
|
|
||||||
user_keys = [
|
user_keys = ["first_name", "last_name", "email", "is_staff", "is_superuser"]
|
||||||
'first_name', 'last_name', 'email', 'is_staff', 'is_superuser',
|
|
||||||
]
|
|
||||||
user_attrs = {k: v for k, v in attrs.items() if k in user_keys}
|
user_attrs = {k: v for k, v in attrs.items() if k in user_keys}
|
||||||
|
|
||||||
profile_keys = [
|
profile_keys = [
|
||||||
'is_cof', 'login_clipper', 'phone', 'occupation', 'departement',
|
"is_cof",
|
||||||
'type_cotiz', 'mailing_cof', 'mailing_bda', 'mailing_bda_revente',
|
"login_clipper",
|
||||||
'comments', 'is_buro', 'petit_cours_accept',
|
"phone",
|
||||||
'petit_cours_remarques',
|
"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}
|
profile_attrs = {k: v for k, v in attrs.items() if k in profile_keys}
|
||||||
|
|
||||||
if is_cof:
|
if is_cof:
|
||||||
profile_attrs['is_cof'] = True
|
profile_attrs["is_cof"] = True
|
||||||
|
|
||||||
if is_staff:
|
if is_staff:
|
||||||
# At the moment, admin is accessible by COF staff.
|
# At the moment, admin is accessible by COF staff.
|
||||||
user_attrs['is_staff'] = True
|
user_attrs["is_staff"] = True
|
||||||
profile_attrs['is_buro'] = True
|
profile_attrs["is_buro"] = True
|
||||||
|
|
||||||
user = User(username=username, **user_attrs)
|
user = User(username=username, **user_attrs)
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
|
@ -56,6 +63,6 @@ def create_staff(username, attrs=None):
|
||||||
def create_root(username, attrs=None):
|
def create_root(username, attrs=None):
|
||||||
if attrs is None:
|
if attrs is None:
|
||||||
attrs = {}
|
attrs = {}
|
||||||
attrs.setdefault('is_staff', True)
|
attrs.setdefault("is_staff", True)
|
||||||
attrs.setdefault('is_superuser', True)
|
attrs.setdefault("is_superuser", True)
|
||||||
return _create_user(username, attrs=attrs)
|
return _create_user(username, attrs=attrs)
|
||||||
|
|
|
@ -1,67 +1,87 @@
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from gestioncof.petits_cours_views import DemandeListView, DemandeDetailView
|
|
||||||
from gestioncof import views, petits_cours_views
|
from gestioncof import petits_cours_views, views
|
||||||
from gestioncof.decorators import buro_required
|
from gestioncof.decorators import buro_required
|
||||||
|
from gestioncof.petits_cours_views import DemandeDetailView, DemandeListView
|
||||||
|
|
||||||
export_patterns = [
|
export_patterns = [
|
||||||
url(r'^members$', views.export_members,
|
url(r"^members$", views.export_members, name="cof.membres_export"),
|
||||||
name='cof.membres_export'),
|
url(
|
||||||
url(r'^mega/avecremarques$', views.export_mega_remarksonly,
|
r"^mega/avecremarques$",
|
||||||
name='cof.mega_export_remarks'),
|
views.export_mega_remarksonly,
|
||||||
url(r'^mega/participants$', views.export_mega_participants,
|
name="cof.mega_export_remarks",
|
||||||
name='cof.mega_export_participants'),
|
),
|
||||||
url(r'^mega/orgas$', views.export_mega_orgas,
|
url(
|
||||||
name='cof.mega_export_orgas'),
|
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"),
|
||||||
name='cof.mega_export'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
petitcours_patterns = [
|
petitcours_patterns = [
|
||||||
url(r'^inscription$', petits_cours_views.inscription,
|
url(
|
||||||
name='petits-cours-inscription'),
|
r"^inscription$",
|
||||||
url(r'^demande$', petits_cours_views.demande,
|
petits_cours_views.inscription,
|
||||||
name='petits-cours-demande'),
|
name="petits-cours-inscription",
|
||||||
url(r'^demande-raw$', petits_cours_views.demande_raw,
|
),
|
||||||
name='petits-cours-demande-raw'),
|
url(r"^demande$", petits_cours_views.demande, name="petits-cours-demande"),
|
||||||
url(r'^demandes$',
|
url(
|
||||||
|
r"^demande-raw$",
|
||||||
|
petits_cours_views.demande_raw,
|
||||||
|
name="petits-cours-demande-raw",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^demandes$",
|
||||||
buro_required(DemandeListView.as_view()),
|
buro_required(DemandeListView.as_view()),
|
||||||
name='petits-cours-demandes-list'),
|
name="petits-cours-demandes-list",
|
||||||
url(r'^demandes/(?P<pk>\d+)$',
|
),
|
||||||
|
url(
|
||||||
|
r"^demandes/(?P<pk>\d+)$",
|
||||||
buro_required(DemandeDetailView.as_view()),
|
buro_required(DemandeDetailView.as_view()),
|
||||||
name='petits-cours-demande-details'),
|
name="petits-cours-demande-details",
|
||||||
url(r'^demandes/(?P<demande_id>\d+)/traitement$',
|
),
|
||||||
|
url(
|
||||||
|
r"^demandes/(?P<demande_id>\d+)/traitement$",
|
||||||
petits_cours_views.traitement,
|
petits_cours_views.traitement,
|
||||||
name='petits-cours-demande-traitement'),
|
name="petits-cours-demande-traitement",
|
||||||
url(r'^demandes/(?P<demande_id>\d+)/retraitement$',
|
),
|
||||||
|
url(
|
||||||
|
r"^demandes/(?P<demande_id>\d+)/retraitement$",
|
||||||
petits_cours_views.retraitement,
|
petits_cours_views.retraitement,
|
||||||
name='petits-cours-demande-retraitement'),
|
name="petits-cours-demande-retraitement",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
surveys_patterns = [
|
surveys_patterns = [
|
||||||
url(r'^(?P<survey_id>\d+)/status$', views.survey_status,
|
url(
|
||||||
name='survey.details.status'),
|
r"^(?P<survey_id>\d+)/status$",
|
||||||
url(r'^(?P<survey_id>\d+)$', views.survey,
|
views.survey_status,
|
||||||
name='survey.details'),
|
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, name="event.details"),
|
||||||
name='event.details'),
|
url(r"^(?P<event_id>\d+)/status$", views.event_status, name="event.details.status"),
|
||||||
url(r'^(?P<event_id>\d+)/status$', views.event_status,
|
|
||||||
name='event.details.status'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
calendar_patterns = [
|
calendar_patterns = [
|
||||||
url(r'^subscription$', views.calendar,
|
url(r"^subscription$", views.calendar, name="calendar"),
|
||||||
name='calendar'),
|
url(
|
||||||
url(r'^(?P<token>[a-z0-9-]+)/calendar.ics$', views.calendar_ics,
|
r"^(?P<token>[a-z0-9-]+)/calendar.ics$", views.calendar_ics, name="calendar.ics"
|
||||||
name='calendar.ics'),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
clubs_patterns = [
|
clubs_patterns = [
|
||||||
url(r'^membres/(?P<name>\w+)', views.membres_club, name='membres-club'),
|
url(r"^membres/(?P<name>\w+)", views.membres_club, name="membres-club"),
|
||||||
url(r'^liste', views.liste_clubs, name='liste-clubs'),
|
url(r"^liste", views.liste_clubs, name="liste-clubs"),
|
||||||
url(r'^change_respo/(?P<club_name>\w+)/(?P<user_id>\d+)',
|
url(
|
||||||
views.change_respo, name='change-respo'),
|
r"^change_respo/(?P<club_name>\w+)/(?P<user_id>\d+)",
|
||||||
|
views.change_respo,
|
||||||
|
name="change-respo",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,5 @@
|
||||||
from django.forms.widgets import Widget
|
|
||||||
from django.forms.utils import flatatt
|
from django.forms.utils import flatatt
|
||||||
|
from django.forms.widgets import Widget
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ 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"
|
||||||
attrs['value'] = value
|
attrs["value"] = value
|
||||||
final_attrs = self.build_attrs(self.attrs, attrs)
|
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))
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
default_app_config = 'kfet.apps.KFetConfig'
|
default_app_config = "kfet.apps.KFetConfig"
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
|
@ -2,7 +2,7 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class KFetConfig(AppConfig):
|
class KFetConfig(AppConfig):
|
||||||
name = 'kfet'
|
name = "kfet"
|
||||||
verbose_name = "Application K-Fêt"
|
verbose_name = "Application K-Fêt"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
|
@ -11,4 +11,5 @@ class KFetConfig(AppConfig):
|
||||||
def register_config(self):
|
def register_config(self):
|
||||||
import djconfig
|
import djconfig
|
||||||
from kfet.forms import KFetConfigForm
|
from kfet.forms import KFetConfigForm
|
||||||
|
|
||||||
djconfig.register(KFetConfigForm)
|
djconfig.register(KFetConfigForm)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
default_app_config = 'kfet.auth.apps.KFetAuthConfig'
|
default_app_config = "kfet.auth.apps.KFetAuthConfig"
|
||||||
|
|
||||||
KFET_GENERIC_USERNAME = 'kfet_genericteam'
|
KFET_GENERIC_USERNAME = "kfet_genericteam"
|
||||||
KFET_GENERIC_TRIGRAMME = 'GNR'
|
KFET_GENERIC_TRIGRAMME = "GNR"
|
||||||
|
|
|
@ -4,11 +4,12 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class KFetAuthConfig(AppConfig):
|
class KFetAuthConfig(AppConfig):
|
||||||
name = 'kfet.auth'
|
name = "kfet.auth"
|
||||||
label = 'kfetauth'
|
label = "kfetauth"
|
||||||
verbose_name = _("K-Fêt - Authentification et Autorisation")
|
verbose_name = _("K-Fêt - Authentification et Autorisation")
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from . import signals # noqa
|
from . import signals # noqa
|
||||||
from .utils import setup_kfet_generic_user
|
from .utils import setup_kfet_generic_user
|
||||||
|
|
||||||
post_migrate.connect(setup_kfet_generic_user, sender=self)
|
post_migrate.connect(setup_kfet_generic_user, sender=self)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
from kfet.models import Account, GenericTeamToken
|
from kfet.models import Account, GenericTeamToken
|
||||||
|
|
||||||
from .utils import get_kfet_generic_user
|
from .utils import get_kfet_generic_user
|
||||||
|
@ -12,11 +13,7 @@ class BaseKFetBackend:
|
||||||
Add extra select related up to Account.
|
Add extra select related up to Account.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return (
|
return User.objects.select_related("profile__account_kfet").get(pk=user_id)
|
||||||
User.objects
|
|
||||||
.select_related('profile__account_kfet')
|
|
||||||
.get(pk=user_id)
|
|
||||||
)
|
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,6 @@ from django.contrib.auth.context_processors import PermWrapper
|
||||||
|
|
||||||
|
|
||||||
def temporary_auth(request):
|
def temporary_auth(request):
|
||||||
if hasattr(request, 'real_user'):
|
if hasattr(request, "real_user"):
|
||||||
return {
|
return {"user": request.real_user, "perms": PermWrapper(request.real_user)}
|
||||||
'user': request.real_user,
|
|
||||||
'perms': PermWrapper(request.real_user),
|
|
||||||
}
|
|
||||||
return {}
|
return {}
|
||||||
|
|
|
@ -5,15 +5,12 @@ from django.forms import widgets
|
||||||
|
|
||||||
|
|
||||||
class KFetPermissionsField(forms.ModelMultipleChoiceField):
|
class KFetPermissionsField(forms.ModelMultipleChoiceField):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
queryset = Permission.objects.filter(
|
queryset = Permission.objects.filter(
|
||||||
content_type__in=ContentType.objects.filter(app_label="kfet"),
|
content_type__in=ContentType.objects.filter(app_label="kfet")
|
||||||
)
|
)
|
||||||
super().__init__(
|
super().__init__(
|
||||||
queryset=queryset,
|
queryset=queryset, widget=widgets.CheckboxSelectMultiple, *args, **kwargs
|
||||||
widget=widgets.CheckboxSelectMultiple,
|
|
||||||
*args, **kwargs
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def label_from_instance(self, obj):
|
def label_from_instance(self, obj):
|
||||||
|
|
|
@ -8,11 +8,11 @@ class GroupForm(forms.ModelForm):
|
||||||
permissions = KFetPermissionsField()
|
permissions = KFetPermissionsField()
|
||||||
|
|
||||||
def clean_name(self):
|
def clean_name(self):
|
||||||
name = self.cleaned_data['name']
|
name = self.cleaned_data["name"]
|
||||||
return 'K-Fêt %s' % name
|
return "K-Fêt %s" % name
|
||||||
|
|
||||||
def clean_permissions(self):
|
def clean_permissions(self):
|
||||||
kfet_perms = self.cleaned_data['permissions']
|
kfet_perms = self.cleaned_data["permissions"]
|
||||||
# TODO: With Django >=1.11, the QuerySet method 'difference' can be
|
# TODO: With Django >=1.11, the QuerySet method 'difference' can be
|
||||||
# used.
|
# used.
|
||||||
# other_groups = self.instance.permissions.difference(
|
# other_groups = self.instance.permissions.difference(
|
||||||
|
@ -21,28 +21,29 @@ class GroupForm(forms.ModelForm):
|
||||||
if self.instance.pk is None:
|
if self.instance.pk is None:
|
||||||
return kfet_perms
|
return kfet_perms
|
||||||
other_perms = self.instance.permissions.exclude(
|
other_perms = self.instance.permissions.exclude(
|
||||||
pk__in=[p.pk for p in self.fields['permissions'].queryset],
|
pk__in=[p.pk for p in self.fields["permissions"].queryset]
|
||||||
)
|
)
|
||||||
return list(kfet_perms) + list(other_perms)
|
return list(kfet_perms) + list(other_perms)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Group
|
model = Group
|
||||||
fields = ['name', 'permissions']
|
fields = ["name", "permissions"]
|
||||||
|
|
||||||
|
|
||||||
class UserGroupForm(forms.ModelForm):
|
class UserGroupForm(forms.ModelForm):
|
||||||
groups = forms.ModelMultipleChoiceField(
|
groups = forms.ModelMultipleChoiceField(
|
||||||
Group.objects.filter(name__icontains='K-Fêt'),
|
Group.objects.filter(name__icontains="K-Fêt"),
|
||||||
label='Statut équipe',
|
label="Statut équipe",
|
||||||
required=False)
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
def clean_groups(self):
|
def clean_groups(self):
|
||||||
kfet_groups = self.cleaned_data.get('groups')
|
kfet_groups = self.cleaned_data.get("groups")
|
||||||
if self.instance.pk is None:
|
if self.instance.pk is None:
|
||||||
return kfet_groups
|
return kfet_groups
|
||||||
other_groups = self.instance.groups.exclude(name__icontains='K-Fêt')
|
other_groups = self.instance.groups.exclude(name__icontains="K-Fêt")
|
||||||
return list(kfet_groups) + list(other_groups)
|
return list(kfet_groups) + list(other_groups)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['groups']
|
fields = ["groups"]
|
||||||
|
|
|
@ -12,21 +12,19 @@ class TemporaryAuthMiddleware:
|
||||||
values from CofProfile and Account of this user.
|
values from CofProfile and Account of this user.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
# avoid multiple db accesses in views and templates
|
# avoid multiple db accesses in views and templates
|
||||||
request.user = (
|
request.user = User.objects.select_related("profile__account_kfet").get(
|
||||||
User.objects
|
pk=request.user.pk
|
||||||
.select_related('profile__account_kfet')
|
|
||||||
.get(pk=request.user.pk)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
temp_request_user = AccountBackend().authenticate(
|
temp_request_user = AccountBackend().authenticate(
|
||||||
request,
|
request, kfet_password=self.get_kfet_password(request)
|
||||||
kfet_password=self.get_kfet_password(request),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if temp_request_user:
|
if temp_request_user:
|
||||||
|
@ -36,7 +34,4 @@ class TemporaryAuthMiddleware:
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
|
|
||||||
def get_kfet_password(self, request):
|
def get_kfet_password(self, request):
|
||||||
return (
|
return request.META.get("HTTP_KFETPASSWORD") or request.POST.get("KFETPASSWORD")
|
||||||
request.META.get('HTTP_KFETPASSWORD') or
|
|
||||||
request.POST.get('KFETPASSWORD')
|
|
||||||
)
|
|
||||||
|
|
|
@ -7,18 +7,26 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('auth', '0006_require_contenttypes_0002'),
|
("auth", "0006_require_contenttypes_0002"),
|
||||||
# Following dependency allows using Account model to set up the kfet
|
# Following dependency allows using Account model to set up the kfet
|
||||||
# generic user in post_migrate receiver.
|
# generic user in post_migrate receiver.
|
||||||
('kfet', '0058_delete_genericteamtoken'),
|
("kfet", "0058_delete_genericteamtoken"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='GenericTeamToken',
|
name="GenericTeamToken",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
|
(
|
||||||
('token', models.CharField(unique=True, max_length=50)),
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
auto_created=True,
|
||||||
|
serialize=False,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("token", models.CharField(unique=True, max_length=50)),
|
||||||
],
|
],
|
||||||
),
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,7 +3,6 @@ from django.utils.crypto import get_random_string
|
||||||
|
|
||||||
|
|
||||||
class GenericTeamTokenManager(models.Manager):
|
class GenericTeamTokenManager(models.Manager):
|
||||||
|
|
||||||
def create_token(self):
|
def create_token(self):
|
||||||
token = get_random_string(50)
|
token = get_random_string(50)
|
||||||
while self.filter(token=token).exists():
|
while self.filter(token=token).exists():
|
||||||
|
|
|
@ -19,22 +19,26 @@ def suggest_auth_generic(sender, request, user, **kwargs):
|
||||||
- logged in user is a kfet staff member (except the generic user).
|
- logged in user is a kfet staff member (except the generic user).
|
||||||
"""
|
"""
|
||||||
# Filter against the next page.
|
# Filter against the next page.
|
||||||
if not(hasattr(request, 'GET') and 'next' in request.GET):
|
if not (hasattr(request, "GET") and "next" in request.GET):
|
||||||
return
|
return
|
||||||
|
|
||||||
next_page = request.GET['next']
|
next_page = request.GET["next"]
|
||||||
generic_url = reverse('kfet.login.generic')
|
generic_url = reverse("kfet.login.generic")
|
||||||
|
|
||||||
if not('k-fet' in next_page and not next_page.startswith(generic_url)):
|
if not ("k-fet" in next_page and not next_page.startswith(generic_url)):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Filter against the logged in user.
|
# Filter against the logged in user.
|
||||||
if not(user.has_perm('kfet.is_team') and user != get_kfet_generic_user()):
|
if not (user.has_perm("kfet.is_team") and user != get_kfet_generic_user()):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Seems legit to add message.
|
# Seems legit to add message.
|
||||||
text = _("K-Fêt — Ouvrir une session partagée ?")
|
text = _("K-Fêt — Ouvrir une session partagée ?")
|
||||||
messages.info(request, mark_safe(
|
messages.info(
|
||||||
'<a href="#" data-url="{}" onclick="submit_url(this)">{}</a>'
|
request,
|
||||||
.format(generic_url, text)
|
mark_safe(
|
||||||
))
|
'<a href="#" data-url="{}" onclick="submit_url(this)">{}</a>'.format(
|
||||||
|
generic_url, text
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
from django.contrib.auth.models import AnonymousUser, Group, Permission, User
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.contrib.auth.models import AnonymousUser, Group, Permission, User
|
|
||||||
from django.test import RequestFactory, TestCase
|
from django.test import RequestFactory, TestCase
|
||||||
|
|
||||||
from kfet.forms import UserGroupForm
|
from kfet.forms import UserGroupForm
|
||||||
|
@ -15,11 +15,11 @@ from .models import GenericTeamToken
|
||||||
from .utils import get_kfet_generic_user
|
from .utils import get_kfet_generic_user
|
||||||
from .views import GenericLoginView
|
from .views import GenericLoginView
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Forms
|
# Forms
|
||||||
##
|
##
|
||||||
|
|
||||||
|
|
||||||
class UserGroupFormTests(TestCase):
|
class UserGroupFormTests(TestCase):
|
||||||
"""Test suite for UserGroupForm."""
|
"""Test suite for UserGroupForm."""
|
||||||
|
|
||||||
|
@ -31,8 +31,7 @@ class UserGroupFormTests(TestCase):
|
||||||
prefix_name = "K-Fêt "
|
prefix_name = "K-Fêt "
|
||||||
names = ["Group 1", "Group 2", "Group 3"]
|
names = ["Group 1", "Group 2", "Group 3"]
|
||||||
self.kfet_groups = [
|
self.kfet_groups = [
|
||||||
Group.objects.create(name=prefix_name+name)
|
Group.objects.create(name=prefix_name + name) for name in names
|
||||||
for name in names
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# create a non-K-Fêt group
|
# create a non-K-Fêt group
|
||||||
|
@ -41,11 +40,9 @@ class UserGroupFormTests(TestCase):
|
||||||
def test_choices(self):
|
def test_choices(self):
|
||||||
"""Only K-Fêt groups are selectable."""
|
"""Only K-Fêt groups are selectable."""
|
||||||
form = UserGroupForm(instance=self.user)
|
form = UserGroupForm(instance=self.user)
|
||||||
groups_field = form.fields['groups']
|
groups_field = form.fields["groups"]
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
groups_field.queryset,
|
groups_field.queryset, [repr(g) for g in self.kfet_groups], ordered=False
|
||||||
[repr(g) for g in self.kfet_groups],
|
|
||||||
ordered=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_keep_others(self):
|
def test_keep_others(self):
|
||||||
|
@ -56,9 +53,7 @@ class UserGroupFormTests(TestCase):
|
||||||
user.groups.add(self.other_group)
|
user.groups.add(self.other_group)
|
||||||
|
|
||||||
# add user to some K-Fêt groups through UserGroupForm
|
# add user to some K-Fêt groups through UserGroupForm
|
||||||
data = {
|
data = {"groups": [group.pk for group in self.kfet_groups]}
|
||||||
'groups': [group.pk for group in self.kfet_groups],
|
|
||||||
}
|
|
||||||
form = UserGroupForm(data, instance=user)
|
form = UserGroupForm(data, instance=user)
|
||||||
|
|
||||||
form.is_valid()
|
form.is_valid()
|
||||||
|
@ -71,7 +66,6 @@ class UserGroupFormTests(TestCase):
|
||||||
|
|
||||||
|
|
||||||
class KFetGenericUserTests(TestCase):
|
class KFetGenericUserTests(TestCase):
|
||||||
|
|
||||||
def test_exists(self):
|
def test_exists(self):
|
||||||
"""
|
"""
|
||||||
The account is set up when app is ready, so it should exist.
|
The account is set up when app is ready, so it should exist.
|
||||||
|
@ -86,44 +80,39 @@ class KFetGenericUserTests(TestCase):
|
||||||
# Backends
|
# Backends
|
||||||
##
|
##
|
||||||
|
|
||||||
class AccountBackendTests(TestCase):
|
|
||||||
|
|
||||||
|
class AccountBackendTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.request = RequestFactory().get('/')
|
self.request = RequestFactory().get("/")
|
||||||
|
|
||||||
def test_valid(self):
|
def test_valid(self):
|
||||||
acc = Account(trigramme='000')
|
acc = Account(trigramme="000")
|
||||||
acc.change_pwd('valid')
|
acc.change_pwd("valid")
|
||||||
acc.save({'username': 'user'})
|
acc.save({"username": "user"})
|
||||||
|
|
||||||
auth = AccountBackend().authenticate(
|
auth = AccountBackend().authenticate(self.request, kfet_password="valid")
|
||||||
self.request, kfet_password='valid')
|
|
||||||
|
|
||||||
self.assertEqual(auth, acc.user)
|
self.assertEqual(auth, acc.user)
|
||||||
|
|
||||||
def test_invalid(self):
|
def test_invalid(self):
|
||||||
auth = AccountBackend().authenticate(
|
auth = AccountBackend().authenticate(self.request, kfet_password="invalid")
|
||||||
self.request, kfet_password='invalid')
|
|
||||||
self.assertIsNone(auth)
|
self.assertIsNone(auth)
|
||||||
|
|
||||||
|
|
||||||
class GenericBackendTests(TestCase):
|
class GenericBackendTests(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.request = RequestFactory().get('/')
|
self.request = RequestFactory().get("/")
|
||||||
|
|
||||||
def test_valid(self):
|
def test_valid(self):
|
||||||
token = GenericTeamToken.objects.create_token()
|
token = GenericTeamToken.objects.create_token()
|
||||||
|
|
||||||
auth = GenericBackend().authenticate(
|
auth = GenericBackend().authenticate(self.request, kfet_token=token.token)
|
||||||
self.request, kfet_token=token.token)
|
|
||||||
|
|
||||||
self.assertEqual(auth, get_kfet_generic_user())
|
self.assertEqual(auth, get_kfet_generic_user())
|
||||||
self.assertEqual(GenericTeamToken.objects.all().count(), 0)
|
self.assertEqual(GenericTeamToken.objects.all().count(), 0)
|
||||||
|
|
||||||
def test_invalid(self):
|
def test_invalid(self):
|
||||||
auth = GenericBackend().authenticate(
|
auth = GenericBackend().authenticate(self.request, kfet_token="invalid")
|
||||||
self.request, kfet_token='invalid')
|
|
||||||
self.assertIsNone(auth)
|
self.assertIsNone(auth)
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,78 +120,74 @@ class GenericBackendTests(TestCase):
|
||||||
# Views
|
# Views
|
||||||
##
|
##
|
||||||
|
|
||||||
class GenericLoginViewTests(TestCase):
|
|
||||||
|
|
||||||
|
class GenericLoginViewTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
patcher_messages = mock.patch('gestioncof.signals.messages')
|
patcher_messages = mock.patch("gestioncof.signals.messages")
|
||||||
patcher_messages.start()
|
patcher_messages.start()
|
||||||
self.addCleanup(patcher_messages.stop)
|
self.addCleanup(patcher_messages.stop)
|
||||||
|
|
||||||
user_acc = Account(trigramme='000')
|
user_acc = Account(trigramme="000")
|
||||||
user_acc.save({'username': 'user'})
|
user_acc.save({"username": "user"})
|
||||||
self.user = user_acc.user
|
self.user = user_acc.user
|
||||||
self.user.set_password('user')
|
self.user.set_password("user")
|
||||||
self.user.save()
|
self.user.save()
|
||||||
|
|
||||||
team_acc = Account(trigramme='100')
|
team_acc = Account(trigramme="100")
|
||||||
team_acc.save({'username': 'team'})
|
team_acc.save({"username": "team"})
|
||||||
self.team = team_acc.user
|
self.team = team_acc.user
|
||||||
self.team.set_password('team')
|
self.team.set_password("team")
|
||||||
self.team.save()
|
self.team.save()
|
||||||
self.team.user_permissions.add(
|
self.team.user_permissions.add(
|
||||||
Permission.objects.get(
|
Permission.objects.get(content_type__app_label="kfet", codename="is_team")
|
||||||
content_type__app_label='kfet', codename='is_team'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.url = reverse('kfet.login.generic')
|
self.url = reverse("kfet.login.generic")
|
||||||
self.generic_user = get_kfet_generic_user()
|
self.generic_user = get_kfet_generic_user()
|
||||||
|
|
||||||
def test_url(self):
|
def test_url(self):
|
||||||
self.assertEqual(self.url, '/k-fet/login/generic')
|
self.assertEqual(self.url, "/k-fet/login/generic")
|
||||||
|
|
||||||
def test_notoken_get(self):
|
def test_notoken_get(self):
|
||||||
"""
|
"""
|
||||||
Send confirmation for user to emit POST request, instead of GET.
|
Send confirmation for user to emit POST request, instead of GET.
|
||||||
"""
|
"""
|
||||||
self.client.login(username='team', password='team')
|
self.client.login(username="team", password="team")
|
||||||
|
|
||||||
r = self.client.get(self.url)
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
self.assertTemplateUsed(r, 'kfet/confirm_form.html')
|
self.assertTemplateUsed(r, "kfet/confirm_form.html")
|
||||||
|
|
||||||
def test_notoken_post(self):
|
def test_notoken_post(self):
|
||||||
"""
|
"""
|
||||||
POST request without token in COOKIES sets a token and redirects to
|
POST request without token in COOKIES sets a token and redirects to
|
||||||
logout url.
|
logout url.
|
||||||
"""
|
"""
|
||||||
self.client.login(username='team', password='team')
|
self.client.login(username="team", password="team")
|
||||||
|
|
||||||
r = self.client.post(self.url)
|
r = self.client.post(self.url)
|
||||||
|
|
||||||
self.assertRedirects(
|
self.assertRedirects(
|
||||||
r, '/logout?next={}'.format(self.url),
|
r, "/logout?next={}".format(self.url), fetch_redirect_response=False
|
||||||
fetch_redirect_response=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_notoken_not_team(self):
|
def test_notoken_not_team(self):
|
||||||
"""
|
"""
|
||||||
Logged in user must be a team user to initiate login as generic user.
|
Logged in user must be a team user to initiate login as generic user.
|
||||||
"""
|
"""
|
||||||
self.client.login(username='user', password='user')
|
self.client.login(username="user", password="user")
|
||||||
|
|
||||||
# With GET.
|
# With GET.
|
||||||
r = self.client.get(self.url)
|
r = self.client.get(self.url)
|
||||||
self.assertRedirects(
|
self.assertRedirects(
|
||||||
r, '/login?next={}'.format(self.url),
|
r, "/login?next={}".format(self.url), fetch_redirect_response=False
|
||||||
fetch_redirect_response=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Also with POST.
|
# Also with POST.
|
||||||
r = self.client.post(self.url)
|
r = self.client.post(self.url)
|
||||||
self.assertRedirects(
|
self.assertRedirects(
|
||||||
r, '/login?next={}'.format(self.url),
|
r, "/login?next={}".format(self.url), fetch_redirect_response=False
|
||||||
fetch_redirect_response=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _set_signed_cookie(self, client, key, value):
|
def _set_signed_cookie(self, client, key, value):
|
||||||
|
@ -216,10 +201,9 @@ class GenericLoginViewTests(TestCase):
|
||||||
try:
|
try:
|
||||||
cookie = client.cookies[key]
|
cookie = client.cookies[key]
|
||||||
# It also can be emptied.
|
# It also can be emptied.
|
||||||
self.assertEqual(cookie.value, '')
|
self.assertEqual(cookie.value, "")
|
||||||
self.assertEqual(
|
self.assertEqual(cookie["expires"], "Thu, 01-Jan-1970 00:00:00 GMT")
|
||||||
cookie['expires'], 'Thu, 01-Jan-1970 00:00:00 GMT')
|
self.assertEqual(cookie["max-age"], 0)
|
||||||
self.assertEqual(cookie['max-age'], 0)
|
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
raise AssertionError("The cookie '%s' still exists." % key)
|
raise AssertionError("The cookie '%s' still exists." % key)
|
||||||
|
|
||||||
|
@ -227,16 +211,16 @@ class GenericLoginViewTests(TestCase):
|
||||||
"""
|
"""
|
||||||
The kfet generic user is logged in.
|
The kfet generic user is logged in.
|
||||||
"""
|
"""
|
||||||
token = GenericTeamToken.objects.create(token='valid')
|
token = GenericTeamToken.objects.create(token="valid")
|
||||||
self._set_signed_cookie(
|
self._set_signed_cookie(
|
||||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME, 'valid')
|
self.client, GenericLoginView.TOKEN_COOKIE_NAME, "valid"
|
||||||
|
)
|
||||||
|
|
||||||
r = self.client.get(self.url)
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
self.assertRedirects(r, reverse('kfet.kpsul'))
|
self.assertRedirects(r, reverse("kfet.kpsul"))
|
||||||
self.assertEqual(r.wsgi_request.user, self.generic_user)
|
self.assertEqual(r.wsgi_request.user, self.generic_user)
|
||||||
self._is_cookie_deleted(
|
self._is_cookie_deleted(self.client, GenericLoginView.TOKEN_COOKIE_NAME)
|
||||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME)
|
|
||||||
with self.assertRaises(GenericTeamToken.DoesNotExist):
|
with self.assertRaises(GenericTeamToken.DoesNotExist):
|
||||||
token.refresh_from_db()
|
token.refresh_from_db()
|
||||||
|
|
||||||
|
@ -245,27 +229,26 @@ class GenericLoginViewTests(TestCase):
|
||||||
If token is invalid, delete it and try again.
|
If token is invalid, delete it and try again.
|
||||||
"""
|
"""
|
||||||
self._set_signed_cookie(
|
self._set_signed_cookie(
|
||||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME, 'invalid')
|
self.client, GenericLoginView.TOKEN_COOKIE_NAME, "invalid"
|
||||||
|
)
|
||||||
|
|
||||||
r = self.client.get(self.url)
|
r = self.client.get(self.url)
|
||||||
|
|
||||||
self.assertRedirects(r, self.url, fetch_redirect_response=False)
|
self.assertRedirects(r, self.url, fetch_redirect_response=False)
|
||||||
self.assertEqual(r.wsgi_request.user, AnonymousUser())
|
self.assertEqual(r.wsgi_request.user, AnonymousUser())
|
||||||
self._is_cookie_deleted(
|
self._is_cookie_deleted(self.client, GenericLoginView.TOKEN_COOKIE_NAME)
|
||||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME)
|
|
||||||
|
|
||||||
def test_flow_ok(self):
|
def test_flow_ok(self):
|
||||||
"""
|
"""
|
||||||
A team user is logged in as the kfet generic user.
|
A team user is logged in as the kfet generic user.
|
||||||
"""
|
"""
|
||||||
self.client.login(username='team', password='team')
|
self.client.login(username="team", password="team")
|
||||||
next_url = '/k-fet/'
|
next_url = "/k-fet/"
|
||||||
|
|
||||||
r = self.client.post(
|
r = self.client.post("{}?next={}".format(self.url, next_url), follow=True)
|
||||||
'{}?next={}'.format(self.url, next_url), follow=True)
|
|
||||||
|
|
||||||
self.assertEqual(r.wsgi_request.user, self.generic_user)
|
self.assertEqual(r.wsgi_request.user, self.generic_user)
|
||||||
self.assertEqual(r.wsgi_request.path, '/k-fet/')
|
self.assertEqual(r.wsgi_request.path, "/k-fet/")
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -276,10 +259,10 @@ class GenericLoginViewTests(TestCase):
|
||||||
# - temporary_auth context processor
|
# - temporary_auth context processor
|
||||||
##
|
##
|
||||||
|
|
||||||
class TemporaryAuthTests(TestCase):
|
|
||||||
|
|
||||||
|
class TemporaryAuthTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
patcher_messages = mock.patch('gestioncof.signals.messages')
|
patcher_messages = mock.patch("gestioncof.signals.messages")
|
||||||
patcher_messages.start()
|
patcher_messages.start()
|
||||||
self.addCleanup(patcher_messages.stop)
|
self.addCleanup(patcher_messages.stop)
|
||||||
|
|
||||||
|
@ -287,22 +270,23 @@ class TemporaryAuthTests(TestCase):
|
||||||
|
|
||||||
self.middleware = TemporaryAuthMiddleware(mock.Mock())
|
self.middleware = TemporaryAuthMiddleware(mock.Mock())
|
||||||
|
|
||||||
user1_acc = Account(trigramme='000')
|
user1_acc = Account(trigramme="000")
|
||||||
user1_acc.change_pwd('kfet_user1')
|
user1_acc.change_pwd("kfet_user1")
|
||||||
user1_acc.save({'username': 'user1'})
|
user1_acc.save({"username": "user1"})
|
||||||
self.user1 = user1_acc.user
|
self.user1 = user1_acc.user
|
||||||
self.user1.set_password('user1')
|
self.user1.set_password("user1")
|
||||||
self.user1.save()
|
self.user1.save()
|
||||||
|
|
||||||
user2_acc = Account(trigramme='100')
|
user2_acc = Account(trigramme="100")
|
||||||
user2_acc.change_pwd('kfet_user2')
|
user2_acc.change_pwd("kfet_user2")
|
||||||
user2_acc.save({'username': 'user2'})
|
user2_acc.save({"username": "user2"})
|
||||||
self.user2 = user2_acc.user
|
self.user2 = user2_acc.user
|
||||||
self.user2.set_password('user2')
|
self.user2.set_password("user2")
|
||||||
self.user2.save()
|
self.user2.save()
|
||||||
|
|
||||||
self.perm = Permission.objects.get(
|
self.perm = Permission.objects.get(
|
||||||
content_type__app_label='kfet', codename='is_team')
|
content_type__app_label="kfet", codename="is_team"
|
||||||
|
)
|
||||||
self.user2.user_permissions.add(self.perm)
|
self.user2.user_permissions.add(self.perm)
|
||||||
|
|
||||||
def test_middleware_header(self):
|
def test_middleware_header(self):
|
||||||
|
@ -310,7 +294,7 @@ class TemporaryAuthTests(TestCase):
|
||||||
A user can be authenticated if ``HTTP_KFETPASSWORD`` header of a
|
A user can be authenticated if ``HTTP_KFETPASSWORD`` header of a
|
||||||
request contains a valid kfet password.
|
request contains a valid kfet password.
|
||||||
"""
|
"""
|
||||||
request = self.factory.get('/', HTTP_KFETPASSWORD='kfet_user2')
|
request = self.factory.get("/", HTTP_KFETPASSWORD="kfet_user2")
|
||||||
request.user = self.user1
|
request.user = self.user1
|
||||||
|
|
||||||
self.middleware(request)
|
self.middleware(request)
|
||||||
|
@ -323,7 +307,7 @@ class TemporaryAuthTests(TestCase):
|
||||||
A user can be authenticated if ``KFETPASSWORD`` of POST data contains
|
A user can be authenticated if ``KFETPASSWORD`` of POST data contains
|
||||||
a valid kfet password.
|
a valid kfet password.
|
||||||
"""
|
"""
|
||||||
request = self.factory.post('/', {'KFETPASSWORD': 'kfet_user2'})
|
request = self.factory.post("/", {"KFETPASSWORD": "kfet_user2"})
|
||||||
request.user = self.user1
|
request.user = self.user1
|
||||||
|
|
||||||
self.middleware(request)
|
self.middleware(request)
|
||||||
|
@ -335,34 +319,33 @@ class TemporaryAuthTests(TestCase):
|
||||||
"""
|
"""
|
||||||
The given password must be a password of an Account.
|
The given password must be a password of an Account.
|
||||||
"""
|
"""
|
||||||
request = self.factory.post('/', {'KFETPASSWORD': 'invalid'})
|
request = self.factory.post("/", {"KFETPASSWORD": "invalid"})
|
||||||
request.user = self.user1
|
request.user = self.user1
|
||||||
|
|
||||||
self.middleware(request)
|
self.middleware(request)
|
||||||
|
|
||||||
self.assertEqual(request.user, self.user1)
|
self.assertEqual(request.user, self.user1)
|
||||||
self.assertFalse(hasattr(request, 'real_user'))
|
self.assertFalse(hasattr(request, "real_user"))
|
||||||
|
|
||||||
def test_context_processor(self):
|
def test_context_processor(self):
|
||||||
"""
|
"""
|
||||||
Context variables give the real authenticated user and his permissions.
|
Context variables give the real authenticated user and his permissions.
|
||||||
"""
|
"""
|
||||||
self.client.login(username='user1', password='user1')
|
self.client.login(username="user1", password="user1")
|
||||||
|
|
||||||
r = self.client.get('/k-fet/accounts/', HTTP_KFETPASSWORD='kfet_user2')
|
r = self.client.get("/k-fet/accounts/", HTTP_KFETPASSWORD="kfet_user2")
|
||||||
|
|
||||||
self.assertEqual(r.context['user'], self.user1)
|
self.assertEqual(r.context["user"], self.user1)
|
||||||
self.assertNotIn('kfet.is_team', r.context['perms'])
|
self.assertNotIn("kfet.is_team", r.context["perms"])
|
||||||
|
|
||||||
def test_auth_not_persistent(self):
|
def test_auth_not_persistent(self):
|
||||||
"""
|
"""
|
||||||
The authentication is temporary, i.e. for one request.
|
The authentication is temporary, i.e. for one request.
|
||||||
"""
|
"""
|
||||||
self.client.login(username='user1', password='user1')
|
self.client.login(username="user1", password="user1")
|
||||||
|
|
||||||
r1 = self.client.get(
|
r1 = self.client.get("/k-fet/accounts/", HTTP_KFETPASSWORD="kfet_user2")
|
||||||
'/k-fet/accounts/', HTTP_KFETPASSWORD='kfet_user2')
|
|
||||||
self.assertEqual(r1.wsgi_request.user, self.user2)
|
self.assertEqual(r1.wsgi_request.user, self.user2)
|
||||||
|
|
||||||
r2 = self.client.get('/k-fet/accounts/')
|
r2 = self.client.get("/k-fet/accounts/")
|
||||||
self.assertEqual(r2.wsgi_request.user, self.user1)
|
self.assertEqual(r2.wsgi_request.user, self.user1)
|
||||||
|
|
|
@ -23,12 +23,9 @@ def setup_kfet_generic_user(**kwargs):
|
||||||
"""
|
"""
|
||||||
generic = get_kfet_generic_user()
|
generic = get_kfet_generic_user()
|
||||||
generic.user_permissions.add(
|
generic.user_permissions.add(
|
||||||
Permission.objects.get(
|
Permission.objects.get(content_type__app_label="kfet", codename="is_team")
|
||||||
content_type__app_label='kfet',
|
|
||||||
codename='is_team',
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def hash_password(password):
|
def hash_password(password):
|
||||||
return hashlib.sha256(password.encode('utf-8')).hexdigest()
|
return hashlib.sha256(password.encode("utf-8")).hexdigest()
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
|
||||||
from django.contrib.auth import authenticate, login
|
from django.contrib.auth import authenticate, login
|
||||||
from django.contrib.auth.decorators import permission_required
|
from django.contrib.auth.decorators import permission_required
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from django.contrib.auth.views import redirect_to_login
|
from django.contrib.auth.views import redirect_to_login
|
||||||
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.core.urlresolvers import reverse, reverse_lazy
|
from django.core.urlresolvers import reverse, reverse_lazy
|
||||||
from django.db.models import Prefetch
|
from django.db.models import Prefetch
|
||||||
from django.http import QueryDict
|
from django.http import QueryDict
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import ugettext_lazy as _
|
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.decorators.http import require_http_methods
|
||||||
|
from django.views.generic import View
|
||||||
from django.views.generic.edit import CreateView, UpdateView
|
from django.views.generic.edit import CreateView, UpdateView
|
||||||
|
|
||||||
from .forms import GroupForm
|
from .forms import GroupForm
|
||||||
|
@ -30,28 +30,33 @@ class GenericLoginView(View):
|
||||||
provider, which can be external. Session is unusable as it will be cleared
|
provider, which can be external. Session is unusable as it will be cleared
|
||||||
on logout.
|
on logout.
|
||||||
"""
|
"""
|
||||||
TOKEN_COOKIE_NAME = 'kfettoken'
|
|
||||||
|
|
||||||
@method_decorator(require_http_methods(['GET', 'POST']))
|
TOKEN_COOKIE_NAME = "kfettoken"
|
||||||
|
|
||||||
|
@method_decorator(require_http_methods(["GET", "POST"]))
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
token = request.get_signed_cookie(self.TOKEN_COOKIE_NAME, None)
|
token = request.get_signed_cookie(self.TOKEN_COOKIE_NAME, None)
|
||||||
if not token:
|
if not token:
|
||||||
if not request.user.has_perm('kfet.is_team'):
|
if not request.user.has_perm("kfet.is_team"):
|
||||||
return redirect_to_login(request.get_full_path())
|
return redirect_to_login(request.get_full_path())
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == "POST":
|
||||||
# Step 1: set token and logout user.
|
# Step 1: set token and logout user.
|
||||||
return self.prepare_auth()
|
return self.prepare_auth()
|
||||||
else:
|
else:
|
||||||
# GET request should not change server/client states. Send a
|
# GET request should not change server/client states. Send a
|
||||||
# confirmation template to emit a POST request.
|
# confirmation template to emit a POST request.
|
||||||
return render(request, 'kfet/confirm_form.html', {
|
return render(
|
||||||
'title': _("Ouvrir une session partagée"),
|
request,
|
||||||
'text': _(
|
"kfet/confirm_form.html",
|
||||||
"Êtes-vous sûr·e de vouloir ouvrir une session "
|
{
|
||||||
"partagée ?"
|
"title": _("Ouvrir une session partagée"),
|
||||||
),
|
"text": _(
|
||||||
})
|
"Êtes-vous sûr·e de vouloir ouvrir une session "
|
||||||
|
"partagée ?"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Step 2: validate token.
|
# Step 2: validate token.
|
||||||
return self.validate_auth(token)
|
return self.validate_auth(token)
|
||||||
|
@ -62,20 +67,19 @@ class GenericLoginView(View):
|
||||||
|
|
||||||
# Prepare callback of logout.
|
# Prepare callback of logout.
|
||||||
here_url = reverse(login_generic)
|
here_url = reverse(login_generic)
|
||||||
if 'next' in self.request.GET:
|
if "next" in self.request.GET:
|
||||||
# Keep given next page.
|
# Keep given next page.
|
||||||
here_qd = QueryDict(mutable=True)
|
here_qd = QueryDict(mutable=True)
|
||||||
here_qd['next'] = self.request.GET['next']
|
here_qd["next"] = self.request.GET["next"]
|
||||||
here_url += '?{}'.format(here_qd.urlencode())
|
here_url += "?{}".format(here_qd.urlencode())
|
||||||
|
|
||||||
logout_url = reverse('cof-logout')
|
logout_url = reverse("cof-logout")
|
||||||
logout_qd = QueryDict(mutable=True)
|
logout_qd = QueryDict(mutable=True)
|
||||||
logout_qd['next'] = here_url
|
logout_qd["next"] = here_url
|
||||||
logout_url += '?{}'.format(logout_qd.urlencode(safe='/'))
|
logout_url += "?{}".format(logout_qd.urlencode(safe="/"))
|
||||||
|
|
||||||
resp = redirect(logout_url)
|
resp = redirect(logout_url)
|
||||||
resp.set_signed_cookie(
|
resp.set_signed_cookie(self.TOKEN_COOKIE_NAME, token.token, httponly=True)
|
||||||
self.TOKEN_COOKIE_NAME, token.token, httponly=True)
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def validate_auth(self, token):
|
def validate_auth(self, token):
|
||||||
|
@ -85,9 +89,9 @@ class GenericLoginView(View):
|
||||||
if user:
|
if user:
|
||||||
# Log in generic user.
|
# Log in generic user.
|
||||||
login(self.request, user)
|
login(self.request, user)
|
||||||
messages.success(self.request, _(
|
messages.success(
|
||||||
"K-Fêt — Ouverture d'une session partagée."
|
self.request, _("K-Fêt — Ouverture d'une session partagée.")
|
||||||
))
|
)
|
||||||
resp = redirect(self.get_next_url())
|
resp = redirect(self.get_next_url())
|
||||||
else:
|
else:
|
||||||
# Try again.
|
# Try again.
|
||||||
|
@ -98,39 +102,34 @@ class GenericLoginView(View):
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def get_next_url(self):
|
def get_next_url(self):
|
||||||
return self.request.GET.get('next', reverse('kfet.kpsul'))
|
return self.request.GET.get("next", reverse("kfet.kpsul"))
|
||||||
|
|
||||||
|
|
||||||
login_generic = GenericLoginView.as_view()
|
login_generic = GenericLoginView.as_view()
|
||||||
|
|
||||||
|
|
||||||
@permission_required('kfet.manage_perms')
|
@permission_required("kfet.manage_perms")
|
||||||
def account_group(request):
|
def account_group(request):
|
||||||
user_pre = Prefetch(
|
user_pre = Prefetch(
|
||||||
'user_set',
|
"user_set", queryset=User.objects.select_related("profile__account_kfet")
|
||||||
queryset=User.objects.select_related('profile__account_kfet'),
|
|
||||||
)
|
)
|
||||||
groups = (
|
groups = Group.objects.filter(name__icontains="K-Fêt").prefetch_related(
|
||||||
Group.objects
|
"permissions", user_pre
|
||||||
.filter(name__icontains='K-Fêt')
|
|
||||||
.prefetch_related('permissions', user_pre)
|
|
||||||
)
|
)
|
||||||
return render(request, 'kfet/account_group.html', {
|
return render(request, "kfet/account_group.html", {"groups": groups})
|
||||||
'groups': groups,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class AccountGroupCreate(SuccessMessageMixin, CreateView):
|
class AccountGroupCreate(SuccessMessageMixin, CreateView):
|
||||||
model = Group
|
model = Group
|
||||||
template_name = 'kfet/account_group_form.html'
|
template_name = "kfet/account_group_form.html"
|
||||||
form_class = GroupForm
|
form_class = GroupForm
|
||||||
success_message = 'Nouveau groupe : %(name)s'
|
success_message = "Nouveau groupe : %(name)s"
|
||||||
success_url = reverse_lazy('kfet.account.group')
|
success_url = reverse_lazy("kfet.account.group")
|
||||||
|
|
||||||
|
|
||||||
class AccountGroupUpdate(SuccessMessageMixin, UpdateView):
|
class AccountGroupUpdate(SuccessMessageMixin, UpdateView):
|
||||||
queryset = Group.objects.filter(name__icontains='K-Fêt')
|
queryset = Group.objects.filter(name__icontains="K-Fêt")
|
||||||
template_name = 'kfet/account_group_form.html'
|
template_name = "kfet/account_group_form.html"
|
||||||
form_class = GroupForm
|
form_class = GroupForm
|
||||||
success_message = 'Groupe modifié : %(name)s'
|
success_message = "Groupe modifié : %(name)s"
|
||||||
success_url = reverse_lazy('kfet.account.group')
|
success_url = reverse_lazy("kfet.account.group")
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from ldap3 import Connection
|
|
||||||
from django.shortcuts import render
|
|
||||||
from django.http import Http404
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.http import Http404
|
||||||
|
from django.shortcuts import render
|
||||||
|
from ldap3 import Connection
|
||||||
|
|
||||||
from gestioncof.models import User
|
from gestioncof.models import User
|
||||||
from kfet.decorators import teamkfet_required
|
from kfet.decorators import teamkfet_required
|
||||||
|
@ -25,81 +25,80 @@ def account_create(request):
|
||||||
raise Http404
|
raise Http404
|
||||||
q = request.GET.get("q")
|
q = request.GET.get("q")
|
||||||
|
|
||||||
if (len(q) == 0):
|
if len(q) == 0:
|
||||||
return render(request, "kfet/account_create_autocomplete.html")
|
return render(request, "kfet/account_create_autocomplete.html")
|
||||||
|
|
||||||
data = {'q': q}
|
data = {"q": q}
|
||||||
|
|
||||||
queries = {}
|
queries = {}
|
||||||
search_words = q.split()
|
search_words = q.split()
|
||||||
|
|
||||||
# Fetching data from User, CofProfile and Account tables
|
# Fetching data from User, CofProfile and Account tables
|
||||||
queries['kfet'] = Account.objects
|
queries["kfet"] = Account.objects
|
||||||
queries['users_cof'] = User.objects.filter(profile__is_cof=True)
|
queries["users_cof"] = User.objects.filter(profile__is_cof=True)
|
||||||
queries['users_notcof'] = User.objects.filter(profile__is_cof=False)
|
queries["users_notcof"] = User.objects.filter(profile__is_cof=False)
|
||||||
|
|
||||||
for word in search_words:
|
for word in search_words:
|
||||||
queries['kfet'] = queries['kfet'].filter(
|
queries["kfet"] = queries["kfet"].filter(
|
||||||
Q(cofprofile__user__username__icontains=word)
|
Q(cofprofile__user__username__icontains=word)
|
||||||
| Q(cofprofile__user__first_name__icontains=word)
|
| Q(cofprofile__user__first_name__icontains=word)
|
||||||
| Q(cofprofile__user__last_name__icontains=word)
|
| Q(cofprofile__user__last_name__icontains=word)
|
||||||
)
|
)
|
||||||
queries['users_cof'] = queries['users_cof'].filter(
|
queries["users_cof"] = queries["users_cof"].filter(
|
||||||
Q(username__icontains=word)
|
Q(username__icontains=word)
|
||||||
| Q(first_name__icontains=word)
|
| Q(first_name__icontains=word)
|
||||||
| Q(last_name__icontains=word)
|
| Q(last_name__icontains=word)
|
||||||
)
|
)
|
||||||
queries['users_notcof'] = queries['users_notcof'].filter(
|
queries["users_notcof"] = queries["users_notcof"].filter(
|
||||||
Q(username__icontains=word)
|
Q(username__icontains=word)
|
||||||
| Q(first_name__icontains=word)
|
| Q(first_name__icontains=word)
|
||||||
| Q(last_name__icontains=word)
|
| Q(last_name__icontains=word)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Clearing redundancies
|
# Clearing redundancies
|
||||||
queries['kfet'] = queries['kfet'].distinct()
|
queries["kfet"] = queries["kfet"].distinct()
|
||||||
usernames = set(
|
usernames = set(
|
||||||
queries['kfet'].values_list('cofprofile__user__username', flat=True))
|
queries["kfet"].values_list("cofprofile__user__username", flat=True)
|
||||||
queries['kfet'] = [
|
)
|
||||||
(account, account.cofprofile.user)
|
queries["kfet"] = [
|
||||||
for account in queries['kfet']
|
(account, account.cofprofile.user) for account in queries["kfet"]
|
||||||
]
|
]
|
||||||
|
|
||||||
queries['users_cof'] = \
|
queries["users_cof"] = (
|
||||||
queries['users_cof'].exclude(username__in=usernames).distinct()
|
queries["users_cof"].exclude(username__in=usernames).distinct()
|
||||||
queries['users_notcof'] = \
|
)
|
||||||
queries['users_notcof'].exclude(username__in=usernames).distinct()
|
queries["users_notcof"] = (
|
||||||
usernames |= set(
|
queries["users_notcof"].exclude(username__in=usernames).distinct()
|
||||||
queries['users_cof'].values_list('username', flat=True))
|
)
|
||||||
usernames |= set(
|
usernames |= set(queries["users_cof"].values_list("username", flat=True))
|
||||||
queries['users_notcof'].values_list('username', flat=True))
|
usernames |= set(queries["users_notcof"].values_list("username", flat=True))
|
||||||
|
|
||||||
# Fetching data from the SPI
|
# Fetching data from the SPI
|
||||||
if getattr(settings, 'LDAP_SERVER_URL', None):
|
if getattr(settings, "LDAP_SERVER_URL", None):
|
||||||
# Fetching
|
# Fetching
|
||||||
ldap_query = '(&{:s})'.format(''.join(
|
ldap_query = "(&{:s})".format(
|
||||||
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=word)
|
"".join(
|
||||||
for word in search_words if word.isalnum()
|
"(|(cn=*{bit:s}*)(uid=*{bit:s}*))".format(bit=word)
|
||||||
))
|
for word in search_words
|
||||||
|
if word.isalnum()
|
||||||
|
)
|
||||||
|
)
|
||||||
if ldap_query != "(&)":
|
if ldap_query != "(&)":
|
||||||
# If none of the bits were legal, we do not perform the query
|
# If none of the bits were legal, we do not perform the query
|
||||||
entries = None
|
entries = None
|
||||||
with Connection(settings.LDAP_SERVER_URL) as conn:
|
with Connection(settings.LDAP_SERVER_URL) as conn:
|
||||||
conn.search(
|
conn.search("dc=spi,dc=ens,dc=fr", ldap_query, attributes=["uid", "cn"])
|
||||||
'dc=spi,dc=ens,dc=fr', ldap_query,
|
|
||||||
attributes=['uid', 'cn']
|
|
||||||
)
|
|
||||||
entries = conn.entries
|
entries = conn.entries
|
||||||
# Clearing redundancies
|
# Clearing redundancies
|
||||||
queries['clippers'] = [
|
queries["clippers"] = [
|
||||||
Clipper(entry.uid.value, entry.cn.value)
|
Clipper(entry.uid.value, entry.cn.value)
|
||||||
for entry in entries
|
for entry in entries
|
||||||
if entry.uid.value
|
if entry.uid.value and entry.uid.value not in usernames
|
||||||
and entry.uid.value not in usernames
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Resulting data
|
# Resulting data
|
||||||
data.update(queries)
|
data.update(queries)
|
||||||
data['options'] = sum([len(query) for query in queries])
|
data["options"] = sum([len(query) for query in queries])
|
||||||
|
|
||||||
return render(request, "kfet/account_create_autocomplete.html", data)
|
return render(request, "kfet/account_create_autocomplete.html", data)
|
||||||
|
|
||||||
|
@ -111,17 +110,19 @@ def account_search(request):
|
||||||
q = request.GET.get("q")
|
q = request.GET.get("q")
|
||||||
words = q.split()
|
words = q.split()
|
||||||
|
|
||||||
data = {'q': q}
|
data = {"q": q}
|
||||||
|
|
||||||
for word in words:
|
for word in words:
|
||||||
query = Account.objects.filter(
|
query = Account.objects.filter(
|
||||||
Q(cofprofile__user__username__icontains=word) |
|
Q(cofprofile__user__username__icontains=word)
|
||||||
Q(cofprofile__user__first_name__icontains=word) |
|
| Q(cofprofile__user__first_name__icontains=word)
|
||||||
Q(cofprofile__user__last_name__icontains=word)
|
| Q(cofprofile__user__last_name__icontains=word)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
query = [(account.trigramme, account.cofprofile.user.get_full_name())
|
query = [
|
||||||
for account in query]
|
(account.trigramme, account.cofprofile.user.get_full_name())
|
||||||
|
for account in query
|
||||||
|
]
|
||||||
|
|
||||||
data['accounts'] = query
|
data["accounts"] = query
|
||||||
return render(request, 'kfet/account_search_autocomplete.html', data)
|
return render(request, "kfet/account_search_autocomplete.html", data)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
default_app_config = 'kfet.cms.apps.KFetCMSAppConfig'
|
default_app_config = "kfet.cms.apps.KFetCMSAppConfig"
|
||||||
|
|
|
@ -2,9 +2,9 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class KFetCMSAppConfig(AppConfig):
|
class KFetCMSAppConfig(AppConfig):
|
||||||
name = 'kfet.cms'
|
name = "kfet.cms"
|
||||||
label = 'kfetcms'
|
label = "kfetcms"
|
||||||
verbose_name = 'CMS K-Fêt'
|
verbose_name = "CMS K-Fêt"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from . import hooks
|
from . import hooks # noqa
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue