Merge branch 'master' into Kerl/drop_py2_compat

This commit is contained in:
Martin Pépin 2018-01-10 20:12:59 +01:00
commit 5a5b60ec4d
48 changed files with 483 additions and 290 deletions

View file

@ -1,4 +1,3 @@
import autocomplete_light
from datetime import timedelta from datetime import timedelta
from custommail.shortcuts import send_mass_custom_mail from custommail.shortcuts import send_mass_custom_mail
@ -7,6 +6,9 @@ from django.db.models import Sum, Count
from django.template.defaultfilters import pluralize from django.template.defaultfilters import pluralize
from django.utils import timezone from django.utils import timezone
from django import forms from django import forms
from dal.autocomplete import ModelSelect2
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\ from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
@ -22,8 +24,17 @@ class ReadOnlyMixin(object):
return readonly_fields + self.readonly_fields_update return readonly_fields + self.readonly_fields_update
class ChoixSpectacleAdminForm(forms.ModelForm):
class Meta:
widgets = {
'participant': ModelSelect2(url='bda-participant-autocomplete'),
'spectacle': ModelSelect2(url='bda-spectacle-autocomplete'),
}
class ChoixSpectacleInline(admin.TabularInline): class ChoixSpectacleInline(admin.TabularInline):
model = ChoixSpectacle model = ChoixSpectacle
form = ChoixSpectacleAdminForm
sortable_field_name = "priority" sortable_field_name = "priority"
@ -178,7 +189,7 @@ class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin):
class ChoixSpectacleAdmin(admin.ModelAdmin): class ChoixSpectacleAdmin(admin.ModelAdmin):
form = autocomplete_light.modelform_factory(ChoixSpectacle, exclude=[]) form = ChoixSpectacleAdminForm
def tirage(self, obj): def tirage(self, obj):
return obj.participant.tirage return obj.participant.tirage

View file

@ -1,12 +0,0 @@
import autocomplete_light
from bda.models import Participant, Spectacle
autocomplete_light.register(
Participant, search_fields=('user__username', 'user__first_name',
'user__last_name'),
autocomplete_js_attributes={'placeholder': 'participant...'})
autocomplete_light.register(
Spectacle, search_fields=('title', ),
autocomplete_js_attributes={'placeholder': 'spectacle...'})

View file

@ -59,7 +59,7 @@ class Migration(migrations.Migration):
('price', models.FloatField(verbose_name=b"Prix d'une place", blank=True)), ('price', models.FloatField(verbose_name=b"Prix d'une place", blank=True)),
('slots', models.IntegerField(verbose_name=b'Places')), ('slots', models.IntegerField(verbose_name=b'Places')),
('priority', models.IntegerField(default=1000, verbose_name=b'Priorit\xc3\xa9')), ('priority', models.IntegerField(default=1000, verbose_name=b'Priorit\xc3\xa9')),
('location', models.ForeignKey(to='bda.Salle')), ('location', models.ForeignKey(to='bda.Salle', on_delete=models.CASCADE)),
], ],
options={ options={
'ordering': ('priority', 'date', 'title'), 'ordering': ('priority', 'date', 'title'),
@ -79,27 +79,27 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='participant', model_name='participant',
name='user', name='user',
field=models.OneToOneField(to=settings.AUTH_USER_MODEL), field=models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='choixspectacle', model_name='choixspectacle',
name='participant', name='participant',
field=models.ForeignKey(to='bda.Participant'), field=models.ForeignKey(to='bda.Participant', on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='choixspectacle', model_name='choixspectacle',
name='spectacle', name='spectacle',
field=models.ForeignKey(related_name='participants', to='bda.Spectacle'), field=models.ForeignKey(related_name='participants', to='bda.Spectacle', on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='attribution', model_name='attribution',
name='participant', name='participant',
field=models.ForeignKey(to='bda.Participant'), field=models.ForeignKey(to='bda.Participant', on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='attribution', model_name='attribution',
name='spectacle', name='spectacle',
field=models.ForeignKey(related_name='attribues', to='bda.Spectacle'), field=models.ForeignKey(related_name='attribues', to='bda.Spectacle', on_delete=models.CASCADE),
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name='choixspectacle', name='choixspectacle',

View file

@ -55,7 +55,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='participant', model_name='participant',
name='user', name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL), field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
), ),
# Create fields `spectacle` for `Participant` and `Spectacle` models. # Create fields `spectacle` for `Participant` and `Spectacle` models.
# These fields are not nullable, but we first create them as nullable # These fields are not nullable, but we first create them as nullable
@ -63,22 +63,22 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='participant', model_name='participant',
name='tirage', name='tirage',
field=models.ForeignKey(to='bda.Tirage', null=True), field=models.ForeignKey(to='bda.Tirage', null=True, on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='spectacle', model_name='spectacle',
name='tirage', name='tirage',
field=models.ForeignKey(to='bda.Tirage', null=True), field=models.ForeignKey(to='bda.Tirage', null=True, on_delete=models.CASCADE),
), ),
migrations.RunPython(fill_tirage_fields, migrations.RunPython.noop), migrations.RunPython(fill_tirage_fields, migrations.RunPython.noop),
migrations.AlterField( migrations.AlterField(
model_name='participant', model_name='participant',
name='tirage', name='tirage',
field=models.ForeignKey(to='bda.Tirage'), field=models.ForeignKey(to='bda.Tirage', on_delete=models.CASCADE),
), ),
migrations.AlterField( migrations.AlterField(
model_name='spectacle', model_name='spectacle',
name='tirage', name='tirage',
field=models.ForeignKey(to='bda.Tirage'), field=models.ForeignKey(to='bda.Tirage', on_delete=models.CASCADE),
), ),
] ]

View file

@ -73,6 +73,7 @@ class Migration(migrations.Migration):
model_name='spectacle', model_name='spectacle',
name='category', name='category',
field=models.ForeignKey(blank=True, to='bda.CategorieSpectacle', field=models.ForeignKey(blank=True, to='bda.CategorieSpectacle',
on_delete=models.CASCADE,
null=True), null=True),
), ),
migrations.AddField( migrations.AddField(
@ -84,6 +85,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='quote', model_name='quote',
name='spectacle', name='spectacle',
field=models.ForeignKey(to='bda.Spectacle'), field=models.ForeignKey(to='bda.Spectacle',
on_delete=models.CASCADE),
), ),
] ]

View file

@ -47,12 +47,14 @@ class Migration(migrations.Migration):
model_name='spectaclerevente', model_name='spectaclerevente',
name='attribution', name='attribution',
field=models.OneToOneField(to='bda.Attribution', field=models.OneToOneField(to='bda.Attribution',
on_delete=models.CASCADE,
related_name='revente'), related_name='revente'),
), ),
migrations.AddField( migrations.AddField(
model_name='spectaclerevente', model_name='spectaclerevente',
name='seller', name='seller',
field=models.ForeignKey(to='bda.Participant', field=models.ForeignKey(to='bda.Participant',
on_delete=models.CASCADE,
verbose_name='Vendeur', verbose_name='Vendeur',
related_name='original_shows'), related_name='original_shows'),
), ),
@ -60,6 +62,7 @@ class Migration(migrations.Migration):
model_name='spectaclerevente', model_name='spectaclerevente',
name='soldTo', name='soldTo',
field=models.ForeignKey(to='bda.Participant', field=models.ForeignKey(to='bda.Participant',
on_delete=models.CASCADE,
verbose_name='Vendue à', null=True, verbose_name='Vendue à', null=True,
blank=True), blank=True),
), ),

View file

@ -4,12 +4,15 @@ from datetime import timedelta
from custommail.shortcuts import send_mass_custom_mail from custommail.shortcuts import send_mass_custom_mail
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core import mail
from django.db import models from django.db import models
from django.db.models import Count from django.db.models import Count
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings from django.conf import settings
from django.utils import timezone, formats from django.utils import timezone, formats
from custommail.models import CustomMail
def get_generic_user(): def get_generic_user():
generic, _ = User.objects.get_or_create( generic, _ = User.objects.get_or_create(
@ -57,9 +60,12 @@ class CategorieSpectacle(models.Model):
class Spectacle(models.Model): class Spectacle(models.Model):
title = models.CharField("Titre", max_length=300) title = models.CharField("Titre", max_length=300)
category = models.ForeignKey(CategorieSpectacle, blank=True, null=True) category = models.ForeignKey(
CategorieSpectacle, on_delete=models.CASCADE,
blank=True, null=True,
)
date = models.DateTimeField("Date & heure") date = models.DateTimeField("Date & heure")
location = models.ForeignKey(Salle) location = models.ForeignKey(Salle, on_delete=models.CASCADE)
vips = models.TextField('Personnalités', blank=True) vips = models.TextField('Personnalités', blank=True)
description = models.TextField("Description", blank=True) description = models.TextField("Description", blank=True)
slots_description = models.TextField("Description des places", blank=True) slots_description = models.TextField("Description des places", blank=True)
@ -69,7 +75,7 @@ class Spectacle(models.Model):
max_length=500) max_length=500)
price = models.FloatField("Prix d'une place") price = models.FloatField("Prix d'une place")
slots = models.IntegerField("Places") slots = models.IntegerField("Places")
tirage = models.ForeignKey(Tirage) tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
listing = models.BooleanField("Les places sont sur listing") listing = models.BooleanField("Les places sont sur listing")
rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True, rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True,
null=True) null=True)
@ -133,7 +139,7 @@ class Spectacle(models.Model):
class Quote(models.Model): class Quote(models.Model):
spectacle = models.ForeignKey(Spectacle) spectacle = models.ForeignKey(Spectacle, on_delete=models.CASCADE)
text = models.TextField('Citation') text = models.TextField('Citation')
author = models.CharField('Auteur', max_length=200) author = models.CharField('Auteur', max_length=200)
@ -147,7 +153,7 @@ PAYMENT_TYPES = (
class Participant(models.Model): class Participant(models.Model):
user = models.ForeignKey(User) user = models.ForeignKey(User, on_delete=models.CASCADE)
choices = models.ManyToManyField(Spectacle, choices = models.ManyToManyField(Spectacle,
through="ChoixSpectacle", through="ChoixSpectacle",
related_name="chosen_by") related_name="chosen_by")
@ -158,7 +164,7 @@ class Participant(models.Model):
paymenttype = models.CharField("Moyen de paiement", paymenttype = models.CharField("Moyen de paiement",
max_length=6, choices=PAYMENT_TYPES, max_length=6, choices=PAYMENT_TYPES,
blank=True) blank=True)
tirage = models.ForeignKey(Tirage) tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
choicesrevente = models.ManyToManyField(Spectacle, choicesrevente = models.ManyToManyField(Spectacle,
related_name="subscribed", related_name="subscribed",
blank=True) blank=True)
@ -174,8 +180,11 @@ DOUBLE_CHOICES = (
class ChoixSpectacle(models.Model): class ChoixSpectacle(models.Model):
participant = models.ForeignKey(Participant) participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
spectacle = models.ForeignKey(Spectacle, related_name="participants") spectacle = models.ForeignKey(
Spectacle, on_delete=models.CASCADE,
related_name="participants",
)
priority = models.PositiveIntegerField("Priorité") priority = models.PositiveIntegerField("Priorité")
double_choice = models.CharField("Nombre de places", double_choice = models.CharField("Nombre de places",
default="1", choices=DOUBLE_CHOICES, default="1", choices=DOUBLE_CHOICES,
@ -202,8 +211,11 @@ class ChoixSpectacle(models.Model):
class Attribution(models.Model): class Attribution(models.Model):
participant = models.ForeignKey(Participant) participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
spectacle = models.ForeignKey(Spectacle, related_name="attribues") spectacle = models.ForeignKey(
Spectacle, on_delete=models.CASCADE,
related_name="attribues",
)
given = models.BooleanField("Donnée", default=False) given = models.BooleanField("Donnée", default=False)
def __str__(self): def __str__(self):
@ -212,18 +224,25 @@ class Attribution(models.Model):
class SpectacleRevente(models.Model): class SpectacleRevente(models.Model):
attribution = models.OneToOneField(Attribution, attribution = models.OneToOneField(
related_name="revente") Attribution, on_delete=models.CASCADE,
related_name="revente",
)
date = models.DateTimeField("Date de mise en vente", date = models.DateTimeField("Date de mise en vente",
default=timezone.now) default=timezone.now)
answered_mail = models.ManyToManyField(Participant, answered_mail = models.ManyToManyField(Participant,
related_name="wanted", related_name="wanted",
blank=True) blank=True)
seller = models.ForeignKey(Participant, seller = models.ForeignKey(
related_name="original_shows", Participant, on_delete=models.CASCADE,
verbose_name="Vendeur") verbose_name="Vendeur",
soldTo = models.ForeignKey(Participant, blank=True, null=True, related_name="original_shows",
verbose_name="Vendue à") )
soldTo = models.ForeignKey(
Participant, on_delete=models.CASCADE,
verbose_name="Vendue à",
blank=True, null=True,
)
notif_sent = models.BooleanField("Notification envoyée", notif_sent = models.BooleanField("Notification envoyée",
default=False) default=False)
@ -310,37 +329,55 @@ class SpectacleRevente(models.Model):
# Envoie un mail au gagnant et au vendeur # Envoie un mail au gagnant et au vendeur
winner = random.choice(inscrits) winner = random.choice(inscrits)
self.soldTo = winner self.soldTo = winner
datatuple = []
mails = []
context = { context = {
'acheteur': winner.user, 'acheteur': winner.user,
'vendeur': seller.user, 'vendeur': seller.user,
'show': spectacle, 'show': spectacle,
} }
datatuple.append((
'bda-revente-winner', c_mails_qs = CustomMail.objects.filter(shortname__in=[
context, 'bda-revente-winner', 'bda-revente-loser',
settings.MAIL_DATA['revente']['FROM'],
[winner.user.email],
))
datatuple.append((
'bda-revente-seller', 'bda-revente-seller',
context, ])
settings.MAIL_DATA['revente']['FROM'],
[seller.user.email] c_mails = {cm.shortname: cm for cm in c_mails_qs}
))
mails.append(
c_mails['bda-revente-winner'].get_message(
context,
from_email=settings.MAIL_DATA['revente']['FROM'],
to=[winner.user.email],
)
)
mails.append(
c_mails['bda-revente-seller'].get_message(
context,
from_email=settings.MAIL_DATA['revente']['FROM'],
to=[seller.user.email],
reply_to=[winner.user.email],
)
)
# Envoie un mail aux perdants # Envoie un mail aux perdants
for inscrit in inscrits: for inscrit in inscrits:
if inscrit != winner: if inscrit != winner:
new_context = dict(context) new_context = dict(context)
new_context['acheteur'] = inscrit.user new_context['acheteur'] = inscrit.user
datatuple.append((
'bda-revente-loser', mails.append(
new_context, c_mails['bda-revente-loser'].get_message(
settings.MAIL_DATA['revente']['FROM'], new_context,
[inscrit.user.email] from_email=settings.MAIL_DATA['revente']['FROM'],
)) to=[inscrit.user.email],
send_mass_custom_mail(datatuple) )
)
mail_conn = mail.get_connection()
mail_conn.send_messages(mails)
# Si personne ne veut de la place, elle part au shotgun # Si personne ne veut de la place, elle part au shotgun
else: else:
self.shotgun = True self.shotgun = True

0
bda/tests/__init__.py Normal file
View file

100
bda/tests/test_models.py Normal file
View file

@ -0,0 +1,100 @@
from datetime import timedelta
from unittest import mock
from django.contrib.auth import get_user_model
from django.core import mail
from django.test import TestCase
from django.utils import timezone
from bda.models import (
Attribution, Participant, Salle, Spectacle, SpectacleRevente, Tirage,
)
User = get_user_model()
class SpectacleReventeTests(TestCase):
fixtures = ['gestioncof/management/data/custommail.json']
def setUp(self):
now = timezone.now()
self.t = Tirage.objects.create(
title='Tirage',
ouverture=now - timedelta(days=7),
fermeture=now - timedelta(days=3),
active=True,
)
self.s = Spectacle.objects.create(
title='Spectacle',
date=now + timedelta(days=20),
location=Salle.objects.create(name='Salle', address='Address'),
price=10.5,
slots=5,
tirage=self.t,
listing=False,
)
self.seller = Participant.objects.create(
user=User.objects.create(
username='seller', email='seller@mail.net'),
tirage=self.t,
)
self.p1 = Participant.objects.create(
user=User.objects.create(username='part1', email='part1@mail.net'),
tirage=self.t,
)
self.p2 = Participant.objects.create(
user=User.objects.create(username='part2', email='part2@mail.net'),
tirage=self.t,
)
self.p3 = Participant.objects.create(
user=User.objects.create(username='part3', email='part3@mail.net'),
tirage=self.t,
)
self.attr = Attribution.objects.create(
participant=self.seller,
spectacle=self.s,
)
self.rev = SpectacleRevente.objects.create(
attribution=self.attr,
seller=self.seller,
)
def test_tirage(self):
revente = self.rev
wanted_by = [self.p1, self.p2, self.p3]
revente.answered_mail = wanted_by
with mock.patch('bda.models.random.choice') as mc:
# Set winner to self.p1.
mc.return_value = self.p1
revente.tirage()
# Call to random.choice used participants in wanted_by.
mc_args, _ = mc.call_args
self.assertEqual(set(mc_args[0]), set(wanted_by))
self.assertEqual(revente.soldTo, self.p1)
self.assertTrue(revente.tirage_done)
mails = {m.to[0]: m for m in mail.outbox}
self.assertEqual(len(mails), 4)
m_seller = mails['seller@mail.net']
self.assertListEqual(m_seller.to, ['seller@mail.net'])
self.assertListEqual(m_seller.reply_to, ['part1@mail.net'])
m_winner = mails['part1@mail.net']
self.assertListEqual(m_winner.to, ['part1@mail.net'])
self.assertCountEqual(
[mails['part2@mail.net'].to, mails['part3@mail.net'].to],
[['part2@mail.net'], ['part3@mail.net']],
)

View file

@ -4,7 +4,7 @@ from django.contrib.auth.models import User
from django.test import TestCase, Client from django.test import TestCase, Client
from django.utils import timezone from django.utils import timezone
from .models import Tirage, Spectacle, Salle, CategorieSpectacle from bda.models import Tirage, Spectacle, Salle, CategorieSpectacle
class TestBdAViews(TestCase): class TestBdAViews(TestCase):

View file

@ -26,6 +26,12 @@ urlpatterns = [
url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$', url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$',
views.unpaid, views.unpaid,
name="bda-unpaid"), name="bda-unpaid"),
url(r'^spectacles/autocomplete$',
views.spectacle_autocomplete,
name="bda-spectacle-autocomplete"),
url(r'^participants/autocomplete$',
views.participant_autocomplete,
name="bda-participant-autocomplete"),
url(r'^liste-revente/(?P<tirage_id>\d+)$', url(r'^liste-revente/(?P<tirage_id>\d+)$',
views.list_revente, views.list_revente,
name="bda-liste-revente"), name="bda-liste-revente"),

View file

@ -31,6 +31,8 @@ from bda.forms import (
InscriptionInlineFormSet, InscriptionInlineFormSet,
) )
from utils.views.autocomplete import Select2QuerySetView
@cof_required @cof_required
def etat_places(request, tirage_id): def etat_places(request, tirage_id):
@ -811,3 +813,26 @@ def catalogue(request, request_type):
return JsonResponse(data_return, safe=False) return JsonResponse(data_return, safe=False)
# Si la requête n'est pas de la forme attendue, on quitte avec une erreur # Si la requête n'est pas de la forme attendue, on quitte avec une erreur
return HttpResponseBadRequest() return HttpResponseBadRequest()
##
# Autocomplete views
#
# https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#create-an-autocomplete-view
##
class ParticipantAutocomplete(Select2QuerySetView):
model = Participant
search_fields = ('user__username', 'user__first_name', 'user__last_name')
participant_autocomplete = buro_required(ParticipantAutocomplete.as_view())
class SpectacleAutocomplete(Select2QuerySetView):
model = Spectacle
search_fields = ('title',)
spectacle_autocomplete = buro_required(SpectacleAutocomplete.as_view())

View file

@ -56,17 +56,22 @@ BASE_DIR = os.path.dirname(
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'gestioncof', 'gestioncof',
# Must be before 'django.contrib.admin'.
# https://django-autocomplete-light.readthedocs.io/en/master/install.html
'dal',
'dal_select2',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.sites', 'django.contrib.sites',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'grappelli',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.admindocs', 'django.contrib.admindocs',
'bda', 'bda',
'autocomplete_light',
'captcha', 'captcha',
'django_cas_ng', 'django_cas_ng',
'bootstrapform', 'bootstrapform',
@ -95,7 +100,7 @@ INSTALLED_APPS = [
'kfet.cms', 'kfet.cms',
] ]
MIDDLEWARE_CLASSES = [ MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
@ -123,9 +128,9 @@ TEMPLATES = [
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'django.core.context_processors.i18n', 'django.template.context_processors.i18n',
'django.core.context_processors.media', 'django.template.context_processors.media',
'django.core.context_processors.static', 'django.template.context_processors.static',
'wagtailmenus.context_processors.wagtailmenus', 'wagtailmenus.context_processors.wagtailmenus',
'djconfig.context_processors.config', 'djconfig.context_processors.config',
'gestioncof.shared.context_processor', 'gestioncof.shared.context_processor',

View file

@ -4,7 +4,7 @@ The settings that are not listed here are imported from .common
""" """
from .common import * # NOQA from .common import * # NOQA
from .common import INSTALLED_APPS, MIDDLEWARE_CLASSES from .common import INSTALLED_APPS, MIDDLEWARE
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
@ -37,10 +37,11 @@ def show_toolbar(request):
return DEBUG return DEBUG
INSTALLED_APPS += ["debug_toolbar", "debug_panel"] INSTALLED_APPS += ["debug_toolbar", "debug_panel"]
MIDDLEWARE_CLASSES = (
["debug_panel.middleware.DebugPanelMiddleware"] MIDDLEWARE = [
+ MIDDLEWARE_CLASSES "debug_panel.middleware.DebugPanelMiddleware"
) ] + MIDDLEWARE
DEBUG_TOOLBAR_CONFIG = { DEBUG_TOOLBAR_CONFIG = {
'SHOW_TOOLBAR_CALLBACK': show_toolbar, 'SHOW_TOOLBAR_CALLBACK': show_toolbar,
} }

View file

@ -2,8 +2,6 @@
Fichier principal de configuration des urls du projet GestioCOF Fichier principal de configuration des urls du projet GestioCOF
""" """
import autocomplete_light
from django.conf import settings from django.conf import settings
from django.conf.urls import include, url from django.conf.urls import include, url
from django.conf.urls.static import static from django.conf.urls.static import static
@ -22,7 +20,6 @@ from gestioncof.urls import export_patterns, petitcours_patterns, \
clubs_patterns clubs_patterns
from gestioncof.autocomplete import autocomplete from gestioncof.autocomplete import autocomplete
autocomplete_light.autodiscover()
admin.autodiscover() admin.autodiscover()
urlpatterns = [ urlpatterns = [
@ -47,18 +44,22 @@ urlpatterns = [
name="cof-denied"), name="cof-denied"),
url(r'^cas/login$', django_cas_views.login, name="cas_login_view"), url(r'^cas/login$', django_cas_views.login, name="cas_login_view"),
url(r'^cas/logout$', django_cas_views.logout), url(r'^cas/logout$', django_cas_views.logout),
url(r'^outsider/login$', gestioncof_views.login_ext), url(r'^outsider/login$', gestioncof_views.login_ext,
name="ext_login_view"),
url(r'^outsider/logout$', django_views.logout, {'next_page': 'home'}), url(r'^outsider/logout$', django_views.logout, {'next_page': 'home'}),
url(r'^login$', gestioncof_views.login, name="cof-login"), url(r'^login$', gestioncof_views.login, name="cof-login"),
url(r'^logout$', gestioncof_views.logout, name="cof-logout"), url(r'^logout$', gestioncof_views.logout, name="cof-logout"),
# Infos persos # Infos persos
url(r'^profile$', gestioncof_views.profile), url(r'^profile$', gestioncof_views.profile,
url(r'^outsider/password-change$', django_views.password_change), name='profile'),
url(r'^outsider/password-change$', django_views.password_change,
name='password_change'),
url(r'^outsider/password-change-done$', url(r'^outsider/password-change-done$',
django_views.password_change_done, django_views.password_change_done,
name='password_change_done'), name='password_change_done'),
# Inscription d'un nouveau membre # Inscription d'un nouveau membre
url(r'^registration$', gestioncof_views.registration), url(r'^registration$', gestioncof_views.registration,
name='registration'),
url(r'^registration/clipper/(?P<login_clipper>[\w-]+)/' url(r'^registration/clipper/(?P<login_clipper>[\w-]+)/'
r'(?P<fullname>.*)$', r'(?P<fullname>.*)$',
gestioncof_views.registration_form2, name="clipper-registration"), gestioncof_views.registration_form2, name="clipper-registration"),
@ -68,7 +69,8 @@ urlpatterns = [
name="empty-registration"), name="empty-registration"),
# Autocompletion # Autocompletion
url(r'^autocomplete/registration$', autocomplete), url(r'^autocomplete/registration$', autocomplete),
url(r'^autocomplete/', include('autocomplete_light.urls')), url(r'^user/autocomplete$', gestioncof_views.user_autocomplete,
name='cof-user-autocomplete'),
# Interface admin # Interface admin
url(r'^admin/logout/', gestioncof_views.logout), url(r'^admin/logout/', gestioncof_views.logout),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')), url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
@ -76,10 +78,11 @@ urlpatterns = [
csv_views.admin_list_export, csv_views.admin_list_export,
{'fields': ['username', ]}), {'fields': ['username', ]}),
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
url(r'^grappelli/', include('grappelli.urls')),
# Liens utiles du COF et du BdA # Liens utiles du COF et du BdA
url(r'^utile_cof$', gestioncof_views.utile_cof), url(r'^utile_cof$', gestioncof_views.utile_cof,
url(r'^utile_bda$', gestioncof_views.utile_bda), name='utile_cof'),
url(r'^utile_bda$', gestioncof_views.utile_bda,
name='utile_bda'),
url(r'^utile_bda/bda_diff$', gestioncof_views.liste_bdadiff), url(r'^utile_bda/bda_diff$', gestioncof_views.liste_bdadiff),
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof), url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof),
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente), url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente),

View file

@ -13,7 +13,7 @@ from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.db.models import Q from django.db.models import Q
import autocomplete_light from dal.autocomplete import ModelSelect2
def add_link_field(target_model='', field='', link_text=str, def add_link_field(target_model='', field='', link_text=str,
@ -217,8 +217,16 @@ def user_str(self):
User.__str__ = user_str User.__str__ = user_str
class EventRegistrationAdminForm(forms.ModelForm):
class Meta:
widgets = {
'user': ModelSelect2(url='cof-user-autocomplete'),
}
class EventRegistrationAdmin(admin.ModelAdmin): class EventRegistrationAdmin(admin.ModelAdmin):
form = autocomplete_light.modelform_factory(EventRegistration, exclude=[]) form = EventRegistrationAdminForm
list_display = ('__str__', 'event', 'user', 'paid') list_display = ('__str__', 'event', 'user', 'paid')
list_filter = ('paid',) list_filter = ('paid',)
search_fields = ('user__username', 'user__first_name', 'user__last_name', search_fields = ('user__username', 'user__first_name', 'user__last_name',

View file

@ -1,8 +0,0 @@
import autocomplete_light
from django.contrib.auth.models import User
autocomplete_light.register(
User, search_fields=('username', 'first_name', 'last_name'),
attrs={'placeholder': 'membre...'}
)

View file

@ -48,7 +48,7 @@ class Migration(migrations.Migration):
('is_buro', models.BooleanField(default=False, verbose_name=b'Membre du Bur\xc3\xb4')), ('is_buro', models.BooleanField(default=False, verbose_name=b'Membre du Bur\xc3\xb4')),
('petits_cours_accept', models.BooleanField(default=False, verbose_name=b'Recevoir des petits cours')), ('petits_cours_accept', models.BooleanField(default=False, verbose_name=b'Recevoir des petits cours')),
('petits_cours_remarques', models.TextField(default=b'', verbose_name='Remarques et pr\xe9cisions pour les petits cours', blank=True)), ('petits_cours_remarques', models.TextField(default=b'', verbose_name='Remarques et pr\xe9cisions pour les petits cours', blank=True)),
('user', models.OneToOneField(related_name='profile', to=settings.AUTH_USER_MODEL)), ('user', models.OneToOneField(related_name='profile', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
], ],
options={ options={
'verbose_name': 'Profil COF', 'verbose_name': 'Profil COF',
@ -91,7 +91,7 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=200, verbose_name=b'Champ')), ('name', models.CharField(max_length=200, verbose_name=b'Champ')),
('fieldtype', models.CharField(default=b'text', max_length=10, verbose_name=b'Type', choices=[(b'text', 'Texte long'), (b'char', 'Texte court')])), ('fieldtype', models.CharField(default=b'text', max_length=10, verbose_name=b'Type', choices=[(b'text', 'Texte long'), (b'char', 'Texte court')])),
('default', models.TextField(verbose_name=b'Valeur par d\xc3\xa9faut', blank=True)), ('default', models.TextField(verbose_name=b'Valeur par d\xc3\xa9faut', blank=True)),
('event', models.ForeignKey(related_name='commentfields', to='gestioncof.Event')), ('event', models.ForeignKey(related_name='commentfields', to='gestioncof.Event', on_delete=models.CASCADE)),
], ],
options={ options={
'verbose_name': 'Champ', 'verbose_name': 'Champ',
@ -102,7 +102,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('content', models.TextField(null=True, verbose_name=b'Contenu', blank=True)), ('content', models.TextField(null=True, verbose_name=b'Contenu', blank=True)),
('commentfield', models.ForeignKey(related_name='values', to='gestioncof.EventCommentField')), ('commentfield', models.ForeignKey(related_name='values', to='gestioncof.EventCommentField', on_delete=models.CASCADE)),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
@ -111,7 +111,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=200, verbose_name=b'Option')), ('name', models.CharField(max_length=200, verbose_name=b'Option')),
('multi_choices', models.BooleanField(default=False, verbose_name=b'Choix multiples')), ('multi_choices', models.BooleanField(default=False, verbose_name=b'Choix multiples')),
('event', models.ForeignKey(related_name='options', to='gestioncof.Event')), ('event', models.ForeignKey(related_name='options', to='gestioncof.Event', on_delete=models.CASCADE)),
], ],
options={ options={
'verbose_name': 'Option', 'verbose_name': 'Option',
@ -122,7 +122,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('value', models.CharField(max_length=200, verbose_name=b'Valeur')), ('value', models.CharField(max_length=200, verbose_name=b'Valeur')),
('event_option', models.ForeignKey(related_name='choices', to='gestioncof.EventOption')), ('event_option', models.ForeignKey(related_name='choices', to='gestioncof.EventOption', on_delete=models.CASCADE)),
], ],
options={ options={
'verbose_name': 'Choix', 'verbose_name': 'Choix',
@ -133,10 +133,10 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('paid', models.BooleanField(default=False, verbose_name=b'A pay\xc3\xa9')), ('paid', models.BooleanField(default=False, verbose_name=b'A pay\xc3\xa9')),
('event', models.ForeignKey(to='gestioncof.Event')), ('event', models.ForeignKey(to='gestioncof.Event', on_delete=models.CASCADE)),
('filledcomments', models.ManyToManyField(to='gestioncof.EventCommentField', through='gestioncof.EventCommentValue')), ('filledcomments', models.ManyToManyField(to='gestioncof.EventCommentField', through='gestioncof.EventCommentValue')),
('options', models.ManyToManyField(to='gestioncof.EventOptionChoice')), ('options', models.ManyToManyField(to='gestioncof.EventOptionChoice')),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
], ],
options={ options={
'verbose_name': 'Inscription', 'verbose_name': 'Inscription',
@ -240,7 +240,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('question', models.CharField(max_length=200, verbose_name=b'Question')), ('question', models.CharField(max_length=200, verbose_name=b'Question')),
('multi_answers', models.BooleanField(default=False, verbose_name=b'Choix multiples')), ('multi_answers', models.BooleanField(default=False, verbose_name=b'Choix multiples')),
('survey', models.ForeignKey(related_name='questions', to='gestioncof.Survey')), ('survey', models.ForeignKey(related_name='questions', to='gestioncof.Survey', on_delete=models.CASCADE)),
], ],
options={ options={
'verbose_name': 'Question', 'verbose_name': 'Question',
@ -251,7 +251,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('answer', models.CharField(max_length=200, verbose_name=b'R\xc3\xa9ponse')), ('answer', models.CharField(max_length=200, verbose_name=b'R\xc3\xa9ponse')),
('survey_question', models.ForeignKey(related_name='answers', to='gestioncof.SurveyQuestion')), ('survey_question', models.ForeignKey(related_name='answers', to='gestioncof.SurveyQuestion', on_delete=models.CASCADE)),
], ],
options={ options={
'verbose_name': 'R\xe9ponse', 'verbose_name': 'R\xe9ponse',
@ -265,12 +265,12 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='surveyanswer', model_name='surveyanswer',
name='survey', name='survey',
field=models.ForeignKey(to='gestioncof.Survey'), field=models.ForeignKey(to='gestioncof.Survey', on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='surveyanswer', model_name='surveyanswer',
name='user', name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL), field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='petitcoursdemande', model_name='petitcoursdemande',
@ -280,47 +280,47 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='petitcoursdemande', model_name='petitcoursdemande',
name='traitee_par', name='traitee_par',
field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True), field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='petitcoursattributioncounter', model_name='petitcoursattributioncounter',
name='matiere', name='matiere',
field=models.ForeignKey(verbose_name='Matiere', to='gestioncof.PetitCoursSubject'), field=models.ForeignKey(verbose_name='Matiere', to='gestioncof.PetitCoursSubject', on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='petitcoursattributioncounter', model_name='petitcoursattributioncounter',
name='user', name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL), field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='petitcoursattribution', model_name='petitcoursattribution',
name='demande', name='demande',
field=models.ForeignKey(verbose_name='Demande', to='gestioncof.PetitCoursDemande'), field=models.ForeignKey(verbose_name='Demande', to='gestioncof.PetitCoursDemande', on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='petitcoursattribution', model_name='petitcoursattribution',
name='matiere', name='matiere',
field=models.ForeignKey(verbose_name='Mati\xe8re', to='gestioncof.PetitCoursSubject'), field=models.ForeignKey(verbose_name='Mati\xe8re', to='gestioncof.PetitCoursSubject', on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='petitcoursattribution', model_name='petitcoursattribution',
name='user', name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL), field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='petitcoursability', model_name='petitcoursability',
name='matiere', name='matiere',
field=models.ForeignKey(verbose_name='Mati\xe8re', to='gestioncof.PetitCoursSubject'), field=models.ForeignKey(verbose_name='Mati\xe8re', to='gestioncof.PetitCoursSubject', on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='petitcoursability', model_name='petitcoursability',
name='user', name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL), field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
), ),
migrations.AddField( migrations.AddField(
model_name='eventcommentvalue', model_name='eventcommentvalue',
name='registration', name='registration',
field=models.ForeignKey(related_name='comments', to='gestioncof.EventRegistration'), field=models.ForeignKey(related_name='comments', to='gestioncof.EventRegistration', on_delete=models.CASCADE),
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name='surveyanswer', name='surveyanswer',

View file

@ -23,7 +23,8 @@ class Migration(migrations.Migration):
('subscribe_to_events', models.BooleanField(default=True)), ('subscribe_to_events', models.BooleanField(default=True)),
('subscribe_to_my_shows', models.BooleanField(default=True)), ('subscribe_to_my_shows', models.BooleanField(default=True)),
('other_shows', models.ManyToManyField(to='bda.Spectacle')), ('other_shows', models.ManyToManyField(to='bda.Spectacle')),
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)), ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)),
], ],
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(

View file

@ -49,7 +49,10 @@ class CofProfile(models.Model):
(COTIZ_GRATIS, _("Gratuit")), (COTIZ_GRATIS, _("Gratuit")),
) )
user = models.OneToOneField(User, related_name="profile") user = models.OneToOneField(
User, on_delete=models.CASCADE,
related_name="profile",
)
login_clipper = models.CharField( login_clipper = models.CharField(
"Login clipper", max_length=32, blank=True "Login clipper", max_length=32, blank=True
) )
@ -130,7 +133,10 @@ class Event(models.Model):
class EventCommentField(models.Model): class EventCommentField(models.Model):
event = models.ForeignKey(Event, related_name="commentfields") event = models.ForeignKey(
Event, on_delete=models.CASCADE,
related_name="commentfields",
)
name = models.CharField("Champ", max_length=200) name = models.CharField("Champ", max_length=200)
fieldtype = models.CharField("Type", max_length=10, fieldtype = models.CharField("Type", max_length=10,
choices=TYPE_COMMENT_FIELD, default="text") choices=TYPE_COMMENT_FIELD, default="text")
@ -144,9 +150,14 @@ class EventCommentField(models.Model):
class EventCommentValue(models.Model): class EventCommentValue(models.Model):
commentfield = models.ForeignKey(EventCommentField, related_name="values") commentfield = models.ForeignKey(
registration = models.ForeignKey("EventRegistration", EventCommentField, on_delete=models.CASCADE,
related_name="comments") related_name="values",
)
registration = models.ForeignKey(
"EventRegistration", on_delete=models.CASCADE,
related_name="comments",
)
content = models.TextField("Contenu", blank=True, null=True) content = models.TextField("Contenu", blank=True, null=True)
def __str__(self): def __str__(self):
@ -154,7 +165,10 @@ class EventCommentValue(models.Model):
class EventOption(models.Model): class EventOption(models.Model):
event = models.ForeignKey(Event, related_name="options") event = models.ForeignKey(
Event, on_delete=models.CASCADE,
related_name="options",
)
name = models.CharField("Option", max_length=200) name = models.CharField("Option", max_length=200)
multi_choices = models.BooleanField("Choix multiples", default=False) multi_choices = models.BooleanField("Choix multiples", default=False)
@ -166,7 +180,10 @@ class EventOption(models.Model):
class EventOptionChoice(models.Model): class EventOptionChoice(models.Model):
event_option = models.ForeignKey(EventOption, related_name="choices") event_option = models.ForeignKey(
EventOption, on_delete=models.CASCADE,
related_name="choices",
)
value = models.CharField("Valeur", max_length=200) value = models.CharField("Valeur", max_length=200)
class Meta: class Meta:
@ -178,8 +195,8 @@ class EventOptionChoice(models.Model):
class EventRegistration(models.Model): class EventRegistration(models.Model):
user = models.ForeignKey(User) user = models.ForeignKey(User, on_delete=models.CASCADE)
event = models.ForeignKey(Event) event = models.ForeignKey(Event, on_delete=models.CASCADE)
options = models.ManyToManyField(EventOptionChoice) options = models.ManyToManyField(EventOptionChoice)
filledcomments = models.ManyToManyField(EventCommentField, filledcomments = models.ManyToManyField(EventCommentField,
through=EventCommentValue) through=EventCommentValue)
@ -207,7 +224,10 @@ class Survey(models.Model):
class SurveyQuestion(models.Model): class SurveyQuestion(models.Model):
survey = models.ForeignKey(Survey, related_name="questions") survey = models.ForeignKey(
Survey, on_delete=models.CASCADE,
related_name="questions",
)
question = models.CharField("Question", max_length=200) question = models.CharField("Question", max_length=200)
multi_answers = models.BooleanField("Choix multiples", default=False) multi_answers = models.BooleanField("Choix multiples", default=False)
@ -219,7 +239,10 @@ class SurveyQuestion(models.Model):
class SurveyQuestionAnswer(models.Model): class SurveyQuestionAnswer(models.Model):
survey_question = models.ForeignKey(SurveyQuestion, related_name="answers") survey_question = models.ForeignKey(
SurveyQuestion, on_delete=models.CASCADE,
related_name="answers",
)
answer = models.CharField("Réponse", max_length=200) answer = models.CharField("Réponse", max_length=200)
class Meta: class Meta:
@ -230,8 +253,8 @@ class SurveyQuestionAnswer(models.Model):
class SurveyAnswer(models.Model): class SurveyAnswer(models.Model):
user = models.ForeignKey(User) user = models.ForeignKey(User, on_delete=models.CASCADE)
survey = models.ForeignKey(Survey) survey = models.ForeignKey(Survey, on_delete=models.CASCADE)
answers = models.ManyToManyField(SurveyQuestionAnswer, answers = models.ManyToManyField(SurveyQuestionAnswer,
related_name="selected_by") related_name="selected_by")
@ -247,7 +270,7 @@ class SurveyAnswer(models.Model):
class CalendarSubscription(models.Model): class CalendarSubscription(models.Model):
token = models.UUIDField() token = models.UUIDField()
user = models.OneToOneField(User) user = models.OneToOneField(User, on_delete=models.CASCADE)
other_shows = models.ManyToManyField(Spectacle) other_shows = models.ManyToManyField(Spectacle)
subscribe_to_events = models.BooleanField(default=True) subscribe_to_events = models.BooleanField(default=True)
subscribe_to_my_shows = models.BooleanField(default=True) subscribe_to_my_shows = models.BooleanField(default=True)

View file

@ -33,8 +33,11 @@ class PetitCoursSubject(models.Model):
class PetitCoursAbility(models.Model): class PetitCoursAbility(models.Model):
user = models.ForeignKey(User) user = models.ForeignKey(User, on_delete=models.CASCADE)
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matière")) matiere = models.ForeignKey(
PetitCoursSubject, on_delete=models.CASCADE,
verbose_name=_("Matière"),
)
niveau = models.CharField(_("Niveau"), niveau = models.CharField(_("Niveau"),
choices=LEVELS_CHOICES, choices=LEVELS_CHOICES,
max_length=choices_length(LEVELS_CHOICES)) max_length=choices_length(LEVELS_CHOICES))
@ -82,7 +85,10 @@ class PetitCoursDemande(models.Model):
remarques = models.TextField(_("Remarques et précisions"), blank=True) remarques = models.TextField(_("Remarques et précisions"), blank=True)
traitee = models.BooleanField(_("Traitée"), default=False) traitee = models.BooleanField(_("Traitée"), default=False)
traitee_par = models.ForeignKey(User, blank=True, null=True) traitee_par = models.ForeignKey(
User, on_delete=models.CASCADE,
blank=True, null=True,
)
processed = models.DateTimeField(_("Date de traitement"), processed = models.DateTimeField(_("Date de traitement"),
blank=True, null=True) blank=True, null=True)
created = models.DateTimeField(_("Date de création"), auto_now_add=True) created = models.DateTimeField(_("Date de création"), auto_now_add=True)
@ -124,9 +130,15 @@ class PetitCoursDemande(models.Model):
class PetitCoursAttribution(models.Model): class PetitCoursAttribution(models.Model):
user = models.ForeignKey(User) user = models.ForeignKey(User, on_delete=models.CASCADE)
demande = models.ForeignKey(PetitCoursDemande, verbose_name=_("Demande")) demande = models.ForeignKey(
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matière")) PetitCoursDemande, on_delete=models.CASCADE,
verbose_name=_("Demande"),
)
matiere = models.ForeignKey(
PetitCoursSubject, on_delete=models.CASCADE,
verbose_name=_("Matière"),
)
date = models.DateTimeField(_("Date d'attribution"), auto_now_add=True) date = models.DateTimeField(_("Date d'attribution"), auto_now_add=True)
rank = models.IntegerField("Rang dans l'email") rank = models.IntegerField("Rang dans l'email")
selected = models.BooleanField(_("Sélectionné par le demandeur"), selected = models.BooleanField(_("Sélectionné par le demandeur"),
@ -143,8 +155,11 @@ class PetitCoursAttribution(models.Model):
class PetitCoursAttributionCounter(models.Model): class PetitCoursAttributionCounter(models.Model):
user = models.ForeignKey(User) user = models.ForeignKey(User, on_delete=models.CASCADE)
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matiere")) matiere = models.ForeignKey(
PetitCoursSubject, on_delete=models.CASCADE,
verbose_name=_("Matiere"),
)
count = models.IntegerField("Nombre d'envois", default=0) count = models.IntegerField("Nombre d'envois", default=0)
@classmethod @classmethod

View file

@ -1,5 +1,4 @@
import json import json
from datetime import datetime
from custommail.shortcuts import render_custom_mail from custommail.shortcuts import render_custom_mail
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
@ -11,6 +10,7 @@ from django.conf import settings
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib import messages from django.contrib import messages
from django.db import transaction from django.db import transaction
from django.utils import timezone
from gestioncof.models import CofProfile from gestioncof.models import CofProfile
from gestioncof.petits_cours_models import ( from gestioncof.petits_cours_models import (
@ -285,7 +285,7 @@ def _traitement_post(request, demande):
attrib.save() attrib.save()
demande.traitee = True demande.traitee = True
demande.traitee_par = request.user demande.traitee_par = request.user
demande.processed = datetime.now() demande.processed = timezone.now()
demande.save() demande.save()
return render(request, return render(request,
"gestioncof/traitement_demande_petit_cours_success.html", "gestioncof/traitement_demande_petit_cours_success.html",

View file

@ -1,6 +0,0 @@
{% extends "admin/base.html" %}
{% block extrahead %}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
{% include 'autocomplete_light/static.html' %}
{% endblock %}

View file

@ -1,78 +0,0 @@
{% extends "admin/base_site.html" %}
<!-- LOADING -->
{% load i18n grp_tags log %}
<!-- JAVASCRIPTS -->
{% block javascripts %}
{{ block.super }}
{% endblock %}
<!-- COLTYPE/BODYCLASS-- >
{% block bodyclass %}dashboard{% endblock %}
{% block content-class %}content-grid{% endblock %}
<!-- BREADCRUMBS -->
{% block breadcrumbs %}
<ul class="grp-horizontal-list">
<li>{% trans "Home" %}</li>
</ul>
{% endblock %}
{% block content_title %}
{% if title %}
<header><h1>{{ title }}</h1></header>
{% endif %}
{% endblock %}
<!-- CONTENT -->
{% block content %}
<div class="g-d-c">
<div class="g-d-12 g-d-f">
{% for app in app_list %}
<div class="grp-module" id="app_{{ app.name|lower }}">
<h2><a href="{{ app.app_url }}" class="grp-section">{% trans app.name %}</a></h2>
{% for model in app.models %}
<div class="grp-row">
{% if model.perms.change %}<a href="{{ model.admin_url }}"><strong>{{ model.name }}</strong></a>{% else %}<span><strong>{{ model.name }}</strong></span>{% endif %}
{% if model.perms.add or model.perms.change %}
<ul class="grp-actions">
{% if model.perms.add %}<li class="grp-add-link"><a href="{{ model.admin_url }}add/">{% trans 'Add' %}</a></li>{% endif %}
{% if model.perms.change %}<li class="grp-change-link"><a href="{{ model.admin_url }}">{% trans 'Change' %}</a></li>{% endif %}
</ul>
{% endif %}
</div>
{% endfor %}
</div>
{% empty %}
<p>{% trans "You don´t have permission to edit anything." %}</p>
{% endfor %}
</div>
<div class="g-d-6 g-d-l">
<div class="grp-module" id="grp-recent-actions-module">
<h2>{% trans 'Recent Actions' %}</h2>
<div class="grp-module">
<h3>{% trans 'My Actions' %}</h3>
{% get_admin_log 20 as admin_log for_user user %}
{% if not admin_log %}
<p>{% trans 'None available' %}</p>
{% else %}
<ul class="grp-listing-small">
{% for entry in admin_log %}
<li class="grp-row{% if entry.is_addition %} grp-add-link{% endif %}{% if entry.is_change %} grp-change-link{% endif %}{% if entry.is_deletion %} grp-delete-link{% endif %}">
{% if entry.is_deletion %}
<span>{{ entry.object_repr }}</span>
{% else %}
<a href="{{ entry.get_admin_url }}">{{ entry.object_repr }}</a>
{% endif %}
<span class="grp-font-color-quiet">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -3,7 +3,7 @@
{% block content %} {% block content %}
<header> <header>
<div class="container banner"> <div class="container banner">
<a href="{% url "gestioncof.views.home" %}"> <a href="{% url "home" %}">
<h1>GestioCOF</h1> <h1>GestioCOF</h1>
{% block homelink %} {% block homelink %}
<span class="glyphicon glyphicon-home" aria-hidden=true></span> <span class="glyphicon glyphicon-home" aria-hidden=true></span>
@ -11,7 +11,7 @@
</a> </a>
<div class="secondary"> <div class="secondary">
<span class="hidden-xxs">&nbsp;&nbsp;|&nbsp; </span> <span class="hidden-xxs">&nbsp;&nbsp;|&nbsp; </span>
<span><a href="{% url "gestioncof.views.logout" %}">Se déconnecter&nbsp;<span class="glyphicon glyphicon-log-out"></span></a></span> <span><a href="{% url "cof-logout" %}">Se déconnecter&nbsp;<span class="glyphicon glyphicon-log-out"></span></a></span>
</div> </div>
<h2 class="member-status">{% if user.first_name %}{{ user.first_name }}{% else %}<tt>{{ user.username }}</tt>{% endif %}, {% if user.profile.is_cof %}<tt class="user-is-cof">au COF{% else %}<tt class="user-is-not-cof">non-COF{% endif %}</tt></h2> <h2 class="member-status">{% if user.first_name %}{{ user.first_name }}{% else %}<tt>{{ user.username }}</tt>{% endif %}, {% if user.profile.is_cof %}<tt class="user-is-cof">au COF{% else %}<tt class="user-is-not-cof">non-COF{% endif %}</tt></h2>
</div><!-- /.container --> </div><!-- /.container -->

View file

@ -5,7 +5,7 @@
{% if event.details %} {% if event.details %}
<p>{{ event.details }}</p> <p>{{ event.details }}</p>
{% endif %} {% endif %}
<form method="post" action="{% url 'gestioncof.views.event' event.id %}"> <form method="post" action="{% url 'event.details' event.id %}">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ form.as_p }}
<input type="submit" class="btn-submit" value="Enregistrer" /> <input type="submit" class="btn-submit" value="Enregistrer" />

View file

@ -14,7 +14,7 @@
<div class="hm-block"> <div class="hm-block">
<ul> <ul>
{% for event in open_events %} {% for event in open_events %}
<li><a href="{% url "gestioncof.views.event" event.id %}">{{ event.title }}</a></li> <li><a href="{% url "event.details" event.id %}">{{ event.title }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@ -24,7 +24,7 @@
<div class="hm-block"> <div class="hm-block">
<ul> <ul>
{% for survey in open_surveys %} {% for survey in open_surveys %}
<li><a href="{% url "gestioncof.views.survey" survey.id %}">{{ survey.title }}</a></li> <li><a href="{% url "survey.details" survey.id %}">{{ survey.title }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@ -69,11 +69,11 @@
<h3 class="block-title">Divers<span class="pull-right glyphicon glyphicon-question-sign"></span></h3> <h3 class="block-title">Divers<span class="pull-right glyphicon glyphicon-question-sign"></span></h3>
<div class="hm-block"> <div class="hm-block">
<ul> <ul>
<li><a href="{% url "gestioncof.views.calendar" %}">Calendrier dynamique</a></li> <li><a href="{% url "calendar" %}">Calendrier dynamique</a></li>
{% if user.profile.is_cof %}<li><a href="{% url "petits-cours-inscription" %}">Inscription pour donner des petits cours</a></li>{% endif %} {% if user.profile.is_cof %}<li><a href="{% url "petits-cours-inscription" %}">Inscription pour donner des petits cours</a></li>{% endif %}
<li><a href="{% url "gestioncof.views.profile" %}">Éditer mon profil</a></li> <li><a href="{% url "profile" %}">Éditer mon profil</a></li>
{% if not user.profile.login_clipper %}<li><a href="{% url "django.contrib.auth.views.password_change" %}">Changer mon mot de passe</a></li>{% endif %} {% if not user.profile.login_clipper %}<li><a href="{% url "password_change" %}">Changer mon mot de passe</a></li>{% endif %}
</ul> </ul>
</div> </div>
{% endif %} {% endif %}
@ -86,16 +86,16 @@
<h4>Général</h4> <h4>Général</h4>
<li><a href="{% url "admin:index" %}">Administration générale</a></li> <li><a href="{% url "admin:index" %}">Administration générale</a></li>
<li><a href="{% url "petits-cours-demandes-list" %}">Demandes de petits cours</a></li> <li><a href="{% url "petits-cours-demandes-list" %}">Demandes de petits cours</a></li>
<li><a href="{% url "gestioncof.views.registration" %}">Inscription d'un nouveau membre</a></li> <li><a href="{% url "registration" %}">Inscription d'un nouveau membre</a></li>
<li><a href="{% url "liste-clubs" %}">Gestion des clubs</a></li> <li><a href="{% url "liste-clubs" %}">Gestion des clubs</a></li>
</ul> </ul>
<ul> <ul>
<h4>Évènements & Sondages</h4> <h4>Évènements & Sondages</h4>
{% for event in events %} {% for event in events %}
<li><a href="{% url "gestioncof.views.event_status" event.id %}">Événement : {{ event.title }}</a></li> <li><a href="{% url "event.details.status" event.id %}">Événement : {{ event.title }}</a></li>
{% endfor %} {% endfor %}
{% for survey in surveys %} {% for survey in surveys %}
<li><a href="{% url "gestioncof.views.survey_status" survey.id %}">Sondage : {{ survey.title }}</a></li> <li><a href="{% url "survey.details.status" survey.id %}">Sondage : {{ survey.title }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@ -120,8 +120,8 @@
<h3 class="block-title">Liens utiles<span class="pull-right glyphicon glyphicon-link"></span></h3> <h3 class="block-title">Liens utiles<span class="pull-right glyphicon glyphicon-link"></span></h3>
<div class="hm-block"> <div class="hm-block">
<ul> <ul>
<li><a href="{% url "gestioncof.views.utile_cof" %}">Liens utiles du COF</a></li> <li><a href="{% url "utile_cof" %}">Liens utiles du COF</a></li>
<li><a href="{% url "gestioncof.views.utile_bda" %}">Liens utiles BdA</a></li> <li><a href="{% url "utile_bda" %}">Liens utiles BdA</a></li>
</ul> </ul>
</div> </div>
</div> </div>

View file

@ -15,7 +15,7 @@
<p class="error">Identifiants incorrects.</p> <p class="error">Identifiants incorrects.</p>
{% endif %} {% endif %}
<form class="form-horizontal" method="post" <form class="form-horizontal" method="post"
action="{% url 'gestioncof.views.login_ext' %}?next={{ next|urlencode }}"> action="{% url 'ext_login_view' %}?next={{ next|urlencode }}">
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
<input class="form-control" id="id_username" maxlength="254" name="username" type="text" placeholder="Nom d'utilisateur"> <input class="form-control" id="id_username" maxlength="254" name="username" type="text" placeholder="Nom d'utilisateur">

View file

@ -12,13 +12,13 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row" style="margin:0;"> <div class="row" style="margin:0;">
<a aria-label="Compte clipper" <a aria-label="Compte clipper"
href="{% url 'django_cas_ng.views.login' %}?next={{ next|urlencode }}"> href="{% url 'cas_login_view' %}?next={{ next|urlencode }}">
<div class="col-xs-12 col-sm-6" id="login_clipper"> <div class="col-xs-12 col-sm-6" id="login_clipper">
Compte clipper Compte clipper
</div> </div>
</a> </a>
<a aria-label="Extérieur" <a aria-label="Extérieur"
href="{% url 'gestioncof.views.login_ext' %}?next={{ next|urlencode }}"> href="{% url 'ext_login_view' %}?next={{ next|urlencode }}">
<div class="col-xs-12 col-sm-6" id="login_outsider"> <div class="col-xs-12 col-sm-6" id="login_outsider">
Extérieur Extérieur
</div> </div>

View file

@ -5,5 +5,5 @@
{% block realcontent %} {% block realcontent %}
<h2>Mot de passe modifié avec succès !</h2> <h2>Mot de passe modifié avec succès !</h2>
<h3><a href="{% url "gestioncof.views.home" %}">Retour au menu principal</a></h3> <h3><a href="{% url "home" %}">Retour au menu principal</a></h3>
{% endblock %} {% endblock %}

View file

@ -5,7 +5,7 @@
{% block realcontent %} {% block realcontent %}
<h2>Changement de mot de passe</h2> <h2>Changement de mot de passe</h2>
<form class="form-horizontal" method="post" action="{% url 'django.contrib.auth.views.password_change' %}"> <form class="form-horizontal" method="post" action="{% url 'password_change' %}">
{% csrf_token %} {% csrf_token %}
{{ form | bootstrap }} {{ form | bootstrap }}
<input type="submit" class="btn btn-primary pull-right" value="Changer" /> <input type="submit" class="btn btn-primary pull-right" value="Changer" />

View file

@ -34,19 +34,23 @@ petitcours_patterns = [
] ]
surveys_patterns = [ surveys_patterns = [
url(r'^(?P<survey_id>\d+)/status$', views.survey_status), url(r'^(?P<survey_id>\d+)/status$', views.survey_status,
url(r'^(?P<survey_id>\d+)$', views.survey), name='survey.details.status'),
url(r'^(?P<survey_id>\d+)$', views.survey,
name='survey.details'),
] ]
events_patterns = [ events_patterns = [
url(r'^(?P<event_id>\d+)$', views.event), url(r'^(?P<event_id>\d+)$', views.event,
url(r'^(?P<event_id>\d+)/status$', views.event_status), name='event.details'),
url(r'^(?P<event_id>\d+)/status$', views.event_status,
name='event.details.status'),
] ]
calendar_patterns = [ calendar_patterns = [
url(r'^subscription$', 'gestioncof.views.calendar'), url(r'^subscription$', views.calendar,
url(r'^(?P<token>[a-z0-9-]+)/calendar.ics$', name='calendar'),
'gestioncof.views.calendar_ics') url(r'^(?P<token>[a-z0-9-]+)/calendar.ics$', views.calendar_ics)
] ]
clubs_patterns = [ clubs_patterns = [

View file

@ -20,6 +20,8 @@ from django.contrib import messages
from django_cas_ng.views import logout as cas_logout_view from django_cas_ng.views import logout as cas_logout_view
from utils.views.autocomplete import Select2QuerySetView
from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \ from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \
SurveyQuestionAnswer SurveyQuestionAnswer
from gestioncof.models import Event, EventRegistration, EventOption, \ from gestioncof.models import Event, EventRegistration, EventOption, \
@ -54,8 +56,8 @@ def home(request):
def login(request): def login(request):
if request.user.is_authenticated(): if request.user.is_authenticated:
return redirect("gestioncof.views.home") return redirect("home")
context = {} context = {}
if request.method == "GET" and 'next' in request.GET: if request.method == "GET" and 'next' in request.GET:
context['next'] = request.GET['next'] context['next'] = request.GET['next']
@ -786,3 +788,18 @@ class ConfigUpdate(FormView):
def form_valid(self, form): def form_valid(self, form):
form.save() form.save()
return super().form_valid(form) return super().form_valid(form)
##
# Autocomplete views
#
# https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#create-an-autocomplete-view
##
class UserAutocomplete(Select2QuerySetView):
model = User
search_fields = ('username', 'first_name', 'last_name')
user_autocomplete = buro_required(UserAutocomplete.as_view())

View file

@ -12,8 +12,11 @@ class TemporaryAuthMiddleware:
values from CofProfile and Account of this user. values from CofProfile and Account of this user.
""" """
def process_request(self, request): def __init__(self, get_response):
if request.user.is_authenticated(): self.get_response = get_response
def __call__(self, request):
if request.user.is_authenticated:
# avoid multiple db accesses in views and templates # avoid multiple db accesses in views and templates
request.user = ( request.user = (
User.objects User.objects
@ -30,6 +33,8 @@ class TemporaryAuthMiddleware:
request.real_user = request.user request.real_user = request.user
request.user = temp_request_user request.user = temp_request_user
return self.get_response(request)
def get_kfet_password(self, request): def get_kfet_password(self, request):
return ( return (
request.META.get('HTTP_KFETPASSWORD') or request.META.get('HTTP_KFETPASSWORD') or

View file

@ -285,6 +285,8 @@ class TemporaryAuthTests(TestCase):
self.factory = RequestFactory() self.factory = RequestFactory()
self.middleware = TemporaryAuthMiddleware(mock.Mock())
user1_acc = Account(trigramme='000') user1_acc = Account(trigramme='000')
user1_acc.change_pwd('kfet_user1') user1_acc.change_pwd('kfet_user1')
user1_acc.save({'username': 'user1'}) user1_acc.save({'username': 'user1'})
@ -311,7 +313,7 @@ class TemporaryAuthTests(TestCase):
request = self.factory.get('/', HTTP_KFETPASSWORD='kfet_user2') request = self.factory.get('/', HTTP_KFETPASSWORD='kfet_user2')
request.user = self.user1 request.user = self.user1
TemporaryAuthMiddleware().process_request(request) self.middleware(request)
self.assertEqual(request.user, self.user2) self.assertEqual(request.user, self.user2)
self.assertEqual(request.real_user, self.user1) self.assertEqual(request.real_user, self.user1)
@ -324,7 +326,7 @@ class TemporaryAuthTests(TestCase):
request = self.factory.post('/', {'KFETPASSWORD': 'kfet_user2'}) request = self.factory.post('/', {'KFETPASSWORD': 'kfet_user2'})
request.user = self.user1 request.user = self.user1
TemporaryAuthMiddleware().process_request(request) self.middleware(request)
self.assertEqual(request.user, self.user2) self.assertEqual(request.user, self.user2)
self.assertEqual(request.real_user, self.user1) self.assertEqual(request.real_user, self.user1)
@ -336,7 +338,7 @@ class TemporaryAuthTests(TestCase):
request = self.factory.post('/', {'KFETPASSWORD': 'invalid'}) request = self.factory.post('/', {'KFETPASSWORD': 'invalid'})
request.user = self.user1 request.user = self.user1
TemporaryAuthMiddleware().process_request(request) self.middleware(request)
self.assertEqual(request.user, self.user1) self.assertEqual(request.user, self.user1)
self.assertFalse(hasattr(request, 'real_user')) self.assertFalse(hasattr(request, 'real_user'))

View file

@ -20,7 +20,7 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='KFetPage', name='KFetPage',
fields=[ fields=[
('page_ptr', models.OneToOneField(serialize=False, primary_key=True, parent_link=True, auto_created=True, to='wagtailcore.Page')), ('page_ptr', models.OneToOneField(serialize=False, primary_key=True, parent_link=True, auto_created=True, to='wagtailcore.Page', on_delete=models.CASCADE)),
('no_header', models.BooleanField(verbose_name='Sans en-tête', help_text="Coché, l'en-tête (avec le titre) de la page n'est pas affiché.", default=False)), ('no_header', models.BooleanField(verbose_name='Sans en-tête', help_text="Coché, l'en-tête (avec le titre) de la page n'est pas affiché.", default=False)),
('content', wagtail.wagtailcore.fields.StreamField((('rich', wagtail.wagtailcore.blocks.RichTextBlock(label='Éditeur')), ('carte', kfet.cms.models.MenuBlock()), ('group_team', wagtail.wagtailcore.blocks.StructBlock((('show_only', wagtail.wagtailcore.blocks.IntegerBlock(help_text='Nombre initial de membres affichés. Laisser vide pour tou-te-s les afficher.', required=False, label='Montrer seulement')), ('members', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailsnippets.blocks.SnippetChooserBlock(kfet.cms.models.MemberTeam), classname='team-group', label='K-Fêt-eux-ses'))))), ('group', wagtail.wagtailcore.blocks.StreamBlock((('rich', wagtail.wagtailcore.blocks.RichTextBlock(label='Éditeur')), ('carte', kfet.cms.models.MenuBlock()), ('group_team', wagtail.wagtailcore.blocks.StructBlock((('show_only', wagtail.wagtailcore.blocks.IntegerBlock(help_text='Nombre initial de membres affichés. Laisser vide pour tou-te-s les afficher.', required=False, label='Montrer seulement')), ('members', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailsnippets.blocks.SnippetChooserBlock(kfet.cms.models.MemberTeam), classname='team-group', label='K-Fêt-eux-ses')))))), label='Contenu groupé'))), verbose_name='Contenu')), ('content', wagtail.wagtailcore.fields.StreamField((('rich', wagtail.wagtailcore.blocks.RichTextBlock(label='Éditeur')), ('carte', kfet.cms.models.MenuBlock()), ('group_team', wagtail.wagtailcore.blocks.StructBlock((('show_only', wagtail.wagtailcore.blocks.IntegerBlock(help_text='Nombre initial de membres affichés. Laisser vide pour tou-te-s les afficher.', required=False, label='Montrer seulement')), ('members', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailsnippets.blocks.SnippetChooserBlock(kfet.cms.models.MemberTeam), classname='team-group', label='K-Fêt-eux-ses'))))), ('group', wagtail.wagtailcore.blocks.StreamBlock((('rich', wagtail.wagtailcore.blocks.RichTextBlock(label='Éditeur')), ('carte', kfet.cms.models.MenuBlock()), ('group_team', wagtail.wagtailcore.blocks.StructBlock((('show_only', wagtail.wagtailcore.blocks.IntegerBlock(help_text='Nombre initial de membres affichés. Laisser vide pour tou-te-s les afficher.', required=False, label='Montrer seulement')), ('members', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailsnippets.blocks.SnippetChooserBlock(kfet.cms.models.MemberTeam), classname='team-group', label='K-Fêt-eux-ses')))))), label='Contenu groupé'))), verbose_name='Contenu')),
('layout', models.CharField(max_length=255, choices=[('kfet/base_col_1.html', 'Une colonne : centrée sur la page'), ('kfet/base_col_2.html', 'Deux colonnes : fixe à gauche, contenu à droite'), ('kfet/base_col_mult.html', 'Contenu scindé sur plusieurs colonnes')], help_text='Comment cette page devrait être affichée ?', verbose_name='Template', default='kfet/base_col_mult.html')), ('layout', models.CharField(max_length=255, choices=[('kfet/base_col_1.html', 'Une colonne : centrée sur la page'), ('kfet/base_col_2.html', 'Deux colonnes : fixe à gauche, contenu à droite'), ('kfet/base_col_mult.html', 'Contenu scindé sur plusieurs colonnes')], help_text='Comment cette page devrait être affichée ?', verbose_name='Template', default='kfet/base_col_mult.html')),

View file

@ -1,10 +1,11 @@
from functools import reduce from functools import reduce
from django.db import models from django.db import models
from django.core.urlresolvers import reverse
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from django.contrib.auth.models import User from django.contrib.auth.models import User
from gestioncof.models import CofProfile from gestioncof.models import CofProfile
from django.urls import reverse
from django.utils.six.moves import reduce
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db import transaction from django.db import transaction

View file

@ -85,7 +85,7 @@ class OpenKfet(CachedMixin, object):
'admin_status': self.admin_status(status), 'admin_status': self.admin_status(status),
'force_close': self.force_close, 'force_close': self.force_close,
} }
return base, {**base, **restrict} return base, dict(base, **restrict)
def export(self, user): def export(self, user):
"""Export internal state for a given user. """Export internal state for a given user.

View file

@ -1,10 +1,12 @@
{% load i18n static %} {% load i18n static %}
{% load wagtailcore_tags %} {% load wagtailcore_tags %}
{% slugurl "kfet" as kfet_home_url %}
<nav class="navbar navbar-fixed-top"> <nav class="navbar navbar-fixed-top">
<div class="container-fluid"> <div class="container-fluid">
<div class="navbar-header"> <div class="navbar-header">
<a class="navbar-brand" href="{% slugurl "k-fet" %}"> <a class="navbar-brand" href="{{ kfet_home_url }}">
<img src="{% static 'kfet/img/logo3.png' %}"> <img src="{% static 'kfet/img/logo3.png' %}">
</a> </a>
</div> </div>
@ -99,7 +101,7 @@
{% endif %} {% endif %}
<li class="divider"></li> <li class="divider"></li>
<li> <li>
<a href="{% url "cof-logout" %}?next={% slugurl "k-fet" %}"> <a href="{% url "cof-logout" %}?next={{ kfet_home_url|urlencode }}">
<span class="glyphicon glyphicon-log-out"></span><span>Déconnexion</span> <span class="glyphicon glyphicon-log-out"></span><span>Déconnexion</span>
</a> </a>
</li> </li>
@ -108,13 +110,13 @@
{% endif %} {% endif %}
{% if user.is_authenticated and not perms.kfet.is_team %} {% if user.is_authenticated and not perms.kfet.is_team %}
<li> <li>
<a href="{% url "cof-logout" %}?next={% slugurl "k-fet" %}" title="Déconnexion"> <a href="{% url "cof-logout" %}?next={{ kfet_home_url|urlencode }}" title="Déconnexion">
<span class="glyphicon glyphicon-log-out"></span> <span class="glyphicon glyphicon-log-out"></span>
</a> </a>
</li> </li>
{% elif not user.is_authenticated %} {% elif not user.is_authenticated %}
<li> <li>
<a href="{% url "cof-login" %}?next={{ request.path }}" title="Connexion"> <a href="{% url "cof-login" %}?next={{ request.path|urlencode }}" title="Connexion">
<span>Connexion</span><!-- <span>Connexion</span><!--
--><span class="glyphicon glyphicon-log-in"></span> --><span class="glyphicon glyphicon-log-in"></span>
</a> </a>

View file

@ -452,7 +452,7 @@ class AccountGroupUpdateViewTests(ViewTestCaseMixin, TestCase):
'kfet.manage_perms', 'kfet.manage_perms',
) )
self.group = Group.objects.create(name='K-Fêt - Group') self.group = Group.objects.create(name='K-Fêt - Group')
self.group.permissions = self.perms.values() self.group.permissions.set(self.perms.values())
def test_get_ok(self): def test_get_ok(self):
r = self.client.get(self.url) r = self.client.get(self.url)

View file

@ -57,7 +57,7 @@ class TestCaseMixin:
'path': request.get_full_path(), 'path': request.get_full_path(),
'username': ( 'username': (
"'{}'".format(request.user) "'{}'".format(request.user)
if request.user.is_authenticated() if request.user.is_authenticated
else 'anonymous' else 'anonymous'
), ),
'code': response.status_code, 'code': response.status_code,
@ -96,7 +96,7 @@ class TestCaseMixin:
'path': request.get_full_path(), 'path': request.get_full_path(),
'username': ( 'username': (
"'%s'" % request.user "'%s'" % request.user
if request.user.is_authenticated() if request.user.is_authenticated
else 'anonymous' else 'anonymous'
), ),
'form_ctx': form_ctx, 'form_ctx': form_ctx,

View file

@ -184,5 +184,5 @@ def user_add_perms(user, perms_labels):
# If permissions have already been fetched for this user, we need to reload # If permissions have already been fetched for this user, we need to reload
# it to avoid using of the previous permissions cache. # it to avoid using of the previous permissions cache.
# https://docs.djangoproject.com/en/1.11/topics/auth/default/#permission-caching # https://docs.djangoproject.com/en/dev/topics/auth/default/#permission-caching
return User.objects.get(pk=user.pk) return User.objects.get(pk=user.pk)

View file

@ -859,30 +859,34 @@ def account_read_json(request):
'trigramme': account.trigramme } 'trigramme': account.trigramme }
return JsonResponse(data) return JsonResponse(data)
@teamkfet_required @teamkfet_required
def kpsul_checkout_data(request): def kpsul_checkout_data(request):
pk = request.POST.get('pk', 0) pk = request.POST.get('pk', 0)
if not pk: if not pk:
pk = 0 pk = 0
data = (Checkout.objects
data = (
Checkout.objects
.annotate( .annotate(
last_statement_by_first_name=F('statements__by__cofprofile__user__first_name'), last_statement_by_first_name=F('statements__by__cofprofile__user__first_name'),
last_statement_by_last_name=F('statements__by__cofprofile__user__last_name'), last_statement_by_last_name=F('statements__by__cofprofile__user__last_name'),
last_statement_by_trigramme=F('statements__by__trigramme'), last_statement_by_trigramme=F('statements__by__trigramme'),
last_statement_balance=F('statements__balance_new'), last_statement_balance=F('statements__balance_new'),
last_statement_at=F('statements__at')) last_statement_at=F('statements__at'))
.values(
'id', 'name', 'balance', 'valid_from', 'valid_to',
'last_statement_balance', 'last_statement_at',
'last_statement_by_trigramme', 'last_statement_by_last_name',
'last_statement_by_first_name')
.select_related( .select_related(
'statements' 'statements'
'statements__by', 'statements__by',
'statements__by__cofprofile__user') 'statements__by__cofprofile__user')
.filter(pk=pk) .filter(pk=pk)
.order_by('statements__at') .order_by('statements__at')
.last()) .values(
'id', 'name', 'balance', 'valid_from', 'valid_to',
'last_statement_balance', 'last_statement_at',
'last_statement_by_trigramme', 'last_statement_by_last_name',
'last_statement_by_first_name')
.last()
)
if data is None: if data is None:
raise Http404 raise Http404
return JsonResponse(data) return JsonResponse(data)
@ -1310,13 +1314,13 @@ def history_json(request):
# Construction de la requête (sur les opérations) pour le prefetch # Construction de la requête (sur les opérations) pour le prefetch
queryset_prefetch = Operation.objects.select_related( queryset_prefetch = Operation.objects.select_related(
'canceled_by__trigramme', 'addcost_for__trigramme', 'article', 'canceled_by', 'addcost_for')
'article__name')
# Construction de la requête principale # Construction de la requête principale
opegroups = (OperationGroup.objects opegroups = (
.prefetch_related(Prefetch('opes', queryset = queryset_prefetch)) OperationGroup.objects
.select_related('on_acc__trigramme', 'valid_by__trigramme') .prefetch_related(Prefetch('opes', queryset=queryset_prefetch))
.select_related('on_acc', 'valid_by')
.order_by('at') .order_by('at')
) )
# Application des filtres # Application des filtres

View file

@ -1,4 +1,4 @@
-r requirements.txt -r requirements.txt
-e git://github.com/jazzband/django-debug-toolbar.git@88ddc7bdf39c7ff660eac054eab225ac22926754#egg=django-debug-toolbar django-debug-toolbar
django-debug-panel django-debug-panel
ipython ipython

View file

@ -1,16 +1,15 @@
configparser==3.5.0 configparser==3.5.0
Django==1.8.* Django==1.11.*
django-autocomplete-light==2.3.3 django-autocomplete-light==3.1.3
django-autoslug==1.9.3 django-autoslug==1.9.3
django-cas-ng==3.5.7 django-cas-ng==3.5.7
django-djconfig==0.5.3 django-djconfig==0.5.3
django-grappelli==2.8.1 django-recaptcha==1.2.1
django-recaptcha==1.0.5
django-redis-cache==1.7.1 django-redis-cache==1.7.1
icalendar
psycopg2 psycopg2
Pillow==3.3.0 Pillow
unicodecsv==0.14.1 unicodecsv
icalendar==3.10
django-bootstrap-form==3.2.1 django-bootstrap-form==3.2.1
asgiref==1.1.1 asgiref==1.1.1
daphne==1.3.0 daphne==1.3.0
@ -24,8 +23,5 @@ python-dateutil
wagtail==1.10.* wagtail==1.10.*
wagtailmenus==2.2.* wagtailmenus==2.2.*
# Remove this when we switch to Django 1.11
djangorestframework==3.6.4
# Production tools # Production tools
wheel wheel

0
utils/__init__.py Normal file
View file

0
utils/views/__init__.py Normal file
View file

View file

@ -0,0 +1,26 @@
from django.db.models import Q
from dal import autocomplete
class Select2QuerySetView(autocomplete.Select2QuerySetView):
model = None
search_fields = []
def get_queryset_filter(self):
q = self.q
filter_q = Q()
if not q:
return filter_q
words = q.split()
for word in words:
for field in self.search_fields:
filter_q |= Q(**{'{}__icontains'.format(field): word})
return filter_q
def get_queryset(self):
return self.model.objects.filter(self.get_queryset_filter())