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/
|
||||
*.log
|
||||
*.sqlite3
|
||||
.coverage
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
image: "python:3.5"
|
||||
|
||||
services:
|
||||
- postgres:latest
|
||||
- redis:latest
|
||||
|
@ -10,7 +12,7 @@ variables:
|
|||
REDIS_PASSWD: "dummy"
|
||||
|
||||
# Cached packages
|
||||
PYTHONPATH: "$CI_PROJECT_DIR/vendor/python"
|
||||
PIP_CACHE_DIR: "$CI_PROJECT_DIR/vendor/pip"
|
||||
|
||||
# postgres service configuration
|
||||
POSTGRES_PASSWORD: "4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
||||
|
@ -20,22 +22,41 @@ variables:
|
|||
# psql password authentication
|
||||
PGPASSWORD: $POSTGRES_PASSWORD
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- vendor/python
|
||||
- vendor/pip
|
||||
- vendor/apt
|
||||
|
||||
before_script:
|
||||
- mkdir -p vendor/{python,pip,apt}
|
||||
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 --cache-dir vendor/pip -t vendor/python -r requirements.txt
|
||||
|
||||
test:
|
||||
stage: test
|
||||
- pip install --upgrade -r requirements.txt coverage
|
||||
- python --version
|
||||
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
|
||||
|
||||
![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
|
||||
|
||||
|
@ -43,6 +44,16 @@ pour profiter de façon transparente des mises à jour du fichier:
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
|
175
bda/admin.py
175
bda/admin.py
|
@ -1,16 +1,24 @@
|
|||
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.db.models import Sum, Count
|
||||
from django.db.models import Count, Sum
|
||||
from django.template.defaultfilters import pluralize
|
||||
from django.utils import timezone
|
||||
from django import forms
|
||||
|
||||
from dal.autocomplete import ModelSelect2
|
||||
|
||||
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
||||
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
||||
from bda.models import (
|
||||
Attribution,
|
||||
CategorieSpectacle,
|
||||
ChoixSpectacle,
|
||||
Participant,
|
||||
Quote,
|
||||
Salle,
|
||||
Spectacle,
|
||||
SpectacleRevente,
|
||||
Tirage,
|
||||
)
|
||||
|
||||
|
||||
class ReadOnlyMixin(object):
|
||||
|
@ -27,8 +35,8 @@ class ReadOnlyMixin(object):
|
|||
class ChoixSpectacleAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
'participant': ModelSelect2(url='bda-participant-autocomplete'),
|
||||
'spectacle': ModelSelect2(url='bda-spectacle-autocomplete'),
|
||||
"participant": ModelSelect2(url="bda-participant-autocomplete"),
|
||||
"spectacle": ModelSelect2(url="bda-spectacle-autocomplete"),
|
||||
}
|
||||
|
||||
|
||||
|
@ -43,10 +51,10 @@ class AttributionTabularAdminForm(forms.ModelForm):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
spectacles = Spectacle.objects.select_related('location')
|
||||
spectacles = Spectacle.objects.select_related("location")
|
||||
if self.listing is not None:
|
||||
spectacles = spectacles.filter(listing=self.listing)
|
||||
self.fields['spectacle'].queryset = spectacles
|
||||
self.fields["spectacle"].queryset = spectacles
|
||||
|
||||
|
||||
class WithoutListingAttributionTabularAdminForm(AttributionTabularAdminForm):
|
||||
|
@ -70,7 +78,7 @@ class AttributionInline(admin.TabularInline):
|
|||
|
||||
|
||||
class WithListingAttributionInline(AttributionInline):
|
||||
exclude = ('given', )
|
||||
exclude = ("given",)
|
||||
form = WithListingAttributionTabularAdminForm
|
||||
listing = True
|
||||
|
||||
|
@ -81,12 +89,10 @@ class WithoutListingAttributionInline(AttributionInline):
|
|||
|
||||
|
||||
class ParticipantAdminForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['choicesrevente'].queryset = (
|
||||
Spectacle.objects
|
||||
.select_related('location')
|
||||
self.fields["choicesrevente"].queryset = Spectacle.objects.select_related(
|
||||
"location"
|
||||
)
|
||||
|
||||
|
||||
|
@ -94,11 +100,13 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
|||
inlines = [WithListingAttributionInline, WithoutListingAttributionInline]
|
||||
|
||||
def get_queryset(self, request):
|
||||
return Participant.objects.annotate(nb_places=Count('attributions'),
|
||||
total=Sum('attributions__price'))
|
||||
return Participant.objects.annotate(
|
||||
nb_places=Count("attributions"), total=Sum("attributions__price")
|
||||
)
|
||||
|
||||
def nb_places(self, obj):
|
||||
return obj.nb_places
|
||||
|
||||
nb_places.admin_order_field = "nb_places"
|
||||
nb_places.short_description = "Nombre de places"
|
||||
|
||||
|
@ -108,33 +116,32 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
|||
return "%.02f €" % tot
|
||||
else:
|
||||
return "0 €"
|
||||
|
||||
total.admin_order_field = "total"
|
||||
total.short_description = "Total à payer"
|
||||
list_display = ("user", "nb_places", "total", "paid", "paymenttype",
|
||||
"tirage")
|
||||
list_display = ("user", "nb_places", "total", "paid", "paymenttype", "tirage")
|
||||
list_filter = ("paid", "tirage")
|
||||
search_fields = ('user__username', 'user__first_name', 'user__last_name')
|
||||
actions = ['send_attribs', ]
|
||||
search_fields = ("user__username", "user__first_name", "user__last_name")
|
||||
actions = ["send_attribs"]
|
||||
actions_on_bottom = True
|
||||
list_per_page = 400
|
||||
readonly_fields = ("total",)
|
||||
readonly_fields_update = ('user', 'tirage')
|
||||
readonly_fields_update = ("user", "tirage")
|
||||
form = ParticipantAdminForm
|
||||
|
||||
def send_attribs(self, request, queryset):
|
||||
datatuple = []
|
||||
for member in queryset.all():
|
||||
attribs = member.attributions.all()
|
||||
context = {'member': member.user}
|
||||
context = {"member": member.user}
|
||||
shortname = ""
|
||||
if len(attribs) == 0:
|
||||
shortname = "bda-attributions-decus"
|
||||
else:
|
||||
shortname = "bda-attributions"
|
||||
context['places'] = attribs
|
||||
context["places"] = attribs
|
||||
print(context)
|
||||
datatuple.append((shortname, context, "bda@ens.fr",
|
||||
[member.user.email]))
|
||||
datatuple.append((shortname, context, "bda@ens.fr", [member.user.email]))
|
||||
send_mass_custom_mail(datatuple)
|
||||
count = len(queryset.all())
|
||||
if count == 1:
|
||||
|
@ -143,24 +150,23 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
|||
else:
|
||||
message_bit = "%d membres ont" % count
|
||||
plural = "s"
|
||||
self.message_user(request, "%s été informé%s avec succès."
|
||||
% (message_bit, plural))
|
||||
self.message_user(
|
||||
request, "%s été informé%s avec succès." % (message_bit, plural)
|
||||
)
|
||||
|
||||
send_attribs.short_description = "Envoyer les résultats par mail"
|
||||
|
||||
|
||||
class AttributionAdminForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if 'spectacle' in self.fields:
|
||||
self.fields['spectacle'].queryset = (
|
||||
Spectacle.objects
|
||||
.select_related('location')
|
||||
if "spectacle" in self.fields:
|
||||
self.fields["spectacle"].queryset = Spectacle.objects.select_related(
|
||||
"location"
|
||||
)
|
||||
if 'participant' in self.fields:
|
||||
self.fields['participant'].queryset = (
|
||||
Participant.objects
|
||||
.select_related('user', 'tirage')
|
||||
if "participant" in self.fields:
|
||||
self.fields["participant"].queryset = Participant.objects.select_related(
|
||||
"user", "tirage"
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
|
@ -171,21 +177,26 @@ class AttributionAdminForm(forms.ModelForm):
|
|||
if participant.tirage != spectacle.tirage:
|
||||
raise forms.ValidationError(
|
||||
"Erreur : le participant et le spectacle n'appartiennent"
|
||||
"pas au même tirage")
|
||||
"pas au même tirage"
|
||||
)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
||||
def paid(self, obj):
|
||||
return obj.participant.paid
|
||||
paid.short_description = 'A payé'
|
||||
|
||||
paid.short_description = "A payé"
|
||||
paid.boolean = True
|
||||
list_display = ("id", "spectacle", "participant", "given", "paid")
|
||||
search_fields = ('spectacle__title', 'participant__user__username',
|
||||
'participant__user__first_name',
|
||||
'participant__user__last_name')
|
||||
search_fields = (
|
||||
"spectacle__title",
|
||||
"participant__user__username",
|
||||
"participant__user__first_name",
|
||||
"participant__user__last_name",
|
||||
)
|
||||
form = AttributionAdminForm
|
||||
readonly_fields_update = ('spectacle', 'participant')
|
||||
readonly_fields_update = ("spectacle", "participant")
|
||||
|
||||
|
||||
class ChoixSpectacleAdmin(admin.ModelAdmin):
|
||||
|
@ -193,13 +204,15 @@ class ChoixSpectacleAdmin(admin.ModelAdmin):
|
|||
|
||||
def tirage(self, obj):
|
||||
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")
|
||||
search_fields = ('participant__user__username',
|
||||
'participant__user__first_name',
|
||||
'participant__user__last_name',
|
||||
'spectacle__title')
|
||||
search_fields = (
|
||||
"participant__user__username",
|
||||
"participant__user__first_name",
|
||||
"participant__user__last_name",
|
||||
"spectacle__title",
|
||||
)
|
||||
|
||||
|
||||
class QuoteInline(admin.TabularInline):
|
||||
|
@ -209,42 +222,36 @@ class QuoteInline(admin.TabularInline):
|
|||
class SpectacleAdmin(admin.ModelAdmin):
|
||||
inlines = [QuoteInline]
|
||||
model = Spectacle
|
||||
list_display = ("title", "date", "tirage", "location", "slots", "price",
|
||||
"listing")
|
||||
list_filter = ("location", "tirage",)
|
||||
list_display = ("title", "date", "tirage", "location", "slots", "price", "listing")
|
||||
list_filter = ("location", "tirage")
|
||||
search_fields = ("title", "location__name")
|
||||
readonly_fields = ("rappel_sent", )
|
||||
readonly_fields = ("rappel_sent",)
|
||||
|
||||
|
||||
class TirageAdmin(admin.ModelAdmin):
|
||||
model = Tirage
|
||||
list_display = ("title", "ouverture", "fermeture", "active",
|
||||
"enable_do_tirage")
|
||||
readonly_fields = ("tokens", )
|
||||
list_filter = ("active", )
|
||||
search_fields = ("title", )
|
||||
list_display = ("title", "ouverture", "fermeture", "active", "enable_do_tirage")
|
||||
readonly_fields = ("tokens",)
|
||||
list_filter = ("active",)
|
||||
search_fields = ("title",)
|
||||
|
||||
|
||||
class SalleAdmin(admin.ModelAdmin):
|
||||
model = Salle
|
||||
search_fields = ('name', 'address')
|
||||
search_fields = ("name", "address")
|
||||
|
||||
|
||||
class SpectacleReventeAdminForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['confirmed_entry'].queryset = (
|
||||
Participant.objects
|
||||
.select_related('user', 'tirage')
|
||||
self.fields["confirmed_entry"].queryset = Participant.objects.select_related(
|
||||
"user", "tirage"
|
||||
)
|
||||
self.fields['seller'].queryset = (
|
||||
Participant.objects
|
||||
.select_related('user', 'tirage')
|
||||
self.fields["seller"].queryset = Participant.objects.select_related(
|
||||
"user", "tirage"
|
||||
)
|
||||
self.fields['soldTo'].queryset = (
|
||||
Participant.objects
|
||||
.select_related('user', 'tirage')
|
||||
self.fields["soldTo"].queryset = Participant.objects.select_related(
|
||||
"user", "tirage"
|
||||
)
|
||||
|
||||
|
||||
|
@ -252,6 +259,7 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
|||
"""
|
||||
Administration des reventes de spectacles
|
||||
"""
|
||||
|
||||
model = SpectacleRevente
|
||||
|
||||
def spectacle(self, obj):
|
||||
|
@ -263,12 +271,14 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
|||
list_display = ("spectacle", "seller", "date", "soldTo")
|
||||
raw_id_fields = ("attribution",)
|
||||
readonly_fields = ("date_tirage",)
|
||||
search_fields = ['attribution__spectacle__title',
|
||||
'seller__user__username',
|
||||
'seller__user__first_name',
|
||||
'seller__user__last_name']
|
||||
search_fields = [
|
||||
"attribution__spectacle__title",
|
||||
"seller__user__username",
|
||||
"seller__user__first_name",
|
||||
"seller__user__last_name",
|
||||
]
|
||||
|
||||
actions = ['transfer', 'reinit']
|
||||
actions = ["transfer", "reinit"]
|
||||
actions_on_bottom = True
|
||||
form = SpectacleReventeAdminForm
|
||||
|
||||
|
@ -284,10 +294,10 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
|||
attrib.save()
|
||||
self.message_user(
|
||||
request,
|
||||
"%d attribution%s %s été transférée%s avec succès." % (
|
||||
count, pluralize(count),
|
||||
pluralize(count, "a,ont"), pluralize(count))
|
||||
"%d attribution%s %s été transférée%s avec succès."
|
||||
% (count, pluralize(count), pluralize(count, "a,ont"), pluralize(count)),
|
||||
)
|
||||
|
||||
transfer.short_description = "Transférer les reventes sélectionnées"
|
||||
|
||||
def reinit(self, request, queryset):
|
||||
|
@ -296,14 +306,15 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
|||
"""
|
||||
count = queryset.count()
|
||||
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))
|
||||
self.message_user(
|
||||
request,
|
||||
"%d attribution%s %s été réinitialisée%s avec succès." % (
|
||||
count, pluralize(count),
|
||||
pluralize(count, "a,ont"), pluralize(count))
|
||||
"%d attribution%s %s été réinitialisée%s avec succès."
|
||||
% (count, pluralize(count), pluralize(count, "a,ont"), pluralize(count)),
|
||||
)
|
||||
|
||||
reinit.short_description = "Réinitialiser les reventes sélectionnées"
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from django.db.models import Max
|
||||
|
||||
import random
|
||||
|
||||
|
||||
|
@ -16,7 +14,7 @@ class Algorithm(object):
|
|||
show.requests
|
||||
- on crée des tables de demandes pour chaque personne, afin de
|
||||
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 = []
|
||||
showdict = {}
|
||||
for show in shows:
|
||||
|
@ -54,16 +52,19 @@ class Algorithm(object):
|
|||
self.ranks[member][show] -= increment
|
||||
|
||||
def appendResult(self, l, member, show):
|
||||
l.append((member,
|
||||
l.append(
|
||||
(
|
||||
member,
|
||||
self.ranks[member][show],
|
||||
self.origranks[member][show],
|
||||
self.choices[member][show].double))
|
||||
self.choices[member][show].double,
|
||||
)
|
||||
)
|
||||
|
||||
def __call__(self, seed):
|
||||
random.seed(seed)
|
||||
results = []
|
||||
shows = sorted(self.shows, key=lambda x: x.nrequests / x.slots,
|
||||
reverse=True)
|
||||
shows = sorted(self.shows, key=lambda x: x.nrequests / x.slots, reverse=True)
|
||||
for show in shows:
|
||||
# On regroupe tous les gens ayant le même rang
|
||||
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:
|
||||
self.appendResult(winners, member, show)
|
||||
self.appendResult(winners, member, show)
|
||||
elif not self.choices[member][show].autoquit \
|
||||
and len(winners) < show.slots:
|
||||
elif (
|
||||
not self.choices[member][show].autoquit
|
||||
and len(winners) < show.slots
|
||||
):
|
||||
self.appendResult(winners, member, show)
|
||||
self.appendResult(losers, member, show)
|
||||
else:
|
||||
|
|
105
bda/forms.py
105
bda/forms.py
|
@ -6,7 +6,6 @@ from bda.models import Attribution, Spectacle, SpectacleRevente
|
|||
|
||||
|
||||
class InscriptionInlineFormSet(BaseInlineFormSet):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
@ -16,9 +15,9 @@ class InscriptionInlineFormSet(BaseInlineFormSet):
|
|||
# set once for all "spectacle" field choices
|
||||
# - restrict choices to the spectacles of this tirage
|
||||
# - 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]
|
||||
self.force_choices('spectacle', choices)
|
||||
self.force_choices("spectacle", choices)
|
||||
|
||||
def force_choices(self, name, choices):
|
||||
"""Set choices of a field.
|
||||
|
@ -30,7 +29,7 @@ class InscriptionInlineFormSet(BaseInlineFormSet):
|
|||
for form in self.forms:
|
||||
field = form.fields[name]
|
||||
if field.empty_label is not None:
|
||||
field.choices = [('', field.empty_label)] + choices
|
||||
field.choices = [("", field.empty_label)] + choices
|
||||
else:
|
||||
field.choices = choices
|
||||
|
||||
|
@ -53,58 +52,61 @@ class ReventeModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
|||
label = "{show}{suffix}"
|
||||
suffix = ""
|
||||
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:
|
||||
suffix = " -- Vendue à {firstname} {lastname}".format(
|
||||
firstname=obj.soldTo.user.first_name,
|
||||
lastname=obj.soldTo.user.last_name,
|
||||
)
|
||||
elif obj.shotgun:
|
||||
suffix = " -- Tirage infructueux"
|
||||
elif obj.notif_sent:
|
||||
suffix = " -- Inscriptions au tirage en cours"
|
||||
else:
|
||||
# Ce n'est pas à nous : on ne voit jamais l'acheteur
|
||||
suffix = " -- Vendue par {firstname} {lastname}".format(
|
||||
firstname=obj.seller.user.first_name,
|
||||
lastname=obj.seller.user.last_name,
|
||||
firstname=obj.seller.user.first_name, lastname=obj.seller.user.last_name
|
||||
)
|
||||
|
||||
return label.format(show=str(obj.attribution.spectacle),
|
||||
suffix=suffix)
|
||||
return label.format(show=str(obj.attribution.spectacle), suffix=suffix)
|
||||
|
||||
|
||||
class ResellForm(forms.Form):
|
||||
attributions = AttributionModelMultipleChoiceField(
|
||||
label='',
|
||||
label="",
|
||||
queryset=Attribution.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False)
|
||||
required=False,
|
||||
)
|
||||
|
||||
def __init__(self, participant, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['attributions'].queryset = (
|
||||
participant.attribution_set
|
||||
.filter(spectacle__date__gte=timezone.now())
|
||||
self.fields["attributions"].queryset = (
|
||||
participant.attribution_set.filter(spectacle__date__gte=timezone.now())
|
||||
.exclude(revente__seller=participant)
|
||||
.select_related('spectacle', 'spectacle__location',
|
||||
'participant__user')
|
||||
.select_related("spectacle", "spectacle__location", "participant__user")
|
||||
)
|
||||
|
||||
|
||||
class AnnulForm(forms.Form):
|
||||
reventes = ReventeModelMultipleChoiceField(
|
||||
own=True,
|
||||
label='',
|
||||
label="",
|
||||
queryset=Attribution.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False)
|
||||
required=False,
|
||||
)
|
||||
|
||||
def __init__(self, participant, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['reventes'].queryset = (
|
||||
participant.original_shows
|
||||
.filter(attribution__spectacle__date__gte=timezone.now(),
|
||||
notif_sent=False,
|
||||
soldTo__isnull=True)
|
||||
.select_related('attribution__spectacle',
|
||||
'attribution__spectacle__location')
|
||||
self.fields["reventes"].queryset = (
|
||||
participant.original_shows.filter(
|
||||
attribution__spectacle__date__gte=timezone.now(), soldTo__isnull=True
|
||||
)
|
||||
.select_related(
|
||||
"attribution__spectacle", "attribution__spectacle__location"
|
||||
)
|
||||
.order_by("-date")
|
||||
)
|
||||
|
||||
|
||||
|
@ -112,69 +114,66 @@ class InscriptionReventeForm(forms.Form):
|
|||
spectacles = forms.ModelMultipleChoiceField(
|
||||
queryset=Spectacle.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False)
|
||||
required=False,
|
||||
)
|
||||
|
||||
def __init__(self, tirage, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['spectacles'].queryset = (
|
||||
tirage.spectacle_set
|
||||
.select_related('location')
|
||||
.filter(date__gte=timezone.now())
|
||||
)
|
||||
self.fields["spectacles"].queryset = tirage.spectacle_set.select_related(
|
||||
"location"
|
||||
).filter(date__gte=timezone.now())
|
||||
|
||||
|
||||
class ReventeTirageAnnulForm(forms.Form):
|
||||
reventes = ReventeModelMultipleChoiceField(
|
||||
own=False,
|
||||
label='',
|
||||
label="",
|
||||
queryset=SpectacleRevente.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False
|
||||
required=False,
|
||||
)
|
||||
|
||||
def __init__(self, participant, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['reventes'].queryset = (
|
||||
participant.entered.filter(soldTo__isnull=True)
|
||||
.select_related('attribution__spectacle',
|
||||
'seller__user')
|
||||
)
|
||||
self.fields["reventes"].queryset = participant.entered.filter(
|
||||
soldTo__isnull=True
|
||||
).select_related("attribution__spectacle", "seller__user")
|
||||
|
||||
|
||||
class ReventeTirageForm(forms.Form):
|
||||
reventes = ReventeModelMultipleChoiceField(
|
||||
own=False,
|
||||
label='',
|
||||
label="",
|
||||
queryset=SpectacleRevente.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False
|
||||
required=False,
|
||||
)
|
||||
|
||||
def __init__(self, participant, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['reventes'].queryset = (
|
||||
self.fields["reventes"].queryset = (
|
||||
SpectacleRevente.objects.filter(
|
||||
notif_sent=True,
|
||||
shotgun=False,
|
||||
tirage_done=False
|
||||
).exclude(confirmed_entry=participant)
|
||||
.select_related('attribution__spectacle')
|
||||
notif_sent=True, shotgun=False, tirage_done=False
|
||||
)
|
||||
.exclude(confirmed_entry=participant)
|
||||
.select_related("attribution__spectacle")
|
||||
)
|
||||
|
||||
|
||||
class SoldForm(forms.Form):
|
||||
reventes = ReventeModelMultipleChoiceField(
|
||||
own=True,
|
||||
label='',
|
||||
label="",
|
||||
queryset=Attribution.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple)
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
)
|
||||
|
||||
def __init__(self, participant, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['reventes'].queryset = (
|
||||
participant.original_shows
|
||||
.filter(soldTo__isnull=False)
|
||||
self.fields["reventes"].queryset = (
|
||||
participant.original_shows.filter(soldTo__isnull=False)
|
||||
.exclude(soldTo=participant)
|
||||
.select_related('attribution__spectacle',
|
||||
'attribution__spectacle__location')
|
||||
.select_related(
|
||||
"attribution__spectacle", "attribution__spectacle__location"
|
||||
)
|
||||
)
|
||||
|
|
|
@ -5,17 +5,15 @@ Crée deux tirages de test et y inscrit les utilisateurs
|
|||
import os
|
||||
import random
|
||||
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
|
||||
from gestioncof.management.base import MyBaseCommand
|
||||
from bda.models import Tirage, Spectacle, Salle, Participant, ChoixSpectacle
|
||||
from bda.models import ChoixSpectacle, Participant, Salle, Spectacle, Tirage
|
||||
from bda.views import do_tirage
|
||||
|
||||
from gestioncof.management.base import MyBaseCommand
|
||||
|
||||
# Où sont stockés les fichiers json
|
||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
||||
'data')
|
||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data")
|
||||
|
||||
|
||||
class Command(MyBaseCommand):
|
||||
|
@ -27,27 +25,29 @@ class Command(MyBaseCommand):
|
|||
# ---
|
||||
|
||||
Tirage.objects.all().delete()
|
||||
Tirage.objects.bulk_create([
|
||||
Tirage.objects.bulk_create(
|
||||
[
|
||||
Tirage(
|
||||
title="Tirage de test 1",
|
||||
ouverture=timezone.now()-timezone.timedelta(days=7),
|
||||
ouverture=timezone.now() - timezone.timedelta(days=7),
|
||||
fermeture=timezone.now(),
|
||||
active=True
|
||||
active=True,
|
||||
),
|
||||
Tirage(
|
||||
title="Tirage de test 2",
|
||||
ouverture=timezone.now(),
|
||||
fermeture=timezone.now()+timezone.timedelta(days=60),
|
||||
active=True
|
||||
fermeture=timezone.now() + timezone.timedelta(days=60),
|
||||
active=True,
|
||||
),
|
||||
]
|
||||
)
|
||||
])
|
||||
tirages = Tirage.objects.all()
|
||||
|
||||
# ---
|
||||
# Salles
|
||||
# ---
|
||||
|
||||
locations = self.from_json('locations.json', DATA_DIR, Salle)
|
||||
locations = self.from_json("locations.json", DATA_DIR, Salle)
|
||||
|
||||
# ---
|
||||
# Spectacles
|
||||
|
@ -60,15 +60,13 @@ class Command(MyBaseCommand):
|
|||
"""
|
||||
show.tirage = random.choice(tirages)
|
||||
show.listing = bool(random.randint(0, 1))
|
||||
show.date = (
|
||||
show.tirage.fermeture
|
||||
+ timezone.timedelta(days=random.randint(60, 90))
|
||||
show.date = show.tirage.fermeture + timezone.timedelta(
|
||||
days=random.randint(60, 90)
|
||||
)
|
||||
show.location = random.choice(locations)
|
||||
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
|
||||
|
@ -79,23 +77,19 @@ class Command(MyBaseCommand):
|
|||
choices = []
|
||||
for user in User.objects.filter(profile__is_cof=True):
|
||||
for tirage in tirages:
|
||||
part, _ = Participant.objects.get_or_create(
|
||||
user=user,
|
||||
tirage=tirage
|
||||
)
|
||||
part, _ = Participant.objects.get_or_create(user=user, tirage=tirage)
|
||||
shows = random.sample(
|
||||
list(tirage.spectacle_set.all()),
|
||||
tirage.spectacle_set.count() // 2
|
||||
list(tirage.spectacle_set.all()), tirage.spectacle_set.count() // 2
|
||||
)
|
||||
for (rank, show) in enumerate(shows):
|
||||
choices.append(ChoixSpectacle(
|
||||
choices.append(
|
||||
ChoixSpectacle(
|
||||
participant=part,
|
||||
spectacle=show,
|
||||
priority=rank + 1,
|
||||
double_choice=random.choice(
|
||||
['1', 'double', 'autoquit']
|
||||
double_choice=random.choice(["1", "double", "autoquit"]),
|
||||
)
|
||||
)
|
||||
))
|
||||
ChoixSpectacle.objects.bulk_create(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.utils import timezone
|
||||
|
||||
from bda.models import SpectacleRevente
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Envoie les mails de notification et effectue " \
|
||||
"les tirages au sort des reventes"
|
||||
help = (
|
||||
"Envoie les mails de notification et effectue les tirages au sort des reventes"
|
||||
)
|
||||
leave_locale_alone = True
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
@ -28,22 +30,18 @@ class Command(BaseCommand):
|
|||
)
|
||||
|
||||
# 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))
|
||||
revente.send_notif()
|
||||
self.stdout.write(
|
||||
"Mails d'inscription à la revente [%s] envoyés"
|
||||
% revente
|
||||
"Mails d'inscription à la revente [%s] envoyés" % revente
|
||||
)
|
||||
|
||||
# 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))
|
||||
winner = revente.tirage()
|
||||
self.stdout.write(
|
||||
"Tirage effectué pour la revente [%s]"
|
||||
% revente
|
||||
)
|
||||
self.stdout.write("Tirage effectué pour la revente [%s]" % revente)
|
||||
|
||||
if winner:
|
||||
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 django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
|
||||
from bda.models import Spectacle
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Envoie les mails de rappel des spectacles dont la date ' \
|
||||
'approche.\nNe renvoie pas les mails déjà envoyés.'
|
||||
help = (
|
||||
"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
|
||||
|
||||
def handle(self, *args, **options):
|
||||
now = timezone.now()
|
||||
delay = timedelta(days=4)
|
||||
shows = Spectacle.objects \
|
||||
.filter(date__range=(now, now+delay)) \
|
||||
.filter(tirage__active=True) \
|
||||
.filter(rappel_sent__isnull=True) \
|
||||
shows = (
|
||||
Spectacle.objects.filter(date__range=(now, now + delay))
|
||||
.filter(tirage__active=True)
|
||||
.filter(rappel_sent__isnull=True)
|
||||
.all()
|
||||
)
|
||||
for show in shows:
|
||||
show.send_rappel()
|
||||
self.stdout.write(
|
||||
'Mails de rappels pour %s envoyés avec succès.' % show)
|
||||
self.stdout.write("Mails de rappels pour %s envoyés avec succès." % show)
|
||||
if not shows:
|
||||
self.stdout.write('Aucun mail à envoyer.')
|
||||
self.stdout.write("Aucun mail à envoyer.")
|
||||
|
|
|
@ -1,108 +1,206 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Attribution',
|
||||
name="Attribution",
|
||||
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(
|
||||
name='ChoixSpectacle',
|
||||
name="ChoixSpectacle",
|
||||
fields=[
|
||||
('id', 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')])),
|
||||
(
|
||||
"id",
|
||||
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={
|
||||
'ordering': ('priority',),
|
||||
'verbose_name': 'voeu',
|
||||
'verbose_name_plural': 'voeux',
|
||||
"ordering": ("priority",),
|
||||
"verbose_name": "voeu",
|
||||
"verbose_name_plural": "voeux",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Participant',
|
||||
name="Participant",
|
||||
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')),
|
||||
('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')])),
|
||||
(
|
||||
"id",
|
||||
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(
|
||||
name='Salle',
|
||||
name="Salle",
|
||||
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')),
|
||||
('address', models.TextField(verbose_name=b'Adresse')),
|
||||
(
|
||||
"id",
|
||||
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(
|
||||
name='Spectacle',
|
||||
name="Spectacle",
|
||||
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')),
|
||||
('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)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
),
|
||||
),
|
||||
("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={
|
||||
'ordering': ('priority', 'date', 'title'),
|
||||
'verbose_name': 'Spectacle',
|
||||
"ordering": ("priority", "date", "title"),
|
||||
"verbose_name": "Spectacle",
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='participant',
|
||||
name='attributions',
|
||||
field=models.ManyToManyField(related_name='attributed_to', through='bda.Attribution', to='bda.Spectacle'),
|
||||
model_name="participant",
|
||||
name="attributions",
|
||||
field=models.ManyToManyField(
|
||||
related_name="attributed_to",
|
||||
through="bda.Attribution",
|
||||
to="bda.Spectacle",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='participant',
|
||||
name='choices',
|
||||
field=models.ManyToManyField(related_name='chosen_by', through='bda.ChoixSpectacle', to='bda.Spectacle'),
|
||||
model_name="participant",
|
||||
name="choices",
|
||||
field=models.ManyToManyField(
|
||||
related_name="chosen_by",
|
||||
through="bda.ChoixSpectacle",
|
||||
to="bda.Spectacle",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='participant',
|
||||
name='user',
|
||||
field=models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
||||
model_name="participant",
|
||||
name="user",
|
||||
field=models.OneToOneField(
|
||||
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='choixspectacle',
|
||||
name='participant',
|
||||
field=models.ForeignKey(to='bda.Participant', on_delete=models.CASCADE),
|
||||
model_name="choixspectacle",
|
||||
name="participant",
|
||||
field=models.ForeignKey(to="bda.Participant", on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='choixspectacle',
|
||||
name='spectacle',
|
||||
field=models.ForeignKey(related_name='participants', to='bda.Spectacle', on_delete=models.CASCADE),
|
||||
model_name="choixspectacle",
|
||||
name="spectacle",
|
||||
field=models.ForeignKey(
|
||||
related_name="participants",
|
||||
to="bda.Spectacle",
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='attribution',
|
||||
name='participant',
|
||||
field=models.ForeignKey(to='bda.Participant', on_delete=models.CASCADE),
|
||||
model_name="attribution",
|
||||
name="participant",
|
||||
field=models.ForeignKey(to="bda.Participant", on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='attribution',
|
||||
name='spectacle',
|
||||
field=models.ForeignKey(related_name='attribues', to='bda.Spectacle', on_delete=models.CASCADE),
|
||||
model_name="attribution",
|
||||
name="spectacle",
|
||||
field=models.ForeignKey(
|
||||
related_name="attribues", to="bda.Spectacle", on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='choixspectacle',
|
||||
unique_together=set([('participant', 'spectacle')]),
|
||||
name="choixspectacle", unique_together=set([("participant", "spectacle")])
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
|
@ -36,49 +36,77 @@ def fill_tirage_fields(apps, schema_editor):
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0001_initial'),
|
||||
]
|
||||
dependencies = [("bda", "0001_initial")]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Tirage',
|
||||
name="Tirage",
|
||||
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')),
|
||||
('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')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
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(
|
||||
model_name='participant',
|
||||
name='user',
|
||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
||||
model_name="participant",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
# Create fields `spectacle` for `Participant` and `Spectacle` models.
|
||||
# These fields are not nullable, but we first create them as nullable
|
||||
# to give a default value for existing instances of these models.
|
||||
migrations.AddField(
|
||||
model_name='participant',
|
||||
name='tirage',
|
||||
field=models.ForeignKey(to='bda.Tirage', null=True, on_delete=models.CASCADE),
|
||||
model_name="participant",
|
||||
name="tirage",
|
||||
field=models.ForeignKey(
|
||||
to="bda.Tirage", null=True, on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectacle',
|
||||
name='tirage',
|
||||
field=models.ForeignKey(to='bda.Tirage', null=True, on_delete=models.CASCADE),
|
||||
model_name="spectacle",
|
||||
name="tirage",
|
||||
field=models.ForeignKey(
|
||||
to="bda.Tirage", null=True, on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
migrations.RunPython(fill_tirage_fields, migrations.RunPython.noop),
|
||||
migrations.AlterField(
|
||||
model_name='participant',
|
||||
name='tirage',
|
||||
field=models.ForeignKey(to='bda.Tirage', on_delete=models.CASCADE),
|
||||
model_name="participant",
|
||||
name="tirage",
|
||||
field=models.ForeignKey(to="bda.Tirage", on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='tirage',
|
||||
field=models.ForeignKey(to='bda.Tirage', on_delete=models.CASCADE),
|
||||
model_name="spectacle",
|
||||
name="tirage",
|
||||
field=models.ForeignKey(to="bda.Tirage", on_delete=models.CASCADE),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -6,19 +6,17 @@ from django.db import migrations, models
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0002_add_tirage'),
|
||||
]
|
||||
dependencies = [("bda", "0002_add_tirage")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='price',
|
||||
model_name="spectacle",
|
||||
name="price",
|
||||
field=models.FloatField(verbose_name=b"Prix d'une place"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tirage',
|
||||
name='active',
|
||||
field=models.BooleanField(default=False, verbose_name=b'Tirage actif'),
|
||||
model_name="tirage",
|
||||
name="active",
|
||||
field=models.BooleanField(default=False, verbose_name=b"Tirage actif"),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -6,20 +6,22 @@ from django.db import migrations, models
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0003_update_tirage_and_spectacle'),
|
||||
]
|
||||
dependencies = [("bda", "0003_update_tirage_and_spectacle")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='spectacle',
|
||||
name='listing',
|
||||
field=models.BooleanField(default=False, verbose_name=b'Les places sont sur listing'),
|
||||
model_name="spectacle",
|
||||
name="listing",
|
||||
field=models.BooleanField(
|
||||
default=False, verbose_name=b"Les places sont sur listing"
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectacle',
|
||||
name='rappel_sent',
|
||||
field=models.DateTimeField(null=True, verbose_name=b'Mail de rappel envoy\xc3\xa9', blank=True),
|
||||
model_name="spectacle",
|
||||
name="rappel_sent",
|
||||
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):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0004_mails-rappel'),
|
||||
]
|
||||
dependencies = [("bda", "0004_mails-rappel")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='choixspectacle',
|
||||
name='priority',
|
||||
field=models.PositiveIntegerField(verbose_name='Priorit\xe9'),
|
||||
model_name="choixspectacle",
|
||||
name="priority",
|
||||
field=models.PositiveIntegerField(verbose_name="Priorit\xe9"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='priority',
|
||||
field=models.IntegerField(default=1000, verbose_name='Priorit\xe9'),
|
||||
model_name="spectacle",
|
||||
name="priority",
|
||||
field=models.IntegerField(default=1000, verbose_name="Priorit\xe9"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='rappel_sent',
|
||||
field=models.DateTimeField(null=True, verbose_name='Mail de rappel envoy\xe9', blank=True),
|
||||
model_name="spectacle",
|
||||
name="rappel_sent",
|
||||
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
|
||||
for tirage in Tirage.objects.using(db_alias).all():
|
||||
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"),
|
||||
tirage.tokens)
|
||||
tirage.tokens,
|
||||
)
|
||||
tirage.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0005_encoding'),
|
||||
]
|
||||
dependencies = [("bda", "0005_encoding")]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField('tirage', 'token', 'tokens'),
|
||||
migrations.RenameField("tirage", "token", "tokens"),
|
||||
migrations.AddField(
|
||||
model_name='tirage',
|
||||
name='enable_do_tirage',
|
||||
model_name="tirage",
|
||||
name="enable_do_tirage",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=b'Le tirage peut \xc3\xaatre lanc\xc3\xa9'),
|
||||
default=False, verbose_name=b"Le tirage peut \xc3\xaatre lanc\xc3\xa9"
|
||||
),
|
||||
),
|
||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
||||
]
|
||||
|
|
|
@ -1,91 +1,100 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0006_add_tirage_switch'),
|
||||
]
|
||||
dependencies = [("bda", "0006_add_tirage_switch")]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CategorieSpectacle',
|
||||
name="CategorieSpectacle",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=100, verbose_name='Nom',
|
||||
unique=True)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"name",
|
||||
models.CharField(max_length=100, verbose_name="Nom", unique=True),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Cat\xe9gorie',
|
||||
},
|
||||
options={"verbose_name": "Cat\xe9gorie"},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Quote',
|
||||
name="Quote",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True)),
|
||||
('text', models.TextField(verbose_name='Citation')),
|
||||
('author', models.CharField(max_length=200,
|
||||
verbose_name='Auteur')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
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(
|
||||
name='spectacle',
|
||||
options={'ordering': ('date', 'title'),
|
||||
'verbose_name': 'Spectacle'},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='spectacle',
|
||||
name='priority',
|
||||
name="spectacle",
|
||||
options={"ordering": ("date", "title"), "verbose_name": "Spectacle"},
|
||||
),
|
||||
migrations.RemoveField(model_name="spectacle", name="priority"),
|
||||
migrations.AddField(
|
||||
model_name='spectacle',
|
||||
name='ext_link',
|
||||
model_name="spectacle",
|
||||
name="ext_link",
|
||||
field=models.CharField(
|
||||
max_length=500,
|
||||
verbose_name='Lien vers le site du spectacle',
|
||||
blank=True),
|
||||
verbose_name="Lien vers le site du spectacle",
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectacle',
|
||||
name='image',
|
||||
field=models.ImageField(upload_to='imgs/shows/', null=True,
|
||||
verbose_name='Image', blank=True),
|
||||
model_name="spectacle",
|
||||
name="image",
|
||||
field=models.ImageField(
|
||||
upload_to="imgs/shows/", null=True, verbose_name="Image", blank=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tirage',
|
||||
name='enable_do_tirage',
|
||||
model_name="tirage",
|
||||
name="enable_do_tirage",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='Le tirage peut \xeatre lanc\xe9'),
|
||||
default=False, verbose_name="Le tirage peut \xeatre lanc\xe9"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tirage',
|
||||
name='tokens',
|
||||
field=models.TextField(verbose_name='Graine(s) du tirage',
|
||||
blank=True),
|
||||
model_name="tirage",
|
||||
name="tokens",
|
||||
field=models.TextField(verbose_name="Graine(s) du tirage", blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectacle',
|
||||
name='category',
|
||||
field=models.ForeignKey(blank=True, to='bda.CategorieSpectacle',
|
||||
model_name="spectacle",
|
||||
name="category",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
to="bda.CategorieSpectacle",
|
||||
on_delete=models.CASCADE,
|
||||
null=True),
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectacle',
|
||||
name='vips',
|
||||
field=models.TextField(verbose_name='Personnalit\xe9s',
|
||||
blank=True),
|
||||
model_name="spectacle",
|
||||
name="vips",
|
||||
field=models.TextField(verbose_name="Personnalit\xe9s", blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='quote',
|
||||
name='spectacle',
|
||||
field=models.ForeignKey(to='bda.Spectacle',
|
||||
on_delete=models.CASCADE),
|
||||
model_name="quote",
|
||||
name="spectacle",
|
||||
field=models.ForeignKey(to="bda.Spectacle", on_delete=models.CASCADE),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,103 +1,110 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0007_extends_spectacle'),
|
||||
]
|
||||
dependencies = [("bda", "0007_extends_spectacle")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='choixspectacle',
|
||||
name='double_choice',
|
||||
model_name="choixspectacle",
|
||||
name="double_choice",
|
||||
field=models.CharField(
|
||||
verbose_name='Nombre de places',
|
||||
choices=[('1', '1 place'),
|
||||
('autoquit', '2 places si possible, 1 sinon'),
|
||||
('double', '2 places sinon rien')],
|
||||
max_length=10, default='1'),
|
||||
verbose_name="Nombre de places",
|
||||
choices=[
|
||||
("1", "1 place"),
|
||||
("autoquit", "2 places si possible, 1 sinon"),
|
||||
("double", "2 places sinon rien"),
|
||||
],
|
||||
max_length=10,
|
||||
default="1",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='participant',
|
||||
name='paymenttype',
|
||||
model_name="participant",
|
||||
name="paymenttype",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[('cash', 'Cash'), ('cb', 'CB'),
|
||||
('cheque', 'Chèque'), ('autre', 'Autre')],
|
||||
max_length=6, verbose_name='Moyen de paiement'),
|
||||
choices=[
|
||||
("cash", "Cash"),
|
||||
("cb", "CB"),
|
||||
("cheque", "Chèque"),
|
||||
("autre", "Autre"),
|
||||
],
|
||||
max_length=6,
|
||||
verbose_name="Moyen de paiement",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='salle',
|
||||
name='address',
|
||||
field=models.TextField(verbose_name='Adresse'),
|
||||
model_name="salle",
|
||||
name="address",
|
||||
field=models.TextField(verbose_name="Adresse"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='salle',
|
||||
name='name',
|
||||
field=models.CharField(verbose_name='Nom', max_length=300),
|
||||
model_name="salle",
|
||||
name="name",
|
||||
field=models.CharField(verbose_name="Nom", max_length=300),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='date',
|
||||
field=models.DateTimeField(verbose_name='Date & heure'),
|
||||
model_name="spectacle",
|
||||
name="date",
|
||||
field=models.DateTimeField(verbose_name="Date & heure"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='description',
|
||||
field=models.TextField(verbose_name='Description', blank=True),
|
||||
model_name="spectacle",
|
||||
name="description",
|
||||
field=models.TextField(verbose_name="Description", blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='listing',
|
||||
field=models.BooleanField(
|
||||
verbose_name='Les places sont sur listing'),
|
||||
model_name="spectacle",
|
||||
name="listing",
|
||||
field=models.BooleanField(verbose_name="Les places sont sur listing"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='price',
|
||||
model_name="spectacle",
|
||||
name="price",
|
||||
field=models.FloatField(verbose_name="Prix d'une place"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='slots',
|
||||
field=models.IntegerField(verbose_name='Places'),
|
||||
model_name="spectacle",
|
||||
name="slots",
|
||||
field=models.IntegerField(verbose_name="Places"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='slots_description',
|
||||
field=models.TextField(verbose_name='Description des places',
|
||||
blank=True),
|
||||
model_name="spectacle",
|
||||
name="slots_description",
|
||||
field=models.TextField(verbose_name="Description des places", blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='title',
|
||||
field=models.CharField(verbose_name='Titre', max_length=300),
|
||||
model_name="spectacle",
|
||||
name="title",
|
||||
field=models.CharField(verbose_name="Titre", max_length=300),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tirage',
|
||||
name='active',
|
||||
field=models.BooleanField(verbose_name='Tirage actif',
|
||||
default=False),
|
||||
model_name="tirage",
|
||||
name="active",
|
||||
field=models.BooleanField(verbose_name="Tirage actif", default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tirage',
|
||||
name='fermeture',
|
||||
model_name="tirage",
|
||||
name="fermeture",
|
||||
field=models.DateTimeField(
|
||||
verbose_name='Date et heure de fermerture du tirage'),
|
||||
verbose_name="Date et heure de fermerture du tirage"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tirage',
|
||||
name='ouverture',
|
||||
model_name="tirage",
|
||||
name="ouverture",
|
||||
field=models.DateTimeField(
|
||||
verbose_name="Date et heure d'ouverture du tirage"),
|
||||
verbose_name="Date et heure d'ouverture du tirage"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tirage',
|
||||
name='title',
|
||||
field=models.CharField(verbose_name='Titre', max_length=300),
|
||||
model_name="tirage",
|
||||
name="title",
|
||||
field=models.CharField(verbose_name="Titre", max_length=300),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,69 +1,87 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0008_py3'),
|
||||
]
|
||||
dependencies = [("bda", "0008_py3")]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SpectacleRevente',
|
||||
name="SpectacleRevente",
|
||||
fields=[
|
||||
('id', models.AutoField(serialize=False, primary_key=True,
|
||||
auto_created=True, verbose_name='ID')),
|
||||
('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)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
serialize=False,
|
||||
primary_key=True,
|
||||
auto_created=True,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"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={
|
||||
'verbose_name': 'Revente',
|
||||
},
|
||||
options={"verbose_name": "Revente"},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='participant',
|
||||
name='choicesrevente',
|
||||
field=models.ManyToManyField(to='bda.Spectacle',
|
||||
related_name='subscribed',
|
||||
blank=True),
|
||||
model_name="participant",
|
||||
name="choicesrevente",
|
||||
field=models.ManyToManyField(
|
||||
to="bda.Spectacle", related_name="subscribed", blank=True
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='answered_mail',
|
||||
field=models.ManyToManyField(to='bda.Participant',
|
||||
related_name='wanted',
|
||||
blank=True),
|
||||
model_name="spectaclerevente",
|
||||
name="answered_mail",
|
||||
field=models.ManyToManyField(
|
||||
to="bda.Participant", related_name="wanted", blank=True
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='attribution',
|
||||
field=models.OneToOneField(to='bda.Attribution',
|
||||
model_name="spectaclerevente",
|
||||
name="attribution",
|
||||
field=models.OneToOneField(
|
||||
to="bda.Attribution", on_delete=models.CASCADE, related_name="revente"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="spectaclerevente",
|
||||
name="seller",
|
||||
field=models.ForeignKey(
|
||||
to="bda.Participant",
|
||||
on_delete=models.CASCADE,
|
||||
related_name='revente'),
|
||||
verbose_name="Vendeur",
|
||||
related_name="original_shows",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='seller',
|
||||
field=models.ForeignKey(to='bda.Participant',
|
||||
model_name="spectaclerevente",
|
||||
name="soldTo",
|
||||
field=models.ForeignKey(
|
||||
to="bda.Participant",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name='Vendeur',
|
||||
related_name='original_shows'),
|
||||
verbose_name="Vendue à",
|
||||
null=True,
|
||||
blank=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='soldTo',
|
||||
field=models.ForeignKey(to='bda.Participant',
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name='Vendue à', null=True,
|
||||
blank=True),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,33 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
def forwards_func(apps, schema_editor):
|
||||
SpectacleRevente = apps.get_model("bda", "SpectacleRevente")
|
||||
|
||||
for revente in SpectacleRevente.objects.all():
|
||||
is_expired = timezone.now() > revente.date_tirage()
|
||||
is_direct = (revente.attribution.spectacle.date >= revente.date and
|
||||
timezone.now() > revente.date + timedelta(minutes=15))
|
||||
is_direct = revente.attribution.spectacle.date >= revente.date and timezone.now() > revente.date + timedelta(
|
||||
minutes=15
|
||||
)
|
||||
revente.shotgun = is_expired or is_direct
|
||||
revente.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0009_revente'),
|
||||
]
|
||||
dependencies = [("bda", "0009_revente")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='shotgun',
|
||||
field=models.BooleanField(default=False, verbose_name='Disponible imm\xe9diatement'),
|
||||
model_name="spectaclerevente",
|
||||
name="shotgun",
|
||||
field=models.BooleanField(
|
||||
default=False, verbose_name="Disponible imm\xe9diatement"
|
||||
),
|
||||
),
|
||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
||||
]
|
||||
|
|
|
@ -6,17 +6,14 @@ from django.db import migrations, models
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0010_spectaclerevente_shotgun'),
|
||||
]
|
||||
dependencies = [("bda", "0010_spectaclerevente_shotgun")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='tirage',
|
||||
name='appear_catalogue',
|
||||
model_name="tirage",
|
||||
name="appear_catalogue",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='Tirage à afficher dans le catalogue'
|
||||
),
|
||||
default=False, verbose_name="Tirage à afficher dans le catalogue"
|
||||
),
|
||||
)
|
||||
]
|
||||
|
|
|
@ -6,24 +6,26 @@ from django.db import migrations, models
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0011_tirage_appear_catalogue'),
|
||||
]
|
||||
dependencies = [("bda", "0011_tirage_appear_catalogue")]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='spectaclerevente',
|
||||
old_name='answered_mail',
|
||||
new_name='confirmed_entry',
|
||||
model_name="spectaclerevente",
|
||||
old_name="answered_mail",
|
||||
new_name="confirmed_entry",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectaclerevente',
|
||||
name='confirmed_entry',
|
||||
field=models.ManyToManyField(blank=True, related_name='entered', to='bda.Participant'),
|
||||
model_name="spectaclerevente",
|
||||
name="confirmed_entry",
|
||||
field=models.ManyToManyField(
|
||||
blank=True, related_name="entered", to="bda.Participant"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='notif_time',
|
||||
field=models.DateTimeField(blank=True, verbose_name="Moment d'envoi de la notification", null=True),
|
||||
model_name="spectaclerevente",
|
||||
name="notif_time",
|
||||
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):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0011_tirage_appear_catalogue'),
|
||||
]
|
||||
dependencies = [("bda", "0011_tirage_appear_catalogue")]
|
||||
|
||||
operations = [
|
||||
# Temporarily allow an extra "tmp" value for the `double_choice` field
|
||||
migrations.AlterField(
|
||||
model_name='choixspectacle',
|
||||
name='double_choice',
|
||||
model_name="choixspectacle",
|
||||
name="double_choice",
|
||||
field=models.CharField(
|
||||
verbose_name='Nombre de places',
|
||||
verbose_name="Nombre de places",
|
||||
max_length=10,
|
||||
default='1',
|
||||
default="1",
|
||||
choices=[
|
||||
('tmp', 'tmp'),
|
||||
('1', '1 place'),
|
||||
('double', '2 places si possible, 1 sinon'),
|
||||
('autoquit', '2 places sinon rien')
|
||||
]
|
||||
("tmp", "tmp"),
|
||||
("1", "1 place"),
|
||||
("double", "2 places si possible, 1 sinon"),
|
||||
("autoquit", "2 places sinon rien"),
|
||||
],
|
||||
),
|
||||
),
|
||||
migrations.RunPython(swap_double_choice, migrations.RunPython.noop),
|
||||
migrations.AlterField(
|
||||
model_name='choixspectacle',
|
||||
name='double_choice',
|
||||
model_name="choixspectacle",
|
||||
name="double_choice",
|
||||
field=models.CharField(
|
||||
verbose_name='Nombre de places',
|
||||
verbose_name="Nombre de places",
|
||||
max_length=10,
|
||||
default='1',
|
||||
default="1",
|
||||
choices=[
|
||||
('1', '1 place'),
|
||||
('double', '2 places si possible, 1 sinon'),
|
||||
('autoquit', '2 places sinon rien')
|
||||
]
|
||||
("1", "1 place"),
|
||||
("double", "2 places si possible, 1 sinon"),
|
||||
("autoquit", "2 places sinon rien"),
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -7,10 +7,6 @@ from django.db import migrations
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0012_notif_time'),
|
||||
('bda', '0012_swap_double_choice'),
|
||||
]
|
||||
dependencies = [("bda", "0012_notif_time"), ("bda", "0012_swap_double_choice")]
|
||||
|
||||
operations = [
|
||||
]
|
||||
operations = []
|
||||
|
|
238
bda/models.py
238
bda/models.py
|
@ -1,23 +1,22 @@
|
|||
import calendar
|
||||
import random
|
||||
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.core import mail
|
||||
from django.db import models
|
||||
from django.db.models import Count
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
from django.utils import timezone, formats
|
||||
|
||||
from custommail.models import CustomMail
|
||||
from django.utils import formats, timezone
|
||||
|
||||
|
||||
def get_generic_user():
|
||||
generic, _ = User.objects.get_or_create(
|
||||
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
|
||||
|
||||
|
@ -29,15 +28,15 @@ class Tirage(models.Model):
|
|||
tokens = models.TextField("Graine(s) du tirage", blank=True)
|
||||
active = models.BooleanField("Tirage actif", default=False)
|
||||
appear_catalogue = models.BooleanField(
|
||||
"Tirage à afficher dans le catalogue",
|
||||
default=False
|
||||
"Tirage à afficher dans le catalogue", default=False
|
||||
)
|
||||
enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
|
||||
default=False)
|
||||
enable_do_tirage = models.BooleanField("Le tirage peut être lancé", default=False)
|
||||
|
||||
def __str__(self):
|
||||
return "%s - %s" % (self.title, formats.localize(
|
||||
timezone.template_localtime(self.fermeture)))
|
||||
return "%s - %s" % (
|
||||
self.title,
|
||||
formats.localize(timezone.template_localtime(self.fermeture)),
|
||||
)
|
||||
|
||||
|
||||
class Salle(models.Model):
|
||||
|
@ -49,7 +48,7 @@ class Salle(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):
|
||||
return self.name
|
||||
|
@ -61,28 +60,26 @@ class CategorieSpectacle(models.Model):
|
|||
class Spectacle(models.Model):
|
||||
title = models.CharField("Titre", max_length=300)
|
||||
category = models.ForeignKey(
|
||||
CategorieSpectacle, on_delete=models.CASCADE,
|
||||
blank=True, null=True,
|
||||
CategorieSpectacle, on_delete=models.CASCADE, blank=True, null=True
|
||||
)
|
||||
date = models.DateTimeField("Date & heure")
|
||||
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)
|
||||
slots_description = models.TextField("Description des places", blank=True)
|
||||
image = models.ImageField('Image', blank=True, null=True,
|
||||
upload_to='imgs/shows/')
|
||||
ext_link = models.CharField('Lien vers le site du spectacle', blank=True,
|
||||
max_length=500)
|
||||
image = models.ImageField("Image", blank=True, null=True, upload_to="imgs/shows/")
|
||||
ext_link = models.CharField(
|
||||
"Lien vers le site du spectacle", blank=True, max_length=500
|
||||
)
|
||||
price = models.FloatField("Prix d'une place")
|
||||
slots = models.IntegerField("Places")
|
||||
tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
|
||||
listing = models.BooleanField("Les places sont sur listing")
|
||||
rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True,
|
||||
null=True)
|
||||
rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Spectacle"
|
||||
ordering = ("date", "title",)
|
||||
ordering = ("date", "title")
|
||||
|
||||
def timestamp(self):
|
||||
return "%d" % calendar.timegm(self.date.utctimetuple())
|
||||
|
@ -92,7 +89,7 @@ class Spectacle(models.Model):
|
|||
self.title,
|
||||
formats.localize(timezone.template_localtime(self.date)),
|
||||
self.location,
|
||||
self.price
|
||||
self.price,
|
||||
)
|
||||
|
||||
def getImgUrl(self):
|
||||
|
@ -101,7 +98,7 @@ class Spectacle(models.Model):
|
|||
"""
|
||||
try:
|
||||
return self.image.url
|
||||
except:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def send_rappel(self):
|
||||
|
@ -111,19 +108,21 @@ class Spectacle(models.Model):
|
|||
"""
|
||||
# On récupère la liste des participants + le BdA
|
||||
members = list(
|
||||
User.objects
|
||||
.filter(participant__attributions=self)
|
||||
.annotate(nb_attr=Count("id")).order_by()
|
||||
User.objects.filter(participant__attributions=self)
|
||||
.annotate(nb_attr=Count("id"))
|
||||
.order_by()
|
||||
)
|
||||
bda_generic = get_generic_user()
|
||||
bda_generic.nb_attr = 1
|
||||
members.append(bda_generic)
|
||||
# On écrit un mail personnalisé à chaque participant
|
||||
datatuple = [(
|
||||
'bda-rappel',
|
||||
{'member': member, "nb_attr": member.nb_attr, 'show': self},
|
||||
settings.MAIL_DATA['rappels']['FROM'],
|
||||
[member.email])
|
||||
datatuple = [
|
||||
(
|
||||
"bda-rappel",
|
||||
{"member": member, "nb_attr": member.nb_attr, "show": self},
|
||||
settings.MAIL_DATA["rappels"]["FROM"],
|
||||
[member.email],
|
||||
)
|
||||
for member in members
|
||||
]
|
||||
send_mass_custom_mail(datatuple)
|
||||
|
@ -140,8 +139,8 @@ class Spectacle(models.Model):
|
|||
|
||||
class Quote(models.Model):
|
||||
spectacle = models.ForeignKey(Spectacle, on_delete=models.CASCADE)
|
||||
text = models.TextField('Citation')
|
||||
author = models.CharField('Auteur', max_length=200)
|
||||
text = models.TextField("Citation")
|
||||
author = models.CharField("Auteur", max_length=200)
|
||||
|
||||
|
||||
PAYMENT_TYPES = (
|
||||
|
@ -154,20 +153,20 @@ PAYMENT_TYPES = (
|
|||
|
||||
class Participant(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
choices = models.ManyToManyField(Spectacle,
|
||||
through="ChoixSpectacle",
|
||||
related_name="chosen_by")
|
||||
attributions = models.ManyToManyField(Spectacle,
|
||||
through="Attribution",
|
||||
related_name="attributed_to")
|
||||
choices = models.ManyToManyField(
|
||||
Spectacle, through="ChoixSpectacle", related_name="chosen_by"
|
||||
)
|
||||
attributions = models.ManyToManyField(
|
||||
Spectacle, through="Attribution", related_name="attributed_to"
|
||||
)
|
||||
paid = models.BooleanField("A payé", default=False)
|
||||
paymenttype = models.CharField("Moyen de paiement",
|
||||
max_length=6, choices=PAYMENT_TYPES,
|
||||
blank=True)
|
||||
paymenttype = models.CharField(
|
||||
"Moyen de paiement", max_length=6, choices=PAYMENT_TYPES, blank=True
|
||||
)
|
||||
tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
|
||||
choicesrevente = models.ManyToManyField(Spectacle,
|
||||
related_name="subscribed",
|
||||
blank=True)
|
||||
choicesrevente = models.ManyToManyField(
|
||||
Spectacle, related_name="subscribed", blank=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "%s - %s" % (self.user, self.tirage.title)
|
||||
|
@ -183,30 +182,32 @@ DOUBLE_CHOICES = (
|
|||
class ChoixSpectacle(models.Model):
|
||||
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
|
||||
spectacle = models.ForeignKey(
|
||||
Spectacle, on_delete=models.CASCADE,
|
||||
related_name="participants",
|
||||
Spectacle, on_delete=models.CASCADE, related_name="participants"
|
||||
)
|
||||
priority = models.PositiveIntegerField("Priorité")
|
||||
double_choice = models.CharField("Nombre de places",
|
||||
default="1", choices=DOUBLE_CHOICES,
|
||||
max_length=10)
|
||||
double_choice = models.CharField(
|
||||
"Nombre de places", default="1", choices=DOUBLE_CHOICES, max_length=10
|
||||
)
|
||||
|
||||
def get_double(self):
|
||||
return self.double_choice != "1"
|
||||
|
||||
double = property(get_double)
|
||||
|
||||
def get_autoquit(self):
|
||||
return self.double_choice == "autoquit"
|
||||
|
||||
autoquit = property(get_autoquit)
|
||||
|
||||
def __str__(self):
|
||||
return "Vœux de %s pour %s" % (
|
||||
self.participant.user.get_full_name(),
|
||||
self.spectacle.title)
|
||||
self.spectacle.title,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ("priority",)
|
||||
unique_together = (("participant", "spectacle",),)
|
||||
unique_together = (("participant", "spectacle"),)
|
||||
verbose_name = "voeu"
|
||||
verbose_name_plural = "voeux"
|
||||
|
||||
|
@ -214,48 +215,49 @@ class ChoixSpectacle(models.Model):
|
|||
class Attribution(models.Model):
|
||||
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
|
||||
spectacle = models.ForeignKey(
|
||||
Spectacle, on_delete=models.CASCADE,
|
||||
related_name="attribues",
|
||||
Spectacle, on_delete=models.CASCADE, related_name="attribues"
|
||||
)
|
||||
given = models.BooleanField("Donnée", default=False)
|
||||
|
||||
def __str__(self):
|
||||
return "%s -- %s, %s" % (self.participant.user, self.spectacle.title,
|
||||
self.spectacle.date)
|
||||
return "%s -- %s, %s" % (
|
||||
self.participant.user,
|
||||
self.spectacle.title,
|
||||
self.spectacle.date,
|
||||
)
|
||||
|
||||
|
||||
class SpectacleRevente(models.Model):
|
||||
attribution = models.OneToOneField(
|
||||
Attribution, on_delete=models.CASCADE,
|
||||
related_name="revente",
|
||||
Attribution, on_delete=models.CASCADE, 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(
|
||||
Participant, on_delete=models.CASCADE,
|
||||
Participant,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Vendeur",
|
||||
related_name="original_shows",
|
||||
)
|
||||
soldTo = models.ForeignKey(
|
||||
Participant, on_delete=models.CASCADE,
|
||||
Participant,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Vendue à",
|
||||
blank=True, null=True,
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
notif_sent = models.BooleanField("Notification envoyée",
|
||||
default=False)
|
||||
notif_sent = models.BooleanField("Notification envoyée", default=False)
|
||||
|
||||
notif_time = models.DateTimeField("Moment d'envoi de la notification",
|
||||
blank=True, null=True)
|
||||
notif_time = models.DateTimeField(
|
||||
"Moment d'envoi de la notification", blank=True, null=True
|
||||
)
|
||||
|
||||
tirage_done = models.BooleanField("Tirage effectué",
|
||||
default=False)
|
||||
tirage_done = models.BooleanField("Tirage effectué", default=False)
|
||||
|
||||
shotgun = models.BooleanField("Disponible immédiatement",
|
||||
default=False)
|
||||
shotgun = models.BooleanField("Disponible immédiatement", default=False)
|
||||
####
|
||||
# Some class attributes
|
||||
###
|
||||
|
@ -282,8 +284,9 @@ class SpectacleRevente(models.Model):
|
|||
def date_tirage(self):
|
||||
"""Renvoie la date du tirage au sort de la revente."""
|
||||
|
||||
remaining_time = (self.attribution.spectacle.date
|
||||
- self.real_notif_time - self.min_margin)
|
||||
remaining_time = (
|
||||
self.attribution.spectacle.date - self.real_notif_time - self.min_margin
|
||||
)
|
||||
|
||||
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.
|
||||
"""
|
||||
spectacle_date = self.attribution.spectacle.date
|
||||
return (spectacle_date <= timezone.now() + self.min_margin
|
||||
+ self.min_wait_time)
|
||||
return spectacle_date <= timezone.now() + self.min_margin + self.min_wait_time
|
||||
|
||||
@property
|
||||
def can_notif(self):
|
||||
return (timezone.now() >= self.date + self.remorse_time)
|
||||
return timezone.now() >= self.date + self.remorse_time
|
||||
|
||||
def __str__(self):
|
||||
return "%s -- %s" % (self.seller,
|
||||
self.attribution.spectacle.title)
|
||||
return "%s -- %s" % (self.seller, self.attribution.spectacle.title)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Revente"
|
||||
|
@ -327,17 +328,19 @@ class SpectacleRevente(models.Model):
|
|||
Envoie une notification pour indiquer la mise en vente d'une place sur
|
||||
BdA-Revente à tous les intéressés.
|
||||
"""
|
||||
inscrits = self.attribution.spectacle.subscribed.select_related('user')
|
||||
datatuple = [(
|
||||
'bda-revente',
|
||||
inscrits = self.attribution.spectacle.subscribed.select_related("user")
|
||||
datatuple = [
|
||||
(
|
||||
"bda-revente",
|
||||
{
|
||||
'member': participant.user,
|
||||
'show': self.attribution.spectacle,
|
||||
'revente': self,
|
||||
'site': Site.objects.get_current()
|
||||
"member": participant.user,
|
||||
"show": self.attribution.spectacle,
|
||||
"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
|
||||
]
|
||||
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
|
||||
leur indiquer qu'il est désormais disponible au shotgun.
|
||||
"""
|
||||
inscrits = self.attribution.spectacle.subscribed.select_related('user')
|
||||
datatuple = [(
|
||||
'bda-shotgun',
|
||||
inscrits = self.attribution.spectacle.subscribed.select_related("user")
|
||||
datatuple = [
|
||||
(
|
||||
"bda-shotgun",
|
||||
{
|
||||
'member': participant.user,
|
||||
'show': self.attribution.spectacle,
|
||||
'site': Site.objects.get_current(),
|
||||
"member": participant.user,
|
||||
"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
|
||||
]
|
||||
send_mass_custom_mail(datatuple)
|
||||
|
@ -389,30 +394,33 @@ class SpectacleRevente(models.Model):
|
|||
mails = []
|
||||
|
||||
context = {
|
||||
'acheteur': winner.user,
|
||||
'vendeur': seller.user,
|
||||
'show': spectacle,
|
||||
"acheteur": winner.user,
|
||||
"vendeur": seller.user,
|
||||
"show": spectacle,
|
||||
}
|
||||
|
||||
c_mails_qs = CustomMail.objects.filter(shortname__in=[
|
||||
'bda-revente-winner', 'bda-revente-loser',
|
||||
'bda-revente-seller',
|
||||
])
|
||||
c_mails_qs = CustomMail.objects.filter(
|
||||
shortname__in=[
|
||||
"bda-revente-winner",
|
||||
"bda-revente-loser",
|
||||
"bda-revente-seller",
|
||||
]
|
||||
)
|
||||
|
||||
c_mails = {cm.shortname: cm for cm in c_mails_qs}
|
||||
|
||||
mails.append(
|
||||
c_mails['bda-revente-winner'].get_message(
|
||||
c_mails["bda-revente-winner"].get_message(
|
||||
context,
|
||||
from_email=settings.MAIL_DATA['revente']['FROM'],
|
||||
from_email=settings.MAIL_DATA["revente"]["FROM"],
|
||||
to=[winner.user.email],
|
||||
)
|
||||
)
|
||||
|
||||
mails.append(
|
||||
c_mails['bda-revente-seller'].get_message(
|
||||
c_mails["bda-revente-seller"].get_message(
|
||||
context,
|
||||
from_email=settings.MAIL_DATA['revente']['FROM'],
|
||||
from_email=settings.MAIL_DATA["revente"]["FROM"],
|
||||
to=[seller.user.email],
|
||||
reply_to=[winner.user.email],
|
||||
)
|
||||
|
@ -422,12 +430,12 @@ class SpectacleRevente(models.Model):
|
|||
for inscrit in inscrits:
|
||||
if inscrit != winner:
|
||||
new_context = dict(context)
|
||||
new_context['acheteur'] = inscrit.user
|
||||
new_context["acheteur"] = inscrit.user
|
||||
|
||||
mails.append(
|
||||
c_mails['bda-revente-loser'].get_message(
|
||||
c_mails["bda-revente-loser"].get_message(
|
||||
new_context,
|
||||
from_email=settings.MAIL_DATA['revente']['FROM'],
|
||||
from_email=settings.MAIL_DATA["revente"]["FROM"],
|
||||
to=[inscrit.user.email],
|
||||
)
|
||||
)
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<h2>Gestion des places que je revends</h2>
|
||||
{% with resell_attributions=resellform.attributions annul_reventes=annulform.reventes sold_reventes=soldform.reventes %}
|
||||
|
||||
{% if resellform.attributions %}
|
||||
{% if resell_attributions %}
|
||||
<br />
|
||||
|
||||
<h3>Places non revendues</h3>
|
||||
|
@ -29,15 +29,13 @@
|
|||
<hr />
|
||||
{% endif %}
|
||||
|
||||
{% if annul_reventes or overdue %}
|
||||
{% if annul_reventes %}
|
||||
<h3>Places en cours de revente</h3>
|
||||
<form action="" method="post">
|
||||
{% if annul_reventes %}
|
||||
<div class="bg-info text-info center-block">
|
||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||
Vous pouvez annuler les places mises en vente il y a moins d'une heure.
|
||||
Vous pouvez annuler les reventes qui n'ont pas encore trouvé preneur·se.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<div class='form-group'>
|
||||
<div class='multiple-checkbox'>
|
||||
|
@ -45,18 +43,10 @@
|
|||
{% for revente in annul_reventes %}
|
||||
<li>{{ revente.tag }} {{ revente.choice_label }}</li>
|
||||
{% endfor %}
|
||||
{% for attrib in overdue %}
|
||||
<li>
|
||||
<input type="checkbox" style="visibility:hidden">
|
||||
{{ attrib.spectacle }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% if annul_reventes %}
|
||||
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
<hr />
|
||||
|
@ -82,7 +72,7 @@
|
|||
<button type="submit" class="btn btn-primary" name="reinit">Réinitialiser</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if not resell_attributions and not annul_attributions and not overdue and not sold_reventes %}
|
||||
{% if not resell_attributions and not annul_reventes and not sold_reventes %}
|
||||
<p>Plus de reventes possibles !</p>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -7,28 +7,33 @@ from django.test import TestCase
|
|||
from django.utils import timezone
|
||||
|
||||
from bda.models import (
|
||||
Attribution, Participant, Salle, Spectacle, SpectacleRevente, Tirage,
|
||||
Attribution,
|
||||
Participant,
|
||||
Salle,
|
||||
Spectacle,
|
||||
SpectacleRevente,
|
||||
Tirage,
|
||||
)
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class SpectacleReventeTests(TestCase):
|
||||
fixtures = ['gestioncof/management/data/custommail.json']
|
||||
fixtures = ["gestioncof/management/data/custommail.json"]
|
||||
|
||||
def setUp(self):
|
||||
now = timezone.now()
|
||||
|
||||
self.t = Tirage.objects.create(
|
||||
title='Tirage',
|
||||
title="Tirage",
|
||||
ouverture=now - timedelta(days=7),
|
||||
fermeture=now - timedelta(days=3),
|
||||
active=True,
|
||||
)
|
||||
self.s = Spectacle.objects.create(
|
||||
title='Spectacle',
|
||||
title="Spectacle",
|
||||
date=now + timedelta(days=20),
|
||||
location=Salle.objects.create(name='Salle', address='Address'),
|
||||
location=Salle.objects.create(name="Salle", address="Address"),
|
||||
price=10.5,
|
||||
slots=5,
|
||||
tirage=self.t,
|
||||
|
@ -36,31 +41,28 @@ class SpectacleReventeTests(TestCase):
|
|||
)
|
||||
|
||||
self.seller = Participant.objects.create(
|
||||
user=User.objects.create(
|
||||
username='seller', email='seller@mail.net'),
|
||||
user=User.objects.create(username="seller", email="seller@mail.net"),
|
||||
tirage=self.t,
|
||||
)
|
||||
self.p1 = Participant.objects.create(
|
||||
user=User.objects.create(username='part1', email='part1@mail.net'),
|
||||
user=User.objects.create(username="part1", email="part1@mail.net"),
|
||||
tirage=self.t,
|
||||
)
|
||||
self.p2 = Participant.objects.create(
|
||||
user=User.objects.create(username='part2', email='part2@mail.net'),
|
||||
user=User.objects.create(username="part2", email="part2@mail.net"),
|
||||
tirage=self.t,
|
||||
)
|
||||
self.p3 = Participant.objects.create(
|
||||
user=User.objects.create(username='part3', email='part3@mail.net'),
|
||||
user=User.objects.create(username="part3", email="part3@mail.net"),
|
||||
tirage=self.t,
|
||||
)
|
||||
|
||||
self.attr = Attribution.objects.create(
|
||||
participant=self.seller,
|
||||
spectacle=self.s,
|
||||
participant=self.seller, spectacle=self.s
|
||||
)
|
||||
|
||||
self.rev = SpectacleRevente.objects.create(
|
||||
attribution=self.attr,
|
||||
seller=self.seller,
|
||||
attribution=self.attr, seller=self.seller
|
||||
)
|
||||
|
||||
def test_tirage(self):
|
||||
|
@ -69,7 +71,7 @@ class SpectacleReventeTests(TestCase):
|
|||
wanted_by = [self.p1, self.p2, self.p3]
|
||||
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.
|
||||
mc.return_value = self.p1
|
||||
|
||||
|
@ -87,14 +89,14 @@ class SpectacleReventeTests(TestCase):
|
|||
|
||||
self.assertEqual(len(mails), 4)
|
||||
|
||||
m_seller = mails['seller@mail.net']
|
||||
self.assertListEqual(m_seller.to, ['seller@mail.net'])
|
||||
self.assertListEqual(m_seller.reply_to, ['part1@mail.net'])
|
||||
m_seller = mails["seller@mail.net"]
|
||||
self.assertListEqual(m_seller.to, ["seller@mail.net"])
|
||||
self.assertListEqual(m_seller.reply_to, ["part1@mail.net"])
|
||||
|
||||
m_winner = mails['part1@mail.net']
|
||||
self.assertListEqual(m_winner.to, ['part1@mail.net'])
|
||||
m_winner = mails["part1@mail.net"]
|
||||
self.assertListEqual(m_winner.to, ["part1@mail.net"])
|
||||
|
||||
self.assertCountEqual(
|
||||
[mails['part2@mail.net'].to, mails['part3@mail.net'].to],
|
||||
[['part2@mail.net'], ['part3@mail.net']],
|
||||
[mails["part2@mail.net"].to, mails["part3@mail.net"].to],
|
||||
[["part2@mail.net"], ["part3@mail.net"]],
|
||||
)
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase, Client
|
||||
from django.utils import timezone
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from bda.models import (Tirage, Spectacle, Salle, CategorieSpectacle,
|
||||
SpectacleRevente, Attribution, Participant)
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from bda.models import (
|
||||
Attribution,
|
||||
CategorieSpectacle,
|
||||
Participant,
|
||||
Salle,
|
||||
Spectacle,
|
||||
SpectacleRevente,
|
||||
Tirage,
|
||||
)
|
||||
|
||||
|
||||
class TestModels(TestCase):
|
||||
|
@ -14,19 +21,29 @@ class TestModels(TestCase):
|
|||
title="Tirage test",
|
||||
appear_catalogue=True,
|
||||
ouverture=timezone.now(),
|
||||
fermeture=timezone.now()
|
||||
fermeture=timezone.now(),
|
||||
)
|
||||
self.category = CategorieSpectacle.objects.create(name="Category")
|
||||
self.location = Salle.objects.create(name="here")
|
||||
self.spectacle_soon = Spectacle.objects.create(
|
||||
title="foo", date=timezone.now()+timedelta(days=1),
|
||||
location=self.location, price=0, slots=42,
|
||||
tirage=self.tirage, listing=False, category=self.category
|
||||
title="foo",
|
||||
date=timezone.now() + timedelta(days=1),
|
||||
location=self.location,
|
||||
price=0,
|
||||
slots=42,
|
||||
tirage=self.tirage,
|
||||
listing=False,
|
||||
category=self.category,
|
||||
)
|
||||
self.spectacle_later = Spectacle.objects.create(
|
||||
title="bar", date=timezone.now()+timedelta(days=30),
|
||||
location=self.location, price=0, slots=42,
|
||||
tirage=self.tirage, listing=False, category=self.category
|
||||
title="bar",
|
||||
date=timezone.now() + timedelta(days=30),
|
||||
location=self.location,
|
||||
price=0,
|
||||
slots=42,
|
||||
tirage=self.tirage,
|
||||
listing=False,
|
||||
category=self.category,
|
||||
)
|
||||
|
||||
user_buyer = User.objects.create_user(
|
||||
|
@ -35,12 +52,8 @@ class TestModels(TestCase):
|
|||
user_seller = User.objects.create_user(
|
||||
username="bda_seller", password="testseller"
|
||||
)
|
||||
self.buyer = Participant.objects.create(
|
||||
user=user_buyer, tirage=self.tirage
|
||||
)
|
||||
self.seller = Participant.objects.create(
|
||||
user=user_seller, tirage=self.tirage
|
||||
)
|
||||
self.buyer = Participant.objects.create(user=user_buyer, tirage=self.tirage)
|
||||
self.seller = Participant.objects.create(user=user_seller, tirage=self.tirage)
|
||||
|
||||
self.attr_soon = Attribution.objects.create(
|
||||
participant=self.seller, spectacle=self.spectacle_soon
|
||||
|
@ -49,12 +62,10 @@ class TestModels(TestCase):
|
|||
participant=self.seller, spectacle=self.spectacle_later
|
||||
)
|
||||
self.revente_soon = SpectacleRevente.objects.create(
|
||||
seller=self.seller,
|
||||
attribution=self.attr_soon
|
||||
seller=self.seller, attribution=self.attr_soon
|
||||
)
|
||||
self.revente_later = SpectacleRevente.objects.create(
|
||||
seller=self.seller,
|
||||
attribution=self.attr_later
|
||||
seller=self.seller, attribution=self.attr_later
|
||||
)
|
||||
|
||||
def test_urgent(self):
|
||||
|
@ -64,6 +75,5 @@ class TestModels(TestCase):
|
|||
def test_tirage(self):
|
||||
self.revente_soon.confirmed_entry.add(self.buyer)
|
||||
|
||||
self.assertEqual(self.revente_soon.tirage(send_mails=False),
|
||||
self.buyer)
|
||||
self.assertEqual(self.revente_soon.tirage(send_mails=False), self.buyer)
|
||||
self.assertIsNone(self.revente_later.tirage(send_mails=False))
|
||||
|
|
|
@ -1,14 +1,92 @@
|
|||
import json
|
||||
from datetime import timedelta
|
||||
from unittest import mock
|
||||
from urllib.parse import urlencode
|
||||
|
||||
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 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):
|
||||
# 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(
|
||||
title="Test tirage",
|
||||
appear_catalogue=True,
|
||||
|
@ -17,82 +95,137 @@ class TestBdAViews(TestCase):
|
|||
)
|
||||
self.category = CategorieSpectacle.objects.create(name="Category")
|
||||
self.location = Salle.objects.create(name="here")
|
||||
Spectacle.objects.bulk_create([
|
||||
Spectacle.objects.bulk_create(
|
||||
[
|
||||
Spectacle(
|
||||
title="foo", date=timezone.now(), location=self.location,
|
||||
price=0, slots=42, tirage=self.tirage, listing=False,
|
||||
category=self.category
|
||||
title="foo",
|
||||
date=timezone.now(),
|
||||
location=self.location,
|
||||
price=0,
|
||||
slots=42,
|
||||
tirage=self.tirage,
|
||||
listing=False,
|
||||
category=self.category,
|
||||
),
|
||||
Spectacle(
|
||||
title="bar", date=timezone.now(), location=self.location,
|
||||
price=1, slots=142, tirage=self.tirage, listing=False,
|
||||
category=self.category
|
||||
title="bar",
|
||||
date=timezone.now(),
|
||||
location=self.location,
|
||||
price=1,
|
||||
slots=142,
|
||||
tirage=self.tirage,
|
||||
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
|
||||
title="baz",
|
||||
date=timezone.now(),
|
||||
location=self.location,
|
||||
price=2,
|
||||
slots=242,
|
||||
tirage=self.tirage,
|
||||
listing=False,
|
||||
category=self.category,
|
||||
),
|
||||
])
|
||||
|
||||
self.bda_user = User.objects.create_user(
|
||||
username="bda_user", password="bda4ever"
|
||||
]
|
||||
)
|
||||
self.bda_user.profile.is_cof = True
|
||||
self.bda_user.profile.is_buro = True
|
||||
self.bda_user.profile.save()
|
||||
|
||||
def bda_participants(self):
|
||||
"""The BdA participants views can be queried"""
|
||||
client = Client()
|
||||
def test_bda_inscriptions(self):
|
||||
# TODO: test the form
|
||||
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()
|
||||
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")
|
||||
tirage_resp = client.get("/bda/spectacles/{}".format(self.tirage.id))
|
||||
show_resp = client.get(
|
||||
"/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_tirage_unpaid(self):
|
||||
url = "/bda/spectacles/unpaid/{}".format(self.tirage.id)
|
||||
self.check_restricted_access(url, validate_user=user_is_staff)
|
||||
|
||||
def test_catalogue(self):
|
||||
"""Test the catalogue JSON API"""
|
||||
client = Client()
|
||||
def test_send_reminders(self):
|
||||
self.require_custommails()
|
||||
# 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
|
||||
resp = client.get("/bda/catalogue/list")
|
||||
def test_catalogue_api(self):
|
||||
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(
|
||||
resp.content.decode("utf-8"),
|
||||
[{"id": self.tirage.id, "title": self.tirage.title}]
|
||||
[{"id": self.tirage.id, "title": self.tirage.title}],
|
||||
)
|
||||
|
||||
# The `details` hook
|
||||
resp = client.get(
|
||||
"/bda/catalogue/details?id={}".format(self.tirage.id)
|
||||
)
|
||||
# Details
|
||||
resp = client.get(url_details)
|
||||
self.assertJSONEqual(
|
||||
resp.content.decode("utf-8"),
|
||||
{
|
||||
"categories": [{
|
||||
"id": self.category.id,
|
||||
"name": self.category.name
|
||||
}],
|
||||
"locations": [{
|
||||
"id": self.location.id,
|
||||
"name": self.location.name
|
||||
}],
|
||||
}
|
||||
"categories": [{"id": self.category.id, "name": self.category.name}],
|
||||
"locations": [{"id": self.location.id, "name": self.location.name}],
|
||||
},
|
||||
)
|
||||
|
||||
# The `descriptions` hook
|
||||
resp = client.get(
|
||||
"/bda/catalogue/descriptions?id={}".format(self.tirage.id)
|
||||
)
|
||||
# Descriptions
|
||||
resp = client.get(url_descriptions)
|
||||
raw = resp.content.decode("utf-8")
|
||||
try:
|
||||
results = json.loads(raw)
|
||||
|
@ -101,5 +234,10 @@ class TestBdAViews(TestCase):
|
|||
self.assertEqual(len(results), 3)
|
||||
self.assertEqual(
|
||||
{(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
|
||||
|
|
123
bda/urls.py
123
bda/urls.py
|
@ -1,62 +1,75 @@
|
|||
from django.conf.urls import url
|
||||
from gestioncof.decorators import buro_required
|
||||
from bda.views import SpectacleListView
|
||||
|
||||
from bda import views
|
||||
from bda.views import SpectacleListView
|
||||
from gestioncof.decorators import buro_required
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^inscription/(?P<tirage_id>\d+)$',
|
||||
url(
|
||||
r"^inscription/(?P<tirage_id>\d+)$",
|
||||
views.inscription,
|
||||
name='bda-tirage-inscription'),
|
||||
url(r'^places/(?P<tirage_id>\d+)$',
|
||||
views.places,
|
||||
name="bda-places-attribuees"),
|
||||
url(r'^etat-places/(?P<tirage_id>\d+)$',
|
||||
views.etat_places,
|
||||
name='bda-etat-places'),
|
||||
url(r'^tirage/(?P<tirage_id>\d+)$', views.tirage),
|
||||
url(r'^spectacles/(?P<tirage_id>\d+)$',
|
||||
buro_required(SpectacleListView.as_view()),
|
||||
name="bda-liste-spectacles"),
|
||||
url(r'^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$',
|
||||
views.spectacle,
|
||||
name="bda-spectacle"),
|
||||
url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$',
|
||||
views.unpaid,
|
||||
name="bda-unpaid"),
|
||||
url(r'^spectacles/autocomplete$',
|
||||
views.spectacle_autocomplete,
|
||||
name="bda-spectacle-autocomplete"),
|
||||
url(r'^participants/autocomplete$',
|
||||
views.participant_autocomplete,
|
||||
name="bda-participant-autocomplete"),
|
||||
|
||||
# Urls BdA-Revente
|
||||
|
||||
url(r'^revente/(?P<tirage_id>\d+)/manage$',
|
||||
views.revente_manage,
|
||||
name='bda-revente-manage'),
|
||||
url(r'^revente/(?P<tirage_id>\d+)/subscribe$',
|
||||
views.revente_subscribe,
|
||||
name="bda-revente-subscribe"),
|
||||
url(r'^revente/(?P<tirage_id>\d+)/tirages$',
|
||||
views.revente_tirages,
|
||||
name="bda-revente-tirages"),
|
||||
url(r'^revente/(?P<spectacle_id>\d+)/buy$',
|
||||
views.revente_buy,
|
||||
name="bda-revente-buy"),
|
||||
url(r'^revente/(?P<revente_id>\d+)/confirm$',
|
||||
views.revente_confirm,
|
||||
name='bda-revente-confirm'),
|
||||
url(r'^revente/(?P<tirage_id>\d+)/shotgun$',
|
||||
views.revente_shotgun,
|
||||
name="bda-revente-shotgun"),
|
||||
|
||||
url(r'^mails-rappel/(?P<spectacle_id>\d+)$',
|
||||
views.send_rappel,
|
||||
name="bda-rappels"
|
||||
name="bda-tirage-inscription",
|
||||
),
|
||||
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
|
||||
name='bda-descriptions'),
|
||||
url(r'^catalogue/(?P<request_type>[a-z]+)$', views.catalogue,
|
||||
name='bda-catalogue'),
|
||||
url(r"^places/(?P<tirage_id>\d+)$", views.places, name="bda-places-attribuees"),
|
||||
url(r"^etat-places/(?P<tirage_id>\d+)$", views.etat_places, name="bda-etat-places"),
|
||||
url(r"^tirage/(?P<tirage_id>\d+)$", views.tirage),
|
||||
url(
|
||||
r"^spectacles/(?P<tirage_id>\d+)$",
|
||||
buro_required(SpectacleListView.as_view()),
|
||||
name="bda-liste-spectacles",
|
||||
),
|
||||
url(
|
||||
r"^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$",
|
||||
views.spectacle,
|
||||
name="bda-spectacle",
|
||||
),
|
||||
url(r"^spectacles/unpaid/(?P<tirage_id>\d+)$", views.unpaid, name="bda-unpaid"),
|
||||
url(
|
||||
r"^spectacles/autocomplete$",
|
||||
views.spectacle_autocomplete,
|
||||
name="bda-spectacle-autocomplete",
|
||||
),
|
||||
url(
|
||||
r"^participants/autocomplete$",
|
||||
views.participant_autocomplete,
|
||||
name="bda-participant-autocomplete",
|
||||
),
|
||||
# Urls BdA-Revente
|
||||
url(
|
||||
r"^revente/(?P<tirage_id>\d+)/manage$",
|
||||
views.revente_manage,
|
||||
name="bda-revente-manage",
|
||||
),
|
||||
url(
|
||||
r"^revente/(?P<tirage_id>\d+)/subscribe$",
|
||||
views.revente_subscribe,
|
||||
name="bda-revente-subscribe",
|
||||
),
|
||||
url(
|
||||
r"^revente/(?P<tirage_id>\d+)/tirages$",
|
||||
views.revente_tirages,
|
||||
name="bda-revente-tirages",
|
||||
),
|
||||
url(
|
||||
r"^revente/(?P<spectacle_id>\d+)/buy$",
|
||||
views.revente_buy,
|
||||
name="bda-revente-buy",
|
||||
),
|
||||
url(
|
||||
r"^revente/(?P<revente_id>\d+)/confirm$",
|
||||
views.revente_confirm,
|
||||
name="bda-revente-confirm",
|
||||
),
|
||||
url(
|
||||
r"^revente/(?P<tirage_id>\d+)/shotgun$",
|
||||
views.revente_shotgun,
|
||||
name="bda-revente-shotgun",
|
||||
),
|
||||
url(r"^mails-rappel/(?P<spectacle_id>\d+)$", views.send_rappel, name="bda-rappels"),
|
||||
url(
|
||||
r"^descriptions/(?P<tirage_id>\d+)$",
|
||||
views.descriptions_spectacles,
|
||||
name="bda-descriptions",
|
||||
),
|
||||
url(r"^catalogue/(?P<request_type>[a-z]+)$", views.catalogue, name="bda-catalogue"),
|
||||
]
|
||||
|
|
639
bda/views.py
639
bda/views.py
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
|
||||
from channels.asgi import get_channel_layer
|
||||
|
||||
if "DJANGO_SETTINGS_MODULE" not in os.environ:
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
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
|
||||
|
||||
|
||||
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")
|
||||
|
||||
|
||||
BASE_DIR = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
TESTING = sys.argv[1] == 'test'
|
||||
TESTING = sys.argv[1] == "test"
|
||||
|
||||
|
||||
# Application definition
|
||||
INSTALLED_APPS = [
|
||||
'shared',
|
||||
|
||||
'gestioncof',
|
||||
|
||||
"shared",
|
||||
"gestioncof",
|
||||
# Must be before 'django.contrib.admin'.
|
||||
# https://django-autocomplete-light.readthedocs.io/en/master/install.html
|
||||
'dal',
|
||||
'dal_select2',
|
||||
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.admindocs',
|
||||
|
||||
'bda',
|
||||
'captcha',
|
||||
'django_cas_ng',
|
||||
'bootstrapform',
|
||||
'kfet',
|
||||
'kfet.open',
|
||||
'channels',
|
||||
'widget_tweaks',
|
||||
'custommail',
|
||||
'djconfig',
|
||||
'wagtail.wagtailforms',
|
||||
'wagtail.wagtailredirects',
|
||||
'wagtail.wagtailembeds',
|
||||
'wagtail.wagtailsites',
|
||||
'wagtail.wagtailusers',
|
||||
'wagtail.wagtailsnippets',
|
||||
'wagtail.wagtaildocs',
|
||||
'wagtail.wagtailimages',
|
||||
'wagtail.wagtailsearch',
|
||||
'wagtail.wagtailadmin',
|
||||
'wagtail.wagtailcore',
|
||||
'wagtail.contrib.modeladmin',
|
||||
'wagtailmenus',
|
||||
'modelcluster',
|
||||
'taggit',
|
||||
'kfet.auth',
|
||||
'kfet.cms',
|
||||
'corsheaders',
|
||||
"dal",
|
||||
"dal_select2",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.sites",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django.contrib.admin",
|
||||
"django.contrib.admindocs",
|
||||
"bda",
|
||||
"captcha",
|
||||
"django_cas_ng",
|
||||
"bootstrapform",
|
||||
"kfet",
|
||||
"kfet.open",
|
||||
"channels",
|
||||
"widget_tweaks",
|
||||
"custommail",
|
||||
"djconfig",
|
||||
"wagtail.wagtailforms",
|
||||
"wagtail.wagtailredirects",
|
||||
"wagtail.wagtailembeds",
|
||||
"wagtail.wagtailsites",
|
||||
"wagtail.wagtailusers",
|
||||
"wagtail.wagtailsnippets",
|
||||
"wagtail.wagtaildocs",
|
||||
"wagtail.wagtailimages",
|
||||
"wagtail.wagtailsearch",
|
||||
"wagtail.wagtailadmin",
|
||||
"wagtail.wagtailcore",
|
||||
"wagtail.contrib.modeladmin",
|
||||
"wagtailmenus",
|
||||
"modelcluster",
|
||||
"taggit",
|
||||
"kfet.auth",
|
||||
"kfet.cms",
|
||||
"corsheaders",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'kfet.auth.middleware.TemporaryAuthMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'djconfig.middleware.DjConfigMiddleware',
|
||||
'wagtail.wagtailcore.middleware.SiteMiddleware',
|
||||
'wagtail.wagtailredirects.middleware.RedirectMiddleware',
|
||||
"corsheaders.middleware.CorsMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.auth.middleware.SessionAuthenticationMiddleware",
|
||||
"kfet.auth.middleware.TemporaryAuthMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"djconfig.middleware.DjConfigMiddleware",
|
||||
"wagtail.wagtailcore.middleware.SiteMiddleware",
|
||||
"wagtail.wagtailredirects.middleware.RedirectMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'cof.urls'
|
||||
ROOT_URLCONF = "cof.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.template.context_processors.i18n',
|
||||
'django.template.context_processors.media',
|
||||
'django.template.context_processors.static',
|
||||
'wagtailmenus.context_processors.wagtailmenus',
|
||||
'djconfig.context_processors.config',
|
||||
'gestioncof.shared.context_processor',
|
||||
'kfet.auth.context_processors.temporary_auth',
|
||||
'kfet.context_processors.config',
|
||||
],
|
||||
},
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"django.template.context_processors.i18n",
|
||||
"django.template.context_processors.media",
|
||||
"django.template.context_processors.static",
|
||||
"wagtailmenus.context_processors.wagtailmenus",
|
||||
"djconfig.context_processors.config",
|
||||
"gestioncof.shared.context_processor",
|
||||
"kfet.auth.context_processors.temporary_auth",
|
||||
"kfet.context_processors.config",
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'NAME': DBNAME,
|
||||
'USER': DBUSER,
|
||||
'PASSWORD': DBPASSWD,
|
||||
'HOST': os.environ.get('DBHOST', 'localhost'),
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql_psycopg2",
|
||||
"NAME": DBNAME,
|
||||
"USER": DBUSER,
|
||||
"PASSWORD": DBPASSWD,
|
||||
"HOST": os.environ.get("DBHOST", "localhost"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,9 +153,9 @@ DATABASES = {
|
|||
# Internationalization
|
||||
# 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
|
||||
|
||||
|
@ -173,35 +167,35 @@ USE_TZ = True
|
|||
SITE_ID = 1
|
||||
|
||||
GRAPPELLI_ADMIN_HEADLINE = "GestioCOF"
|
||||
GRAPPELLI_ADMIN_TITLE = "<a href=\"/\">GestioCOF</a>"
|
||||
GRAPPELLI_ADMIN_TITLE = '<a href="/">GestioCOF</a>'
|
||||
|
||||
MAIL_DATA = {
|
||||
'petits_cours': {
|
||||
'FROM': "Le COF <cof@ens.fr>",
|
||||
'BCC': "archivescof@gmail.com",
|
||||
'REPLYTO': "cof@ens.fr"},
|
||||
'rappels': {
|
||||
'FROM': 'Le BdA <bda@ens.fr>',
|
||||
'REPLYTO': 'Le BdA <bda@ens.fr>'},
|
||||
'revente': {
|
||||
'FROM': 'BdA-Revente <bda-revente@ens.fr>',
|
||||
'REPLYTO': 'BdA-Revente <bda-revente@ens.fr>'},
|
||||
"petits_cours": {
|
||||
"FROM": "Le COF <cof@ens.fr>",
|
||||
"BCC": "archivescof@gmail.com",
|
||||
"REPLYTO": "cof@ens.fr",
|
||||
},
|
||||
"rappels": {"FROM": "Le BdA <bda@ens.fr>", "REPLYTO": "Le BdA <bda@ens.fr>"},
|
||||
"revente": {
|
||||
"FROM": "BdA-Revente <bda-revente@ens.fr>",
|
||||
"REPLYTO": "BdA-Revente <bda-revente@ens.fr>",
|
||||
},
|
||||
}
|
||||
|
||||
LOGIN_URL = "cof-login"
|
||||
LOGIN_REDIRECT_URL = "home"
|
||||
|
||||
CAS_SERVER_URL = 'https://cas.eleves.ens.fr/'
|
||||
CAS_VERSION = '3'
|
||||
CAS_SERVER_URL = "https://cas.eleves.ens.fr/"
|
||||
CAS_VERSION = "3"
|
||||
CAS_LOGIN_MSG = None
|
||||
CAS_IGNORE_REFERER = True
|
||||
CAS_REDIRECT_URL = '/'
|
||||
CAS_REDIRECT_URL = "/"
|
||||
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
'gestioncof.shared.COFCASBackend',
|
||||
'kfet.auth.backends.GenericBackend',
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
"gestioncof.shared.COFCASBackend",
|
||||
"kfet.auth.backends.GenericBackend",
|
||||
)
|
||||
|
||||
|
||||
|
@ -214,21 +208,16 @@ AUTHENTICATION_BACKENDS = (
|
|||
NOCAPTCHA = True
|
||||
RECAPTCHA_USE_SSL = True
|
||||
|
||||
CORS_ORIGIN_WHITELIST = (
|
||||
'bda.ens.fr',
|
||||
'www.bda.ens.fr'
|
||||
'cof.ens.fr',
|
||||
'www.cof.ens.fr',
|
||||
)
|
||||
CORS_ORIGIN_WHITELIST = ("bda.ens.fr", "www.bda.ens.fr" "cof.ens.fr", "www.cof.ens.fr")
|
||||
|
||||
# Cache settings
|
||||
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'redis_cache.RedisCache',
|
||||
'LOCATION': 'redis://:{passwd}@{host}:{port}/db'
|
||||
.format(passwd=REDIS_PASSWD, host=REDIS_HOST,
|
||||
port=REDIS_PORT, db=REDIS_DB),
|
||||
"default": {
|
||||
"BACKEND": "redis_cache.RedisCache",
|
||||
"LOCATION": "redis://:{passwd}@{host}:{port}/db".format(
|
||||
passwd=REDIS_PASSWD, host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,20 +228,25 @@ CHANNEL_LAYERS = {
|
|||
"default": {
|
||||
"BACKEND": "asgi_redis.RedisChannelLayer",
|
||||
"CONFIG": {
|
||||
"hosts": [(
|
||||
"redis://:{passwd}@{host}:{port}/{db}"
|
||||
.format(passwd=REDIS_PASSWD, host=REDIS_HOST,
|
||||
port=REDIS_PORT, db=REDIS_DB)
|
||||
)],
|
||||
"hosts": [
|
||||
(
|
||||
"redis://:{passwd}@{host}:{port}/{db}".format(
|
||||
passwd=REDIS_PASSWD,
|
||||
host=REDIS_HOST,
|
||||
port=REDIS_PORT,
|
||||
db=REDIS_DB,
|
||||
)
|
||||
)
|
||||
]
|
||||
},
|
||||
"ROUTING": "cof.routing.routing",
|
||||
}
|
||||
}
|
||||
|
||||
FORMAT_MODULE_PATH = 'cof.locale'
|
||||
FORMAT_MODULE_PATH = "cof.locale"
|
||||
|
||||
# Wagtail settings
|
||||
|
||||
WAGTAIL_SITE_NAME = 'GestioCOF'
|
||||
WAGTAIL_SITE_NAME = "GestioCOF"
|
||||
WAGTAIL_ENABLE_UPDATE_CHECK = False
|
||||
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 INSTALLED_APPS, MIDDLEWARE, TESTING
|
||||
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
DEBUG = True
|
||||
|
||||
if TESTING:
|
||||
PASSWORD_HASHERS = [
|
||||
'django.contrib.auth.hashers.MD5PasswordHasher',
|
||||
]
|
||||
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
|
||||
|
||||
|
||||
# ---
|
||||
# Apache static/media config
|
||||
# ---
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = '/srv/gestiocof/static/'
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_ROOT = "/srv/gestiocof/static/"
|
||||
|
||||
MEDIA_ROOT = '/srv/gestiocof/media/'
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = "/srv/gestiocof/media/"
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
|
||||
# ---
|
||||
# Debug tool bar
|
||||
# ---
|
||||
|
||||
|
||||
def show_toolbar(request):
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
if not TESTING:
|
||||
INSTALLED_APPS += ["debug_toolbar", "debug_panel"]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"debug_panel.middleware.DebugPanelMiddleware"
|
||||
] + MIDDLEWARE
|
||||
MIDDLEWARE = ["debug_panel.middleware.DebugPanelMiddleware"] + MIDDLEWARE
|
||||
|
||||
DEBUG_TOOLBAR_CONFIG = {
|
||||
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
|
||||
}
|
||||
DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": show_toolbar}
|
||||
|
|
|
@ -8,21 +8,16 @@ import os
|
|||
from .dev import * # NOQA
|
||||
from .dev import BASE_DIR
|
||||
|
||||
|
||||
# Use sqlite for local development
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"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
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache"
|
||||
}
|
||||
}
|
||||
CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}}
|
||||
|
||||
# Use the default in memory asgi backend for local development
|
||||
CHANNEL_LAYERS = {
|
||||
|
|
|
@ -8,21 +8,13 @@ import os
|
|||
from .common import * # NOQA
|
||||
from .common import BASE_DIR, import_secret
|
||||
|
||||
|
||||
DEBUG = False
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
"cof.ens.fr",
|
||||
"www.cof.ens.fr",
|
||||
"dev.cof.ens.fr"
|
||||
]
|
||||
ALLOWED_HOSTS = ["cof.ens.fr", "www.cof.ens.fr", "dev.cof.ens.fr"]
|
||||
|
||||
|
||||
STATIC_ROOT = os.path.join(
|
||||
os.path.dirname(os.path.dirname(BASE_DIR)),
|
||||
"public",
|
||||
"gestion",
|
||||
"static",
|
||||
os.path.dirname(os.path.dirname(BASE_DIR)), "public", "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
|
||||
SERVER_EMAIL = "root@vagrant"
|
||||
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.static import static
|
||||
from django.contrib import admin
|
||||
from django.views.generic.base import TemplateView
|
||||
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 wagtail.wagtailadmin import urls as wagtailadmin_urls
|
||||
from wagtail.wagtailcore import urls as wagtail_urls
|
||||
from wagtail.wagtaildocs import urls as wagtaildocs_urls
|
||||
|
||||
from gestioncof import views as gestioncof_views, csv_views
|
||||
from gestioncof.urls import export_patterns, petitcours_patterns, \
|
||||
surveys_patterns, events_patterns, calendar_patterns, \
|
||||
clubs_patterns
|
||||
from gestioncof import csv_views, views as gestioncof_views
|
||||
from gestioncof.autocomplete import autocomplete
|
||||
from gestioncof.urls import (
|
||||
calendar_patterns,
|
||||
clubs_patterns,
|
||||
events_patterns,
|
||||
export_patterns,
|
||||
petitcours_patterns,
|
||||
surveys_patterns,
|
||||
)
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = [
|
||||
# Page d'accueil
|
||||
url(r'^$', gestioncof_views.home, name='home'),
|
||||
url(r"^$", gestioncof_views.home, name="home"),
|
||||
# Le BdA
|
||||
url(r'^bda/', include('bda.urls')),
|
||||
url(r"^bda/", include("bda.urls")),
|
||||
# Les exports
|
||||
url(r'^export/', include(export_patterns)),
|
||||
url(r"^export/", include(export_patterns)),
|
||||
# Les petits cours
|
||||
url(r'^petitcours/', include(petitcours_patterns)),
|
||||
url(r"^petitcours/", include(petitcours_patterns)),
|
||||
# Les sondages
|
||||
url(r'^survey/', include(surveys_patterns)),
|
||||
url(r"^survey/", include(surveys_patterns)),
|
||||
# Evenements
|
||||
url(r'^event/', include(events_patterns)),
|
||||
url(r"^event/", include(events_patterns)),
|
||||
# Calendrier
|
||||
url(r'^calendar/', include(calendar_patterns)),
|
||||
url(r"^calendar/", include(calendar_patterns)),
|
||||
# Clubs
|
||||
url(r'^clubs/', include(clubs_patterns)),
|
||||
url(r"^clubs/", include(clubs_patterns)),
|
||||
# Authentification
|
||||
url(r'^cof/denied$', TemplateView.as_view(template_name='cof-denied.html'),
|
||||
name="cof-denied"),
|
||||
url(r'^cas/login$', django_cas_views.login, name="cas_login_view"),
|
||||
url(r'^cas/logout$', django_cas_views.logout),
|
||||
url(r'^outsider/login$', gestioncof_views.login_ext,
|
||||
name="ext_login_view"),
|
||||
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"),
|
||||
url(
|
||||
r"^cof/denied$",
|
||||
TemplateView.as_view(template_name="cof-denied.html"),
|
||||
name="cof-denied",
|
||||
),
|
||||
url(r"^cas/login$", django_cas_views.login, name="cas_login_view"),
|
||||
url(r"^cas/logout$", django_cas_views.logout),
|
||||
url(r"^outsider/login$", gestioncof_views.login_ext, name="ext_login_view"),
|
||||
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
|
||||
url(r'^profile$', gestioncof_views.profile,
|
||||
name='profile'),
|
||||
url(r'^outsider/password-change$', django_views.password_change,
|
||||
name='password_change'),
|
||||
url(r'^outsider/password-change-done$',
|
||||
url(r"^profile$", gestioncof_views.profile, name="profile"),
|
||||
url(
|
||||
r"^outsider/password-change$",
|
||||
django_views.password_change,
|
||||
name="password_change",
|
||||
),
|
||||
url(
|
||||
r"^outsider/password-change-done$",
|
||||
django_views.password_change_done,
|
||||
name='password_change_done'),
|
||||
name="password_change_done",
|
||||
),
|
||||
# Inscription d'un nouveau membre
|
||||
url(r'^registration$', gestioncof_views.registration,
|
||||
name='registration'),
|
||||
url(r'^registration/clipper/(?P<login_clipper>[\w-]+)/'
|
||||
r'(?P<fullname>.*)$',
|
||||
gestioncof_views.registration_form2, name="clipper-registration"),
|
||||
url(r'^registration/user/(?P<username>.+)$',
|
||||
gestioncof_views.registration_form2, name="user-registration"),
|
||||
url(r'^registration/empty$', gestioncof_views.registration_form2,
|
||||
name="empty-registration"),
|
||||
url(r"^registration$", gestioncof_views.registration, name="registration"),
|
||||
url(
|
||||
r"^registration/clipper/(?P<login_clipper>[\w-]+)/" r"(?P<fullname>.*)$",
|
||||
gestioncof_views.registration_form2,
|
||||
name="clipper-registration",
|
||||
),
|
||||
url(
|
||||
r"^registration/user/(?P<username>.+)$",
|
||||
gestioncof_views.registration_form2,
|
||||
name="user-registration",
|
||||
),
|
||||
url(
|
||||
r"^registration/empty$",
|
||||
gestioncof_views.registration_form2,
|
||||
name="empty-registration",
|
||||
),
|
||||
# Autocompletion
|
||||
url(r'^autocomplete/registration$', autocomplete,
|
||||
name="cof.registration.autocomplete"),
|
||||
url(r'^user/autocomplete$', gestioncof_views.user_autocomplete,
|
||||
name='cof-user-autocomplete'),
|
||||
url(
|
||||
r"^autocomplete/registration$",
|
||||
autocomplete,
|
||||
name="cof.registration.autocomplete",
|
||||
),
|
||||
url(
|
||||
r"^user/autocomplete$",
|
||||
gestioncof_views.user_autocomplete,
|
||||
name="cof-user-autocomplete",
|
||||
),
|
||||
# Interface admin
|
||||
url(r'^admin/logout/', gestioncof_views.logout),
|
||||
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/logout/", gestioncof_views.logout),
|
||||
url(r"^admin/doc/", include("django.contrib.admindocs.urls")),
|
||||
url(
|
||||
r"^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/",
|
||||
csv_views.admin_list_export,
|
||||
{'fields': ['username', ]}),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
{"fields": ["username"]},
|
||||
),
|
||||
url(r"^admin/", include(admin.site.urls)),
|
||||
# Liens utiles du COF et du BdA
|
||||
url(r'^utile_cof$', gestioncof_views.utile_cof,
|
||||
name='utile_cof'),
|
||||
url(r'^utile_bda$', gestioncof_views.utile_bda,
|
||||
name='utile_bda'),
|
||||
url(r'^utile_bda/bda_diff$', gestioncof_views.liste_bdadiff,
|
||||
name="ml_diffbda"),
|
||||
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof,
|
||||
name='ml_diffcof'),
|
||||
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente,
|
||||
name="ml_bda_revente"),
|
||||
url(r'^k-fet/', include('kfet.urls')),
|
||||
url(r'^cms/', include(wagtailadmin_urls)),
|
||||
url(r'^documents/', include(wagtaildocs_urls)),
|
||||
url(r"^utile_cof$", gestioncof_views.utile_cof, name="utile_cof"),
|
||||
url(r"^utile_bda$", gestioncof_views.utile_bda, name="utile_bda"),
|
||||
url(r"^utile_bda/bda_diff$", gestioncof_views.liste_bdadiff, name="ml_diffbda"),
|
||||
url(r"^utile_cof/diff_cof$", gestioncof_views.liste_diffcof, name="ml_diffcof"),
|
||||
url(
|
||||
r"^utile_bda/bda_revente$",
|
||||
gestioncof_views.liste_bdarevente,
|
||||
name="ml_bda_revente",
|
||||
),
|
||||
url(r"^k-fet/", include("kfet.urls")),
|
||||
url(r"^cms/", include(wagtailadmin_urls)),
|
||||
url(r"^documents/", include(wagtaildocs_urls)),
|
||||
# djconfig
|
||||
url(r"^config", gestioncof_views.ConfigUpdate.as_view(),
|
||||
name='config.edit'),
|
||||
url(r"^config", gestioncof_views.ConfigUpdate.as_view(), name="config.edit"),
|
||||
]
|
||||
|
||||
if 'debug_toolbar' in settings.INSTALLED_APPS:
|
||||
if "debug_toolbar" in settings.INSTALLED_APPS:
|
||||
import debug_toolbar
|
||||
urlpatterns += [
|
||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
||||
]
|
||||
|
||||
urlpatterns += [url(r"^__debug__/", include(debug_toolbar.urls))]
|
||||
|
||||
if settings.DEBUG:
|
||||
# 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.
|
||||
urlpatterns += static(settings.MEDIA_URL,
|
||||
document_root=settings.MEDIA_ROOT)
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
# Wagtail for uncatched
|
||||
urlpatterns += [
|
||||
url(r'', include(wagtail_urls)),
|
||||
]
|
||||
urlpatterns += [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.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.models import Group, Permission, User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
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,
|
||||
desc_text=str):
|
||||
def add_link_field(target_model="", field="", link_text=str, desc_text=str):
|
||||
def add_link(cls):
|
||||
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:
|
||||
return ""
|
||||
url = reverse(reverse_path, args=(link_obj.id,))
|
||||
return mark_safe("<a href='%s'>%s</a>"
|
||||
% (url, link_text(link_obj)))
|
||||
return mark_safe("<a href='%s'>%s</a>" % (url, link_text(link_obj)))
|
||||
|
||||
link.allow_tags = True
|
||||
link.short_description = desc_text(reverse_name + ' link')
|
||||
link.short_description = desc_text(reverse_name + " link")
|
||||
cls.link = link
|
||||
cls.readonly_fields =\
|
||||
list(getattr(cls, 'readonly_fields', [])) + ['link']
|
||||
cls.readonly_fields = list(getattr(cls, "readonly_fields", [])) + ["link"]
|
||||
return cls
|
||||
|
||||
return add_link
|
||||
|
||||
|
||||
|
@ -43,32 +55,28 @@ class SurveyQuestionAnswerInline(admin.TabularInline):
|
|||
model = SurveyQuestionAnswer
|
||||
|
||||
|
||||
@add_link_field(desc_text=lambda x: "Réponses",
|
||||
link_text=lambda x: "Éditer les réponses")
|
||||
@add_link_field(
|
||||
desc_text=lambda x: "Réponses", link_text=lambda x: "Éditer les réponses"
|
||||
)
|
||||
class SurveyQuestionInline(admin.TabularInline):
|
||||
model = SurveyQuestion
|
||||
|
||||
|
||||
class SurveyQuestionAdmin(admin.ModelAdmin):
|
||||
search_fields = ('survey__title', 'answer')
|
||||
inlines = [
|
||||
SurveyQuestionAnswerInline,
|
||||
]
|
||||
search_fields = ("survey__title", "answer")
|
||||
inlines = [SurveyQuestionAnswerInline]
|
||||
|
||||
|
||||
class SurveyAdmin(admin.ModelAdmin):
|
||||
search_fields = ('title', 'details')
|
||||
inlines = [
|
||||
SurveyQuestionInline,
|
||||
]
|
||||
search_fields = ("title", "details")
|
||||
inlines = [SurveyQuestionInline]
|
||||
|
||||
|
||||
class EventOptionChoiceInline(admin.TabularInline):
|
||||
model = EventOptionChoice
|
||||
|
||||
|
||||
@add_link_field(desc_text=lambda x: "Choix",
|
||||
link_text=lambda x: "Éditer les choix")
|
||||
@add_link_field(desc_text=lambda x: "Choix", link_text=lambda x: "Éditer les choix")
|
||||
class EventOptionInline(admin.TabularInline):
|
||||
model = EventOption
|
||||
|
||||
|
@ -78,18 +86,13 @@ class EventCommentFieldInline(admin.TabularInline):
|
|||
|
||||
|
||||
class EventOptionAdmin(admin.ModelAdmin):
|
||||
search_fields = ('event__title', 'name')
|
||||
inlines = [
|
||||
EventOptionChoiceInline,
|
||||
]
|
||||
search_fields = ("event__title", "name")
|
||||
inlines = [EventOptionChoiceInline]
|
||||
|
||||
|
||||
class EventAdmin(admin.ModelAdmin):
|
||||
search_fields = ('title', 'location', 'description')
|
||||
inlines = [
|
||||
EventOptionInline,
|
||||
EventCommentFieldInline,
|
||||
]
|
||||
search_fields = ("title", "location", "description")
|
||||
inlines = [EventOptionInline, EventCommentFieldInline]
|
||||
|
||||
|
||||
class CofProfileInline(admin.StackedInline):
|
||||
|
@ -98,10 +101,9 @@ class CofProfileInline(admin.StackedInline):
|
|||
|
||||
|
||||
class FkeyLookup(object):
|
||||
def __init__(self, fkeydecl, short_description=None,
|
||||
admin_order_field=None):
|
||||
self.fk, fkattrs = fkeydecl.split('__', 1)
|
||||
self.fkattrs = fkattrs.split('__')
|
||||
def __init__(self, fkeydecl, short_description=None, admin_order_field=None):
|
||||
self.fk, fkattrs = fkeydecl.split("__", 1)
|
||||
self.fkattrs = fkattrs.split("__")
|
||||
|
||||
self.short_description = short_description or self.fkattrs[-1]
|
||||
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)
|
||||
except CofProfile.DoesNotExist:
|
||||
return ""
|
||||
|
||||
getter.short_description = short_description
|
||||
getter.boolean = boolean
|
||||
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_occupation = ProfileInfo("occupation", "Occupation")
|
||||
User.profile_departement = ProfileInfo("departement", "Departement")
|
||||
User.profile_mailing_cof = ProfileInfo("mailing_cof", "ML COF", True)
|
||||
User.profile_mailing_bda = ProfileInfo("mailing_bda", "ML BdA", True)
|
||||
User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente",
|
||||
"ML BdA-R", True)
|
||||
User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente", "ML BdA-R", True)
|
||||
|
||||
|
||||
class UserProfileAdmin(UserAdmin):
|
||||
|
@ -147,7 +149,8 @@ class UserProfileAdmin(UserAdmin):
|
|||
return obj.profile.is_buro
|
||||
except CofProfile.DoesNotExist:
|
||||
return False
|
||||
is_buro.short_description = 'Membre du Buro'
|
||||
|
||||
is_buro.short_description = "Membre du Buro"
|
||||
is_buro.boolean = True
|
||||
|
||||
def is_cof(self, obj):
|
||||
|
@ -155,27 +158,33 @@ class UserProfileAdmin(UserAdmin):
|
|||
return obj.profile.is_cof
|
||||
except CofProfile.DoesNotExist:
|
||||
return False
|
||||
is_cof.short_description = 'Membre du COF'
|
||||
|
||||
is_cof.short_description = "Membre du COF"
|
||||
is_cof.boolean = True
|
||||
|
||||
list_display = (
|
||||
UserAdmin.list_display
|
||||
+ ('profile_login_clipper', 'profile_phone', 'profile_occupation',
|
||||
'profile_mailing_cof', 'profile_mailing_bda',
|
||||
'profile_mailing_bda_revente', 'is_cof', 'is_buro', )
|
||||
list_display = UserAdmin.list_display + (
|
||||
"profile_login_clipper",
|
||||
"profile_phone",
|
||||
"profile_occupation",
|
||||
"profile_mailing_cof",
|
||||
"profile_mailing_bda",
|
||||
"profile_mailing_bda_revente",
|
||||
"is_cof",
|
||||
"is_buro",
|
||||
)
|
||||
list_display_links = ('username', 'email', 'first_name', 'last_name')
|
||||
list_filter = UserAdmin.list_filter \
|
||||
+ ('profile__is_cof', 'profile__is_buro', 'profile__mailing_cof',
|
||||
'profile__mailing_bda')
|
||||
search_fields = UserAdmin.search_fields + ('profile__phone',)
|
||||
inlines = [
|
||||
CofProfileInline,
|
||||
]
|
||||
list_display_links = ("username", "email", "first_name", "last_name")
|
||||
list_filter = UserAdmin.list_filter + (
|
||||
"profile__is_cof",
|
||||
"profile__is_buro",
|
||||
"profile__mailing_cof",
|
||||
"profile__mailing_bda",
|
||||
)
|
||||
search_fields = UserAdmin.search_fields + ("profile__phone",)
|
||||
inlines = [CofProfileInline]
|
||||
|
||||
staff_fieldsets = [
|
||||
(None, {'fields': ['username', 'password']}),
|
||||
(_('Personal info'), {'fields': ['first_name', 'last_name', 'email']}),
|
||||
(None, {"fields": ["username", "password"]}),
|
||||
(_("Personal info"), {"fields": ["first_name", "last_name", "email"]}),
|
||||
]
|
||||
|
||||
def get_fieldsets(self, request, user=None):
|
||||
|
@ -184,15 +193,15 @@ class UserProfileAdmin(UserAdmin):
|
|||
return super().get_fieldsets(request, user)
|
||||
|
||||
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:
|
||||
# Si le groupe COF n'était pas déjà dans la bdd
|
||||
# On lui assigne les bonnes permissions
|
||||
perms = Permission.objects.filter(
|
||||
Q(content_type__app_label='gestioncof')
|
||||
| Q(content_type__app_label='bda')
|
||||
| (Q(content_type__app_label='auth')
|
||||
& Q(content_type__model='user')))
|
||||
Q(content_type__app_label="gestioncof")
|
||||
| Q(content_type__app_label="bda")
|
||||
| (Q(content_type__app_label="auth") & Q(content_type__model="user"))
|
||||
)
|
||||
cof_group.permissions = perms
|
||||
# On y associe les membres du Burô
|
||||
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)
|
||||
else:
|
||||
return self.username
|
||||
|
||||
|
||||
User.__str__ = user_str
|
||||
|
||||
|
||||
class EventRegistrationAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
'user': ModelSelect2(url='cof-user-autocomplete'),
|
||||
}
|
||||
widgets = {"user": ModelSelect2(url="cof-user-autocomplete")}
|
||||
|
||||
|
||||
class EventRegistrationAdmin(admin.ModelAdmin):
|
||||
form = EventRegistrationAdminForm
|
||||
|
||||
list_display = ('__str__', 'event', 'user', 'paid')
|
||||
list_filter = ('paid',)
|
||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
||||
'user__email', 'event__title')
|
||||
list_display = ("__str__", "event", "user", "paid")
|
||||
list_filter = ("paid",)
|
||||
search_fields = (
|
||||
"user__username",
|
||||
"user__first_name",
|
||||
"user__last_name",
|
||||
"user__email",
|
||||
"event__title",
|
||||
)
|
||||
|
||||
|
||||
class PetitCoursAbilityAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'matiere', 'niveau', 'agrege')
|
||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
||||
'user__email', 'matiere__name', 'niveau')
|
||||
list_filter = ('matiere', 'niveau', 'agrege')
|
||||
list_display = ("user", "matiere", "niveau", "agrege")
|
||||
search_fields = (
|
||||
"user__username",
|
||||
"user__first_name",
|
||||
"user__last_name",
|
||||
"user__email",
|
||||
"matiere__name",
|
||||
"niveau",
|
||||
)
|
||||
list_filter = ("matiere", "niveau", "agrege")
|
||||
|
||||
|
||||
class PetitCoursAttributionAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'demande', 'matiere', 'rank', )
|
||||
search_fields = ('user__username', 'matiere__name')
|
||||
list_display = ("user", "demande", "matiere", "rank")
|
||||
search_fields = ("user__username", "matiere__name")
|
||||
|
||||
|
||||
class PetitCoursAttributionCounterAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'matiere', 'count', )
|
||||
list_filter = ('matiere',)
|
||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
||||
'user__email', 'matiere__name')
|
||||
actions = ['reset', ]
|
||||
list_display = ("user", "matiere", "count")
|
||||
list_filter = ("matiere",)
|
||||
search_fields = (
|
||||
"user__username",
|
||||
"user__first_name",
|
||||
"user__last_name",
|
||||
"user__email",
|
||||
"matiere__name",
|
||||
)
|
||||
actions = ["reset"]
|
||||
actions_on_bottom = True
|
||||
|
||||
def reset(self, request, queryset):
|
||||
queryset.update(count=0)
|
||||
|
||||
reset.short_description = "Remise à zéro du compteur"
|
||||
|
||||
|
||||
class PetitCoursDemandeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'email', 'agrege_requis', 'niveau', 'created',
|
||||
'traitee', 'processed')
|
||||
list_filter = ('traitee', 'niveau')
|
||||
search_fields = ('name', 'email', 'phone', 'lieu', 'remarques')
|
||||
list_display = (
|
||||
"name",
|
||||
"email",
|
||||
"agrege_requis",
|
||||
"niveau",
|
||||
"created",
|
||||
"traitee",
|
||||
"processed",
|
||||
)
|
||||
list_filter = ("traitee", "niveau")
|
||||
search_fields = ("name", "email", "phone", "lieu", "remarques")
|
||||
|
||||
|
||||
class ClubAdminForm(forms.ModelForm):
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
respos = cleaned_data.get('respos')
|
||||
members = cleaned_data.get('membres')
|
||||
respos = cleaned_data.get("respos")
|
||||
members = cleaned_data.get("membres")
|
||||
for respo in respos.all():
|
||||
if respo not in members:
|
||||
raise forms.ValidationError(
|
||||
"Erreur : le respo %s n'est pas membre du club."
|
||||
% respo.get_full_name())
|
||||
% respo.get_full_name()
|
||||
)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class ClubAdmin(admin.ModelAdmin):
|
||||
list_display = ['name']
|
||||
list_display = ["name"]
|
||||
form = ClubAdminForm
|
||||
|
||||
|
||||
|
@ -294,7 +328,6 @@ admin.site.register(Club, ClubAdmin)
|
|||
admin.site.register(PetitCoursSubject)
|
||||
admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin)
|
||||
admin.site.register(PetitCoursAttribution, PetitCoursAttributionAdmin)
|
||||
admin.site.register(PetitCoursAttributionCounter,
|
||||
PetitCoursAttributionCounterAdmin)
|
||||
admin.site.register(PetitCoursAttributionCounter, PetitCoursAttributionCounterAdmin)
|
||||
admin.site.register(PetitCoursDemande, PetitCoursDemandeAdmin)
|
||||
admin.site.register(EventRegistration, EventRegistrationAdmin)
|
||||
|
|
|
@ -2,14 +2,16 @@ from django.apps import AppConfig
|
|||
|
||||
|
||||
class GestioncofConfig(AppConfig):
|
||||
name = 'gestioncof'
|
||||
name = "gestioncof"
|
||||
verbose_name = "Gestion des adhérents du COF"
|
||||
|
||||
def ready(self):
|
||||
from . import signals
|
||||
from . import signals # noqa
|
||||
|
||||
self.register_config()
|
||||
|
||||
def register_config(self):
|
||||
import djconfig
|
||||
from .forms import 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 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.models import CofProfile
|
||||
|
||||
|
||||
class Clipper(object):
|
||||
|
@ -19,68 +18,71 @@ class Clipper(object):
|
|||
self.clipper = clipper
|
||||
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
|
||||
def autocomplete(request):
|
||||
if "q" not in request.GET:
|
||||
raise Http404
|
||||
q = request.GET['q']
|
||||
data = {
|
||||
'q': q,
|
||||
}
|
||||
q = request.GET["q"]
|
||||
data = {"q": q}
|
||||
|
||||
queries = {}
|
||||
bits = q.split()
|
||||
|
||||
# Fetching data from User and CofProfile tables
|
||||
queries['members'] = CofProfile.objects.filter(is_cof=True)
|
||||
queries['users'] = User.objects.filter(profile__is_cof=False)
|
||||
queries["members"] = CofProfile.objects.filter(is_cof=True)
|
||||
queries["users"] = User.objects.filter(profile__is_cof=False)
|
||||
for bit in bits:
|
||||
queries['members'] = queries['members'].filter(
|
||||
queries["members"] = queries["members"].filter(
|
||||
Q(user__first_name__icontains=bit)
|
||||
| Q(user__last_name__icontains=bit)
|
||||
| Q(user__username__icontains=bit)
|
||||
| Q(login_clipper__icontains=bit))
|
||||
queries['users'] = queries['users'].filter(
|
||||
| Q(login_clipper__icontains=bit)
|
||||
)
|
||||
queries["users"] = queries["users"].filter(
|
||||
Q(first_name__icontains=bit)
|
||||
| Q(last_name__icontains=bit)
|
||||
| Q(username__icontains=bit))
|
||||
queries['members'] = queries['members'].distinct()
|
||||
queries['users'] = queries['users'].distinct()
|
||||
| Q(username__icontains=bit)
|
||||
)
|
||||
queries["members"] = queries["members"].distinct()
|
||||
queries["users"] = queries["users"].distinct()
|
||||
|
||||
# Clearing redundancies
|
||||
usernames = (
|
||||
set(queries['members'].values_list('login_clipper', flat='True'))
|
||||
| set(queries['users'].values_list('profile__login_clipper',
|
||||
flat='True'))
|
||||
usernames = set(queries["members"].values_list("login_clipper", flat="True")) | set(
|
||||
queries["users"].values_list("profile__login_clipper", flat="True")
|
||||
)
|
||||
|
||||
# Fetching data from the SPI
|
||||
if getattr(settings, 'LDAP_SERVER_URL', None):
|
||||
if getattr(settings, "LDAP_SERVER_URL", None):
|
||||
# Fetching
|
||||
ldap_query = '(&{:s})'.format(''.join(
|
||||
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=bit)
|
||||
for bit in bits if bit.isalnum()
|
||||
))
|
||||
ldap_query = "(&{:s})".format(
|
||||
"".join(
|
||||
"(|(cn=*{bit:s}*)(uid=*{bit:s}*))".format(bit=bit)
|
||||
for bit in bits
|
||||
if bit.isalnum()
|
||||
)
|
||||
)
|
||||
if ldap_query != "(&)":
|
||||
# If none of the bits were legal, we do not perform the query
|
||||
entries = None
|
||||
with Connection(settings.LDAP_SERVER_URL) as conn:
|
||||
conn.search(
|
||||
'dc=spi,dc=ens,dc=fr', ldap_query,
|
||||
attributes=['uid', 'cn']
|
||||
)
|
||||
conn.search("dc=spi,dc=ens,dc=fr", ldap_query, attributes=["uid", "cn"])
|
||||
entries = conn.entries
|
||||
# Clearing redundancies
|
||||
queries['clippers'] = [
|
||||
queries["clippers"] = [
|
||||
Clipper(entry.uid.value, entry.cn.value)
|
||||
for entry in entries
|
||||
if entry.uid.value
|
||||
and entry.uid.value not in usernames
|
||||
if entry.uid.value and entry.uid.value not in usernames
|
||||
]
|
||||
|
||||
# Resulting data
|
||||
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)
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import csv
|
||||
|
||||
from django.apps import apps
|
||||
from django.http import HttpResponse, HttpResponseForbidden
|
||||
from django.template.defaultfilters import slugify
|
||||
from django.apps import apps
|
||||
|
||||
|
||||
def export(qs, fields=None):
|
||||
model = qs.model
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename=%s.csv' \
|
||||
% slugify(model.__name__)
|
||||
response = HttpResponse(content_type="text/csv")
|
||||
response["Content-Disposition"] = "attachment; filename=%s.csv" % slugify(
|
||||
model.__name__
|
||||
)
|
||||
writer = csv.writer(response)
|
||||
# Write headers to CSV file
|
||||
if fields:
|
||||
|
@ -32,8 +34,9 @@ def export(qs, fields=None):
|
|||
return response
|
||||
|
||||
|
||||
def admin_list_export(request, model_name, app_label, queryset=None,
|
||||
fields=None, list_display=True):
|
||||
def admin_list_export(
|
||||
request, model_name, app_label, queryset=None, fields=None, list_display=True
|
||||
):
|
||||
"""
|
||||
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/',
|
||||
|
|
|
@ -5,9 +5,10 @@ def is_cof(user):
|
|||
try:
|
||||
profile = user.profile
|
||||
return profile.is_cof
|
||||
except:
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
cof_required = user_passes_test(is_cof)
|
||||
|
||||
|
||||
|
@ -15,7 +16,8 @@ def is_buro(user):
|
|||
try:
|
||||
profile = user.profile
|
||||
return profile.is_buro
|
||||
except:
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
buro_required = user_passes_test(is_buro)
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
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.widgets import CheckboxSelectMultiple, RadioSelect
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from djconfig.forms import ConfigForm
|
||||
|
||||
from gestioncof.models import CofProfile, EventCommentValue, \
|
||||
CalendarSubscription, Club
|
||||
from gestioncof.widgets import TriStateCheckbox
|
||||
|
||||
from bda.models import Spectacle
|
||||
from gestioncof.models import CalendarSubscription, Club, CofProfile, EventCommentValue
|
||||
from gestioncof.widgets import TriStateCheckbox
|
||||
|
||||
|
||||
class EventForm(forms.Form):
|
||||
|
@ -28,31 +25,33 @@ class EventForm(forms.Form):
|
|||
choices[choice.event_option.id].append(choice.id)
|
||||
all_choices = choices
|
||||
for option in event.options.all():
|
||||
choices = [(choice.id, choice.value)
|
||||
for choice in option.choices.all()]
|
||||
choices = [(choice.id, choice.value) for choice in option.choices.all()]
|
||||
if option.multi_choices:
|
||||
initial = [] if option.id not in all_choices \
|
||||
else all_choices[option.id]
|
||||
initial = [] if option.id not in all_choices else all_choices[option.id]
|
||||
field = forms.MultipleChoiceField(
|
||||
label=option.name,
|
||||
choices=choices,
|
||||
widget=CheckboxSelectMultiple,
|
||||
required=False,
|
||||
initial=initial)
|
||||
initial=initial,
|
||||
)
|
||||
else:
|
||||
initial = None if option.id not in all_choices \
|
||||
else all_choices[option.id][0]
|
||||
field = forms.ChoiceField(label=option.name,
|
||||
initial = (
|
||||
None if option.id not in all_choices else all_choices[option.id][0]
|
||||
)
|
||||
field = forms.ChoiceField(
|
||||
label=option.name,
|
||||
choices=choices,
|
||||
widget=RadioSelect,
|
||||
required=False,
|
||||
initial=initial)
|
||||
initial=initial,
|
||||
)
|
||||
field.option_id = option.id
|
||||
self.fields["option_%d" % option.id] = field
|
||||
|
||||
def choices(self):
|
||||
for name, value in self.cleaned_data.items():
|
||||
if name.startswith('option_'):
|
||||
if name.startswith("option_"):
|
||||
yield (self.fields[name].option_id, value)
|
||||
|
||||
|
||||
|
@ -69,31 +68,33 @@ class SurveyForm(forms.Form):
|
|||
else:
|
||||
answers[answer.survey_question.id].append(answer.id)
|
||||
for question in survey.questions.all():
|
||||
choices = [(answer.id, answer.answer)
|
||||
for answer in question.answers.all()]
|
||||
choices = [(answer.id, answer.answer) for answer in question.answers.all()]
|
||||
if question.multi_answers:
|
||||
initial = [] if question.id not in answers\
|
||||
else answers[question.id]
|
||||
initial = [] if question.id not in answers else answers[question.id]
|
||||
field = forms.MultipleChoiceField(
|
||||
label=question.question,
|
||||
choices=choices,
|
||||
widget=CheckboxSelectMultiple,
|
||||
required=False,
|
||||
initial=initial)
|
||||
initial=initial,
|
||||
)
|
||||
else:
|
||||
initial = None if question.id not in answers\
|
||||
else answers[question.id][0]
|
||||
field = forms.ChoiceField(label=question.question,
|
||||
initial = (
|
||||
None if question.id not in answers else answers[question.id][0]
|
||||
)
|
||||
field = forms.ChoiceField(
|
||||
label=question.question,
|
||||
choices=choices,
|
||||
widget=RadioSelect,
|
||||
required=False,
|
||||
initial=initial)
|
||||
initial=initial,
|
||||
)
|
||||
field.question_id = question.id
|
||||
self.fields["question_%d" % question.id] = field
|
||||
|
||||
def answers(self):
|
||||
for name, value in self.cleaned_data.items():
|
||||
if name.startswith('question_'):
|
||||
if name.startswith("question_"):
|
||||
yield (self.fields[name].question_id, value)
|
||||
|
||||
|
||||
|
@ -104,8 +105,7 @@ class SurveyStatusFilterForm(forms.Form):
|
|||
for question in survey.questions.all():
|
||||
for answer in question.answers.all():
|
||||
name = "question_%d_answer_%d" % (question.id, answer.id)
|
||||
if self.is_bound \
|
||||
and self.data.get(self.add_prefix(name), None):
|
||||
if self.is_bound and self.data.get(self.add_prefix(name), None):
|
||||
initial = self.data.get(self.add_prefix(name), None)
|
||||
else:
|
||||
initial = "none"
|
||||
|
@ -114,16 +114,20 @@ class SurveyStatusFilterForm(forms.Form):
|
|||
choices=[("yes", "yes"), ("no", "no"), ("none", "none")],
|
||||
widget=TriStateCheckbox,
|
||||
required=False,
|
||||
initial=initial)
|
||||
initial=initial,
|
||||
)
|
||||
field.question_id = question.id
|
||||
field.answer_id = answer.id
|
||||
self.fields[name] = field
|
||||
|
||||
def filters(self):
|
||||
for name, value in self.cleaned_data.items():
|
||||
if name.startswith('question_'):
|
||||
yield (self.fields[name].question_id,
|
||||
self.fields[name].answer_id, value)
|
||||
if name.startswith("question_"):
|
||||
yield (
|
||||
self.fields[name].question_id,
|
||||
self.fields[name].answer_id,
|
||||
value,
|
||||
)
|
||||
|
||||
|
||||
class EventStatusFilterForm(forms.Form):
|
||||
|
@ -133,8 +137,7 @@ class EventStatusFilterForm(forms.Form):
|
|||
for option in event.options.all():
|
||||
for choice in option.choices.all():
|
||||
name = "option_%d_choice_%d" % (option.id, choice.id)
|
||||
if self.is_bound \
|
||||
and self.data.get(self.add_prefix(name), None):
|
||||
if self.is_bound and self.data.get(self.add_prefix(name), None):
|
||||
initial = self.data.get(self.add_prefix(name), None)
|
||||
else:
|
||||
initial = "none"
|
||||
|
@ -143,7 +146,8 @@ class EventStatusFilterForm(forms.Form):
|
|||
choices=[("yes", "yes"), ("no", "no"), ("none", "none")],
|
||||
widget=TriStateCheckbox,
|
||||
required=False,
|
||||
initial=initial)
|
||||
initial=initial,
|
||||
)
|
||||
field.option_id = option.id
|
||||
field.choice_id = choice.id
|
||||
self.fields[name] = field
|
||||
|
@ -153,48 +157,45 @@ class EventStatusFilterForm(forms.Form):
|
|||
initial = self.data.get(self.add_prefix(name), None)
|
||||
else:
|
||||
initial = "none"
|
||||
field = forms.ChoiceField(label="Événement payé",
|
||||
choices=[("yes", "yes"), ("no", "no"),
|
||||
("none", "none")],
|
||||
field = forms.ChoiceField(
|
||||
label="Événement payé",
|
||||
choices=[("yes", "yes"), ("no", "no"), ("none", "none")],
|
||||
widget=TriStateCheckbox,
|
||||
required=False,
|
||||
initial=initial)
|
||||
initial=initial,
|
||||
)
|
||||
self.fields[name] = field
|
||||
|
||||
def filters(self):
|
||||
for name, value in self.cleaned_data.items():
|
||||
if name.startswith('option_'):
|
||||
yield (self.fields[name].option_id,
|
||||
self.fields[name].choice_id, value)
|
||||
if name.startswith("option_"):
|
||||
yield (self.fields[name].option_id, self.fields[name].choice_id, value)
|
||||
elif name == "event_has_paid":
|
||||
yield ("has_paid", None, value)
|
||||
|
||||
|
||||
class UserProfileForm(forms.ModelForm):
|
||||
first_name = forms.CharField(label=_('Prénom'), max_length=30)
|
||||
last_name = forms.CharField(label=_('Nom'), max_length=30)
|
||||
class UserForm(forms.ModelForm):
|
||||
class Meta:
|
||||
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:
|
||||
model = CofProfile
|
||||
fields = ["first_name", "last_name", "phone", "mailing_cof",
|
||||
"mailing_bda", "mailing_bda_revente"]
|
||||
fields = [
|
||||
"phone",
|
||||
"mailing_cof",
|
||||
"mailing_bda",
|
||||
"mailing_bda_revente",
|
||||
"mailing_unernestaparis",
|
||||
]
|
||||
|
||||
|
||||
class RegistrationUserForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kw):
|
||||
super().__init__(*args, **kw)
|
||||
self.fields['username'].help_text = ""
|
||||
self.fields["username"].help_text = ""
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
|
@ -205,22 +206,23 @@ class RegistrationPassUserForm(RegistrationUserForm):
|
|||
"""
|
||||
Formulaire pour changer le mot de passe d'un utilisateur.
|
||||
"""
|
||||
password1 = forms.CharField(label=_('Mot de passe'),
|
||||
widget=forms.PasswordInput)
|
||||
password2 = forms.CharField(label=_('Confirmation du 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"), widget=forms.PasswordInput
|
||||
)
|
||||
|
||||
def clean_password2(self):
|
||||
pass1 = self.cleaned_data['password1']
|
||||
pass2 = self.cleaned_data['password2']
|
||||
pass1 = self.cleaned_data["password1"]
|
||||
pass2 = self.cleaned_data["password2"]
|
||||
if pass1 and pass2:
|
||||
if pass1 != pass2:
|
||||
raise forms.ValidationError(_('Mots de passe non identiques.'))
|
||||
raise forms.ValidationError(_("Mots de passe non identiques."))
|
||||
return pass2
|
||||
|
||||
def save(self, commit=True, *args, **kwargs):
|
||||
user = super().save(commit, *args, **kwargs)
|
||||
user.set_password(self.cleaned_data['password2'])
|
||||
user.set_password(self.cleaned_data["password2"])
|
||||
if commit:
|
||||
user.save()
|
||||
return user
|
||||
|
@ -229,44 +231,62 @@ class RegistrationPassUserForm(RegistrationUserForm):
|
|||
class RegistrationProfileForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kw):
|
||||
super().__init__(*args, **kw)
|
||||
self.fields['mailing_cof'].initial = True
|
||||
self.fields['mailing_bda'].initial = True
|
||||
self.fields['mailing_bda_revente'].initial = True
|
||||
self.fields["mailing_cof"].initial = True
|
||||
self.fields["mailing_bda"].initial = True
|
||||
self.fields["mailing_bda_revente"].initial = True
|
||||
self.fields["mailing_unernestaparis"].initial = True
|
||||
|
||||
self.fields.keyOrder = [
|
||||
'login_clipper',
|
||||
'phone',
|
||||
'occupation',
|
||||
'departement',
|
||||
'is_cof',
|
||||
'type_cotiz',
|
||||
'mailing_cof',
|
||||
'mailing_bda',
|
||||
'mailing_bda_revente',
|
||||
'comments'
|
||||
"login_clipper",
|
||||
"phone",
|
||||
"occupation",
|
||||
"departement",
|
||||
"is_cof",
|
||||
"type_cotiz",
|
||||
"mailing_cof",
|
||||
"mailing_bda",
|
||||
"mailing_bda_revente",
|
||||
"mailing_unernestaparis",
|
||||
"comments",
|
||||
]
|
||||
|
||||
class Meta:
|
||||
model = CofProfile
|
||||
fields = ("login_clipper", "phone", "occupation",
|
||||
"departement", "is_cof", "type_cotiz", "mailing_cof",
|
||||
"mailing_bda", "mailing_bda_revente", "comments")
|
||||
fields = (
|
||||
"login_clipper",
|
||||
"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'),
|
||||
('paid', 'Oui payé'),)
|
||||
|
||||
STATUS_CHOICES = (
|
||||
("no", "Non"),
|
||||
("wait", "Oui mais attente paiement"),
|
||||
("paid", "Oui payé"),
|
||||
)
|
||||
|
||||
|
||||
class AdminEventForm(forms.Form):
|
||||
status = forms.ChoiceField(label="Inscription", initial="no",
|
||||
choices=STATUS_CHOICES, widget=RadioSelect)
|
||||
status = forms.ChoiceField(
|
||||
label="Inscription", initial="no", choices=STATUS_CHOICES, widget=RadioSelect
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.event = kwargs.pop("event")
|
||||
registration = kwargs.pop("current_registration", None)
|
||||
current_choices, paid = \
|
||||
(registration.options.all(), registration.paid) \
|
||||
if registration is not None else ([], None)
|
||||
current_choices, paid = (
|
||||
(registration.options.all(), registration.paid)
|
||||
if registration is not None
|
||||
else ([], None)
|
||||
)
|
||||
if paid is True:
|
||||
kwargs["initial"] = {"status": "paid"}
|
||||
elif paid is False:
|
||||
|
@ -282,66 +302,69 @@ class AdminEventForm(forms.Form):
|
|||
choices[choice.event_option.id].append(choice.id)
|
||||
all_choices = choices
|
||||
for option in self.event.options.all():
|
||||
choices = [(choice.id, choice.value)
|
||||
for choice in option.choices.all()]
|
||||
choices = [(choice.id, choice.value) for choice in option.choices.all()]
|
||||
if option.multi_choices:
|
||||
initial = [] if option.id not in all_choices\
|
||||
else all_choices[option.id]
|
||||
initial = [] if option.id not in all_choices else all_choices[option.id]
|
||||
field = forms.MultipleChoiceField(
|
||||
label=option.name,
|
||||
choices=choices,
|
||||
widget=CheckboxSelectMultiple,
|
||||
required=False,
|
||||
initial=initial)
|
||||
initial=initial,
|
||||
)
|
||||
else:
|
||||
initial = None if option.id not in all_choices\
|
||||
else all_choices[option.id][0]
|
||||
field = forms.ChoiceField(label=option.name,
|
||||
initial = (
|
||||
None if option.id not in all_choices else all_choices[option.id][0]
|
||||
)
|
||||
field = forms.ChoiceField(
|
||||
label=option.name,
|
||||
choices=choices,
|
||||
widget=RadioSelect,
|
||||
required=False,
|
||||
initial=initial)
|
||||
initial=initial,
|
||||
)
|
||||
field.option_id = option.id
|
||||
self.fields["option_%d" % option.id] = field
|
||||
for commentfield in self.event.commentfields.all():
|
||||
initial = commentfield.default
|
||||
if registration is not None:
|
||||
try:
|
||||
initial = registration.comments \
|
||||
.get(commentfield=commentfield).content
|
||||
initial = registration.comments.get(
|
||||
commentfield=commentfield
|
||||
).content
|
||||
except EventCommentValue.DoesNotExist:
|
||||
pass
|
||||
widget = forms.Textarea if commentfield.fieldtype == "text" \
|
||||
else forms.TextInput
|
||||
field = forms.CharField(label=commentfield.name,
|
||||
widget=widget,
|
||||
required=False,
|
||||
initial=initial)
|
||||
widget = (
|
||||
forms.Textarea if commentfield.fieldtype == "text" else forms.TextInput
|
||||
)
|
||||
field = forms.CharField(
|
||||
label=commentfield.name, widget=widget, required=False, initial=initial
|
||||
)
|
||||
field.comment_id = commentfield.id
|
||||
self.fields["comment_%d" % commentfield.id] = field
|
||||
|
||||
def choices(self):
|
||||
for name, value in self.cleaned_data.items():
|
||||
if name.startswith('option_'):
|
||||
if name.startswith("option_"):
|
||||
yield (self.fields[name].option_id, value)
|
||||
|
||||
def comments(self):
|
||||
for name, value in self.cleaned_data.items():
|
||||
if name.startswith('comment_'):
|
||||
if name.startswith("comment_"):
|
||||
yield (self.fields[name].comment_id, value)
|
||||
|
||||
|
||||
class BaseEventRegistrationFormset(BaseFormSet):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.events = kwargs.pop('events')
|
||||
self.current_registrations = kwargs.pop('current_registrations', None)
|
||||
self.events = kwargs.pop("events")
|
||||
self.current_registrations = kwargs.pop("current_registrations", None)
|
||||
self.extra = len(self.events)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _construct_form(self, index, **kwargs):
|
||||
kwargs['event'] = self.events[index]
|
||||
kwargs["event"] = self.events[index]
|
||||
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)
|
||||
|
||||
|
||||
|
@ -350,34 +373,36 @@ EventFormset = formset_factory(AdminEventForm, BaseEventRegistrationFormset)
|
|||
|
||||
class CalendarForm(forms.ModelForm):
|
||||
subscribe_to_events = forms.BooleanField(
|
||||
initial=True,
|
||||
label="Événements du COF",
|
||||
required=False)
|
||||
initial=True, label="Événements du COF", required=False
|
||||
)
|
||||
subscribe_to_my_shows = forms.BooleanField(
|
||||
initial=True,
|
||||
label="Les spectacles pour lesquels j'ai obtenu une place",
|
||||
required=False)
|
||||
required=False,
|
||||
)
|
||||
other_shows = forms.ModelMultipleChoiceField(
|
||||
label="Spectacles supplémentaires",
|
||||
queryset=Spectacle.objects.filter(tirage__active=True),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False)
|
||||
required=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = CalendarSubscription
|
||||
fields = ['subscribe_to_events', 'subscribe_to_my_shows',
|
||||
'other_shows']
|
||||
fields = ["subscribe_to_events", "subscribe_to_my_shows", "other_shows"]
|
||||
|
||||
|
||||
class ClubsForm(forms.Form):
|
||||
"""
|
||||
Formulaire d'inscription d'un membre à plusieurs clubs du COF.
|
||||
"""
|
||||
|
||||
clubs = forms.ModelMultipleChoiceField(
|
||||
label="Inscriptions aux clubs du COF",
|
||||
queryset=Club.objects.all(),
|
||||
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
|
||||
# ---
|
||||
|
||||
|
||||
class GestioncofConfigForm(ConfigForm):
|
||||
gestion_banner = forms.CharField(
|
||||
label=_("Announcements banner"),
|
||||
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
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import os
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
def from_json(self, filename, data_dir, klass,
|
||||
callback=lambda obj: obj):
|
||||
def from_json(self, filename, data_dir, klass, callback=lambda obj: obj):
|
||||
"""
|
||||
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
|
||||
sur chaque objet avant enregistrement.
|
||||
"""
|
||||
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)
|
||||
objects = []
|
||||
nb_new = 0
|
||||
|
@ -36,6 +35,7 @@ class MyBaseCommand(BaseCommand):
|
|||
objects.append(obj)
|
||||
nb_new += 1
|
||||
self.stdout.write("- {:d} objets créés".format(nb_new))
|
||||
self.stdout.write("- {:d} objets gardés en l'état"
|
||||
.format(len(objects)-nb_new))
|
||||
self.stdout.write(
|
||||
"- {:d} objets gardés en l'état".format(len(objects) - nb_new)
|
||||
)
|
||||
return objects
|
||||
|
|
|
@ -15,13 +15,14 @@ from django.core.management import call_command
|
|||
|
||||
from gestioncof.management.base import MyBaseCommand
|
||||
from gestioncof.petits_cours_models import (
|
||||
PetitCoursAbility, PetitCoursSubject, LEVELS_CHOICES,
|
||||
PetitCoursAttributionCounter
|
||||
LEVELS_CHOICES,
|
||||
PetitCoursAbility,
|
||||
PetitCoursAttributionCounter,
|
||||
PetitCoursSubject,
|
||||
)
|
||||
|
||||
# Où sont stockés les fichiers json
|
||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
||||
'data')
|
||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data")
|
||||
|
||||
|
||||
class Command(MyBaseCommand):
|
||||
|
@ -32,11 +33,11 @@ class Command(MyBaseCommand):
|
|||
Permet de ne pas créer l'utilisateur "root".
|
||||
"""
|
||||
parser.add_argument(
|
||||
'--no-root',
|
||||
action='store_true',
|
||||
dest='no-root',
|
||||
"--no-root",
|
||||
action="store_true",
|
||||
dest="no-root",
|
||||
default=False,
|
||||
help='Ne crée pas l\'utilisateur "root"'
|
||||
help='Ne crée pas l\'utilisateur "root"',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
@ -45,24 +46,25 @@ class Command(MyBaseCommand):
|
|||
# ---
|
||||
|
||||
# Gaulois
|
||||
gaulois = self.from_json('gaulois.json', DATA_DIR, User)
|
||||
gaulois = self.from_json("gaulois.json", DATA_DIR, User)
|
||||
for user in gaulois:
|
||||
user.profile.is_cof = True
|
||||
user.profile.save()
|
||||
|
||||
# Romains
|
||||
self.from_json('romains.json', DATA_DIR, User)
|
||||
self.from_json("romains.json", DATA_DIR, User)
|
||||
|
||||
# Root
|
||||
no_root = options.get('no-root', False)
|
||||
no_root = options.get("no-root", False)
|
||||
if not no_root:
|
||||
self.stdout.write("Création de l'utilisateur root")
|
||||
root, _ = User.objects.get_or_create(
|
||||
username='root',
|
||||
first_name='super',
|
||||
last_name='user',
|
||||
email='root@localhost')
|
||||
root.set_password('root')
|
||||
username="root",
|
||||
first_name="super",
|
||||
last_name="user",
|
||||
email="root@localhost",
|
||||
)
|
||||
root.set_password("root")
|
||||
root.is_staff = True
|
||||
root.is_superuser = True
|
||||
root.profile.is_cof = True
|
||||
|
@ -87,18 +89,17 @@ class Command(MyBaseCommand):
|
|||
# L'utilisateur est compétent dans une matière
|
||||
subject = random.choice(subjects)
|
||||
if not PetitCoursAbility.objects.filter(
|
||||
user=user,
|
||||
matiere=subject).exists():
|
||||
user=user, matiere=subject
|
||||
).exists():
|
||||
PetitCoursAbility.objects.create(
|
||||
user=user,
|
||||
matiere=subject,
|
||||
niveau=random.choice(levels),
|
||||
agrege=bool(random.randint(0, 1))
|
||||
agrege=bool(random.randint(0, 1)),
|
||||
)
|
||||
# On initialise son compteur d'attributions
|
||||
PetitCoursAttributionCounter.objects.get_or_create(
|
||||
user=user,
|
||||
matiere=subject
|
||||
user=user, matiere=subject
|
||||
)
|
||||
self.stdout.write("- {:d} inscriptions".format(nb_of_teachers))
|
||||
|
||||
|
@ -106,10 +107,10 @@ class Command(MyBaseCommand):
|
|||
# Le BdA
|
||||
# ---
|
||||
|
||||
call_command('loadbdadevdata')
|
||||
call_command("loadbdadevdata")
|
||||
|
||||
# ---
|
||||
# La K-Fêt
|
||||
# ---
|
||||
|
||||
call_command('loadkfetdevdata')
|
||||
call_command("loadkfetdevdata")
|
||||
|
|
|
@ -4,35 +4,29 @@ Import des mails de GestioCOF dans la base de donnée
|
|||
|
||||
import json
|
||||
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.core.management.base import BaseCommand
|
||||
|
||||
DATA_LOCATION = os.path.join(os.path.dirname(__file__), "..", "data", "custommail.json")
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = ("Va chercher les données mails de GestioCOF stocké au format json "
|
||||
"dans /gestioncof/management/data/custommails.json. Le format des "
|
||||
"données est celui donné par la commande :"
|
||||
" `python manage.py dumpdata custommail --natural-foreign` "
|
||||
"La bonne façon de mettre à jour ce fichier est donc de le "
|
||||
"charger à l'aide de syncmails, le faire les modifications à "
|
||||
"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 dummy_log(__):
|
||||
pass
|
||||
|
||||
def handle(self, *args, **options):
|
||||
path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(__file__)),
|
||||
'data', 'custommail.json')
|
||||
with open(path, 'r') as jsonfile:
|
||||
|
||||
# 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}
|
||||
assoc = {"types": {}, "mails": {}}
|
||||
status = {"synced": 0, "unchanged": 0}
|
||||
|
||||
for obj in mail_data:
|
||||
fields = obj['fields']
|
||||
fields = obj["fields"]
|
||||
|
||||
# Pour les trois types d'objets :
|
||||
# - On récupère les objets référencés par les clefs étrangères
|
||||
|
@ -41,47 +35,55 @@ class Command(BaseCommand):
|
|||
# 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'])
|
||||
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
|
||||
assoc["types"][obj["pk"]] = var_type
|
||||
|
||||
# Custom mails
|
||||
if obj['model'] == 'custommail.custommail':
|
||||
if obj["model"] == "custommail.custommail":
|
||||
mail = None
|
||||
try:
|
||||
mail = CustomMail.objects.get(
|
||||
shortname=fields['shortname'])
|
||||
status['unchanged'] += 1
|
||||
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
|
||||
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'])
|
||||
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']
|
||||
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)
|
||||
if verbosity:
|
||||
log("{synced:d} mails synchronized {unchanged:d} unchanged".format(**status))
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = (
|
||||
"Va chercher les données mails de GestioCOF stocké au format json "
|
||||
"dans /gestioncof/management/data/custommails.json. Le format des "
|
||||
"données est celui donné par la commande :"
|
||||
" `python manage.py dumpdata custommail --natural-foreign` "
|
||||
"La bonne façon de mettre à jour ce fichier est donc de le "
|
||||
"charger à l'aide de syncmails, le faire les modifications à "
|
||||
"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):
|
||||
load_from_file(log=self.stdout.write)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -6,14 +6,14 @@ from django.db import migrations, models
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0001_initial'),
|
||||
]
|
||||
dependencies = [("gestioncof", "0001_initial")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='petitcoursdemande',
|
||||
name='processed',
|
||||
field=models.DateTimeField(null=True, verbose_name='Date de traitement', blank=True),
|
||||
model_name="petitcoursdemande",
|
||||
name="processed",
|
||||
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):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0002_enable_unprocessed_demandes'),
|
||||
]
|
||||
dependencies = [("gestioncof", "0002_enable_unprocessed_demandes")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='image',
|
||||
field=models.ImageField(upload_to=b'imgs/events/', null=True, verbose_name=b'Image', blank=True),
|
||||
model_name="event",
|
||||
name="image",
|
||||
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")
|
||||
db_alias = schema_editor.connection.alias
|
||||
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",
|
||||
title="Bienvenue au COF",
|
||||
content="Mail de bienvenue au COF, envoyé automatiquement à " \
|
||||
+ "l'inscription.\n\n" \
|
||||
+ "Les balises {{ ... }} sont interprétées comme expliqué " \
|
||||
content="Mail de bienvenue au COF, envoyé automatiquement à "
|
||||
+ "l'inscription.\n\n"
|
||||
+ "Les balises {{ ... }} sont interprétées comme expliqué "
|
||||
+ "ci-dessous à l'envoi.",
|
||||
comments="{{ nom }} \t fullname de la personne.\n"\
|
||||
+ "{{ prenom }} \t prénom de la personne.")
|
||||
])
|
||||
comments="{{ nom }} \t fullname de la personne.\n"
|
||||
+ "{{ prenom }} \t prénom de la personne.",
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0003_event_image'),
|
||||
]
|
||||
dependencies = [("gestioncof", "0003_event_image")]
|
||||
|
||||
operations = [
|
||||
# Pas besoin de supprimer le mail lors de la migration dans l'autre
|
||||
# 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):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0004_registration_mail'),
|
||||
]
|
||||
dependencies = [("gestioncof", "0004_registration_mail")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='custommail',
|
||||
options={'verbose_name': 'Mail personnalisable', 'verbose_name_plural': 'Mails personnalisables'},
|
||||
name="custommail",
|
||||
options={
|
||||
"verbose_name": "Mail personnalisable",
|
||||
"verbose_name_plural": "Mails personnalisables",
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='eventoptionchoice',
|
||||
options={'verbose_name': 'Choix', 'verbose_name_plural': 'Choix'},
|
||||
name="eventoptionchoice",
|
||||
options={"verbose_name": "Choix", "verbose_name_plural": "Choix"},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='is_buro',
|
||||
field=models.BooleanField(default=False, verbose_name='Membre du Bur\xf4'),
|
||||
model_name="cofprofile",
|
||||
name="is_buro",
|
||||
field=models.BooleanField(default=False, verbose_name="Membre du Bur\xf4"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='num',
|
||||
field=models.IntegerField(default=0, verbose_name="Num\xe9ro d'adh\xe9rent", blank=True),
|
||||
model_name="cofprofile",
|
||||
name="num",
|
||||
field=models.IntegerField(
|
||||
default=0, verbose_name="Num\xe9ro d'adh\xe9rent", blank=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='phone',
|
||||
field=models.CharField(max_length=20, verbose_name='T\xe9l\xe9phone', blank=True),
|
||||
model_name="cofprofile",
|
||||
name="phone",
|
||||
field=models.CharField(
|
||||
max_length=20, verbose_name="T\xe9l\xe9phone", blank=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='old',
|
||||
field=models.BooleanField(default=False, verbose_name='Archiver (\xe9v\xe9nement fini)'),
|
||||
model_name="event",
|
||||
name="old",
|
||||
field=models.BooleanField(
|
||||
default=False, verbose_name="Archiver (\xe9v\xe9nement fini)"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='start_date',
|
||||
field=models.DateField(null=True, verbose_name='Date de d\xe9but', blank=True),
|
||||
model_name="event",
|
||||
name="start_date",
|
||||
field=models.DateField(
|
||||
null=True, verbose_name="Date de d\xe9but", blank=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventcommentfield',
|
||||
name='default',
|
||||
field=models.TextField(verbose_name='Valeur par d\xe9faut', blank=True),
|
||||
model_name="eventcommentfield",
|
||||
name="default",
|
||||
field=models.TextField(verbose_name="Valeur par d\xe9faut", blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventregistration',
|
||||
name='paid',
|
||||
field=models.BooleanField(default=False, verbose_name='A pay\xe9'),
|
||||
model_name="eventregistration",
|
||||
name="paid",
|
||||
field=models.BooleanField(default=False, verbose_name="A pay\xe9"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='survey',
|
||||
name='details',
|
||||
field=models.TextField(verbose_name='D\xe9tails', blank=True),
|
||||
model_name="survey",
|
||||
name="details",
|
||||
field=models.TextField(verbose_name="D\xe9tails", blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveyquestionanswer',
|
||||
name='answer',
|
||||
field=models.CharField(max_length=200, verbose_name='R\xe9ponse'),
|
||||
model_name="surveyquestionanswer",
|
||||
name="answer",
|
||||
field=models.CharField(max_length=200, verbose_name="R\xe9ponse"),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,51 +1,66 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0004_mails-rappel'),
|
||||
("bda", "0004_mails-rappel"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('gestioncof', '0005_encoding'),
|
||||
("gestioncof", "0005_encoding"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CalendarSubscription',
|
||||
name="CalendarSubscription",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True)),
|
||||
('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)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
),
|
||||
),
|
||||
("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(
|
||||
name='custommail',
|
||||
options={'verbose_name': 'Mail personnalisable',
|
||||
'verbose_name_plural': 'Mails personnalisables'},
|
||||
name="custommail",
|
||||
options={
|
||||
"verbose_name": "Mail personnalisable",
|
||||
"verbose_name_plural": "Mails personnalisables",
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='eventoptionchoice',
|
||||
options={'verbose_name': 'Choix', 'verbose_name_plural': 'Choix'},
|
||||
name="eventoptionchoice",
|
||||
options={"verbose_name": "Choix", "verbose_name_plural": "Choix"},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='end_date',
|
||||
field=models.DateTimeField(null=True, verbose_name=b'Date de fin',
|
||||
blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='start_date',
|
||||
model_name="event",
|
||||
name="end_date",
|
||||
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 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0006_add_calendar'),
|
||||
]
|
||||
dependencies = [("gestioncof", "0006_add_calendar")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='club',
|
||||
name='name',
|
||||
field=models.CharField(unique=True, max_length=200,
|
||||
verbose_name='Nom')
|
||||
model_name="club",
|
||||
name="name",
|
||||
field=models.CharField(unique=True, max_length=200, verbose_name="Nom"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='club',
|
||||
name='description',
|
||||
field=models.TextField(verbose_name='Description', blank=True)
|
||||
model_name="club",
|
||||
name="description",
|
||||
field=models.TextField(verbose_name="Description", blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='club',
|
||||
name='membres',
|
||||
field=models.ManyToManyField(related_name='clubs',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
blank=True),
|
||||
model_name="club",
|
||||
name="membres",
|
||||
field=models.ManyToManyField(
|
||||
related_name="clubs", to=settings.AUTH_USER_MODEL, blank=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='club',
|
||||
name='respos',
|
||||
field=models.ManyToManyField(related_name='clubs_geres',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
blank=True),
|
||||
model_name="club",
|
||||
name="respos",
|
||||
field=models.ManyToManyField(
|
||||
related_name="clubs_geres", to=settings.AUTH_USER_MODEL, blank=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='start_date',
|
||||
field=models.DateTimeField(null=True,
|
||||
verbose_name='Date de d\xe9but',
|
||||
blank=True),
|
||||
model_name="event",
|
||||
name="start_date",
|
||||
field=models.DateTimeField(
|
||||
null=True, verbose_name="Date de d\xe9but", blank=True
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def forwards(apps, schema_editor):
|
||||
|
@ -11,243 +11,266 @@ def forwards(apps, schema_editor):
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0007_alter_club'),
|
||||
]
|
||||
dependencies = [("gestioncof", "0007_alter_club")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='clipper',
|
||||
name='fullname',
|
||||
field=models.CharField(verbose_name='Nom complet', max_length=200),
|
||||
model_name="clipper",
|
||||
name="fullname",
|
||||
field=models.CharField(verbose_name="Nom complet", max_length=200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='clipper',
|
||||
name='username',
|
||||
field=models.CharField(verbose_name='Identifiant', max_length=20),
|
||||
model_name="clipper",
|
||||
name="username",
|
||||
field=models.CharField(verbose_name="Identifiant", max_length=20),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='comments',
|
||||
model_name="cofprofile",
|
||||
name="comments",
|
||||
field=models.TextField(
|
||||
verbose_name="Commentaires visibles par l'utilisateur",
|
||||
blank=True),
|
||||
verbose_name="Commentaires visibles par l'utilisateur", blank=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='is_cof',
|
||||
field=models.BooleanField(verbose_name='Membre du COF',
|
||||
default=False),
|
||||
model_name="cofprofile",
|
||||
name="is_cof",
|
||||
field=models.BooleanField(verbose_name="Membre du COF", default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='login_clipper',
|
||||
field=models.CharField(verbose_name='Login clipper', max_length=8,
|
||||
blank=True),
|
||||
model_name="cofprofile",
|
||||
name="login_clipper",
|
||||
field=models.CharField(
|
||||
verbose_name="Login clipper", max_length=8, blank=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='mailing_bda',
|
||||
field=models.BooleanField(verbose_name='Recevoir les mails BdA',
|
||||
default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='mailing_bda_revente',
|
||||
model_name="cofprofile",
|
||||
name="mailing_bda",
|
||||
field=models.BooleanField(
|
||||
verbose_name='Recevoir les mails de revente de places BdA',
|
||||
default=False),
|
||||
verbose_name="Recevoir les mails BdA", default=False
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='mailing_cof',
|
||||
field=models.BooleanField(verbose_name='Recevoir les mails COF',
|
||||
default=False),
|
||||
model_name="cofprofile",
|
||||
name="mailing_bda_revente",
|
||||
field=models.BooleanField(
|
||||
verbose_name="Recevoir les mails de revente de places BdA",
|
||||
default=False,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='occupation',
|
||||
field=models.CharField(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'),
|
||||
model_name="cofprofile",
|
||||
name="mailing_cof",
|
||||
field=models.BooleanField(
|
||||
verbose_name="Recevoir les mails COF", default=False
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='petits_cours_accept',
|
||||
field=models.BooleanField(verbose_name='Recevoir des petits cours',
|
||||
default=False),
|
||||
model_name="cofprofile",
|
||||
name="occupation",
|
||||
field=models.CharField(
|
||||
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(
|
||||
model_name='cofprofile',
|
||||
name='petits_cours_remarques',
|
||||
model_name="cofprofile",
|
||||
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(
|
||||
blank=True,
|
||||
verbose_name='Remarques et précisions pour les petits cours',
|
||||
default=''),
|
||||
verbose_name="Remarques et précisions pour les petits cours",
|
||||
default="",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='type_cotiz',
|
||||
model_name="cofprofile",
|
||||
name="type_cotiz",
|
||||
field=models.CharField(
|
||||
verbose_name='Type de cotisation',
|
||||
choices=[('etudiant', 'Normalien étudiant'),
|
||||
('normalien', 'Normalien élève'),
|
||||
('exterieur', 'Extérieur')],
|
||||
max_length=9, default='normalien'),
|
||||
verbose_name="Type de cotisation",
|
||||
choices=[
|
||||
("etudiant", "Normalien étudiant"),
|
||||
("normalien", "Normalien élève"),
|
||||
("exterieur", "Extérieur"),
|
||||
],
|
||||
max_length=9,
|
||||
default="normalien",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='custommail',
|
||||
name='comments',
|
||||
model_name="custommail",
|
||||
name="comments",
|
||||
field=models.TextField(
|
||||
verbose_name='Informations contextuelles sur le mail',
|
||||
blank=True),
|
||||
verbose_name="Informations contextuelles sur le mail", blank=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='custommail',
|
||||
name='content',
|
||||
field=models.TextField(verbose_name='Contenu'),
|
||||
model_name="custommail",
|
||||
name="content",
|
||||
field=models.TextField(verbose_name="Contenu"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='custommail',
|
||||
name='title',
|
||||
field=models.CharField(verbose_name='Titre', max_length=200),
|
||||
model_name="custommail",
|
||||
name="title",
|
||||
field=models.CharField(verbose_name="Titre", max_length=200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='description',
|
||||
field=models.TextField(verbose_name='Description', blank=True),
|
||||
model_name="event",
|
||||
name="description",
|
||||
field=models.TextField(verbose_name="Description", blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='end_date',
|
||||
field=models.DateTimeField(null=True, verbose_name='Date de fin',
|
||||
blank=True),
|
||||
model_name="event",
|
||||
name="end_date",
|
||||
field=models.DateTimeField(
|
||||
null=True, verbose_name="Date de fin", blank=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='image',
|
||||
field=models.ImageField(upload_to='imgs/events/', null=True,
|
||||
verbose_name='Image', blank=True),
|
||||
model_name="event",
|
||||
name="image",
|
||||
field=models.ImageField(
|
||||
upload_to="imgs/events/", null=True, verbose_name="Image", blank=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='location',
|
||||
field=models.CharField(verbose_name='Lieu', max_length=200),
|
||||
model_name="event",
|
||||
name="location",
|
||||
field=models.CharField(verbose_name="Lieu", max_length=200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='registration_open',
|
||||
field=models.BooleanField(verbose_name='Inscriptions ouvertes',
|
||||
default=True),
|
||||
model_name="event",
|
||||
name="registration_open",
|
||||
field=models.BooleanField(
|
||||
verbose_name="Inscriptions ouvertes", default=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='title',
|
||||
field=models.CharField(verbose_name='Titre', max_length=200),
|
||||
model_name="event",
|
||||
name="title",
|
||||
field=models.CharField(verbose_name="Titre", max_length=200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventcommentfield',
|
||||
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',
|
||||
model_name="eventcommentfield",
|
||||
name="fieldtype",
|
||||
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'),
|
||||
verbose_name="Type",
|
||||
choices=[("text", "Texte long"), ("char", "Texte court")],
|
||||
max_length=10,
|
||||
default="text",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='petitcoursattribution',
|
||||
name='rank',
|
||||
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(
|
||||
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"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='petitcoursattributioncounter',
|
||||
name='count',
|
||||
field=models.IntegerField(verbose_name="Nombre d'envois",
|
||||
default=0),
|
||||
model_name="petitcoursattributioncounter",
|
||||
name="count",
|
||||
field=models.IntegerField(verbose_name="Nombre d'envois", default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='petitcoursdemande',
|
||||
name='niveau',
|
||||
model_name="petitcoursdemande",
|
||||
name="niveau",
|
||||
field=models.CharField(
|
||||
verbose_name='Niveau',
|
||||
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, default=''),
|
||||
verbose_name="Niveau",
|
||||
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,
|
||||
default="",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='survey',
|
||||
name='old',
|
||||
field=models.BooleanField(verbose_name='Archiver (sondage fini)',
|
||||
default=False),
|
||||
model_name="survey",
|
||||
name="old",
|
||||
field=models.BooleanField(
|
||||
verbose_name="Archiver (sondage fini)", default=False
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='survey',
|
||||
name='survey_open',
|
||||
field=models.BooleanField(verbose_name='Sondage ouvert',
|
||||
default=True),
|
||||
model_name="survey",
|
||||
name="survey_open",
|
||||
field=models.BooleanField(verbose_name="Sondage ouvert", default=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='survey',
|
||||
name='title',
|
||||
field=models.CharField(verbose_name='Titre', max_length=200),
|
||||
model_name="survey",
|
||||
name="title",
|
||||
field=models.CharField(verbose_name="Titre", max_length=200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveyquestion',
|
||||
name='multi_answers',
|
||||
field=models.BooleanField(verbose_name='Choix multiples',
|
||||
default=False),
|
||||
model_name="surveyquestion",
|
||||
name="multi_answers",
|
||||
field=models.BooleanField(verbose_name="Choix multiples", default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveyquestion',
|
||||
name='question',
|
||||
field=models.CharField(verbose_name='Question', max_length=200),
|
||||
model_name="surveyquestion",
|
||||
name="question",
|
||||
field=models.CharField(verbose_name="Question", max_length=200),
|
||||
),
|
||||
migrations.RunPython(forwards, migrations.RunPython.noop),
|
||||
]
|
||||
|
|
|
@ -6,12 +6,6 @@ from django.db import migrations, models
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0008_py3'),
|
||||
]
|
||||
dependencies = [("gestioncof", "0008_py3")]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='Clipper',
|
||||
),
|
||||
]
|
||||
operations = [migrations.DeleteModel(name="Clipper")]
|
||||
|
|
|
@ -5,12 +5,6 @@ from django.db import migrations
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0009_delete_clipper'),
|
||||
]
|
||||
dependencies = [("gestioncof", "0009_delete_clipper")]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='CustomMail',
|
||||
),
|
||||
]
|
||||
operations = [migrations.DeleteModel(name="CustomMail")]
|
||||
|
|
|
@ -6,14 +6,14 @@ from django.db import migrations, models
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0010_delete_custommail'),
|
||||
]
|
||||
dependencies = [("gestioncof", "0010_delete_custommail")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='login_clipper',
|
||||
field=models.CharField(verbose_name='Login clipper', blank=True, max_length=32),
|
||||
model_name="cofprofile",
|
||||
name="login_clipper",
|
||||
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):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0010_delete_custommail'),
|
||||
]
|
||||
dependencies = [("gestioncof", "0010_delete_custommail")]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='cofprofile',
|
||||
name='num',
|
||||
),
|
||||
]
|
||||
operations = [migrations.RemoveField(model_name="cofprofile", name="num")]
|
||||
|
|
|
@ -7,9 +7,8 @@ from django.db import migrations, models
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0011_remove_cofprofile_num'),
|
||||
('gestioncof', '0011_longer_clippers'),
|
||||
("gestioncof", "0011_remove_cofprofile_num"),
|
||||
("gestioncof", "0011_longer_clippers"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -6,42 +6,42 @@ from django.db import migrations, models
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0012_merge'),
|
||||
]
|
||||
dependencies = [("gestioncof", "0012_merge")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='occupation',
|
||||
model_name="cofprofile",
|
||||
name="occupation",
|
||||
field=models.CharField(
|
||||
verbose_name='Occupation',
|
||||
verbose_name="Occupation",
|
||||
max_length=9,
|
||||
default='1A',
|
||||
default="1A",
|
||||
choices=[
|
||||
('exterieur', 'Extérieur'),
|
||||
('1A', '1A'),
|
||||
('2A', '2A'),
|
||||
('3A', '3A'),
|
||||
('4A', '4A'),
|
||||
('archicube', 'Archicube'),
|
||||
('doctorant', 'Doctorant'),
|
||||
('CST', 'CST'),
|
||||
('PEI', 'PEI')
|
||||
]),
|
||||
("exterieur", "Extérieur"),
|
||||
("1A", "1A"),
|
||||
("2A", "2A"),
|
||||
("3A", "3A"),
|
||||
("4A", "4A"),
|
||||
("archicube", "Archicube"),
|
||||
("doctorant", "Doctorant"),
|
||||
("CST", "CST"),
|
||||
("PEI", "PEI"),
|
||||
],
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cofprofile',
|
||||
name='type_cotiz',
|
||||
model_name="cofprofile",
|
||||
name="type_cotiz",
|
||||
field=models.CharField(
|
||||
verbose_name='Type de cotisation',
|
||||
verbose_name="Type de cotisation",
|
||||
max_length=9,
|
||||
default='normalien',
|
||||
default="normalien",
|
||||
choices=[
|
||||
('etudiant', 'Normalien étudiant'),
|
||||
('normalien', 'Normalien élève'),
|
||||
('exterieur', 'Extérieur'),
|
||||
('gratis', 'Gratuit')
|
||||
]),
|
||||
("etudiant", "Normalien étudiant"),
|
||||
("normalien", "Normalien élève"),
|
||||
("exterieur", "Extérieur"),
|
||||
("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.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.db.models.signals import post_save, post_delete
|
||||
|
||||
from gestioncof.petits_cours_models import choices_length
|
||||
|
||||
from bda.models import Spectacle
|
||||
from gestioncof.petits_cours_models import choices_length
|
||||
|
||||
TYPE_COMMENT_FIELD = (
|
||||
('text', _("Texte long")),
|
||||
('char', _("Texte court")),
|
||||
)
|
||||
TYPE_COMMENT_FIELD = (("text", _("Texte long")), ("char", _("Texte court")))
|
||||
|
||||
|
||||
class CofProfile(models.Model):
|
||||
|
@ -49,39 +45,39 @@ class CofProfile(models.Model):
|
|||
(COTIZ_GRATIS, _("Gratuit")),
|
||||
)
|
||||
|
||||
user = models.OneToOneField(
|
||||
User, on_delete=models.CASCADE,
|
||||
related_name="profile",
|
||||
)
|
||||
login_clipper = models.CharField(
|
||||
"Login clipper", max_length=32, blank=True
|
||||
)
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
|
||||
login_clipper = models.CharField("Login clipper", max_length=32, blank=True)
|
||||
is_cof = models.BooleanField("Membre du COF", default=False)
|
||||
phone = models.CharField("Téléphone", max_length=20, blank=True)
|
||||
occupation = models.CharField(_("Occupation"),
|
||||
occupation = models.CharField(
|
||||
_("Occupation"),
|
||||
default="1A",
|
||||
choices=OCCUPATION_CHOICES,
|
||||
max_length=choices_length(
|
||||
OCCUPATION_CHOICES))
|
||||
departement = models.CharField(_("Département"), max_length=50,
|
||||
blank=True)
|
||||
type_cotiz = models.CharField(_("Type de cotisation"),
|
||||
max_length=choices_length(OCCUPATION_CHOICES),
|
||||
)
|
||||
departement = models.CharField(_("Département"), max_length=50, blank=True)
|
||||
type_cotiz = models.CharField(
|
||||
_("Type de cotisation"),
|
||||
default="normalien",
|
||||
choices=TYPE_COTIZ_CHOICES,
|
||||
max_length=choices_length(
|
||||
TYPE_COTIZ_CHOICES))
|
||||
max_length=choices_length(TYPE_COTIZ_CHOICES),
|
||||
)
|
||||
mailing_cof = models.BooleanField("Recevoir les mails COF", 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(
|
||||
"Recevoir les mails de revente de places BdA", default=False)
|
||||
comments = models.TextField(
|
||||
"Commentaires visibles par l'utilisateur", blank=True)
|
||||
"Recevoir les mails de revente de places BdA", default=False
|
||||
)
|
||||
comments = models.TextField("Commentaires visibles par l'utilisateur", blank=True)
|
||||
is_buro = models.BooleanField("Membre du Burô", default=False)
|
||||
petits_cours_accept = models.BooleanField(
|
||||
"Recevoir des petits cours", default=False)
|
||||
"Recevoir des petits cours", default=False
|
||||
)
|
||||
petits_cours_remarques = models.TextField(
|
||||
_("Remarques et précisions pour les petits cours"),
|
||||
blank=True, default="")
|
||||
_("Remarques et précisions pour les petits cours"), blank=True, default=""
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Profil COF"
|
||||
|
@ -105,8 +101,7 @@ def post_delete_user(sender, instance, *args, **kwargs):
|
|||
class Club(models.Model):
|
||||
name = models.CharField("Nom", max_length=200, unique=True)
|
||||
description = models.TextField("Description", blank=True)
|
||||
respos = models.ManyToManyField(User, related_name="clubs_geres",
|
||||
blank=True)
|
||||
respos = models.ManyToManyField(User, related_name="clubs_geres", blank=True)
|
||||
membres = models.ManyToManyField(User, related_name="clubs", blank=True)
|
||||
|
||||
def __str__(self):
|
||||
|
@ -119,10 +114,8 @@ class Event(models.Model):
|
|||
start_date = models.DateTimeField("Date de début", blank=True, null=True)
|
||||
end_date = models.DateTimeField("Date de fin", blank=True, null=True)
|
||||
description = models.TextField("Description", blank=True)
|
||||
image = models.ImageField("Image", blank=True, null=True,
|
||||
upload_to="imgs/events/")
|
||||
registration_open = models.BooleanField("Inscriptions ouvertes",
|
||||
default=True)
|
||||
image = models.ImageField("Image", blank=True, null=True, upload_to="imgs/events/")
|
||||
registration_open = models.BooleanField("Inscriptions ouvertes", default=True)
|
||||
old = models.BooleanField("Archiver (événement fini)", default=False)
|
||||
|
||||
class Meta:
|
||||
|
@ -134,12 +127,12 @@ class Event(models.Model):
|
|||
|
||||
class EventCommentField(models.Model):
|
||||
event = models.ForeignKey(
|
||||
Event, on_delete=models.CASCADE,
|
||||
related_name="commentfields",
|
||||
Event, on_delete=models.CASCADE, related_name="commentfields"
|
||||
)
|
||||
name = models.CharField("Champ", max_length=200)
|
||||
fieldtype = models.CharField("Type", max_length=10,
|
||||
choices=TYPE_COMMENT_FIELD, default="text")
|
||||
fieldtype = models.CharField(
|
||||
"Type", max_length=10, choices=TYPE_COMMENT_FIELD, default="text"
|
||||
)
|
||||
default = models.TextField("Valeur par défaut", blank=True)
|
||||
|
||||
class Meta:
|
||||
|
@ -151,12 +144,10 @@ class EventCommentField(models.Model):
|
|||
|
||||
class EventCommentValue(models.Model):
|
||||
commentfield = models.ForeignKey(
|
||||
EventCommentField, on_delete=models.CASCADE,
|
||||
related_name="values",
|
||||
EventCommentField, on_delete=models.CASCADE, related_name="values"
|
||||
)
|
||||
registration = models.ForeignKey(
|
||||
"EventRegistration", on_delete=models.CASCADE,
|
||||
related_name="comments",
|
||||
"EventRegistration", on_delete=models.CASCADE, related_name="comments"
|
||||
)
|
||||
content = models.TextField("Contenu", blank=True, null=True)
|
||||
|
||||
|
@ -165,10 +156,7 @@ class EventCommentValue(models.Model):
|
|||
|
||||
|
||||
class EventOption(models.Model):
|
||||
event = models.ForeignKey(
|
||||
Event, on_delete=models.CASCADE,
|
||||
related_name="options",
|
||||
)
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="options")
|
||||
name = models.CharField("Option", max_length=200)
|
||||
multi_choices = models.BooleanField("Choix multiples", default=False)
|
||||
|
||||
|
@ -181,8 +169,7 @@ class EventOption(models.Model):
|
|||
|
||||
class EventOptionChoice(models.Model):
|
||||
event_option = models.ForeignKey(
|
||||
EventOption, on_delete=models.CASCADE,
|
||||
related_name="choices",
|
||||
EventOption, on_delete=models.CASCADE, related_name="choices"
|
||||
)
|
||||
value = models.CharField("Valeur", max_length=200)
|
||||
|
||||
|
@ -198,8 +185,9 @@ class EventRegistration(models.Model):
|
|||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||
options = models.ManyToManyField(EventOptionChoice)
|
||||
filledcomments = models.ManyToManyField(EventCommentField,
|
||||
through=EventCommentValue)
|
||||
filledcomments = models.ManyToManyField(
|
||||
EventCommentField, through=EventCommentValue
|
||||
)
|
||||
paid = models.BooleanField("A payé", default=False)
|
||||
|
||||
class Meta:
|
||||
|
@ -225,8 +213,7 @@ class Survey(models.Model):
|
|||
|
||||
class SurveyQuestion(models.Model):
|
||||
survey = models.ForeignKey(
|
||||
Survey, on_delete=models.CASCADE,
|
||||
related_name="questions",
|
||||
Survey, on_delete=models.CASCADE, related_name="questions"
|
||||
)
|
||||
question = models.CharField("Question", max_length=200)
|
||||
multi_answers = models.BooleanField("Choix multiples", default=False)
|
||||
|
@ -240,8 +227,7 @@ class SurveyQuestion(models.Model):
|
|||
|
||||
class SurveyQuestionAnswer(models.Model):
|
||||
survey_question = models.ForeignKey(
|
||||
SurveyQuestion, on_delete=models.CASCADE,
|
||||
related_name="answers",
|
||||
SurveyQuestion, on_delete=models.CASCADE, related_name="answers"
|
||||
)
|
||||
answer = models.CharField("Réponse", max_length=200)
|
||||
|
||||
|
@ -255,8 +241,7 @@ class SurveyQuestionAnswer(models.Model):
|
|||
class SurveyAnswer(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
survey = models.ForeignKey(Survey, on_delete=models.CASCADE)
|
||||
answers = models.ManyToManyField(SurveyQuestionAnswer,
|
||||
related_name="selected_by")
|
||||
answers = models.ManyToManyField(SurveyQuestionAnswer, related_name="selected_by")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Réponses"
|
||||
|
@ -265,7 +250,8 @@ class SurveyAnswer(models.Model):
|
|||
def __str__(self):
|
||||
return "Réponse de %s sondage %s" % (
|
||||
self.user.get_full_name(),
|
||||
self.survey.title)
|
||||
self.survey.title,
|
||||
)
|
||||
|
||||
|
||||
class CalendarSubscription(models.Model):
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
from captcha.fields import ReCaptchaField
|
||||
|
||||
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.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):
|
||||
|
@ -20,33 +19,44 @@ class BaseMatieresFormSet(BaseInlineFormSet):
|
|||
form = self.forms[i]
|
||||
if not form.cleaned_data:
|
||||
continue
|
||||
matiere = form.cleaned_data['matiere']
|
||||
niveau = form.cleaned_data['niveau']
|
||||
delete = form.cleaned_data['DELETE']
|
||||
matiere = form.cleaned_data["matiere"]
|
||||
niveau = form.cleaned_data["niveau"]
|
||||
delete = form.cleaned_data["DELETE"]
|
||||
if not delete and (matiere, niveau) in matieres:
|
||||
raise forms.ValidationError(
|
||||
"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))
|
||||
|
||||
|
||||
class DemandeForm(ModelForm):
|
||||
captcha = ReCaptchaField(attrs={'theme': 'clean', 'lang': 'fr'})
|
||||
captcha = ReCaptchaField(attrs={"theme": "clean", "lang": "fr"})
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['matieres'].help_text = ''
|
||||
self.fields["matieres"].help_text = ""
|
||||
|
||||
class Meta:
|
||||
model = PetitCoursDemande
|
||||
fields = ('name', 'email', 'phone', 'quand', 'freq', 'lieu',
|
||||
'matieres', 'agrege_requis', 'niveau', 'remarques')
|
||||
widgets = {'matieres': forms.CheckboxSelectMultiple}
|
||||
fields = (
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"quand",
|
||||
"freq",
|
||||
"lieu",
|
||||
"matieres",
|
||||
"agrege_requis",
|
||||
"niveau",
|
||||
"remarques",
|
||||
)
|
||||
widgets = {"matieres": forms.CheckboxSelectMultiple}
|
||||
|
||||
|
||||
MatieresFormSet = inlineformset_factory(
|
||||
User,
|
||||
PetitCoursAbility,
|
||||
fields=("matiere", "niveau", "agrege"),
|
||||
formset=BaseMatieresFormSet
|
||||
formset=BaseMatieresFormSet,
|
||||
)
|
||||
|
|
|
@ -1,28 +1,30 @@
|
|||
from functools import reduce
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.db.models import Min
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
def choices_length(choices):
|
||||
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
|
||||
|
||||
|
||||
LEVELS_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)")),
|
||||
("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)")),
|
||||
)
|
||||
|
||||
|
||||
class PetitCoursSubject(models.Model):
|
||||
name = models.CharField(_("Matière"), max_length=30)
|
||||
users = models.ManyToManyField(User, related_name="petits_cours_matieres",
|
||||
through="PetitCoursAbility")
|
||||
users = models.ManyToManyField(
|
||||
User, related_name="petits_cours_matieres", through="PetitCoursAbility"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Matière de petits cours"
|
||||
|
@ -35,12 +37,11 @@ class PetitCoursSubject(models.Model):
|
|||
class PetitCoursAbility(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
matiere = models.ForeignKey(
|
||||
PetitCoursSubject, on_delete=models.CASCADE,
|
||||
verbose_name=_("Matière"),
|
||||
PetitCoursSubject, on_delete=models.CASCADE, 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)
|
||||
|
||||
class Meta:
|
||||
|
@ -56,41 +57,50 @@ class PetitCoursAbility(models.Model):
|
|||
class PetitCoursDemande(models.Model):
|
||||
name = models.CharField(_("Nom/prénom"), max_length=200)
|
||||
email = models.CharField(_("Adresse email"), max_length=300)
|
||||
phone = models.CharField(_("Téléphone (facultatif)"),
|
||||
max_length=20, blank=True)
|
||||
phone = models.CharField(_("Téléphone (facultatif)"), max_length=20, blank=True)
|
||||
quand = models.CharField(
|
||||
_("Quand ?"),
|
||||
help_text=_("Indiquez ici la période désirée pour les petits"
|
||||
" cours (vacances scolaires, semaine, week-end)."),
|
||||
max_length=300, blank=True)
|
||||
help_text=_(
|
||||
"Indiquez ici la période désirée pour les petits"
|
||||
" cours (vacances scolaires, semaine, week-end)."
|
||||
),
|
||||
max_length=300,
|
||||
blank=True,
|
||||
)
|
||||
freq = models.CharField(
|
||||
_("Fréquence"),
|
||||
help_text=_("Indiquez ici la fréquence envisagée "
|
||||
"(hebdomadaire, 2 fois par semaine, ...)"),
|
||||
max_length=300, blank=True)
|
||||
help_text=_(
|
||||
"Indiquez ici la fréquence envisagée "
|
||||
"(hebdomadaire, 2 fois par semaine, ...)"
|
||||
),
|
||||
max_length=300,
|
||||
blank=True,
|
||||
)
|
||||
lieu = models.CharField(
|
||||
_("Lieu (si préférence)"),
|
||||
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(
|
||||
PetitCoursSubject, verbose_name=_("Matières"),
|
||||
related_name="demandes")
|
||||
PetitCoursSubject, verbose_name=_("Matières"), related_name="demandes"
|
||||
)
|
||||
agrege_requis = models.BooleanField(_("Agrégé requis"), default=False)
|
||||
niveau = models.CharField(_("Niveau"),
|
||||
niveau = models.CharField(
|
||||
_("Niveau"),
|
||||
default="",
|
||||
choices=LEVELS_CHOICES,
|
||||
max_length=choices_length(LEVELS_CHOICES))
|
||||
max_length=choices_length(LEVELS_CHOICES),
|
||||
)
|
||||
|
||||
remarques = models.TextField(_("Remarques et précisions"), blank=True)
|
||||
|
||||
traitee = models.BooleanField(_("Traitée"), default=False)
|
||||
traitee_par = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE,
|
||||
blank=True, null=True,
|
||||
User, on_delete=models.CASCADE, blank=True, null=True
|
||||
)
|
||||
processed = models.DateTimeField(_("Date de traitement"),
|
||||
blank=True, null=True)
|
||||
processed = models.DateTimeField(_("Date de traitement"), blank=True, null=True)
|
||||
created = models.DateTimeField(_("Date de création"), auto_now_add=True)
|
||||
|
||||
def get_candidates(self, redo=False):
|
||||
|
@ -105,18 +115,15 @@ class PetitCoursDemande(models.Model):
|
|||
matiere=matiere,
|
||||
niveau=self.niveau,
|
||||
user__profile__is_cof=True,
|
||||
user__profile__petits_cours_accept=True
|
||||
user__profile__petits_cours_accept=True,
|
||||
)
|
||||
if self.agrege_requis:
|
||||
candidates = candidates.filter(agrege=True)
|
||||
if redo:
|
||||
attrs = self.petitcoursattribution_set.filter(matiere=matiere)
|
||||
already_proposed = [
|
||||
attr.user
|
||||
for attr in attrs
|
||||
]
|
||||
already_proposed = [attr.user for attr in attrs]
|
||||
candidates = candidates.exclude(user__in=already_proposed)
|
||||
candidates = candidates.order_by('?').select_related().all()
|
||||
candidates = candidates.order_by("?").select_related().all()
|
||||
yield (matiere, candidates)
|
||||
|
||||
class Meta:
|
||||
|
@ -124,25 +131,20 @@ class PetitCoursDemande(models.Model):
|
|||
verbose_name_plural = "Demandes de petits cours"
|
||||
|
||||
def __str__(self):
|
||||
return "Demande {:d} du {:s}".format(
|
||||
self.id, self.created.strftime("%d %b %Y")
|
||||
)
|
||||
return "Demande {:d} du {:s}".format(self.id, self.created.strftime("%d %b %Y"))
|
||||
|
||||
|
||||
class PetitCoursAttribution(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
demande = models.ForeignKey(
|
||||
PetitCoursDemande, on_delete=models.CASCADE,
|
||||
verbose_name=_("Demande"),
|
||||
PetitCoursDemande, on_delete=models.CASCADE, verbose_name=_("Demande")
|
||||
)
|
||||
matiere = models.ForeignKey(
|
||||
PetitCoursSubject, on_delete=models.CASCADE,
|
||||
verbose_name=_("Matière"),
|
||||
PetitCoursSubject, on_delete=models.CASCADE, verbose_name=_("Matière")
|
||||
)
|
||||
date = models.DateTimeField(_("Date d'attribution"), auto_now_add=True)
|
||||
rank = models.IntegerField("Rang dans l'email")
|
||||
selected = models.BooleanField(_("Sélectionné par le demandeur"),
|
||||
default=False)
|
||||
selected = models.BooleanField(_("Sélectionné par le demandeur"), default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Attribution de petits cours"
|
||||
|
@ -157,8 +159,7 @@ class PetitCoursAttribution(models.Model):
|
|||
class PetitCoursAttributionCounter(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
matiere = models.ForeignKey(
|
||||
PetitCoursSubject, on_delete=models.CASCADE,
|
||||
verbose_name=_("Matiere"),
|
||||
PetitCoursSubject, on_delete=models.CASCADE, verbose_name=_("Matiere")
|
||||
)
|
||||
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
|
||||
compteurs de tout le monde.
|
||||
"""
|
||||
counter, created = cls.objects.get_or_create(
|
||||
user=user,
|
||||
matiere=matiere,
|
||||
)
|
||||
counter, created = cls.objects.get_or_create(user=user, matiere=matiere)
|
||||
if created:
|
||||
mincount = (
|
||||
cls.objects.filter(matiere=matiere).exclude(user=user)
|
||||
.aggregate(Min('count'))
|
||||
['count__min']
|
||||
cls.objects.filter(matiere=matiere)
|
||||
.exclude(user=user)
|
||||
.aggregate(Min("count"))["count__min"]
|
||||
)
|
||||
counter.count = mincount or 0
|
||||
counter.save()
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
import json
|
||||
|
||||
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.contrib.auth.decorators import login_required
|
||||
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.shortcuts import get_object_or_404, redirect, render
|
||||
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.models import CofProfile
|
||||
from gestioncof.petits_cours_forms import DemandeForm, MatieresFormSet
|
||||
from gestioncof.petits_cours_models import (
|
||||
PetitCoursAbility,
|
||||
PetitCoursAttribution,
|
||||
PetitCoursAttributionCounter,
|
||||
PetitCoursDemande,
|
||||
)
|
||||
|
||||
|
||||
class DemandeListView(ListView):
|
||||
queryset = (
|
||||
PetitCoursDemande.objects
|
||||
.prefetch_related('matieres')
|
||||
.order_by('traitee', '-id')
|
||||
queryset = PetitCoursDemande.objects.prefetch_related("matieres").order_by(
|
||||
"traitee", "-id"
|
||||
)
|
||||
template_name = "petits_cours_demandes_list.html"
|
||||
paginate_by = 20
|
||||
|
@ -33,10 +33,8 @@ class DemandeListView(ListView):
|
|||
|
||||
class DemandeDetailView(DetailView):
|
||||
model = PetitCoursDemande
|
||||
queryset = (
|
||||
PetitCoursDemande.objects
|
||||
.prefetch_related('petitcoursattribution_set',
|
||||
'matieres')
|
||||
queryset = PetitCoursDemande.objects.prefetch_related(
|
||||
"petitcoursattribution_set", "matieres"
|
||||
)
|
||||
template_name = "gestioncof/details_demande_petit_cours.html"
|
||||
context_object_name = "demande"
|
||||
|
@ -44,7 +42,7 @@ class DemandeDetailView(DetailView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
obj = self.object
|
||||
context['attributions'] = obj.petitcoursattribution_set.all()
|
||||
context["attributions"] = obj.petitcoursattribution_set.all()
|
||||
return context
|
||||
|
||||
|
||||
|
@ -64,13 +62,15 @@ def traitement(request, demande_id, redo=False):
|
|||
tuples = []
|
||||
for candidate in candidates:
|
||||
user = candidate.user
|
||||
tuples.append((
|
||||
tuples.append(
|
||||
(
|
||||
candidate,
|
||||
PetitCoursAttributionCounter.get_uptodate(user, matiere)
|
||||
))
|
||||
PetitCoursAttributionCounter.get_uptodate(user, matiere),
|
||||
)
|
||||
)
|
||||
tuples = sorted(tuples, key=lambda c: c[1].count)
|
||||
candidates, _ = zip(*tuples)
|
||||
candidates = candidates[0:min(3, len(candidates))]
|
||||
candidates = candidates[0 : min(3, len(candidates))]
|
||||
attribdata[matiere.id] = []
|
||||
proposals[matiere] = []
|
||||
for candidate in candidates:
|
||||
|
@ -83,8 +83,9 @@ def traitement(request, demande_id, redo=False):
|
|||
proposed_for[user].append(matiere)
|
||||
else:
|
||||
unsatisfied.append(matiere)
|
||||
return _finalize_traitement(request, demande, proposals,
|
||||
proposed_for, unsatisfied, attribdata, redo)
|
||||
return _finalize_traitement(
|
||||
request, demande, proposals, proposed_for, unsatisfied, attribdata, redo
|
||||
)
|
||||
|
||||
|
||||
@buro_required
|
||||
|
@ -92,25 +93,38 @@ def retraitement(request, demande_id):
|
|||
return traitement(request, demande_id, redo=True)
|
||||
|
||||
|
||||
def _finalize_traitement(request, demande, proposals, proposed_for,
|
||||
unsatisfied, attribdata, redo=False, errors=None):
|
||||
def _finalize_traitement(
|
||||
request,
|
||||
demande,
|
||||
proposals,
|
||||
proposed_for,
|
||||
unsatisfied,
|
||||
attribdata,
|
||||
redo=False,
|
||||
errors=None,
|
||||
):
|
||||
proposals = proposals.items()
|
||||
proposed_for = proposed_for.items()
|
||||
attribdata = list(attribdata.items())
|
||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||
mainmail = render_custom_mail("petits-cours-mail-demandeur", {
|
||||
mainmail = render_custom_mail(
|
||||
"petits-cours-mail-demandeur",
|
||||
{
|
||||
"proposals": proposals,
|
||||
"unsatisfied": unsatisfied,
|
||||
"extra":
|
||||
'<textarea name="extra" '
|
||||
"extra": '<textarea name="extra" '
|
||||
'style="width:99%; height: 90px;">'
|
||||
'</textarea>'
|
||||
})
|
||||
"</textarea>",
|
||||
},
|
||||
)
|
||||
if errors is not None:
|
||||
for error in errors:
|
||||
messages.error(request, error)
|
||||
return render(request, "gestioncof/traitement_demande_petit_cours.html",
|
||||
{"demande": demande,
|
||||
return render(
|
||||
request,
|
||||
"gestioncof/traitement_demande_petit_cours.html",
|
||||
{
|
||||
"demande": demande,
|
||||
"unsatisfied": unsatisfied,
|
||||
"proposals": proposals,
|
||||
"proposed_for": proposed_for,
|
||||
|
@ -118,17 +132,17 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
|
|||
"mainmail": mainmail,
|
||||
"attribdata": json.dumps(attribdata),
|
||||
"redo": redo,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _generate_eleve_email(demande, proposed_for):
|
||||
return [
|
||||
(
|
||||
user,
|
||||
render_custom_mail('petit-cours-mail-eleve', {
|
||||
"demande": demande,
|
||||
"matieres": matieres
|
||||
})
|
||||
render_custom_mail(
|
||||
"petit-cours-mail-eleve", {"demande": demande, "matieres": matieres}
|
||||
),
|
||||
)
|
||||
for user, matieres in proposed_for
|
||||
]
|
||||
|
@ -143,25 +157,30 @@ def _traitement_other_preparing(request, demande):
|
|||
errors = []
|
||||
for matiere, candidates in demande.get_candidates(redo):
|
||||
if candidates:
|
||||
candidates = dict([(candidate.user.id, candidate.user)
|
||||
for candidate in candidates])
|
||||
candidates = dict(
|
||||
[(candidate.user.id, candidate.user) for candidate in candidates]
|
||||
)
|
||||
attribdata[matiere.id] = []
|
||||
proposals[matiere] = []
|
||||
for choice_id in range(min(3, len(candidates))):
|
||||
choice = int(
|
||||
request.POST["proposal-{:d}-{:d}"
|
||||
.format(matiere.id, choice_id)]
|
||||
request.POST["proposal-{:d}-{:d}".format(matiere.id, choice_id)]
|
||||
)
|
||||
if choice == -1:
|
||||
continue
|
||||
if choice not in candidates:
|
||||
errors.append("Choix invalide pour la proposition {:d}"
|
||||
"en {!s}".format(choice_id + 1, matiere))
|
||||
errors.append(
|
||||
"Choix invalide pour la proposition {:d}"
|
||||
"en {!s}".format(choice_id + 1, matiere)
|
||||
)
|
||||
continue
|
||||
user = candidates[choice]
|
||||
if user in proposals[matiere]:
|
||||
errors.append("La proposition {:d} en {!s} est un doublon"
|
||||
.format(choice_id + 1, matiere))
|
||||
errors.append(
|
||||
"La proposition {:d} en {!s} est un doublon".format(
|
||||
choice_id + 1, matiere
|
||||
)
|
||||
)
|
||||
continue
|
||||
proposals[matiere].append(user)
|
||||
attribdata[matiere.id].append(user.id)
|
||||
|
@ -172,15 +191,24 @@ def _traitement_other_preparing(request, demande):
|
|||
if not proposals[matiere]:
|
||||
errors.append("Aucune proposition pour {!s}".format(matiere))
|
||||
elif len(proposals[matiere]) < 3:
|
||||
errors.append("Seulement {:d} proposition{:s} pour {!s}"
|
||||
.format(
|
||||
errors.append(
|
||||
"Seulement {:d} proposition{:s} pour {!s}".format(
|
||||
len(proposals[matiere]),
|
||||
"s" if len(proposals[matiere]) > 1 else "",
|
||||
matiere))
|
||||
matiere,
|
||||
)
|
||||
)
|
||||
else:
|
||||
unsatisfied.append(matiere)
|
||||
return _finalize_traitement(request, demande, proposals, proposed_for,
|
||||
unsatisfied, attribdata, errors=errors)
|
||||
return _finalize_traitement(
|
||||
request,
|
||||
demande,
|
||||
proposals,
|
||||
proposed_for,
|
||||
unsatisfied,
|
||||
attribdata,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
|
||||
def _traitement_other(request, demande, redo):
|
||||
|
@ -198,10 +226,12 @@ def _traitement_other(request, demande, redo):
|
|||
tuples = []
|
||||
for candidate in candidates:
|
||||
user = candidate.user
|
||||
tuples.append((
|
||||
tuples.append(
|
||||
(
|
||||
candidate,
|
||||
PetitCoursAttributionCounter.get_uptodate(user, matiere)
|
||||
))
|
||||
PetitCoursAttributionCounter.get_uptodate(user, matiere),
|
||||
)
|
||||
)
|
||||
tuples = sorted(tuples, key=lambda c: c[1].count)
|
||||
candidates, _ = zip(*tuples)
|
||||
attribdata[matiere.id] = []
|
||||
|
@ -218,13 +248,16 @@ def _traitement_other(request, demande, redo):
|
|||
unsatisfied.append(matiere)
|
||||
proposals = proposals.items()
|
||||
proposed_for = proposed_for.items()
|
||||
return render(request,
|
||||
return render(
|
||||
request,
|
||||
"gestioncof/traitement_demande_petit_cours_autre_niveau.html",
|
||||
{"demande": demande,
|
||||
{
|
||||
"demande": demande,
|
||||
"unsatisfied": unsatisfied,
|
||||
"proposals": proposals,
|
||||
"proposed_for": proposed_for,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _traitement_post(request, demande):
|
||||
|
@ -252,24 +285,32 @@ def _traitement_post(request, demande):
|
|||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||
mainmail_object, mainmail_body = render_custom_mail(
|
||||
"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']
|
||||
bccaddress = settings.MAIL_DATA['petits_cours']['BCC']
|
||||
replyto = settings.MAIL_DATA['petits_cours']['REPLYTO']
|
||||
frommail = settings.MAIL_DATA["petits_cours"]["FROM"]
|
||||
bccaddress = settings.MAIL_DATA["petits_cours"]["BCC"]
|
||||
replyto = settings.MAIL_DATA["petits_cours"]["REPLYTO"]
|
||||
mails_to_send = []
|
||||
for (user, (mail_object, body)) in proposed_mails:
|
||||
msg = mail.EmailMessage(mail_object, body, frommail, [user.email],
|
||||
[bccaddress], headers={'Reply-To': replyto})
|
||||
mails_to_send.append(msg)
|
||||
mails_to_send.append(mail.EmailMessage(mainmail_object, mainmail_body,
|
||||
frommail, [demande.email],
|
||||
msg = mail.EmailMessage(
|
||||
mail_object,
|
||||
body,
|
||||
frommail,
|
||||
[user.email],
|
||||
[bccaddress],
|
||||
headers={'Reply-To': replyto}))
|
||||
headers={"Reply-To": replyto},
|
||||
)
|
||||
mails_to_send.append(msg)
|
||||
mails_to_send.append(
|
||||
mail.EmailMessage(
|
||||
mainmail_object,
|
||||
mainmail_body,
|
||||
frommail,
|
||||
[demande.email],
|
||||
[bccaddress],
|
||||
headers={"Reply-To": replyto},
|
||||
)
|
||||
)
|
||||
connection = mail.get_connection(fail_silently=False)
|
||||
connection.send_messages(mails_to_send)
|
||||
with transaction.atomic():
|
||||
|
@ -280,18 +321,19 @@ def _traitement_post(request, demande):
|
|||
)
|
||||
counter.count += 1
|
||||
counter.save()
|
||||
attrib = PetitCoursAttribution(user=user, matiere=matiere,
|
||||
demande=demande, rank=rank + 1)
|
||||
attrib = PetitCoursAttribution(
|
||||
user=user, matiere=matiere, demande=demande, rank=rank + 1
|
||||
)
|
||||
attrib.save()
|
||||
demande.traitee = True
|
||||
demande.traitee_par = request.user
|
||||
demande.processed = timezone.now()
|
||||
demande.save()
|
||||
return render(request,
|
||||
return render(
|
||||
request,
|
||||
"gestioncof/traitement_demande_petit_cours_success.html",
|
||||
{"demande": demande,
|
||||
"redo": redo,
|
||||
})
|
||||
{"demande": demande, "redo": redo},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -308,22 +350,25 @@ def inscription(request):
|
|||
profile.petits_cours_remarques = request.POST["remarques"]
|
||||
profile.save()
|
||||
with transaction.atomic():
|
||||
abilities = (
|
||||
PetitCoursAbility.objects.filter(user=request.user).all()
|
||||
)
|
||||
abilities = PetitCoursAbility.objects.filter(user=request.user).all()
|
||||
for ability in abilities:
|
||||
PetitCoursAttributionCounter.get_uptodate(
|
||||
ability.user,
|
||||
ability.matiere
|
||||
ability.user, ability.matiere
|
||||
)
|
||||
success = True
|
||||
formset = MatieresFormSet(instance=request.user)
|
||||
else:
|
||||
formset = MatieresFormSet(instance=request.user)
|
||||
return render(request, "inscription-petit-cours.html",
|
||||
{"formset": formset, "success": success,
|
||||
return render(
|
||||
request,
|
||||
"inscription-petit-cours.html",
|
||||
{
|
||||
"formset": formset,
|
||||
"success": success,
|
||||
"receive_proposals": profile.petits_cours_accept,
|
||||
"remarques": profile.petits_cours_remarques})
|
||||
"remarques": profile.petits_cours_remarques,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
|
@ -336,8 +381,9 @@ def demande(request):
|
|||
success = True
|
||||
else:
|
||||
form = DemandeForm()
|
||||
return render(request, "demande-petit-cours.html", {"form": form,
|
||||
"success": success})
|
||||
return render(
|
||||
request, "demande-petit-cours.html", {"form": form, "success": success}
|
||||
)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
|
@ -350,5 +396,6 @@ def demande_raw(request):
|
|||
success = True
|
||||
else:
|
||||
form = DemandeForm()
|
||||
return render(request, "demande-petit-cours-raw.html",
|
||||
{"form": form, "success": success})
|
||||
return render(
|
||||
request, "demande-petit-cours-raw.html", {"form": form, "success": success}
|
||||
)
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
from django_cas_ng.backends import CASBackend
|
||||
|
||||
from gestioncof.models import CofProfile
|
||||
|
||||
|
||||
class COFCASBackend(CASBackend):
|
||||
|
||||
def clean_username(self, username):
|
||||
# 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
|
||||
|
@ -24,9 +20,6 @@ class COFCASBackend(CASBackend):
|
|||
|
||||
|
||||
def context_processor(request):
|
||||
'''Append extra data to the context of the given request'''
|
||||
data = {
|
||||
"user": request.user,
|
||||
"site": Site.objects.get_current(),
|
||||
}
|
||||
"""Append extra data to the context of the given request"""
|
||||
data = {"user": request.user, "site": Site.objects.get_current()}
|
||||
return data
|
||||
|
|
|
@ -2,22 +2,21 @@ from django.contrib import messages
|
|||
from django.contrib.auth.signals import user_logged_in
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django_cas_ng.signals import cas_user_authenticated
|
||||
|
||||
|
||||
@receiver(user_logged_in)
|
||||
def messages_on_out_login(request, user, **kwargs):
|
||||
if user.backend.startswith('django.contrib.auth'):
|
||||
msg = _('Connexion à GestioCOF réussie. Bienvenue {}.').format(
|
||||
user.get_short_name(),
|
||||
if user.backend.startswith("django.contrib.auth"):
|
||||
msg = _("Connexion à GestioCOF réussie. Bienvenue {}.").format(
|
||||
user.get_short_name()
|
||||
)
|
||||
messages.success(request, msg)
|
||||
|
||||
|
||||
@receiver(cas_user_authenticated)
|
||||
def mesagges_on_cas_login(request, user, **kwargs):
|
||||
msg = _('Connexion à GestioCOF par CAS réussie. Bienvenue {}.').format(
|
||||
user.get_short_name(),
|
||||
msg = _("Connexion à GestioCOF par CAS réussie. Bienvenue {}.").format(
|
||||
user.get_short_name()
|
||||
)
|
||||
messages.success(request, msg)
|
||||
|
|
|
@ -5,23 +5,20 @@
|
|||
|
||||
{% block realcontent %}
|
||||
<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%;">
|
||||
{% csrf_token %}
|
||||
<fieldset"center-block">
|
||||
{% for field in form %}
|
||||
{{ field | bootstrap }}
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
{{ user_form | bootstrap }}
|
||||
{{ profile_form | bootstrap }}
|
||||
</div>
|
||||
|
||||
{% if user.profile.comments %}
|
||||
<div class="row" style="margin: 0 15%;">
|
||||
<h4>Commentaires</h4>
|
||||
<p>
|
||||
{{ user.profile.comments }}
|
||||
</p>
|
||||
<p>{{ user.profile.comments }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" class="btn btn-primary pull-right" value="Enregistrer" />
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import re
|
||||
|
||||
from django import template
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
import re
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
|
@ -12,6 +12,7 @@ def key(d, key_name):
|
|||
value = d[key_name]
|
||||
except KeyError:
|
||||
from django.conf import settings
|
||||
|
||||
value = settings.TEMPLATE_STRING_IF_INVALID
|
||||
return value
|
||||
|
||||
|
@ -19,16 +20,15 @@ def key(d, key_name):
|
|||
def highlight_text(text, q):
|
||||
q2 = "|".join(re.escape(word) for word in q.split())
|
||||
pattern = re.compile(r"(?P<filter>%s)" % q2, re.IGNORECASE)
|
||||
return mark_safe(re.sub(pattern,
|
||||
r"<span class='highlight'>\g<filter></span>",
|
||||
text))
|
||||
return mark_safe(
|
||||
re.sub(pattern, r"<span class='highlight'>\g<filter></span>", text)
|
||||
)
|
||||
|
||||
|
||||
@register.filter
|
||||
def highlight_user(user, q):
|
||||
if user.first_name and user.last_name:
|
||||
text = "%s %s (<tt>%s</tt>)" % (user.first_name, user.last_name,
|
||||
user.username)
|
||||
text = "%s %s (<tt>%s</tt>)" % (user.first_name, user.last_name, user.username)
|
||||
else:
|
||||
text = user.username
|
||||
return highlight_text(text, q)
|
||||
|
|
|
@ -12,17 +12,17 @@ from gestioncof.models import CofProfile, User
|
|||
|
||||
class SimpleTest(TestCase):
|
||||
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
|
||||
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.
|
||||
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.
|
||||
u.save()
|
||||
CofProfile.objects.get(user__username='foo').delete()
|
||||
self.assertFalse(User.objects.filter(username='foo').exists())
|
||||
CofProfile.objects.get(user__username="foo").delete()
|
||||
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 .utils import create_user, create_member, create_staff
|
||||
from .utils import create_member, create_staff, create_user
|
||||
|
||||
|
||||
class ViewTestCaseMixin(BaseViewTestCaseMixin):
|
||||
|
@ -18,7 +18,7 @@ class ViewTestCaseMixin(BaseViewTestCaseMixin):
|
|||
|
||||
def get_users_base(self):
|
||||
return {
|
||||
'user': create_user('user'),
|
||||
'member': create_member('member'),
|
||||
'staff': create_staff('staff'),
|
||||
"user": create_user("user"),
|
||||
"member": create_member("member"),
|
||||
"staff": create_staff("staff"),
|
||||
}
|
||||
|
|
|
@ -7,28 +7,35 @@ def _create_user(username, is_cof=False, is_staff=False, attrs=None):
|
|||
if attrs is None:
|
||||
attrs = {}
|
||||
|
||||
password = attrs.pop('password', username)
|
||||
password = attrs.pop("password", username)
|
||||
|
||||
user_keys = [
|
||||
'first_name', 'last_name', 'email', 'is_staff', 'is_superuser',
|
||||
]
|
||||
user_keys = ["first_name", "last_name", "email", "is_staff", "is_superuser"]
|
||||
user_attrs = {k: v for k, v in attrs.items() if k in user_keys}
|
||||
|
||||
profile_keys = [
|
||||
'is_cof', 'login_clipper', 'phone', 'occupation', 'departement',
|
||||
'type_cotiz', 'mailing_cof', 'mailing_bda', 'mailing_bda_revente',
|
||||
'comments', 'is_buro', 'petit_cours_accept',
|
||||
'petit_cours_remarques',
|
||||
"is_cof",
|
||||
"login_clipper",
|
||||
"phone",
|
||||
"occupation",
|
||||
"departement",
|
||||
"type_cotiz",
|
||||
"mailing_cof",
|
||||
"mailing_bda",
|
||||
"mailing_bda_revente",
|
||||
"comments",
|
||||
"is_buro",
|
||||
"petit_cours_accept",
|
||||
"petit_cours_remarques",
|
||||
]
|
||||
profile_attrs = {k: v for k, v in attrs.items() if k in profile_keys}
|
||||
|
||||
if is_cof:
|
||||
profile_attrs['is_cof'] = True
|
||||
profile_attrs["is_cof"] = True
|
||||
|
||||
if is_staff:
|
||||
# At the moment, admin is accessible by COF staff.
|
||||
user_attrs['is_staff'] = True
|
||||
profile_attrs['is_buro'] = True
|
||||
user_attrs["is_staff"] = True
|
||||
profile_attrs["is_buro"] = True
|
||||
|
||||
user = User(username=username, **user_attrs)
|
||||
user.set_password(password)
|
||||
|
@ -56,6 +63,6 @@ def create_staff(username, attrs=None):
|
|||
def create_root(username, attrs=None):
|
||||
if attrs is None:
|
||||
attrs = {}
|
||||
attrs.setdefault('is_staff', True)
|
||||
attrs.setdefault('is_superuser', True)
|
||||
attrs.setdefault("is_staff", True)
|
||||
attrs.setdefault("is_superuser", True)
|
||||
return _create_user(username, attrs=attrs)
|
||||
|
|
|
@ -1,67 +1,87 @@
|
|||
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.petits_cours_views import DemandeDetailView, DemandeListView
|
||||
|
||||
export_patterns = [
|
||||
url(r'^members$', views.export_members,
|
||||
name='cof.membres_export'),
|
||||
url(r'^mega/avecremarques$', views.export_mega_remarksonly,
|
||||
name='cof.mega_export_remarks'),
|
||||
url(r'^mega/participants$', views.export_mega_participants,
|
||||
name='cof.mega_export_participants'),
|
||||
url(r'^mega/orgas$', views.export_mega_orgas,
|
||||
name='cof.mega_export_orgas'),
|
||||
url(r"^members$", views.export_members, name="cof.membres_export"),
|
||||
url(
|
||||
r"^mega/avecremarques$",
|
||||
views.export_mega_remarksonly,
|
||||
name="cof.mega_export_remarks",
|
||||
),
|
||||
url(
|
||||
r"^mega/participants$",
|
||||
views.export_mega_participants,
|
||||
name="cof.mega_export_participants",
|
||||
),
|
||||
url(r"^mega/orgas$", views.export_mega_orgas, name="cof.mega_export_orgas"),
|
||||
# url(r'^mega/(?P<type>.+)$', views.export_mega_bytype),
|
||||
url(r'^mega$', views.export_mega,
|
||||
name='cof.mega_export'),
|
||||
url(r"^mega$", views.export_mega, name="cof.mega_export"),
|
||||
]
|
||||
|
||||
petitcours_patterns = [
|
||||
url(r'^inscription$', petits_cours_views.inscription,
|
||||
name='petits-cours-inscription'),
|
||||
url(r'^demande$', petits_cours_views.demande,
|
||||
name='petits-cours-demande'),
|
||||
url(r'^demande-raw$', petits_cours_views.demande_raw,
|
||||
name='petits-cours-demande-raw'),
|
||||
url(r'^demandes$',
|
||||
url(
|
||||
r"^inscription$",
|
||||
petits_cours_views.inscription,
|
||||
name="petits-cours-inscription",
|
||||
),
|
||||
url(r"^demande$", petits_cours_views.demande, name="petits-cours-demande"),
|
||||
url(
|
||||
r"^demande-raw$",
|
||||
petits_cours_views.demande_raw,
|
||||
name="petits-cours-demande-raw",
|
||||
),
|
||||
url(
|
||||
r"^demandes$",
|
||||
buro_required(DemandeListView.as_view()),
|
||||
name='petits-cours-demandes-list'),
|
||||
url(r'^demandes/(?P<pk>\d+)$',
|
||||
name="petits-cours-demandes-list",
|
||||
),
|
||||
url(
|
||||
r"^demandes/(?P<pk>\d+)$",
|
||||
buro_required(DemandeDetailView.as_view()),
|
||||
name='petits-cours-demande-details'),
|
||||
url(r'^demandes/(?P<demande_id>\d+)/traitement$',
|
||||
name="petits-cours-demande-details",
|
||||
),
|
||||
url(
|
||||
r"^demandes/(?P<demande_id>\d+)/traitement$",
|
||||
petits_cours_views.traitement,
|
||||
name='petits-cours-demande-traitement'),
|
||||
url(r'^demandes/(?P<demande_id>\d+)/retraitement$',
|
||||
name="petits-cours-demande-traitement",
|
||||
),
|
||||
url(
|
||||
r"^demandes/(?P<demande_id>\d+)/retraitement$",
|
||||
petits_cours_views.retraitement,
|
||||
name='petits-cours-demande-retraitement'),
|
||||
name="petits-cours-demande-retraitement",
|
||||
),
|
||||
]
|
||||
|
||||
surveys_patterns = [
|
||||
url(r'^(?P<survey_id>\d+)/status$', views.survey_status,
|
||||
name='survey.details.status'),
|
||||
url(r'^(?P<survey_id>\d+)$', views.survey,
|
||||
name='survey.details'),
|
||||
url(
|
||||
r"^(?P<survey_id>\d+)/status$",
|
||||
views.survey_status,
|
||||
name="survey.details.status",
|
||||
),
|
||||
url(r"^(?P<survey_id>\d+)$", views.survey, name="survey.details"),
|
||||
]
|
||||
|
||||
events_patterns = [
|
||||
url(r'^(?P<event_id>\d+)$', views.event,
|
||||
name='event.details'),
|
||||
url(r'^(?P<event_id>\d+)/status$', views.event_status,
|
||||
name='event.details.status'),
|
||||
url(r"^(?P<event_id>\d+)$", views.event, name="event.details"),
|
||||
url(r"^(?P<event_id>\d+)/status$", views.event_status, name="event.details.status"),
|
||||
]
|
||||
|
||||
calendar_patterns = [
|
||||
url(r'^subscription$', views.calendar,
|
||||
name='calendar'),
|
||||
url(r'^(?P<token>[a-z0-9-]+)/calendar.ics$', views.calendar_ics,
|
||||
name='calendar.ics'),
|
||||
url(r"^subscription$", views.calendar, name="calendar"),
|
||||
url(
|
||||
r"^(?P<token>[a-z0-9-]+)/calendar.ics$", views.calendar_ics, name="calendar.ics"
|
||||
),
|
||||
]
|
||||
|
||||
clubs_patterns = [
|
||||
url(r'^membres/(?P<name>\w+)', views.membres_club, name='membres-club'),
|
||||
url(r'^liste', views.liste_clubs, name='liste-clubs'),
|
||||
url(r'^change_respo/(?P<club_name>\w+)/(?P<user_id>\d+)',
|
||||
views.change_respo, name='change-respo'),
|
||||
url(r"^membres/(?P<name>\w+)", views.membres_club, name="membres-club"),
|
||||
url(r"^liste", views.liste_clubs, name="liste-clubs"),
|
||||
url(
|
||||
r"^change_respo/(?P<club_name>\w+)/(?P<user_id>\d+)",
|
||||
views.change_respo,
|
||||
name="change-respo",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,58 +1,74 @@
|
|||
import unicodecsv
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
from icalendar import Calendar, Event as Vevent
|
||||
from custommail.shortcuts import send_custom_mail
|
||||
|
||||
from django.shortcuts import redirect, get_object_or_404, render
|
||||
from django.http import Http404, HttpResponse, HttpResponseForbidden
|
||||
import unicodecsv
|
||||
from custommail.shortcuts import send_custom_mail
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.views import (
|
||||
login as django_login_view, logout as django_logout_view,
|
||||
login as django_login_view,
|
||||
logout as django_logout_view,
|
||||
redirect_to_login,
|
||||
)
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.views.generic import FormView
|
||||
from django.http import Http404, HttpResponse, HttpResponseForbidden
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib import messages
|
||||
|
||||
from django.views.generic import FormView
|
||||
from django_cas_ng.views import logout as cas_logout_view
|
||||
from icalendar import Calendar, Event as Vevent
|
||||
|
||||
from utils.views.autocomplete import Select2QuerySetView
|
||||
|
||||
from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \
|
||||
SurveyQuestionAnswer
|
||||
from gestioncof.models import Event, EventRegistration, EventOption, \
|
||||
EventOptionChoice
|
||||
from gestioncof.models import EventCommentField, EventCommentValue, \
|
||||
CalendarSubscription
|
||||
from gestioncof.models import CofProfile, Club
|
||||
from bda.models import Spectacle, Tirage
|
||||
from gestioncof.decorators import buro_required, cof_required
|
||||
from gestioncof.forms import (
|
||||
UserProfileForm, EventStatusFilterForm, SurveyForm, SurveyStatusFilterForm,
|
||||
RegistrationUserForm, RegistrationProfileForm, EventForm, CalendarForm,
|
||||
EventFormset, RegistrationPassUserForm, ClubsForm, GestioncofConfigForm
|
||||
CalendarForm,
|
||||
ClubsForm,
|
||||
EventForm,
|
||||
EventFormset,
|
||||
EventStatusFilterForm,
|
||||
GestioncofConfigForm,
|
||||
ProfileForm,
|
||||
RegistrationPassUserForm,
|
||||
RegistrationProfileForm,
|
||||
RegistrationUserForm,
|
||||
SurveyForm,
|
||||
SurveyStatusFilterForm,
|
||||
UserForm,
|
||||
)
|
||||
|
||||
from bda.models import Tirage, Spectacle
|
||||
from gestioncof.models import (
|
||||
CalendarSubscription,
|
||||
Club,
|
||||
CofProfile,
|
||||
Event,
|
||||
EventCommentField,
|
||||
EventCommentValue,
|
||||
EventOption,
|
||||
EventOptionChoice,
|
||||
EventRegistration,
|
||||
Survey,
|
||||
SurveyAnswer,
|
||||
SurveyQuestion,
|
||||
SurveyQuestionAnswer,
|
||||
)
|
||||
from utils.views.autocomplete import Select2QuerySetView
|
||||
|
||||
|
||||
@login_required
|
||||
def home(request):
|
||||
data = {"surveys": Survey.objects.filter(old=False).all(),
|
||||
data = {
|
||||
"surveys": Survey.objects.filter(old=False).all(),
|
||||
"events": Event.objects.filter(old=False).all(),
|
||||
"open_surveys":
|
||||
Survey.objects.filter(survey_open=True, old=False).all(),
|
||||
"open_events":
|
||||
Event.objects.filter(registration_open=True, old=False).all(),
|
||||
"open_surveys": Survey.objects.filter(survey_open=True, old=False).all(),
|
||||
"open_events": Event.objects.filter(registration_open=True, old=False).all(),
|
||||
"active_tirages": Tirage.objects.filter(active=True).all(),
|
||||
"open_tirages":
|
||||
Tirage.objects.filter(active=True,
|
||||
ouverture__lte=timezone.now()).all(),
|
||||
"now": timezone.now()}
|
||||
"open_tirages": Tirage.objects.filter(
|
||||
active=True, ouverture__lte=timezone.now()
|
||||
).all(),
|
||||
"now": timezone.now(),
|
||||
}
|
||||
return render(request, "home.html", data)
|
||||
|
||||
|
||||
|
@ -60,8 +76,8 @@ def login(request):
|
|||
if request.user.is_authenticated:
|
||||
return redirect("home")
|
||||
context = {}
|
||||
if request.method == "GET" and 'next' in request.GET:
|
||||
context['next'] = request.GET['next']
|
||||
if request.method == "GET" and "next" in request.GET:
|
||||
context["next"] = request.GET["next"]
|
||||
return render(request, "login_switch.html", context)
|
||||
|
||||
|
||||
|
@ -72,34 +88,33 @@ def login_ext(request):
|
|||
if not user.has_usable_password() or user.password in ("", "!"):
|
||||
profile, created = CofProfile.objects.get_or_create(user=user)
|
||||
if profile.login_clipper:
|
||||
return render(request, "error.html",
|
||||
{"error_type": "use_clipper_login"})
|
||||
return render(
|
||||
request, "error.html", {"error_type": "use_clipper_login"}
|
||||
)
|
||||
else:
|
||||
return render(request, "error.html",
|
||||
{"error_type": "no_password"})
|
||||
return render(request, "error.html", {"error_type": "no_password"})
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
context = {}
|
||||
if request.method == "GET" and 'next' in request.GET:
|
||||
context['next'] = request.GET['next']
|
||||
if request.method == "POST" and 'next' in request.POST:
|
||||
context['next'] = request.POST['next']
|
||||
return django_login_view(request, template_name='login.html',
|
||||
extra_context=context)
|
||||
if request.method == "GET" and "next" in request.GET:
|
||||
context["next"] = request.GET["next"]
|
||||
if request.method == "POST" and "next" in request.POST:
|
||||
context["next"] = request.POST["next"]
|
||||
return django_login_view(request, template_name="login.html", extra_context=context)
|
||||
|
||||
|
||||
@login_required
|
||||
def logout(request, next_page=None):
|
||||
if next_page is None:
|
||||
next_page = request.GET.get('next', None)
|
||||
next_page = request.GET.get("next", None)
|
||||
|
||||
profile = getattr(request.user, 'profile', None)
|
||||
profile = getattr(request.user, "profile", None)
|
||||
|
||||
if profile and profile.login_clipper:
|
||||
msg = _('Déconnexion de GestioCOF et CAS réussie. À bientôt {}.')
|
||||
msg = _("Déconnexion de GestioCOF et CAS réussie. À bientôt {}.")
|
||||
logout_view = cas_logout_view
|
||||
else:
|
||||
msg = _('Déconnexion de GestioCOF réussie. À bientôt {}.')
|
||||
msg = _("Déconnexion de GestioCOF réussie. À bientôt {}.")
|
||||
logout_view = django_logout_view
|
||||
|
||||
messages.success(request, msg.format(request.user.get_short_name()))
|
||||
|
@ -109,8 +124,7 @@ def logout(request, next_page=None):
|
|||
@login_required
|
||||
def survey(request, survey_id):
|
||||
survey = get_object_or_404(
|
||||
Survey.objects.prefetch_related('questions', 'questions__answers'),
|
||||
id=survey_id,
|
||||
Survey.objects.prefetch_related("questions", "questions__answers"), id=survey_id
|
||||
)
|
||||
if not survey.survey_open or survey.old:
|
||||
raise Http404
|
||||
|
@ -118,10 +132,11 @@ def survey(request, survey_id):
|
|||
deleted = False
|
||||
if request.method == "POST":
|
||||
form = SurveyForm(request.POST, survey=survey)
|
||||
if request.POST.get('delete'):
|
||||
if request.POST.get("delete"):
|
||||
try:
|
||||
current_answer = SurveyAnswer.objects.get(user=request.user,
|
||||
survey=survey)
|
||||
current_answer = SurveyAnswer.objects.get(
|
||||
user=request.user, survey=survey
|
||||
)
|
||||
current_answer.delete()
|
||||
current_answer = None
|
||||
except SurveyAnswer.DoesNotExist:
|
||||
|
@ -133,9 +148,9 @@ def survey(request, survey_id):
|
|||
if form.is_valid():
|
||||
all_answers = []
|
||||
for question_id, answers_ids in form.answers():
|
||||
question = get_object_or_404(SurveyQuestion,
|
||||
id=question_id,
|
||||
survey=survey)
|
||||
question = get_object_or_404(
|
||||
SurveyQuestion, id=question_id, survey=survey
|
||||
)
|
||||
if type(answers_ids) != list:
|
||||
answers_ids = [answers_ids]
|
||||
if not question.multi_answers and len(answers_ids) > 1:
|
||||
|
@ -145,50 +160,48 @@ def survey(request, survey_id):
|
|||
continue
|
||||
answer_id = int(answer_id)
|
||||
answer = SurveyQuestionAnswer.objects.get(
|
||||
id=answer_id,
|
||||
survey_question=question)
|
||||
id=answer_id, survey_question=question
|
||||
)
|
||||
all_answers.append(answer)
|
||||
try:
|
||||
current_answer = SurveyAnswer.objects.get(
|
||||
user=request.user, survey=survey)
|
||||
user=request.user, survey=survey
|
||||
)
|
||||
except SurveyAnswer.DoesNotExist:
|
||||
current_answer = SurveyAnswer(user=request.user,
|
||||
survey=survey)
|
||||
current_answer = SurveyAnswer(user=request.user, survey=survey)
|
||||
current_answer.save()
|
||||
current_answer.answers = all_answers
|
||||
current_answer.save()
|
||||
success = True
|
||||
else:
|
||||
try:
|
||||
current_answer = SurveyAnswer.objects.get(user=request.user,
|
||||
survey=survey)
|
||||
form = SurveyForm(survey=survey,
|
||||
current_answers=current_answer.answers)
|
||||
current_answer = SurveyAnswer.objects.get(user=request.user, survey=survey)
|
||||
form = SurveyForm(survey=survey, current_answers=current_answer.answers)
|
||||
except SurveyAnswer.DoesNotExist:
|
||||
current_answer = None
|
||||
form = SurveyForm(survey=survey)
|
||||
# Messages
|
||||
if success:
|
||||
if deleted:
|
||||
messages.success(request,
|
||||
"Votre réponse a bien été supprimée")
|
||||
messages.success(request, "Votre réponse a bien été supprimée")
|
||||
else:
|
||||
messages.success(request,
|
||||
messages.success(
|
||||
request,
|
||||
"Votre réponse a bien été enregistrée ! Vous "
|
||||
"pouvez cependant la modifier jusqu'à la fin "
|
||||
"du sondage.")
|
||||
return render(request, "gestioncof/survey.html", {
|
||||
"survey": survey,
|
||||
"form": form,
|
||||
"current_answer": current_answer
|
||||
})
|
||||
"du sondage.",
|
||||
)
|
||||
return render(
|
||||
request,
|
||||
"gestioncof/survey.html",
|
||||
{"survey": survey, "form": form, "current_answer": current_answer},
|
||||
)
|
||||
|
||||
|
||||
def get_event_form_choices(event, form):
|
||||
all_choices = []
|
||||
for option_id, choices_ids in form.choices():
|
||||
option = get_object_or_404(EventOption, id=option_id,
|
||||
event=event)
|
||||
option = get_object_or_404(EventOption, id=option_id, event=event)
|
||||
if type(choices_ids) != list:
|
||||
choices_ids = [choices_ids]
|
||||
if not option.multi_choices and len(choices_ids) > 1:
|
||||
|
@ -197,22 +210,19 @@ def get_event_form_choices(event, form):
|
|||
if not choice_id:
|
||||
continue
|
||||
choice_id = int(choice_id)
|
||||
choice = EventOptionChoice.objects.get(
|
||||
id=choice_id,
|
||||
event_option=option)
|
||||
choice = EventOptionChoice.objects.get(id=choice_id, event_option=option)
|
||||
all_choices.append(choice)
|
||||
return all_choices
|
||||
|
||||
|
||||
def update_event_form_comments(event, form, registration):
|
||||
for commentfield_id, value in form.comments():
|
||||
field = get_object_or_404(EventCommentField, id=commentfield_id,
|
||||
event=event)
|
||||
field = get_object_or_404(EventCommentField, id=commentfield_id, event=event)
|
||||
if value == field.default:
|
||||
continue
|
||||
(storage, _) = EventCommentValue.objects.get_or_create(
|
||||
commentfield=field,
|
||||
registration=registration)
|
||||
commentfield=field, registration=registration
|
||||
)
|
||||
storage.content = value
|
||||
storage.save()
|
||||
|
||||
|
@ -227,27 +237,29 @@ def event(request, event_id):
|
|||
form = EventForm(request.POST, event=event)
|
||||
if form.is_valid():
|
||||
all_choices = get_event_form_choices(event, form)
|
||||
(current_registration, _) = \
|
||||
EventRegistration.objects.get_or_create(user=request.user,
|
||||
event=event)
|
||||
(current_registration, _) = EventRegistration.objects.get_or_create(
|
||||
user=request.user, event=event
|
||||
)
|
||||
current_registration.options = all_choices
|
||||
current_registration.save()
|
||||
success = True
|
||||
else:
|
||||
try:
|
||||
current_registration = \
|
||||
EventRegistration.objects.get(user=request.user, event=event)
|
||||
form = EventForm(event=event,
|
||||
current_choices=current_registration.options)
|
||||
current_registration = EventRegistration.objects.get(
|
||||
user=request.user, event=event
|
||||
)
|
||||
form = EventForm(event=event, current_choices=current_registration.options)
|
||||
except EventRegistration.DoesNotExist:
|
||||
form = EventForm(event=event)
|
||||
# Messages
|
||||
if success:
|
||||
messages.success(request, "Votre inscription a bien été enregistrée ! "
|
||||
messages.success(
|
||||
request,
|
||||
"Votre inscription a bien été enregistrée ! "
|
||||
"Vous pouvez cependant la modifier jusqu'à "
|
||||
"la fin des inscriptions.")
|
||||
return render(request, "gestioncof/event.html",
|
||||
{"event": event, "form": form})
|
||||
"la fin des inscriptions.",
|
||||
)
|
||||
return render(request, "gestioncof/event.html", {"event": event, "form": form})
|
||||
|
||||
|
||||
def clean_post_for_status(initial):
|
||||
|
@ -271,19 +283,21 @@ def event_status(request, event_id):
|
|||
if value == "yes":
|
||||
registrations_query = registrations_query.filter(paid=True)
|
||||
elif value == "no":
|
||||
registrations_query = registrations_query.filter(
|
||||
paid=False)
|
||||
registrations_query = registrations_query.filter(paid=False)
|
||||
continue
|
||||
choice = get_object_or_404(EventOptionChoice, id=choice_id,
|
||||
event_option__id=option_id)
|
||||
choice = get_object_or_404(
|
||||
EventOptionChoice, id=choice_id, event_option__id=option_id
|
||||
)
|
||||
if value == "none":
|
||||
continue
|
||||
if value == "yes":
|
||||
registrations_query = registrations_query.filter(
|
||||
options__id__exact=choice.id)
|
||||
options__id__exact=choice.id
|
||||
)
|
||||
elif value == "no":
|
||||
registrations_query = registrations_query.exclude(
|
||||
options__id__exact=choice.id)
|
||||
options__id__exact=choice.id
|
||||
)
|
||||
user_choices = registrations_query.prefetch_related("user").all()
|
||||
options = EventOption.objects.filter(event=event).all()
|
||||
choices_count = {}
|
||||
|
@ -293,10 +307,17 @@ def event_status(request, event_id):
|
|||
for user_choice in user_choices:
|
||||
for choice in user_choice.options.all():
|
||||
choices_count[choice.id] += 1
|
||||
return render(request, "event_status.html",
|
||||
{"event": event, "user_choices": user_choices,
|
||||
"options": options, "choices_count": choices_count,
|
||||
"form": form})
|
||||
return render(
|
||||
request,
|
||||
"event_status.html",
|
||||
{
|
||||
"event": event,
|
||||
"user_choices": user_choices,
|
||||
"options": options,
|
||||
"choices_count": choices_count,
|
||||
"form": form,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@buro_required
|
||||
|
@ -307,16 +328,15 @@ def survey_status(request, survey_id):
|
|||
form = SurveyStatusFilterForm(post_data or None, survey=survey)
|
||||
if form.is_valid():
|
||||
for question_id, answer_id, value in form.filters():
|
||||
answer = get_object_or_404(SurveyQuestionAnswer, id=answer_id,
|
||||
survey_question__id=question_id)
|
||||
answer = get_object_or_404(
|
||||
SurveyQuestionAnswer, id=answer_id, survey_question__id=question_id
|
||||
)
|
||||
if value == "none":
|
||||
continue
|
||||
if value == "yes":
|
||||
answers_query = answers_query.filter(
|
||||
answers__id__exact=answer.id)
|
||||
answers_query = answers_query.filter(answers__id__exact=answer.id)
|
||||
elif value == "no":
|
||||
answers_query = answers_query.exclude(
|
||||
answers__id__exact=answer.id)
|
||||
answers_query = answers_query.exclude(answers__id__exact=answer.id)
|
||||
user_answers = answers_query.prefetch_related("user").all()
|
||||
questions = SurveyQuestion.objects.filter(survey=survey).all()
|
||||
answers_count = {}
|
||||
|
@ -326,33 +346,41 @@ def survey_status(request, survey_id):
|
|||
for user_answer in user_answers:
|
||||
for answer in user_answer.answers.all():
|
||||
answers_count[answer.id] += 1
|
||||
return render(request, "survey_status.html",
|
||||
{"survey": survey, "user_answers": user_answers,
|
||||
"questions": questions, "answers_count": answers_count,
|
||||
"form": form})
|
||||
return render(
|
||||
request,
|
||||
"survey_status.html",
|
||||
{
|
||||
"survey": survey,
|
||||
"user_answers": user_answers,
|
||||
"questions": questions,
|
||||
"answers_count": answers_count,
|
||||
"form": form,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@cof_required
|
||||
def profile(request):
|
||||
user = request.user
|
||||
data = request.POST if request.method == "POST" else None
|
||||
user_form = UserForm(data=data, instance=user, prefix="u")
|
||||
profile_form = ProfileForm(data=data, instance=user.profile, prefix="p")
|
||||
if request.method == "POST":
|
||||
form = UserProfileForm(request.POST, instance=request.user.profile)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request,
|
||||
"Votre profil a été mis à jour avec succès !")
|
||||
else:
|
||||
form = UserProfileForm(instance=request.user.profile)
|
||||
return render(request, "gestioncof/profile.html", {"form": form})
|
||||
if user_form.is_valid() and profile_form.is_valid():
|
||||
user_form.save()
|
||||
profile_form.save()
|
||||
messages.success(request, _("Votre profil a été mis à jour avec succès !"))
|
||||
context = {"user_form": user_form, "profile_form": profile_form}
|
||||
return render(request, "gestioncof/profile.html", context)
|
||||
|
||||
|
||||
def registration_set_ro_fields(user_form, profile_form):
|
||||
user_form.fields['username'].widget.attrs['readonly'] = True
|
||||
profile_form.fields['login_clipper'].widget.attrs['readonly'] = True
|
||||
user_form.fields["username"].widget.attrs["readonly"] = True
|
||||
profile_form.fields["login_clipper"].widget.attrs["readonly"] = True
|
||||
|
||||
|
||||
@buro_required
|
||||
def registration_form2(request, login_clipper=None, username=None,
|
||||
fullname=None):
|
||||
def registration_form2(request, login_clipper=None, username=None, fullname=None):
|
||||
events = Event.objects.filter(old=False).all()
|
||||
member = None
|
||||
if login_clipper:
|
||||
|
@ -363,20 +391,24 @@ def registration_form2(request, login_clipper=None, username=None,
|
|||
except User.DoesNotExist:
|
||||
# new user, but prefill
|
||||
# user
|
||||
user_form = RegistrationUserForm(initial={
|
||||
'username': login_clipper,
|
||||
'email': "%s@clipper.ens.fr" % login_clipper})
|
||||
user_form = RegistrationUserForm(
|
||||
initial={
|
||||
"username": login_clipper,
|
||||
"email": "%s@clipper.ens.fr" % login_clipper,
|
||||
}
|
||||
)
|
||||
if fullname:
|
||||
bits = fullname.split(" ")
|
||||
user_form.fields['first_name'].initial = bits[0]
|
||||
user_form.fields["first_name"].initial = bits[0]
|
||||
if len(bits) > 1:
|
||||
user_form.fields['last_name'].initial = " ".join(bits[1:])
|
||||
user_form.fields["last_name"].initial = " ".join(bits[1:])
|
||||
# profile
|
||||
profile_form = RegistrationProfileForm(initial={
|
||||
'login_clipper': login_clipper})
|
||||
profile_form = RegistrationProfileForm(
|
||||
initial={"login_clipper": login_clipper}
|
||||
)
|
||||
registration_set_ro_fields(user_form, profile_form)
|
||||
# events & clubs
|
||||
event_formset = EventFormset(events=events, prefix='events')
|
||||
event_formset = EventFormset(events=events, prefix="events")
|
||||
clubs_form = ClubsForm()
|
||||
if username:
|
||||
member = get_object_or_404(User, username=username)
|
||||
|
@ -390,26 +422,33 @@ def registration_form2(request, login_clipper=None, username=None,
|
|||
for event in events:
|
||||
try:
|
||||
current_registrations.append(
|
||||
EventRegistration.objects.get(user=member, event=event))
|
||||
EventRegistration.objects.get(user=member, event=event)
|
||||
)
|
||||
except EventRegistration.DoesNotExist:
|
||||
current_registrations.append(None)
|
||||
event_formset = EventFormset(
|
||||
events=events, prefix='events',
|
||||
current_registrations=current_registrations)
|
||||
events=events, prefix="events", current_registrations=current_registrations
|
||||
)
|
||||
# Clubs
|
||||
clubs_form = ClubsForm(initial={'clubs': member.clubs.all()})
|
||||
clubs_form = ClubsForm(initial={"clubs": member.clubs.all()})
|
||||
elif not login_clipper:
|
||||
# new user
|
||||
user_form = RegistrationPassUserForm()
|
||||
profile_form = RegistrationProfileForm()
|
||||
event_formset = EventFormset(events=events, prefix='events')
|
||||
event_formset = EventFormset(events=events, prefix="events")
|
||||
clubs_form = ClubsForm()
|
||||
return render(request, "gestioncof/registration_form.html",
|
||||
{"member": member, "login_clipper": login_clipper,
|
||||
return render(
|
||||
request,
|
||||
"gestioncof/registration_form.html",
|
||||
{
|
||||
"member": member,
|
||||
"login_clipper": login_clipper,
|
||||
"user_form": user_form,
|
||||
"profile_form": profile_form,
|
||||
"event_formset": event_formset,
|
||||
"clubs_form": clubs_form})
|
||||
"clubs_form": clubs_form,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@buro_required
|
||||
|
@ -423,15 +462,14 @@ def registration(request):
|
|||
# Remplissage des formulaires
|
||||
# -----
|
||||
|
||||
if 'password1' in request_dict or 'password2' in request_dict:
|
||||
if "password1" in request_dict or "password2" in request_dict:
|
||||
user_form = RegistrationPassUserForm(request_dict)
|
||||
else:
|
||||
user_form = RegistrationUserForm(request_dict)
|
||||
profile_form = RegistrationProfileForm(request_dict)
|
||||
clubs_form = ClubsForm(request_dict)
|
||||
events = Event.objects.filter(old=False).all()
|
||||
event_formset = EventFormset(events=events, data=request_dict,
|
||||
prefix='events')
|
||||
event_formset = EventFormset(events=events, data=request_dict, prefix="events")
|
||||
if "user_exists" in request_dict and request_dict["user_exists"]:
|
||||
username = request_dict["username"]
|
||||
try:
|
||||
|
@ -453,38 +491,44 @@ def registration(request):
|
|||
profile, _ = CofProfile.objects.get_or_create(user=member)
|
||||
was_cof = profile.is_cof
|
||||
# Maintenant on remplit le formulaire de profil
|
||||
profile_form = RegistrationProfileForm(request_dict,
|
||||
instance=profile)
|
||||
if (profile_form.is_valid() and event_formset.is_valid()
|
||||
and clubs_form.is_valid()):
|
||||
profile_form = RegistrationProfileForm(request_dict, instance=profile)
|
||||
if (
|
||||
profile_form.is_valid()
|
||||
and event_formset.is_valid()
|
||||
and clubs_form.is_valid()
|
||||
):
|
||||
# Enregistrement du profil
|
||||
profile = profile_form.save()
|
||||
if profile.is_cof and not was_cof:
|
||||
send_custom_mail(
|
||||
"welcome", "cof@ens.fr", [member.email],
|
||||
context={'member': member},
|
||||
"welcome",
|
||||
"cof@ens.fr",
|
||||
[member.email],
|
||||
context={"member": member},
|
||||
)
|
||||
# Enregistrement des inscriptions aux événements
|
||||
for form in event_formset:
|
||||
if 'status' not in form.cleaned_data:
|
||||
form.cleaned_data['status'] = 'no'
|
||||
if form.cleaned_data['status'] == 'no':
|
||||
if "status" not in form.cleaned_data:
|
||||
form.cleaned_data["status"] = "no"
|
||||
if form.cleaned_data["status"] == "no":
|
||||
try:
|
||||
current_registration = EventRegistration.objects \
|
||||
.get(user=member, event=form.event)
|
||||
current_registration = EventRegistration.objects.get(
|
||||
user=member, event=form.event
|
||||
)
|
||||
current_registration.delete()
|
||||
except EventRegistration.DoesNotExist:
|
||||
pass
|
||||
continue
|
||||
all_choices = get_event_form_choices(form.event, form)
|
||||
(current_registration, created_reg) = \
|
||||
EventRegistration.objects.get_or_create(
|
||||
user=member, event=form.event)
|
||||
update_event_form_comments(form.event, form,
|
||||
current_registration)
|
||||
(
|
||||
current_registration,
|
||||
created_reg,
|
||||
) = EventRegistration.objects.get_or_create(
|
||||
user=member, event=form.event
|
||||
)
|
||||
update_event_form_comments(form.event, form, current_registration)
|
||||
current_registration.options = all_choices
|
||||
current_registration.paid = \
|
||||
(form.cleaned_data['status'] == 'paid')
|
||||
current_registration.paid = form.cleaned_data["status"] == "paid"
|
||||
current_registration.save()
|
||||
# if form.event.title == "Mega 15" and created_reg:
|
||||
# field = EventCommentField.objects.get(
|
||||
|
@ -502,7 +546,7 @@ def registration(request):
|
|||
# send_custom_mail(...)
|
||||
# Enregistrement des inscriptions aux clubs
|
||||
member.clubs.clear()
|
||||
for club in clubs_form.cleaned_data['clubs']:
|
||||
for club in clubs_form.cleaned_data["clubs"]:
|
||||
club.membres.add(member)
|
||||
club.save()
|
||||
|
||||
|
@ -510,20 +554,29 @@ def registration(request):
|
|||
# Success
|
||||
# ---
|
||||
|
||||
msg = ("L'inscription de {:s} (<tt>{:s}</tt>) a été "
|
||||
"enregistrée avec succès."
|
||||
.format(member.get_full_name(), member.email))
|
||||
msg = (
|
||||
"L'inscription de {:s} (<tt>{:s}</tt>) a été "
|
||||
"enregistrée avec succès.".format(
|
||||
member.get_full_name(), member.email
|
||||
)
|
||||
)
|
||||
if profile.is_cof:
|
||||
msg += "\nIl est désormais membre du COF n°{:d} !".format(
|
||||
member.profile.id)
|
||||
messages.success(request, msg, extra_tags='safe')
|
||||
return render(request, "gestioncof/registration_post.html",
|
||||
{"user_form": user_form,
|
||||
member.profile.id
|
||||
)
|
||||
messages.success(request, msg, extra_tags="safe")
|
||||
return render(
|
||||
request,
|
||||
"gestioncof/registration_post.html",
|
||||
{
|
||||
"user_form": user_form,
|
||||
"profile_form": profile_form,
|
||||
"member": member,
|
||||
"login_clipper": login_clipper,
|
||||
"event_formset": event_formset,
|
||||
"clubs_form": clubs_form})
|
||||
"clubs_form": clubs_form,
|
||||
},
|
||||
)
|
||||
else:
|
||||
return render(request, "registration.html")
|
||||
|
||||
|
@ -539,13 +592,14 @@ def membres_club(request, name):
|
|||
# ou respo du club.
|
||||
user = request.user
|
||||
club = get_object_or_404(Club, name=name)
|
||||
if not request.user.profile.is_buro \
|
||||
and club not in user.clubs_geres.all():
|
||||
return HttpResponseForbidden('<h1>Permission denied</h1>')
|
||||
if not request.user.profile.is_buro and club not in user.clubs_geres.all():
|
||||
return HttpResponseForbidden("<h1>Permission denied</h1>")
|
||||
members_no_respo = club.membres.exclude(clubs_geres=club).all()
|
||||
return render(request, 'membres_clubs.html',
|
||||
{'club': club,
|
||||
'members_no_respo': members_no_respo})
|
||||
return render(
|
||||
request,
|
||||
"membres_clubs.html",
|
||||
{"club": club, "members_no_respo": members_no_respo},
|
||||
)
|
||||
|
||||
|
||||
@buro_required
|
||||
|
@ -558,49 +612,78 @@ def change_respo(request, club_name, user_id):
|
|||
club.respos.add(user)
|
||||
else:
|
||||
raise Http404
|
||||
return redirect('membres-club', name=club_name)
|
||||
return redirect("membres-club", name=club_name)
|
||||
|
||||
|
||||
@cof_required
|
||||
def liste_clubs(request):
|
||||
clubs = Club.objects
|
||||
if request.user.profile.is_buro:
|
||||
data = {'owned_clubs': clubs.all()}
|
||||
data = {"owned_clubs": clubs.all()}
|
||||
else:
|
||||
data = {'owned_clubs': request.user.clubs_geres.all(),
|
||||
'other_clubs': clubs.exclude(respos=request.user)}
|
||||
return render(request, 'liste_clubs.html', data)
|
||||
data = {
|
||||
"owned_clubs": request.user.clubs_geres.all(),
|
||||
"other_clubs": clubs.exclude(respos=request.user),
|
||||
}
|
||||
return render(request, "liste_clubs.html", data)
|
||||
|
||||
|
||||
@buro_required
|
||||
def export_members(request):
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename=membres_cof.csv'
|
||||
response = HttpResponse(content_type="text/csv")
|
||||
response["Content-Disposition"] = "attachment; filename=membres_cof.csv"
|
||||
|
||||
writer = unicodecsv.writer(response)
|
||||
for profile in CofProfile.objects.filter(is_cof=True).all():
|
||||
user = profile.user
|
||||
bits = [user.id, user.username, user.first_name, user.last_name,
|
||||
user.email, profile.phone, profile.occupation,
|
||||
profile.departement, profile.type_cotiz]
|
||||
bits = [
|
||||
user.id,
|
||||
user.username,
|
||||
user.first_name,
|
||||
user.last_name,
|
||||
user.email,
|
||||
profile.phone,
|
||||
profile.occupation,
|
||||
profile.departement,
|
||||
profile.type_cotiz,
|
||||
]
|
||||
writer.writerow([str(bit) for bit in bits])
|
||||
|
||||
return response
|
||||
|
||||
|
||||
# ----------------------------------------
|
||||
# Début des exports Mega machins hardcodés
|
||||
# ----------------------------------------
|
||||
|
||||
|
||||
MEGA_YEAR = 2018
|
||||
MEGA_EVENT_NAME = "MEGA 2018"
|
||||
MEGA_COMMENTFIELD_NAME = "Commentaires"
|
||||
MEGA_CONSCRITORGAFIELD_NAME = "Orga ? Conscrit ?"
|
||||
MEGA_CONSCRIT = "Conscrit"
|
||||
MEGA_ORGA = "Orga"
|
||||
|
||||
|
||||
def csv_export_mega(filename, qs):
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename=' + filename
|
||||
response = HttpResponse(content_type="text/csv")
|
||||
response["Content-Disposition"] = "attachment; filename=" + filename
|
||||
writer = unicodecsv.writer(response)
|
||||
|
||||
for reg in qs.all():
|
||||
user = reg.user
|
||||
profile = user.profile
|
||||
comments = "---".join(
|
||||
[comment.content for comment in reg.comments.all()])
|
||||
bits = [user.username, user.first_name, user.last_name, user.email,
|
||||
profile.phone, user.id,
|
||||
profile.comments if profile.comments else "", comments]
|
||||
comments = "---".join([comment.content for comment in reg.comments.all()])
|
||||
bits = [
|
||||
user.username,
|
||||
user.first_name,
|
||||
user.last_name,
|
||||
user.email,
|
||||
profile.phone,
|
||||
user.id,
|
||||
profile.comments if profile.comments else "",
|
||||
comments,
|
||||
]
|
||||
|
||||
writer.writerow([str(bit) for bit in bits])
|
||||
|
||||
|
@ -609,19 +692,27 @@ def csv_export_mega(filename, qs):
|
|||
|
||||
@buro_required
|
||||
def export_mega_remarksonly(request):
|
||||
filename = 'remarques_mega_2017.csv'
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename=' + filename
|
||||
filename = "remarques_mega_{}.csv".format(MEGA_YEAR)
|
||||
response = HttpResponse(content_type="text/csv")
|
||||
response["Content-Disposition"] = "attachment; filename=" + filename
|
||||
writer = unicodecsv.writer(response)
|
||||
|
||||
event = Event.objects.get(title="MEGA 2017")
|
||||
commentfield = event.commentfields.get(name="Commentaire")
|
||||
event = Event.objects.get(title=MEGA_EVENT_NAME)
|
||||
commentfield = event.commentfields.get(name=MEGA_COMMENTFIELD_NAME)
|
||||
for val in commentfield.values.all():
|
||||
reg = val.registration
|
||||
user = reg.user
|
||||
profile = user.profile
|
||||
bits = [user.username, user.first_name, user.last_name, user.email,
|
||||
profile.phone, profile.id, profile.comments, val.content]
|
||||
bits = [
|
||||
user.username,
|
||||
user.first_name,
|
||||
user.last_name,
|
||||
user.email,
|
||||
profile.phone,
|
||||
profile.id,
|
||||
profile.comments,
|
||||
val.content,
|
||||
]
|
||||
writer.writerow([str(bit) for bit in bits])
|
||||
|
||||
return response
|
||||
|
@ -647,32 +738,36 @@ def export_mega_remarksonly(request):
|
|||
|
||||
@buro_required
|
||||
def export_mega_orgas(request):
|
||||
event = Event.objects.get(title="MEGA 2017")
|
||||
type_option = event.options.get(name="Conscrit/Orga ?")
|
||||
participant_type = type_option.choices.get(value="Orga").id
|
||||
event = Event.objects.get(title=MEGA_EVENT_NAME)
|
||||
type_option = event.options.get(name=MEGA_CONSCRITORGAFIELD_NAME)
|
||||
participant_type = type_option.choices.get(value=MEGA_ORGA).id
|
||||
qs = EventRegistration.objects.filter(event=event).filter(
|
||||
options__id=participant_type
|
||||
)
|
||||
return csv_export_mega('orgas_mega_2017.csv', qs)
|
||||
return csv_export_mega("orgas_mega_{}.csv".format(MEGA_YEAR), qs)
|
||||
|
||||
|
||||
@buro_required
|
||||
def export_mega_participants(request):
|
||||
event = Event.objects.get(title="MEGA 2017")
|
||||
type_option = event.options.get(name="Conscrit/Orga ?")
|
||||
participant_type = type_option.choices.get(value="Conscrit").id
|
||||
event = Event.objects.get(title=MEGA_EVENT_NAME)
|
||||
type_option = event.options.get(name=MEGA_CONSCRITORGAFIELD_NAME)
|
||||
participant_type = type_option.choices.get(value=MEGA_CONSCRIT).id
|
||||
qs = EventRegistration.objects.filter(event=event).filter(
|
||||
options__id=participant_type
|
||||
)
|
||||
return csv_export_mega('participants_mega_2017.csv', qs)
|
||||
return csv_export_mega("conscrits_mega_{}.csv".format(MEGA_YEAR), qs)
|
||||
|
||||
|
||||
@buro_required
|
||||
def export_mega(request):
|
||||
event = Event.objects.filter(title="MEGA 2017")
|
||||
qs = EventRegistration.objects.filter(event=event) \
|
||||
.order_by("user__username")
|
||||
return csv_export_mega('all_mega_2017.csv', qs)
|
||||
event = Event.objects.filter(title=MEGA_EVENT_NAME)
|
||||
qs = EventRegistration.objects.filter(event=event).order_by("user__username")
|
||||
return csv_export_mega("all_mega_{}.csv".format(MEGA_YEAR), qs)
|
||||
|
||||
|
||||
# ------------------------------
|
||||
# Fin des exports Mega hardcodés
|
||||
# ------------------------------
|
||||
|
||||
|
||||
@buro_required
|
||||
|
@ -683,32 +778,28 @@ def utile_cof(request):
|
|||
@buro_required
|
||||
def utile_bda(request):
|
||||
tirages = Tirage.objects.all()
|
||||
return render(request, "utile_bda.html", {'tirages': tirages})
|
||||
return render(request, "utile_bda.html", {"tirages": tirages})
|
||||
|
||||
|
||||
@buro_required
|
||||
def liste_bdadiff(request):
|
||||
titre = "BdA diffusion"
|
||||
personnes = CofProfile.objects.filter(mailing_bda=True, is_cof=True).all()
|
||||
return render(request, "liste_mails.html",
|
||||
{"titre": titre, "personnes": personnes})
|
||||
return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes})
|
||||
|
||||
|
||||
@buro_required
|
||||
def liste_bdarevente(request):
|
||||
titre = "BdA revente"
|
||||
personnes = CofProfile.objects.filter(mailing_bda_revente=True,
|
||||
is_cof=True).all()
|
||||
return render(request, "liste_mails.html", {"titre": titre,
|
||||
"personnes": personnes})
|
||||
personnes = CofProfile.objects.filter(mailing_bda_revente=True, is_cof=True).all()
|
||||
return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes})
|
||||
|
||||
|
||||
@buro_required
|
||||
def liste_diffcof(request):
|
||||
titre = "Diffusion COF"
|
||||
personnes = CofProfile.objects.filter(mailing_cof=True, is_cof=True).all()
|
||||
return render(request, "liste_mails.html", {"titre": titre,
|
||||
"personnes": personnes})
|
||||
return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes})
|
||||
|
||||
|
||||
@cof_required
|
||||
|
@ -717,7 +808,7 @@ def calendar(request):
|
|||
instance = CalendarSubscription.objects.get(user=request.user)
|
||||
except CalendarSubscription.DoesNotExist:
|
||||
instance = None
|
||||
if request.method == 'POST':
|
||||
if request.method == "POST":
|
||||
form = CalendarForm(request.POST, instance=instance)
|
||||
if form.is_valid():
|
||||
subscription = form.save(commit=False)
|
||||
|
@ -726,19 +817,26 @@ def calendar(request):
|
|||
subscription.token = uuid.uuid4()
|
||||
subscription.save()
|
||||
form.save_m2m()
|
||||
messages.success(request,
|
||||
"Calendrier mis à jour avec succès.")
|
||||
return render(request, "gestioncof/calendar_subscription.html",
|
||||
{'form': form,
|
||||
'token': str(subscription.token)})
|
||||
messages.success(request, "Calendrier mis à jour avec succès.")
|
||||
return render(
|
||||
request,
|
||||
"gestioncof/calendar_subscription.html",
|
||||
{"form": form, "token": str(subscription.token)},
|
||||
)
|
||||
else:
|
||||
messages.error(request, "Formulaire incorrect.")
|
||||
return render(request, "gestioncof/calendar_subscription.html",
|
||||
{'form': form})
|
||||
return render(
|
||||
request, "gestioncof/calendar_subscription.html", {"form": form}
|
||||
)
|
||||
else:
|
||||
return render(request, "gestioncof/calendar_subscription.html",
|
||||
{'form': CalendarForm(instance=instance),
|
||||
'token': instance.token if instance else None})
|
||||
return render(
|
||||
request,
|
||||
"gestioncof/calendar_subscription.html",
|
||||
{
|
||||
"form": CalendarForm(instance=instance),
|
||||
"token": instance.token if instance else None,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def calendar_ics(request, token):
|
||||
|
@ -746,33 +844,33 @@ def calendar_ics(request, token):
|
|||
shows = subscription.other_shows.all()
|
||||
if subscription.subscribe_to_my_shows:
|
||||
shows |= Spectacle.objects.filter(
|
||||
attribues__participant__user=subscription.user,
|
||||
tirage__active=True)
|
||||
attribues__participant__user=subscription.user, tirage__active=True
|
||||
)
|
||||
shows = shows.distinct()
|
||||
vcal = Calendar()
|
||||
site = Site.objects.get_current()
|
||||
for show in shows:
|
||||
vevent = Vevent()
|
||||
vevent.add('dtstart', show.date)
|
||||
vevent.add('dtend', show.date + timedelta(seconds=7200))
|
||||
vevent.add('summary', show.title)
|
||||
vevent.add('location', show.location.name)
|
||||
vevent.add('uid', 'show-{:d}-{:d}@{:s}'.format(
|
||||
show.pk, show.tirage_id, site.domain))
|
||||
vevent.add("dtstart", show.date)
|
||||
vevent.add("dtend", show.date + timedelta(seconds=7200))
|
||||
vevent.add("summary", show.title)
|
||||
vevent.add("location", show.location.name)
|
||||
vevent.add(
|
||||
"uid", "show-{:d}-{:d}@{:s}".format(show.pk, show.tirage_id, site.domain)
|
||||
)
|
||||
vcal.add_component(vevent)
|
||||
if subscription.subscribe_to_events:
|
||||
for event in Event.objects.filter(old=False).all():
|
||||
vevent = Vevent()
|
||||
vevent.add('dtstart', event.start_date)
|
||||
vevent.add('dtend', event.end_date)
|
||||
vevent.add('summary', event.title)
|
||||
vevent.add('location', event.location)
|
||||
vevent.add('description', event.description)
|
||||
vevent.add('uid', 'event-{:d}@{:s}'.format(
|
||||
event.pk, site.domain))
|
||||
vevent.add("dtstart", event.start_date)
|
||||
vevent.add("dtend", event.end_date)
|
||||
vevent.add("summary", event.title)
|
||||
vevent.add("location", event.location)
|
||||
vevent.add("description", event.description)
|
||||
vevent.add("uid", "event-{:d}@{:s}".format(event.pk, site.domain))
|
||||
vcal.add_component(vevent)
|
||||
response = HttpResponse(content=vcal.to_ical())
|
||||
response['Content-Type'] = "text/calendar"
|
||||
response["Content-Type"] = "text/calendar"
|
||||
return response
|
||||
|
||||
|
||||
|
@ -800,7 +898,7 @@ class ConfigUpdate(FormView):
|
|||
|
||||
class UserAutocomplete(Select2QuerySetView):
|
||||
model = User
|
||||
search_fields = ('username', 'first_name', 'last_name')
|
||||
search_fields = ("username", "first_name", "last_name")
|
||||
|
||||
|
||||
user_autocomplete = buro_required(UserAutocomplete.as_view())
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from django.forms.widgets import Widget
|
||||
from django.forms.utils import flatatt
|
||||
from django.forms.widgets import Widget
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
||||
|
@ -13,8 +13,8 @@ class TriStateCheckbox(Widget):
|
|||
|
||||
def render(self, name, value, attrs=None, choices=()):
|
||||
if value is None:
|
||||
value = 'none'
|
||||
attrs['value'] = value
|
||||
value = "none"
|
||||
attrs["value"] = value
|
||||
final_attrs = self.build_attrs(self.attrs, attrs)
|
||||
output = ["<span class=\"tristate\"%s></span>" % flatatt(final_attrs)]
|
||||
return mark_safe('\n'.join(output))
|
||||
output = ['<span class="tristate"%s></span>' % flatatt(final_attrs)]
|
||||
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):
|
||||
name = 'kfet'
|
||||
name = "kfet"
|
||||
verbose_name = "Application K-Fêt"
|
||||
|
||||
def ready(self):
|
||||
|
@ -11,4 +11,5 @@ class KFetConfig(AppConfig):
|
|||
def register_config(self):
|
||||
import djconfig
|
||||
from kfet.forms import 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_TRIGRAMME = 'GNR'
|
||||
KFET_GENERIC_USERNAME = "kfet_genericteam"
|
||||
KFET_GENERIC_TRIGRAMME = "GNR"
|
||||
|
|
|
@ -4,11 +4,12 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
|
||||
class KFetAuthConfig(AppConfig):
|
||||
name = 'kfet.auth'
|
||||
label = 'kfetauth'
|
||||
name = "kfet.auth"
|
||||
label = "kfetauth"
|
||||
verbose_name = _("K-Fêt - Authentification et Autorisation")
|
||||
|
||||
def ready(self):
|
||||
from . import signals # noqa
|
||||
from .utils import setup_kfet_generic_user
|
||||
|
||||
post_migrate.connect(setup_kfet_generic_user, sender=self)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
|
||||
from kfet.models import Account, GenericTeamToken
|
||||
|
||||
from .utils import get_kfet_generic_user
|
||||
|
@ -12,11 +13,7 @@ class BaseKFetBackend:
|
|||
Add extra select related up to Account.
|
||||
"""
|
||||
try:
|
||||
return (
|
||||
User.objects
|
||||
.select_related('profile__account_kfet')
|
||||
.get(pk=user_id)
|
||||
)
|
||||
return User.objects.select_related("profile__account_kfet").get(pk=user_id)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
|
|
@ -2,9 +2,6 @@ from django.contrib.auth.context_processors import PermWrapper
|
|||
|
||||
|
||||
def temporary_auth(request):
|
||||
if hasattr(request, 'real_user'):
|
||||
return {
|
||||
'user': request.real_user,
|
||||
'perms': PermWrapper(request.real_user),
|
||||
}
|
||||
if hasattr(request, "real_user"):
|
||||
return {"user": request.real_user, "perms": PermWrapper(request.real_user)}
|
||||
return {}
|
||||
|
|
|
@ -5,15 +5,12 @@ from django.forms import widgets
|
|||
|
||||
|
||||
class KFetPermissionsField(forms.ModelMultipleChoiceField):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
queryset = Permission.objects.filter(
|
||||
content_type__in=ContentType.objects.filter(app_label="kfet"),
|
||||
content_type__in=ContentType.objects.filter(app_label="kfet")
|
||||
)
|
||||
super().__init__(
|
||||
queryset=queryset,
|
||||
widget=widgets.CheckboxSelectMultiple,
|
||||
*args, **kwargs
|
||||
queryset=queryset, widget=widgets.CheckboxSelectMultiple, *args, **kwargs
|
||||
)
|
||||
|
||||
def label_from_instance(self, obj):
|
||||
|
|
|
@ -8,11 +8,11 @@ class GroupForm(forms.ModelForm):
|
|||
permissions = KFetPermissionsField()
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name']
|
||||
return 'K-Fêt %s' % name
|
||||
name = self.cleaned_data["name"]
|
||||
return "K-Fêt %s" % name
|
||||
|
||||
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
|
||||
# used.
|
||||
# other_groups = self.instance.permissions.difference(
|
||||
|
@ -21,28 +21,29 @@ class GroupForm(forms.ModelForm):
|
|||
if self.instance.pk is None:
|
||||
return kfet_perms
|
||||
other_perms = self.instance.permissions.exclude(
|
||||
pk__in=[p.pk for p in self.fields['permissions'].queryset],
|
||||
pk__in=[p.pk for p in self.fields["permissions"].queryset]
|
||||
)
|
||||
return list(kfet_perms) + list(other_perms)
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = ['name', 'permissions']
|
||||
fields = ["name", "permissions"]
|
||||
|
||||
|
||||
class UserGroupForm(forms.ModelForm):
|
||||
groups = forms.ModelMultipleChoiceField(
|
||||
Group.objects.filter(name__icontains='K-Fêt'),
|
||||
label='Statut équipe',
|
||||
required=False)
|
||||
Group.objects.filter(name__icontains="K-Fêt"),
|
||||
label="Statut équipe",
|
||||
required=False,
|
||||
)
|
||||
|
||||
def clean_groups(self):
|
||||
kfet_groups = self.cleaned_data.get('groups')
|
||||
kfet_groups = self.cleaned_data.get("groups")
|
||||
if self.instance.pk is None:
|
||||
return kfet_groups
|
||||
other_groups = self.instance.groups.exclude(name__icontains='K-Fêt')
|
||||
other_groups = self.instance.groups.exclude(name__icontains="K-Fêt")
|
||||
return list(kfet_groups) + list(other_groups)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['groups']
|
||||
fields = ["groups"]
|
||||
|
|
|
@ -12,21 +12,19 @@ class TemporaryAuthMiddleware:
|
|||
values from CofProfile and Account of this user.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
if request.user.is_authenticated:
|
||||
# avoid multiple db accesses in views and templates
|
||||
request.user = (
|
||||
User.objects
|
||||
.select_related('profile__account_kfet')
|
||||
.get(pk=request.user.pk)
|
||||
request.user = User.objects.select_related("profile__account_kfet").get(
|
||||
pk=request.user.pk
|
||||
)
|
||||
|
||||
temp_request_user = AccountBackend().authenticate(
|
||||
request,
|
||||
kfet_password=self.get_kfet_password(request),
|
||||
request, kfet_password=self.get_kfet_password(request)
|
||||
)
|
||||
|
||||
if temp_request_user:
|
||||
|
@ -36,7 +34,4 @@ class TemporaryAuthMiddleware:
|
|||
return self.get_response(request)
|
||||
|
||||
def get_kfet_password(self, request):
|
||||
return (
|
||||
request.META.get('HTTP_KFETPASSWORD') or
|
||||
request.POST.get('KFETPASSWORD')
|
||||
)
|
||||
return request.META.get("HTTP_KFETPASSWORD") or request.POST.get("KFETPASSWORD")
|
||||
|
|
|
@ -7,18 +7,26 @@ from django.db import migrations, models
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0006_require_contenttypes_0002'),
|
||||
("auth", "0006_require_contenttypes_0002"),
|
||||
# Following dependency allows using Account model to set up the kfet
|
||||
# generic user in post_migrate receiver.
|
||||
('kfet', '0058_delete_genericteamtoken'),
|
||||
("kfet", "0058_delete_genericteamtoken"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='GenericTeamToken',
|
||||
name="GenericTeamToken",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
|
||||
('token', models.CharField(unique=True, max_length=50)),
|
||||
],
|
||||
(
|
||||
"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):
|
||||
|
||||
def create_token(self):
|
||||
token = get_random_string(50)
|
||||
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).
|
||||
"""
|
||||
# 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
|
||||
|
||||
next_page = request.GET['next']
|
||||
generic_url = reverse('kfet.login.generic')
|
||||
next_page = request.GET["next"]
|
||||
generic_url = reverse("kfet.login.generic")
|
||||
|
||||
if not('k-fet' in next_page and not next_page.startswith(generic_url)):
|
||||
if not ("k-fet" in next_page and not next_page.startswith(generic_url)):
|
||||
return
|
||||
|
||||
# Filter against the logged in user.
|
||||
if not(user.has_perm('kfet.is_team') and user != get_kfet_generic_user()):
|
||||
if not (user.has_perm("kfet.is_team") and user != get_kfet_generic_user()):
|
||||
return
|
||||
|
||||
# Seems legit to add message.
|
||||
text = _("K-Fêt — Ouvrir une session partagée ?")
|
||||
messages.info(request, mark_safe(
|
||||
'<a href="#" data-url="{}" onclick="submit_url(this)">{}</a>'
|
||||
.format(generic_url, text)
|
||||
))
|
||||
messages.info(
|
||||
request,
|
||||
mark_safe(
|
||||
'<a href="#" data-url="{}" onclick="submit_url(this)">{}</a>'.format(
|
||||
generic_url, text
|
||||
)
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser, Group, Permission, User
|
||||
from django.core import signing
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import AnonymousUser, Group, Permission, User
|
||||
from django.test import RequestFactory, TestCase
|
||||
|
||||
from kfet.forms import UserGroupForm
|
||||
|
@ -15,11 +15,11 @@ from .models import GenericTeamToken
|
|||
from .utils import get_kfet_generic_user
|
||||
from .views import GenericLoginView
|
||||
|
||||
|
||||
##
|
||||
# Forms
|
||||
##
|
||||
|
||||
|
||||
class UserGroupFormTests(TestCase):
|
||||
"""Test suite for UserGroupForm."""
|
||||
|
||||
|
@ -31,8 +31,7 @@ class UserGroupFormTests(TestCase):
|
|||
prefix_name = "K-Fêt "
|
||||
names = ["Group 1", "Group 2", "Group 3"]
|
||||
self.kfet_groups = [
|
||||
Group.objects.create(name=prefix_name+name)
|
||||
for name in names
|
||||
Group.objects.create(name=prefix_name + name) for name in names
|
||||
]
|
||||
|
||||
# create a non-K-Fêt group
|
||||
|
@ -41,11 +40,9 @@ class UserGroupFormTests(TestCase):
|
|||
def test_choices(self):
|
||||
"""Only K-Fêt groups are selectable."""
|
||||
form = UserGroupForm(instance=self.user)
|
||||
groups_field = form.fields['groups']
|
||||
groups_field = form.fields["groups"]
|
||||
self.assertQuerysetEqual(
|
||||
groups_field.queryset,
|
||||
[repr(g) for g in self.kfet_groups],
|
||||
ordered=False,
|
||||
groups_field.queryset, [repr(g) for g in self.kfet_groups], ordered=False
|
||||
)
|
||||
|
||||
def test_keep_others(self):
|
||||
|
@ -56,9 +53,7 @@ class UserGroupFormTests(TestCase):
|
|||
user.groups.add(self.other_group)
|
||||
|
||||
# add user to some K-Fêt groups through UserGroupForm
|
||||
data = {
|
||||
'groups': [group.pk for group in self.kfet_groups],
|
||||
}
|
||||
data = {"groups": [group.pk for group in self.kfet_groups]}
|
||||
form = UserGroupForm(data, instance=user)
|
||||
|
||||
form.is_valid()
|
||||
|
@ -71,7 +66,6 @@ class UserGroupFormTests(TestCase):
|
|||
|
||||
|
||||
class KFetGenericUserTests(TestCase):
|
||||
|
||||
def test_exists(self):
|
||||
"""
|
||||
The account is set up when app is ready, so it should exist.
|
||||
|
@ -86,44 +80,39 @@ class KFetGenericUserTests(TestCase):
|
|||
# Backends
|
||||
##
|
||||
|
||||
class AccountBackendTests(TestCase):
|
||||
|
||||
class AccountBackendTests(TestCase):
|
||||
def setUp(self):
|
||||
self.request = RequestFactory().get('/')
|
||||
self.request = RequestFactory().get("/")
|
||||
|
||||
def test_valid(self):
|
||||
acc = Account(trigramme='000')
|
||||
acc.change_pwd('valid')
|
||||
acc.save({'username': 'user'})
|
||||
acc = Account(trigramme="000")
|
||||
acc.change_pwd("valid")
|
||||
acc.save({"username": "user"})
|
||||
|
||||
auth = AccountBackend().authenticate(
|
||||
self.request, kfet_password='valid')
|
||||
auth = AccountBackend().authenticate(self.request, kfet_password="valid")
|
||||
|
||||
self.assertEqual(auth, acc.user)
|
||||
|
||||
def test_invalid(self):
|
||||
auth = AccountBackend().authenticate(
|
||||
self.request, kfet_password='invalid')
|
||||
auth = AccountBackend().authenticate(self.request, kfet_password="invalid")
|
||||
self.assertIsNone(auth)
|
||||
|
||||
|
||||
class GenericBackendTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.request = RequestFactory().get('/')
|
||||
self.request = RequestFactory().get("/")
|
||||
|
||||
def test_valid(self):
|
||||
token = GenericTeamToken.objects.create_token()
|
||||
|
||||
auth = GenericBackend().authenticate(
|
||||
self.request, kfet_token=token.token)
|
||||
auth = GenericBackend().authenticate(self.request, kfet_token=token.token)
|
||||
|
||||
self.assertEqual(auth, get_kfet_generic_user())
|
||||
self.assertEqual(GenericTeamToken.objects.all().count(), 0)
|
||||
|
||||
def test_invalid(self):
|
||||
auth = GenericBackend().authenticate(
|
||||
self.request, kfet_token='invalid')
|
||||
auth = GenericBackend().authenticate(self.request, kfet_token="invalid")
|
||||
self.assertIsNone(auth)
|
||||
|
||||
|
||||
|
@ -131,78 +120,74 @@ class GenericBackendTests(TestCase):
|
|||
# Views
|
||||
##
|
||||
|
||||
class GenericLoginViewTests(TestCase):
|
||||
|
||||
class GenericLoginViewTests(TestCase):
|
||||
def setUp(self):
|
||||
patcher_messages = mock.patch('gestioncof.signals.messages')
|
||||
patcher_messages = mock.patch("gestioncof.signals.messages")
|
||||
patcher_messages.start()
|
||||
self.addCleanup(patcher_messages.stop)
|
||||
|
||||
user_acc = Account(trigramme='000')
|
||||
user_acc.save({'username': 'user'})
|
||||
user_acc = Account(trigramme="000")
|
||||
user_acc.save({"username": "user"})
|
||||
self.user = user_acc.user
|
||||
self.user.set_password('user')
|
||||
self.user.set_password("user")
|
||||
self.user.save()
|
||||
|
||||
team_acc = Account(trigramme='100')
|
||||
team_acc.save({'username': 'team'})
|
||||
team_acc = Account(trigramme="100")
|
||||
team_acc.save({"username": "team"})
|
||||
self.team = team_acc.user
|
||||
self.team.set_password('team')
|
||||
self.team.set_password("team")
|
||||
self.team.save()
|
||||
self.team.user_permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type__app_label='kfet', codename='is_team'),
|
||||
Permission.objects.get(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()
|
||||
|
||||
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):
|
||||
"""
|
||||
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)
|
||||
|
||||
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):
|
||||
"""
|
||||
POST request without token in COOKIES sets a token and redirects to
|
||||
logout url.
|
||||
"""
|
||||
self.client.login(username='team', password='team')
|
||||
self.client.login(username="team", password="team")
|
||||
|
||||
r = self.client.post(self.url)
|
||||
|
||||
self.assertRedirects(
|
||||
r, '/logout?next={}'.format(self.url),
|
||||
fetch_redirect_response=False,
|
||||
r, "/logout?next={}".format(self.url), fetch_redirect_response=False
|
||||
)
|
||||
|
||||
def test_notoken_not_team(self):
|
||||
"""
|
||||
Logged in user must be a team user to initiate login as generic user.
|
||||
"""
|
||||
self.client.login(username='user', password='user')
|
||||
self.client.login(username="user", password="user")
|
||||
|
||||
# With GET.
|
||||
r = self.client.get(self.url)
|
||||
self.assertRedirects(
|
||||
r, '/login?next={}'.format(self.url),
|
||||
fetch_redirect_response=False,
|
||||
r, "/login?next={}".format(self.url), fetch_redirect_response=False
|
||||
)
|
||||
|
||||
# Also with POST.
|
||||
r = self.client.post(self.url)
|
||||
self.assertRedirects(
|
||||
r, '/login?next={}'.format(self.url),
|
||||
fetch_redirect_response=False,
|
||||
r, "/login?next={}".format(self.url), fetch_redirect_response=False
|
||||
)
|
||||
|
||||
def _set_signed_cookie(self, client, key, value):
|
||||
|
@ -216,10 +201,9 @@ class GenericLoginViewTests(TestCase):
|
|||
try:
|
||||
cookie = client.cookies[key]
|
||||
# It also can be emptied.
|
||||
self.assertEqual(cookie.value, '')
|
||||
self.assertEqual(
|
||||
cookie['expires'], 'Thu, 01-Jan-1970 00:00:00 GMT')
|
||||
self.assertEqual(cookie['max-age'], 0)
|
||||
self.assertEqual(cookie.value, "")
|
||||
self.assertEqual(cookie["expires"], "Thu, 01-Jan-1970 00:00:00 GMT")
|
||||
self.assertEqual(cookie["max-age"], 0)
|
||||
except AssertionError:
|
||||
raise AssertionError("The cookie '%s' still exists." % key)
|
||||
|
||||
|
@ -227,16 +211,16 @@ class GenericLoginViewTests(TestCase):
|
|||
"""
|
||||
The kfet generic user is logged in.
|
||||
"""
|
||||
token = GenericTeamToken.objects.create(token='valid')
|
||||
token = GenericTeamToken.objects.create(token="valid")
|
||||
self._set_signed_cookie(
|
||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME, 'valid')
|
||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME, "valid"
|
||||
)
|
||||
|
||||
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._is_cookie_deleted(
|
||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME)
|
||||
self._is_cookie_deleted(self.client, GenericLoginView.TOKEN_COOKIE_NAME)
|
||||
with self.assertRaises(GenericTeamToken.DoesNotExist):
|
||||
token.refresh_from_db()
|
||||
|
||||
|
@ -245,27 +229,26 @@ class GenericLoginViewTests(TestCase):
|
|||
If token is invalid, delete it and try again.
|
||||
"""
|
||||
self._set_signed_cookie(
|
||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME, 'invalid')
|
||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME, "invalid"
|
||||
)
|
||||
|
||||
r = self.client.get(self.url)
|
||||
|
||||
self.assertRedirects(r, self.url, fetch_redirect_response=False)
|
||||
self.assertEqual(r.wsgi_request.user, AnonymousUser())
|
||||
self._is_cookie_deleted(
|
||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME)
|
||||
self._is_cookie_deleted(self.client, GenericLoginView.TOKEN_COOKIE_NAME)
|
||||
|
||||
def test_flow_ok(self):
|
||||
"""
|
||||
A team user is logged in as the kfet generic user.
|
||||
"""
|
||||
self.client.login(username='team', password='team')
|
||||
next_url = '/k-fet/'
|
||||
self.client.login(username="team", password="team")
|
||||
next_url = "/k-fet/"
|
||||
|
||||
r = self.client.post(
|
||||
'{}?next={}'.format(self.url, next_url), follow=True)
|
||||
r = self.client.post("{}?next={}".format(self.url, next_url), follow=True)
|
||||
|
||||
self.assertEqual(r.wsgi_request.user, self.generic_user)
|
||||
self.assertEqual(r.wsgi_request.path, '/k-fet/')
|
||||
self.assertEqual(r.wsgi_request.path, "/k-fet/")
|
||||
|
||||
|
||||
##
|
||||
|
@ -276,10 +259,10 @@ class GenericLoginViewTests(TestCase):
|
|||
# - temporary_auth context processor
|
||||
##
|
||||
|
||||
class TemporaryAuthTests(TestCase):
|
||||
|
||||
class TemporaryAuthTests(TestCase):
|
||||
def setUp(self):
|
||||
patcher_messages = mock.patch('gestioncof.signals.messages')
|
||||
patcher_messages = mock.patch("gestioncof.signals.messages")
|
||||
patcher_messages.start()
|
||||
self.addCleanup(patcher_messages.stop)
|
||||
|
||||
|
@ -287,22 +270,23 @@ class TemporaryAuthTests(TestCase):
|
|||
|
||||
self.middleware = TemporaryAuthMiddleware(mock.Mock())
|
||||
|
||||
user1_acc = Account(trigramme='000')
|
||||
user1_acc.change_pwd('kfet_user1')
|
||||
user1_acc.save({'username': 'user1'})
|
||||
user1_acc = Account(trigramme="000")
|
||||
user1_acc.change_pwd("kfet_user1")
|
||||
user1_acc.save({"username": "user1"})
|
||||
self.user1 = user1_acc.user
|
||||
self.user1.set_password('user1')
|
||||
self.user1.set_password("user1")
|
||||
self.user1.save()
|
||||
|
||||
user2_acc = Account(trigramme='100')
|
||||
user2_acc.change_pwd('kfet_user2')
|
||||
user2_acc.save({'username': 'user2'})
|
||||
user2_acc = Account(trigramme="100")
|
||||
user2_acc.change_pwd("kfet_user2")
|
||||
user2_acc.save({"username": "user2"})
|
||||
self.user2 = user2_acc.user
|
||||
self.user2.set_password('user2')
|
||||
self.user2.set_password("user2")
|
||||
self.user2.save()
|
||||
|
||||
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)
|
||||
|
||||
def test_middleware_header(self):
|
||||
|
@ -310,7 +294,7 @@ class TemporaryAuthTests(TestCase):
|
|||
A user can be authenticated if ``HTTP_KFETPASSWORD`` header of a
|
||||
request contains a valid kfet password.
|
||||
"""
|
||||
request = self.factory.get('/', HTTP_KFETPASSWORD='kfet_user2')
|
||||
request = self.factory.get("/", HTTP_KFETPASSWORD="kfet_user2")
|
||||
request.user = self.user1
|
||||
|
||||
self.middleware(request)
|
||||
|
@ -323,7 +307,7 @@ class TemporaryAuthTests(TestCase):
|
|||
A user can be authenticated if ``KFETPASSWORD`` of POST data contains
|
||||
a valid kfet password.
|
||||
"""
|
||||
request = self.factory.post('/', {'KFETPASSWORD': 'kfet_user2'})
|
||||
request = self.factory.post("/", {"KFETPASSWORD": "kfet_user2"})
|
||||
request.user = self.user1
|
||||
|
||||
self.middleware(request)
|
||||
|
@ -335,34 +319,33 @@ class TemporaryAuthTests(TestCase):
|
|||
"""
|
||||
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
|
||||
|
||||
self.middleware(request)
|
||||
|
||||
self.assertEqual(request.user, self.user1)
|
||||
self.assertFalse(hasattr(request, 'real_user'))
|
||||
self.assertFalse(hasattr(request, "real_user"))
|
||||
|
||||
def test_context_processor(self):
|
||||
"""
|
||||
Context variables give the real authenticated user and his permissions.
|
||||
"""
|
||||
self.client.login(username='user1', password='user1')
|
||||
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.assertNotIn('kfet.is_team', r.context['perms'])
|
||||
self.assertEqual(r.context["user"], self.user1)
|
||||
self.assertNotIn("kfet.is_team", r.context["perms"])
|
||||
|
||||
def test_auth_not_persistent(self):
|
||||
"""
|
||||
The authentication is temporary, i.e. for one request.
|
||||
"""
|
||||
self.client.login(username='user1', password='user1')
|
||||
self.client.login(username="user1", password="user1")
|
||||
|
||||
r1 = self.client.get(
|
||||
'/k-fet/accounts/', HTTP_KFETPASSWORD='kfet_user2')
|
||||
r1 = self.client.get("/k-fet/accounts/", HTTP_KFETPASSWORD="kfet_user2")
|
||||
self.assertEqual(r1.wsgi_request.user, self.user2)
|
||||
|
||||
r2 = self.client.get('/k-fet/accounts/')
|
||||
r2 = self.client.get("/k-fet/accounts/")
|
||||
self.assertEqual(r2.wsgi_request.user, self.user1)
|
||||
|
|
|
@ -23,12 +23,9 @@ def setup_kfet_generic_user(**kwargs):
|
|||
"""
|
||||
generic = get_kfet_generic_user()
|
||||
generic.user_permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type__app_label='kfet',
|
||||
codename='is_team',
|
||||
)
|
||||
Permission.objects.get(content_type__app_label="kfet", codename="is_team")
|
||||
)
|
||||
|
||||
|
||||
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.messages.views import SuccessMessageMixin
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.db.models import Prefetch
|
||||
from django.http import QueryDict
|
||||
from django.shortcuts import redirect, render
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import View
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.views.generic import View
|
||||
from django.views.generic.edit import CreateView, UpdateView
|
||||
|
||||
from .forms import GroupForm
|
||||
|
@ -30,28 +30,33 @@ class GenericLoginView(View):
|
|||
provider, which can be external. Session is unusable as it will be cleared
|
||||
on logout.
|
||||
"""
|
||||
TOKEN_COOKIE_NAME = 'kfettoken'
|
||||
|
||||
@method_decorator(require_http_methods(['GET', 'POST']))
|
||||
TOKEN_COOKIE_NAME = "kfettoken"
|
||||
|
||||
@method_decorator(require_http_methods(["GET", "POST"]))
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
token = request.get_signed_cookie(self.TOKEN_COOKIE_NAME, None)
|
||||
if not token:
|
||||
if not request.user.has_perm('kfet.is_team'):
|
||||
if not request.user.has_perm("kfet.is_team"):
|
||||
return redirect_to_login(request.get_full_path())
|
||||
|
||||
if request.method == 'POST':
|
||||
if request.method == "POST":
|
||||
# Step 1: set token and logout user.
|
||||
return self.prepare_auth()
|
||||
else:
|
||||
# GET request should not change server/client states. Send a
|
||||
# confirmation template to emit a POST request.
|
||||
return render(request, 'kfet/confirm_form.html', {
|
||||
'title': _("Ouvrir une session partagée"),
|
||||
'text': _(
|
||||
return render(
|
||||
request,
|
||||
"kfet/confirm_form.html",
|
||||
{
|
||||
"title": _("Ouvrir une session partagée"),
|
||||
"text": _(
|
||||
"Êtes-vous sûr·e de vouloir ouvrir une session "
|
||||
"partagée ?"
|
||||
),
|
||||
})
|
||||
},
|
||||
)
|
||||
else:
|
||||
# Step 2: validate token.
|
||||
return self.validate_auth(token)
|
||||
|
@ -62,20 +67,19 @@ class GenericLoginView(View):
|
|||
|
||||
# Prepare callback of logout.
|
||||
here_url = reverse(login_generic)
|
||||
if 'next' in self.request.GET:
|
||||
if "next" in self.request.GET:
|
||||
# Keep given next page.
|
||||
here_qd = QueryDict(mutable=True)
|
||||
here_qd['next'] = self.request.GET['next']
|
||||
here_url += '?{}'.format(here_qd.urlencode())
|
||||
here_qd["next"] = self.request.GET["next"]
|
||||
here_url += "?{}".format(here_qd.urlencode())
|
||||
|
||||
logout_url = reverse('cof-logout')
|
||||
logout_url = reverse("cof-logout")
|
||||
logout_qd = QueryDict(mutable=True)
|
||||
logout_qd['next'] = here_url
|
||||
logout_url += '?{}'.format(logout_qd.urlencode(safe='/'))
|
||||
logout_qd["next"] = here_url
|
||||
logout_url += "?{}".format(logout_qd.urlencode(safe="/"))
|
||||
|
||||
resp = redirect(logout_url)
|
||||
resp.set_signed_cookie(
|
||||
self.TOKEN_COOKIE_NAME, token.token, httponly=True)
|
||||
resp.set_signed_cookie(self.TOKEN_COOKIE_NAME, token.token, httponly=True)
|
||||
return resp
|
||||
|
||||
def validate_auth(self, token):
|
||||
|
@ -85,9 +89,9 @@ class GenericLoginView(View):
|
|||
if user:
|
||||
# Log in generic user.
|
||||
login(self.request, user)
|
||||
messages.success(self.request, _(
|
||||
"K-Fêt — Ouverture d'une session partagée."
|
||||
))
|
||||
messages.success(
|
||||
self.request, _("K-Fêt — Ouverture d'une session partagée.")
|
||||
)
|
||||
resp = redirect(self.get_next_url())
|
||||
else:
|
||||
# Try again.
|
||||
|
@ -98,39 +102,34 @@ class GenericLoginView(View):
|
|||
return resp
|
||||
|
||||
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()
|
||||
|
||||
|
||||
@permission_required('kfet.manage_perms')
|
||||
@permission_required("kfet.manage_perms")
|
||||
def account_group(request):
|
||||
user_pre = Prefetch(
|
||||
'user_set',
|
||||
queryset=User.objects.select_related('profile__account_kfet'),
|
||||
"user_set", queryset=User.objects.select_related("profile__account_kfet")
|
||||
)
|
||||
groups = (
|
||||
Group.objects
|
||||
.filter(name__icontains='K-Fêt')
|
||||
.prefetch_related('permissions', user_pre)
|
||||
groups = Group.objects.filter(name__icontains="K-Fêt").prefetch_related(
|
||||
"permissions", user_pre
|
||||
)
|
||||
return render(request, 'kfet/account_group.html', {
|
||||
'groups': groups,
|
||||
})
|
||||
return render(request, "kfet/account_group.html", {"groups": groups})
|
||||
|
||||
|
||||
class AccountGroupCreate(SuccessMessageMixin, CreateView):
|
||||
model = Group
|
||||
template_name = 'kfet/account_group_form.html'
|
||||
template_name = "kfet/account_group_form.html"
|
||||
form_class = GroupForm
|
||||
success_message = 'Nouveau groupe : %(name)s'
|
||||
success_url = reverse_lazy('kfet.account.group')
|
||||
success_message = "Nouveau groupe : %(name)s"
|
||||
success_url = reverse_lazy("kfet.account.group")
|
||||
|
||||
|
||||
class AccountGroupUpdate(SuccessMessageMixin, UpdateView):
|
||||
queryset = Group.objects.filter(name__icontains='K-Fêt')
|
||||
template_name = 'kfet/account_group_form.html'
|
||||
queryset = Group.objects.filter(name__icontains="K-Fêt")
|
||||
template_name = "kfet/account_group_form.html"
|
||||
form_class = GroupForm
|
||||
success_message = 'Groupe modifié : %(name)s'
|
||||
success_url = reverse_lazy('kfet.account.group')
|
||||
success_message = "Groupe modifié : %(name)s"
|
||||
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.db.models import Q
|
||||
from django.http import Http404
|
||||
from django.shortcuts import render
|
||||
from ldap3 import Connection
|
||||
|
||||
from gestioncof.models import User
|
||||
from kfet.decorators import teamkfet_required
|
||||
|
@ -25,81 +25,80 @@ def account_create(request):
|
|||
raise Http404
|
||||
q = request.GET.get("q")
|
||||
|
||||
if (len(q) == 0):
|
||||
if len(q) == 0:
|
||||
return render(request, "kfet/account_create_autocomplete.html")
|
||||
|
||||
data = {'q': q}
|
||||
data = {"q": q}
|
||||
|
||||
queries = {}
|
||||
search_words = q.split()
|
||||
|
||||
# Fetching data from User, CofProfile and Account tables
|
||||
queries['kfet'] = Account.objects
|
||||
queries['users_cof'] = User.objects.filter(profile__is_cof=True)
|
||||
queries['users_notcof'] = User.objects.filter(profile__is_cof=False)
|
||||
queries["kfet"] = Account.objects
|
||||
queries["users_cof"] = User.objects.filter(profile__is_cof=True)
|
||||
queries["users_notcof"] = User.objects.filter(profile__is_cof=False)
|
||||
|
||||
for word in search_words:
|
||||
queries['kfet'] = queries['kfet'].filter(
|
||||
queries["kfet"] = queries["kfet"].filter(
|
||||
Q(cofprofile__user__username__icontains=word)
|
||||
| Q(cofprofile__user__first_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(first_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(first_name__icontains=word)
|
||||
| Q(last_name__icontains=word)
|
||||
)
|
||||
|
||||
# Clearing redundancies
|
||||
queries['kfet'] = queries['kfet'].distinct()
|
||||
queries["kfet"] = queries["kfet"].distinct()
|
||||
usernames = set(
|
||||
queries['kfet'].values_list('cofprofile__user__username', flat=True))
|
||||
queries['kfet'] = [
|
||||
(account, account.cofprofile.user)
|
||||
for account in queries['kfet']
|
||||
queries["kfet"].values_list("cofprofile__user__username", flat=True)
|
||||
)
|
||||
queries["kfet"] = [
|
||||
(account, account.cofprofile.user) for account in queries["kfet"]
|
||||
]
|
||||
|
||||
queries['users_cof'] = \
|
||||
queries['users_cof'].exclude(username__in=usernames).distinct()
|
||||
queries['users_notcof'] = \
|
||||
queries['users_notcof'].exclude(username__in=usernames).distinct()
|
||||
usernames |= set(
|
||||
queries['users_cof'].values_list('username', flat=True))
|
||||
usernames |= set(
|
||||
queries['users_notcof'].values_list('username', flat=True))
|
||||
queries["users_cof"] = (
|
||||
queries["users_cof"].exclude(username__in=usernames).distinct()
|
||||
)
|
||||
queries["users_notcof"] = (
|
||||
queries["users_notcof"].exclude(username__in=usernames).distinct()
|
||||
)
|
||||
usernames |= set(queries["users_cof"].values_list("username", flat=True))
|
||||
usernames |= set(queries["users_notcof"].values_list("username", flat=True))
|
||||
|
||||
# Fetching data from the SPI
|
||||
if getattr(settings, 'LDAP_SERVER_URL', None):
|
||||
if getattr(settings, "LDAP_SERVER_URL", None):
|
||||
# Fetching
|
||||
ldap_query = '(&{:s})'.format(''.join(
|
||||
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=word)
|
||||
for word in search_words if word.isalnum()
|
||||
))
|
||||
ldap_query = "(&{:s})".format(
|
||||
"".join(
|
||||
"(|(cn=*{bit:s}*)(uid=*{bit:s}*))".format(bit=word)
|
||||
for word in search_words
|
||||
if word.isalnum()
|
||||
)
|
||||
)
|
||||
if ldap_query != "(&)":
|
||||
# If none of the bits were legal, we do not perform the query
|
||||
entries = None
|
||||
with Connection(settings.LDAP_SERVER_URL) as conn:
|
||||
conn.search(
|
||||
'dc=spi,dc=ens,dc=fr', ldap_query,
|
||||
attributes=['uid', 'cn']
|
||||
)
|
||||
conn.search("dc=spi,dc=ens,dc=fr", ldap_query, attributes=["uid", "cn"])
|
||||
entries = conn.entries
|
||||
# Clearing redundancies
|
||||
queries['clippers'] = [
|
||||
queries["clippers"] = [
|
||||
Clipper(entry.uid.value, entry.cn.value)
|
||||
for entry in entries
|
||||
if entry.uid.value
|
||||
and entry.uid.value not in usernames
|
||||
if entry.uid.value and entry.uid.value not in usernames
|
||||
]
|
||||
|
||||
# Resulting data
|
||||
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)
|
||||
|
||||
|
@ -111,17 +110,19 @@ def account_search(request):
|
|||
q = request.GET.get("q")
|
||||
words = q.split()
|
||||
|
||||
data = {'q': q}
|
||||
data = {"q": q}
|
||||
|
||||
for word in words:
|
||||
query = Account.objects.filter(
|
||||
Q(cofprofile__user__username__icontains=word) |
|
||||
Q(cofprofile__user__first_name__icontains=word) |
|
||||
Q(cofprofile__user__last_name__icontains=word)
|
||||
Q(cofprofile__user__username__icontains=word)
|
||||
| Q(cofprofile__user__first_name__icontains=word)
|
||||
| Q(cofprofile__user__last_name__icontains=word)
|
||||
).distinct()
|
||||
|
||||
query = [(account.trigramme, account.cofprofile.user.get_full_name())
|
||||
for account in query]
|
||||
query = [
|
||||
(account.trigramme, account.cofprofile.user.get_full_name())
|
||||
for account in query
|
||||
]
|
||||
|
||||
data['accounts'] = query
|
||||
return render(request, 'kfet/account_search_autocomplete.html', data)
|
||||
data["accounts"] = query
|
||||
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):
|
||||
name = 'kfet.cms'
|
||||
label = 'kfetcms'
|
||||
verbose_name = 'CMS K-Fêt'
|
||||
name = "kfet.cms"
|
||||
label = "kfetcms"
|
||||
verbose_name = "CMS K-Fêt"
|
||||
|
||||
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