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 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.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
@ -22,8 +24,17 @@ class ReadOnlyMixin(object):
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):
model = ChoixSpectacle
form = ChoixSpectacleAdminForm
sortable_field_name = "priority"
@ -178,7 +189,7 @@ class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin):
class ChoixSpectacleAdmin(admin.ModelAdmin):
form = autocomplete_light.modelform_factory(ChoixSpectacle, exclude=[])
form = ChoixSpectacleAdminForm
def tirage(self, obj):
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)),
('slots', models.IntegerField(verbose_name=b'Places')),
('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={
'ordering': ('priority', 'date', 'title'),
@ -79,27 +79,27 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='participant',
name='user',
field=models.OneToOneField(to=settings.AUTH_USER_MODEL),
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'),
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'),
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'),
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'),
field=models.ForeignKey(related_name='attribues', to='bda.Spectacle', on_delete=models.CASCADE),
),
migrations.AlterUniqueTogether(
name='choixspectacle',

View file

@ -55,7 +55,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='participant',
name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
),
# Create fields `spectacle` for `Participant` and `Spectacle` models.
# These fields are not nullable, but we first create them as nullable
@ -63,22 +63,22 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='participant',
name='tirage',
field=models.ForeignKey(to='bda.Tirage', null=True),
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),
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'),
field=models.ForeignKey(to='bda.Tirage', on_delete=models.CASCADE),
),
migrations.AlterField(
model_name='spectacle',
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',
name='category',
field=models.ForeignKey(blank=True, to='bda.CategorieSpectacle',
on_delete=models.CASCADE,
null=True),
),
migrations.AddField(
@ -84,6 +85,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='quote',
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',
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,
verbose_name='Vendeur',
related_name='original_shows'),
),
@ -60,6 +62,7 @@ class Migration(migrations.Migration):
model_name='spectaclerevente',
name='soldTo',
field=models.ForeignKey(to='bda.Participant',
on_delete=models.CASCADE,
verbose_name='Vendue à', null=True,
blank=True),
),

View file

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

View file

@ -26,6 +26,12 @@ urlpatterns = [
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"),
url(r'^liste-revente/(?P<tirage_id>\d+)$',
views.list_revente,
name="bda-liste-revente"),

View file

@ -31,6 +31,8 @@ from bda.forms import (
InscriptionInlineFormSet,
)
from utils.views.autocomplete import Select2QuerySetView
@cof_required
def etat_places(request, tirage_id):
@ -811,3 +813,26 @@ def catalogue(request, request_type):
return JsonResponse(data_return, safe=False)
# Si la requête n'est pas de la forme attendue, on quitte avec une erreur
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
INSTALLED_APPS = [
'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',
'grappelli',
'django.contrib.admin',
'django.contrib.admindocs',
'bda',
'autocomplete_light',
'captcha',
'django_cas_ng',
'bootstrapform',
@ -95,7 +100,7 @@ INSTALLED_APPS = [
'kfet.cms',
]
MIDDLEWARE_CLASSES = [
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@ -123,9 +128,9 @@ TEMPLATES = [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
'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',

View file

@ -4,7 +4,7 @@ The settings that are not listed here are imported from .common
"""
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'
@ -37,10 +37,11 @@ def show_toolbar(request):
return DEBUG
INSTALLED_APPS += ["debug_toolbar", "debug_panel"]
MIDDLEWARE_CLASSES = (
["debug_panel.middleware.DebugPanelMiddleware"]
+ MIDDLEWARE_CLASSES
)
MIDDLEWARE = [
"debug_panel.middleware.DebugPanelMiddleware"
] + MIDDLEWARE
DEBUG_TOOLBAR_CONFIG = {
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
}

View file

@ -2,8 +2,6 @@
Fichier principal de configuration des urls du projet GestioCOF
"""
import autocomplete_light
from django.conf import settings
from django.conf.urls import include, url
from django.conf.urls.static import static
@ -22,7 +20,6 @@ from gestioncof.urls import export_patterns, petitcours_patterns, \
clubs_patterns
from gestioncof.autocomplete import autocomplete
autocomplete_light.autodiscover()
admin.autodiscover()
urlpatterns = [
@ -47,18 +44,22 @@ urlpatterns = [
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),
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),
url(r'^outsider/password-change$', django_views.password_change),
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'),
# 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-]+)/'
r'(?P<fullname>.*)$',
gestioncof_views.registration_form2, name="clipper-registration"),
@ -68,7 +69,8 @@ urlpatterns = [
name="empty-registration"),
# Autocompletion
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
url(r'^admin/logout/', gestioncof_views.logout),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
@ -76,10 +78,11 @@ urlpatterns = [
csv_views.admin_list_export,
{'fields': ['username', ]}),
url(r'^admin/', include(admin.site.urls)),
url(r'^grappelli/', include('grappelli.urls')),
# Liens utiles du COF et du BdA
url(r'^utile_cof$', gestioncof_views.utile_cof),
url(r'^utile_bda$', gestioncof_views.utile_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),
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof),
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.db.models import Q
import autocomplete_light
from dal.autocomplete import ModelSelect2
def add_link_field(target_model='', field='', link_text=str,
@ -217,8 +217,16 @@ def user_str(self):
User.__str__ = user_str
class EventRegistrationAdminForm(forms.ModelForm):
class Meta:
widgets = {
'user': ModelSelect2(url='cof-user-autocomplete'),
}
class EventRegistrationAdmin(admin.ModelAdmin):
form = autocomplete_light.modelform_factory(EventRegistration, exclude=[])
form = EventRegistrationAdminForm
list_display = ('__str__', 'event', 'user', 'paid')
list_filter = ('paid',)
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')),
('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)),
('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={
'verbose_name': 'Profil COF',
@ -91,7 +91,7 @@ class Migration(migrations.Migration):
('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')])),
('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={
'verbose_name': 'Champ',
@ -102,7 +102,7 @@ class Migration(migrations.Migration):
fields=[
('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)),
('commentfield', models.ForeignKey(related_name='values', to='gestioncof.EventCommentField')),
('commentfield', models.ForeignKey(related_name='values', to='gestioncof.EventCommentField', on_delete=models.CASCADE)),
],
),
migrations.CreateModel(
@ -111,7 +111,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=200, verbose_name=b'Option')),
('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={
'verbose_name': 'Option',
@ -122,7 +122,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('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={
'verbose_name': 'Choix',
@ -133,10 +133,10 @@ class Migration(migrations.Migration):
fields=[
('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')),
('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')),
('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={
'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)),
('question', models.CharField(max_length=200, verbose_name=b'Question')),
('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={
'verbose_name': 'Question',
@ -251,7 +251,7 @@ class Migration(migrations.Migration):
fields=[
('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')),
('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={
'verbose_name': 'R\xe9ponse',
@ -265,12 +265,12 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='surveyanswer',
name='survey',
field=models.ForeignKey(to='gestioncof.Survey'),
field=models.ForeignKey(to='gestioncof.Survey', on_delete=models.CASCADE),
),
migrations.AddField(
model_name='surveyanswer',
name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
),
migrations.AddField(
model_name='petitcoursdemande',
@ -280,47 +280,47 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='petitcoursdemande',
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(
model_name='petitcoursattributioncounter',
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(
model_name='petitcoursattributioncounter',
name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
),
migrations.AddField(
model_name='petitcoursattribution',
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(
model_name='petitcoursattribution',
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(
model_name='petitcoursattribution',
name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
),
migrations.AddField(
model_name='petitcoursability',
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(
model_name='petitcoursability',
name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
),
migrations.AddField(
model_name='eventcommentvalue',
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(
name='surveyanswer',

View file

@ -23,7 +23,8 @@ class Migration(migrations.Migration):
('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)),
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)),
],
),
migrations.AlterModelOptions(

View file

@ -49,7 +49,10 @@ class CofProfile(models.Model):
(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", max_length=32, blank=True
)
@ -130,7 +133,10 @@ class Event(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)
fieldtype = models.CharField("Type", max_length=10,
choices=TYPE_COMMENT_FIELD, default="text")
@ -144,9 +150,14 @@ class EventCommentField(models.Model):
class EventCommentValue(models.Model):
commentfield = models.ForeignKey(EventCommentField, related_name="values")
registration = models.ForeignKey("EventRegistration",
related_name="comments")
commentfield = models.ForeignKey(
EventCommentField, on_delete=models.CASCADE,
related_name="values",
)
registration = models.ForeignKey(
"EventRegistration", on_delete=models.CASCADE,
related_name="comments",
)
content = models.TextField("Contenu", blank=True, null=True)
def __str__(self):
@ -154,7 +165,10 @@ class EventCommentValue(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)
multi_choices = models.BooleanField("Choix multiples", default=False)
@ -166,7 +180,10 @@ class EventOption(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)
class Meta:
@ -178,8 +195,8 @@ class EventOptionChoice(models.Model):
class EventRegistration(models.Model):
user = models.ForeignKey(User)
event = models.ForeignKey(Event)
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)
@ -207,7 +224,10 @@ class Survey(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)
multi_answers = models.BooleanField("Choix multiples", default=False)
@ -219,7 +239,10 @@ class SurveyQuestion(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)
class Meta:
@ -230,8 +253,8 @@ class SurveyQuestionAnswer(models.Model):
class SurveyAnswer(models.Model):
user = models.ForeignKey(User)
survey = models.ForeignKey(Survey)
user = models.ForeignKey(User, on_delete=models.CASCADE)
survey = models.ForeignKey(Survey, on_delete=models.CASCADE)
answers = models.ManyToManyField(SurveyQuestionAnswer,
related_name="selected_by")
@ -247,7 +270,7 @@ class SurveyAnswer(models.Model):
class CalendarSubscription(models.Model):
token = models.UUIDField()
user = models.OneToOneField(User)
user = models.OneToOneField(User, on_delete=models.CASCADE)
other_shows = models.ManyToManyField(Spectacle)
subscribe_to_events = 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):
user = models.ForeignKey(User)
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matière"))
user = models.ForeignKey(User, on_delete=models.CASCADE)
matiere = models.ForeignKey(
PetitCoursSubject, on_delete=models.CASCADE,
verbose_name=_("Matière"),
)
niveau = models.CharField(_("Niveau"),
choices=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)
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"),
blank=True, null=True)
created = models.DateTimeField(_("Date de création"), auto_now_add=True)
@ -124,9 +130,15 @@ class PetitCoursDemande(models.Model):
class PetitCoursAttribution(models.Model):
user = models.ForeignKey(User)
demande = models.ForeignKey(PetitCoursDemande, verbose_name=_("Demande"))
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matière"))
user = models.ForeignKey(User, on_delete=models.CASCADE)
demande = models.ForeignKey(
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)
rank = models.IntegerField("Rang dans l'email")
selected = models.BooleanField(_("Sélectionné par le demandeur"),
@ -143,8 +155,11 @@ class PetitCoursAttribution(models.Model):
class PetitCoursAttributionCounter(models.Model):
user = models.ForeignKey(User)
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matiere"))
user = models.ForeignKey(User, on_delete=models.CASCADE)
matiere = models.ForeignKey(
PetitCoursSubject, on_delete=models.CASCADE,
verbose_name=_("Matiere"),
)
count = models.IntegerField("Nombre d'envois", default=0)
@classmethod

View file

@ -1,5 +1,4 @@
import json
from datetime import datetime
from custommail.shortcuts import render_custom_mail
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 import messages
from django.db import transaction
from django.utils import timezone
from gestioncof.models import CofProfile
from gestioncof.petits_cours_models import (
@ -285,7 +285,7 @@ def _traitement_post(request, demande):
attrib.save()
demande.traitee = True
demande.traitee_par = request.user
demande.processed = datetime.now()
demande.processed = timezone.now()
demande.save()
return render(request,
"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 %}
<header>
<div class="container banner">
<a href="{% url "gestioncof.views.home" %}">
<a href="{% url "home" %}">
<h1>GestioCOF</h1>
{% block homelink %}
<span class="glyphicon glyphicon-home" aria-hidden=true></span>
@ -11,7 +11,7 @@
</a>
<div class="secondary">
<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>
<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 -->

View file

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

View file

@ -14,7 +14,7 @@
<div class="hm-block">
<ul>
{% 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 %}
</ul>
</div>
@ -24,7 +24,7 @@
<div class="hm-block">
<ul>
{% 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 %}
</ul>
</div>
@ -69,11 +69,11 @@
<h3 class="block-title">Divers<span class="pull-right glyphicon glyphicon-question-sign"></span></h3>
<div class="hm-block">
<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 %}
<li><a href="{% url "gestioncof.views.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 %}
<li><a href="{% url "profile" %}">Éditer mon profil</a></li>
{% if not user.profile.login_clipper %}<li><a href="{% url "password_change" %}">Changer mon mot de passe</a></li>{% endif %}
</ul>
</div>
{% endif %}
@ -86,16 +86,16 @@
<h4>Général</h4>
<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 "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>
</ul>
<ul>
<h4>Évènements & Sondages</h4>
{% 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 %}
{% 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 %}
</ul>
</div>
@ -120,8 +120,8 @@
<h3 class="block-title">Liens utiles<span class="pull-right glyphicon glyphicon-link"></span></h3>
<div class="hm-block">
<ul>
<li><a href="{% url "gestioncof.views.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_cof" %}">Liens utiles du COF</a></li>
<li><a href="{% url "utile_bda" %}">Liens utiles BdA</a></li>
</ul>
</div>
</div>

View file

@ -15,7 +15,7 @@
<p class="error">Identifiants incorrects.</p>
{% endif %}
<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 %}
<div class="form-group">
<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="row" style="margin:0;">
<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">
Compte clipper
</div>
</a>
<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">
Extérieur
</div>

View file

@ -5,5 +5,5 @@
{% block realcontent %}
<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 %}

View file

@ -5,7 +5,7 @@
{% block realcontent %}
<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 %}
{{ form | bootstrap }}
<input type="submit" class="btn btn-primary pull-right" value="Changer" />

View file

@ -34,19 +34,23 @@ petitcours_patterns = [
]
surveys_patterns = [
url(r'^(?P<survey_id>\d+)/status$', views.survey_status),
url(r'^(?P<survey_id>\d+)$', views.survey),
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),
url(r'^(?P<event_id>\d+)/status$', views.event_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$', 'gestioncof.views.calendar'),
url(r'^(?P<token>[a-z0-9-]+)/calendar.ics$',
'gestioncof.views.calendar_ics')
url(r'^subscription$', views.calendar,
name='calendar'),
url(r'^(?P<token>[a-z0-9-]+)/calendar.ics$', views.calendar_ics)
]
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 utils.views.autocomplete import Select2QuerySetView
from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \
SurveyQuestionAnswer
from gestioncof.models import Event, EventRegistration, EventOption, \
@ -54,8 +56,8 @@ def home(request):
def login(request):
if request.user.is_authenticated():
return redirect("gestioncof.views.home")
if request.user.is_authenticated:
return redirect("home")
context = {}
if request.method == "GET" and 'next' in request.GET:
context['next'] = request.GET['next']
@ -786,3 +788,18 @@ class ConfigUpdate(FormView):
def form_valid(self, form):
form.save()
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.
"""
def process_request(self, request):
if request.user.is_authenticated():
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
@ -30,6 +33,8 @@ class TemporaryAuthMiddleware:
request.real_user = request.user
request.user = temp_request_user
return self.get_response(request)
def get_kfet_password(self, request):
return (
request.META.get('HTTP_KFETPASSWORD') or

View file

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

View file

@ -20,7 +20,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='KFetPage',
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)),
('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')),

View file

@ -1,10 +1,11 @@
from functools import reduce
from django.db import models
from django.core.urlresolvers import reverse
from django.core.validators import RegexValidator
from django.contrib.auth.models import User
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.translation import ugettext_lazy as _
from django.db import transaction

View file

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

View file

@ -1,10 +1,12 @@
{% load i18n static %}
{% load wagtailcore_tags %}
{% slugurl "kfet" as kfet_home_url %}
<nav class="navbar navbar-fixed-top">
<div class="container-fluid">
<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' %}">
</a>
</div>
@ -99,7 +101,7 @@
{% endif %}
<li class="divider"></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>
</a>
</li>
@ -108,13 +110,13 @@
{% endif %}
{% if user.is_authenticated and not perms.kfet.is_team %}
<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>
</a>
</li>
{% elif not user.is_authenticated %}
<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 class="glyphicon glyphicon-log-in"></span>
</a>

View file

@ -452,7 +452,7 @@ class AccountGroupUpdateViewTests(ViewTestCaseMixin, TestCase):
'kfet.manage_perms',
)
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):
r = self.client.get(self.url)

View file

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

View file

@ -859,30 +859,34 @@ def account_read_json(request):
'trigramme': account.trigramme }
return JsonResponse(data)
@teamkfet_required
def kpsul_checkout_data(request):
pk = request.POST.get('pk', 0)
if not pk:
pk = 0
data = (Checkout.objects
data = (
Checkout.objects
.annotate(
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_trigramme=F('statements__by__trigramme'),
last_statement_balance=F('statements__balance_new'),
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(
'statements'
'statements__by',
'statements__by__cofprofile__user')
.filter(pk=pk)
.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:
raise Http404
return JsonResponse(data)
@ -1310,13 +1314,13 @@ def history_json(request):
# Construction de la requête (sur les opérations) pour le prefetch
queryset_prefetch = Operation.objects.select_related(
'canceled_by__trigramme', 'addcost_for__trigramme',
'article__name')
'article', 'canceled_by', 'addcost_for')
# Construction de la requête principale
opegroups = (OperationGroup.objects
.prefetch_related(Prefetch('opes', queryset = queryset_prefetch))
.select_related('on_acc__trigramme', 'valid_by__trigramme')
opegroups = (
OperationGroup.objects
.prefetch_related(Prefetch('opes', queryset=queryset_prefetch))
.select_related('on_acc', 'valid_by')
.order_by('at')
)
# Application des filtres

View file

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

View file

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