diff --git a/gestioncof/admin.py b/gestioncof/admin.py
index a177c26c..0d7d7143 100644
--- a/gestioncof/admin.py
+++ b/gestioncof/admin.py
@@ -1,9 +1,3 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
-
from django import forms
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
@@ -18,13 +12,12 @@ from django.contrib.auth.admin import UserAdmin
from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
from django.db.models import Q
-import django.utils.six as six
import autocomplete_light
-def add_link_field(target_model='', field='', link_text=six.text_type,
- desc_text=six.text_type):
+def add_link_field(target_model='', field='', link_text=str,
+ desc_text=str):
def add_link(cls):
reverse_name = target_model or cls.model.__name__.lower()
@@ -139,7 +132,6 @@ def ProfileInfo(field, short_description, boolean=False):
User.profile_login_clipper = FkeyLookup("profile__login_clipper",
"Login clipper")
-User.profile_num = FkeyLookup("profile__num", "Numéro")
User.profile_phone = ProfileInfo("phone", "Téléphone")
User.profile_occupation = ProfileInfo("occupation", "Occupation")
User.profile_departement = ProfileInfo("departement", "Departement")
@@ -166,10 +158,12 @@ class UserProfileAdmin(UserAdmin):
is_cof.short_description = 'Membre du COF'
is_cof.boolean = True
- list_display = ('profile_num',) + UserAdmin.list_display \
+ list_display = (
+ UserAdmin.list_display
+ ('profile_login_clipper', 'profile_phone', 'profile_occupation',
'profile_mailing_cof', 'profile_mailing_bda',
'profile_mailing_bda_revente', 'is_cof', 'is_buro', )
+ )
list_display_links = ('username', 'email', 'first_name', 'last_name')
list_filter = UserAdmin.list_filter \
+ ('profile__is_cof', 'profile__is_buro', 'profile__mailing_cof',
@@ -215,21 +209,17 @@ class UserProfileAdmin(UserAdmin):
# FIXME: This is absolutely horrible.
-def user_unicode(self):
+def user_str(self):
if self.first_name and self.last_name:
- return "%s %s (%s)" % (self.first_name, self.last_name, self.username)
+ return "{} ({})".format(self.get_full_name(), self.username)
else:
return self.username
-if six.PY2:
- User.__unicode__ = user_unicode
-else:
- User.__str__ = user_unicode
+User.__str__ = user_str
class EventRegistrationAdmin(admin.ModelAdmin):
form = autocomplete_light.modelform_factory(EventRegistration, exclude=[])
- list_display = ('__unicode__' if six.PY2 else '__str__', 'event', 'user',
- 'paid')
+ list_display = ('__str__', 'event', 'user', 'paid')
list_filter = ('paid',)
search_fields = ('user__username', 'user__first_name', 'user__last_name',
'user__email', 'event__title')
diff --git a/gestioncof/forms.py b/gestioncof/forms.py
index 3a519a39..b52f5417 100644
--- a/gestioncof/forms.py
+++ b/gestioncof/forms.py
@@ -1,21 +1,13 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
-
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from django.forms.widgets import RadioSelect, CheckboxSelectMultiple
from django.forms.formsets import BaseFormSet, formset_factory
-from django.db.models import Max
from django.core.validators import MinLengthValidator
from gestioncof.models import CofProfile, EventCommentValue, \
CalendarSubscription, Club
from gestioncof.widgets import TriStateCheckbox
-from gestioncof.shared import lock_table, unlock_table
from bda.models import Spectacle
@@ -243,7 +235,6 @@ class RegistrationProfileForm(forms.ModelForm):
self.fields['mailing_cof'].initial = True
self.fields['mailing_bda'].initial = True
self.fields['mailing_bda_revente'].initial = True
- self.fields['num'].widget.attrs['readonly'] = True
self.fields.keyOrder = [
'login_clipper',
@@ -251,7 +242,6 @@ class RegistrationProfileForm(forms.ModelForm):
'occupation',
'departement',
'is_cof',
- 'num',
'type_cotiz',
'mailing_cof',
'mailing_bda',
@@ -259,24 +249,9 @@ class RegistrationProfileForm(forms.ModelForm):
'comments'
]
- def save(self, *args, **kw):
- instance = super(RegistrationProfileForm, self).save(*args, **kw)
- if instance.is_cof and not instance.num:
- # Generate new number
- try:
- lock_table(CofProfile)
- aggregate = CofProfile.objects.aggregate(Max('num'))
- instance.num = aggregate['num__max'] + 1
- instance.save()
- self.cleaned_data['num'] = instance.num
- self.data['num'] = instance.num
- finally:
- unlock_table(CofProfile)
- return instance
-
class Meta:
model = CofProfile
- fields = ("login_clipper", "num", "phone", "occupation",
+ fields = ("login_clipper", "phone", "occupation",
"departement", "is_cof", "type_cotiz", "mailing_cof",
"mailing_bda", "mailing_bda_revente", "comments")
diff --git a/gestioncof/migrations/0011_remove_cofprofile_num.py b/gestioncof/migrations/0011_remove_cofprofile_num.py
new file mode 100644
index 00000000..f39ce367
--- /dev/null
+++ b/gestioncof/migrations/0011_remove_cofprofile_num.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('gestioncof', '0010_delete_custommail'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='cofprofile',
+ name='num',
+ ),
+ ]
diff --git a/gestioncof/models.py b/gestioncof/models.py
index 2215b296..f1ad35e1 100644
--- a/gestioncof/models.py
+++ b/gestioncof/models.py
@@ -1,11 +1,7 @@
-# -*- coding: utf-8 -*-
-
from django.db import models
from django.dispatch import receiver
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
-from django.utils.encoding import python_2_unicode_compatible
-import django.utils.six as six
from django.db.models.signals import post_save, post_delete
from gestioncof.petits_cours_models import choices_length
@@ -35,12 +31,10 @@ TYPE_COMMENT_FIELD = (
)
-@python_2_unicode_compatible
class CofProfile(models.Model):
user = models.OneToOneField(User, related_name="profile")
login_clipper = models.CharField("Login clipper", max_length=8, blank=True)
is_cof = models.BooleanField("Membre du COF", default=False)
- num = models.IntegerField("Numéro d'adhérent", blank=True, default=0)
phone = models.CharField("Téléphone", max_length=20, blank=True)
occupation = models.CharField(_("Occupation"),
default="1A",
@@ -72,7 +66,7 @@ class CofProfile(models.Model):
verbose_name_plural = "Profils COF"
def __str__(self):
- return six.text_type(self.user.username)
+ return self.user.username
@receiver(post_save, sender=User)
@@ -86,7 +80,6 @@ def post_delete_user(sender, instance, *args, **kwargs):
instance.user.delete()
-@python_2_unicode_compatible
class Club(models.Model):
name = models.CharField("Nom", max_length=200, unique=True)
description = models.TextField("Description", blank=True)
@@ -98,7 +91,6 @@ class Club(models.Model):
return self.name
-@python_2_unicode_compatible
class Event(models.Model):
title = models.CharField("Titre", max_length=200)
location = models.CharField("Lieu", max_length=200)
@@ -115,10 +107,9 @@ class Event(models.Model):
verbose_name = "Événement"
def __str__(self):
- return six.text_type(self.title)
+ return self.title
-@python_2_unicode_compatible
class EventCommentField(models.Model):
event = models.ForeignKey(Event, related_name="commentfields")
name = models.CharField("Champ", max_length=200)
@@ -130,10 +121,9 @@ class EventCommentField(models.Model):
verbose_name = "Champ"
def __str__(self):
- return six.text_type(self.name)
+ return self.name
-@python_2_unicode_compatible
class EventCommentValue(models.Model):
commentfield = models.ForeignKey(EventCommentField, related_name="values")
registration = models.ForeignKey("EventRegistration",
@@ -144,7 +134,6 @@ class EventCommentValue(models.Model):
return "Commentaire de %s" % self.commentfield
-@python_2_unicode_compatible
class EventOption(models.Model):
event = models.ForeignKey(Event, related_name="options")
name = models.CharField("Option", max_length=200)
@@ -154,10 +143,9 @@ class EventOption(models.Model):
verbose_name = "Option"
def __str__(self):
- return six.text_type(self.name)
+ return self.name
-@python_2_unicode_compatible
class EventOptionChoice(models.Model):
event_option = models.ForeignKey(EventOption, related_name="choices")
value = models.CharField("Valeur", max_length=200)
@@ -167,10 +155,9 @@ class EventOptionChoice(models.Model):
verbose_name_plural = "Choix"
def __str__(self):
- return six.text_type(self.value)
+ return self.value
-@python_2_unicode_compatible
class EventRegistration(models.Model):
user = models.ForeignKey(User)
event = models.ForeignKey(Event)
@@ -184,11 +171,9 @@ class EventRegistration(models.Model):
unique_together = ("user", "event")
def __str__(self):
- return "Inscription de %s à %s" % (six.text_type(self.user),
- six.text_type(self.event.title))
+ return "Inscription de {} à {}".format(self.user, self.event.title)
-@python_2_unicode_compatible
class Survey(models.Model):
title = models.CharField("Titre", max_length=200)
details = models.TextField("Détails", blank=True)
@@ -199,10 +184,9 @@ class Survey(models.Model):
verbose_name = "Sondage"
def __str__(self):
- return six.text_type(self.title)
+ return self.title
-@python_2_unicode_compatible
class SurveyQuestion(models.Model):
survey = models.ForeignKey(Survey, related_name="questions")
question = models.CharField("Question", max_length=200)
@@ -212,10 +196,9 @@ class SurveyQuestion(models.Model):
verbose_name = "Question"
def __str__(self):
- return six.text_type(self.question)
+ return self.question
-@python_2_unicode_compatible
class SurveyQuestionAnswer(models.Model):
survey_question = models.ForeignKey(SurveyQuestion, related_name="answers")
answer = models.CharField("Réponse", max_length=200)
@@ -224,10 +207,9 @@ class SurveyQuestionAnswer(models.Model):
verbose_name = "Réponse"
def __str__(self):
- return six.text_type(self.answer)
+ return self.answer
-@python_2_unicode_compatible
class SurveyAnswer(models.Model):
user = models.ForeignKey(User)
survey = models.ForeignKey(Survey)
@@ -244,7 +226,6 @@ class SurveyAnswer(models.Model):
self.survey.title)
-@python_2_unicode_compatible
class CalendarSubscription(models.Model):
token = models.UUIDField()
user = models.OneToOneField(User)
diff --git a/gestioncof/petits_cours_views.py b/gestioncof/petits_cours_views.py
index 3fa0dc57..087c9cef 100644
--- a/gestioncof/petits_cours_views.py
+++ b/gestioncof/petits_cours_views.py
@@ -12,15 +12,15 @@ from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib import messages
+from django.db import transaction
from gestioncof.models import CofProfile
from gestioncof.petits_cours_models import (
PetitCoursDemande, PetitCoursAttribution, PetitCoursAttributionCounter,
- PetitCoursAbility, PetitCoursSubject
+ PetitCoursAbility
)
from gestioncof.petits_cours_forms import DemandeForm, MatieresFormSet
from gestioncof.decorators import buro_required
-from gestioncof.shared import lock_table, unlock_tables
class DemandeListView(ListView):
@@ -274,17 +274,17 @@ def _traitement_post(request, demande):
headers={'Reply-To': replyto}))
connection = mail.get_connection(fail_silently=False)
connection.send_messages(mails_to_send)
- lock_table(PetitCoursAttributionCounter, PetitCoursAttribution, User)
- for matiere in proposals:
- for rank, user in enumerate(proposals[matiere]):
- counter = PetitCoursAttributionCounter.objects.get(user=user,
- matiere=matiere)
- counter.count += 1
- counter.save()
- attrib = PetitCoursAttribution(user=user, matiere=matiere,
- demande=demande, rank=rank + 1)
- attrib.save()
- unlock_tables()
+ with transaction.atomic():
+ for matiere in proposals:
+ for rank, user in enumerate(proposals[matiere]):
+ counter = PetitCoursAttributionCounter.objects.get(
+ user=user, matiere=matiere
+ )
+ counter.count += 1
+ counter.save()
+ attrib = PetitCoursAttribution(user=user, matiere=matiere,
+ demande=demande, rank=rank + 1)
+ attrib.save()
demande.traitee = True
demande.traitee_par = request.user
demande.processed = datetime.now()
@@ -309,17 +309,15 @@ def inscription(request):
profile.petits_cours_accept = "receive_proposals" in request.POST
profile.petits_cours_remarques = request.POST["remarques"]
profile.save()
- lock_table(PetitCoursAttributionCounter, PetitCoursAbility, User,
- PetitCoursSubject)
- abilities = (
- PetitCoursAbility.objects.filter(user=request.user).all()
- )
- for ability in abilities:
- PetitCoursAttributionCounter.get_uptodate(
- ability.user,
- ability.matiere
+ with transaction.atomic():
+ abilities = (
+ PetitCoursAbility.objects.filter(user=request.user).all()
)
- unlock_tables()
+ for ability in abilities:
+ PetitCoursAttributionCounter.get_uptodate(
+ ability.user,
+ ability.matiere
+ )
success = True
formset = MatieresFormSet(instance=request.user)
else:
diff --git a/gestioncof/shared.py b/gestioncof/shared.py
index 63aceae5..e4180b49 100644
--- a/gestioncof/shared.py
+++ b/gestioncof/shared.py
@@ -1,15 +1,8 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
-
from django.contrib.sites.models import Site
from django.conf import settings
from django_cas_ng.backends import CASBackend
from django_cas_ng.utils import get_cas_client
from django.contrib.auth import get_user_model
-from django.db import connection
from gestioncof.models import CofProfile
@@ -74,25 +67,3 @@ def context_processor(request):
"site": Site.objects.get_current(),
}
return data
-
-
-def lock_table(*models):
- query = "LOCK TABLES "
- for i, model in enumerate(models):
- table = model._meta.db_table
- if i > 0:
- query += ", "
- query += "%s WRITE" % table
- cursor = connection.cursor()
- cursor.execute(query)
- row = cursor.fetchone()
- return row
-
-
-def unlock_tables(*models):
- cursor = connection.cursor()
- cursor.execute("UNLOCK TABLES")
- row = cursor.fetchone()
- return row
-
-unlock_table = unlock_tables
diff --git a/gestioncof/views.py b/gestioncof/views.py
index 457a99c4..6a728ea6 100644
--- a/gestioncof/views.py
+++ b/gestioncof/views.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
import unicodecsv
import uuid
from datetime import timedelta
@@ -14,7 +12,6 @@ from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.utils import timezone
from django.contrib import messages
-import django.utils.six as six
from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \
SurveyQuestionAnswer
@@ -403,12 +400,8 @@ def registration_form2(request, login_clipper=None, username=None,
def registration(request):
if request.POST:
request_dict = request.POST.copy()
- # num ne peut pas être défini manuellement
- if "num" in request_dict:
- del request_dict["num"]
member = None
login_clipper = None
- success = False
# -----
# Remplissage des formulaires
@@ -445,7 +438,6 @@ def registration(request):
member = user_form.save()
profile, _ = CofProfile.objects.get_or_create(user=member)
was_cof = profile.is_cof
- request_dict["num"] = profile.num
# Maintenant on remplit le formulaire de profil
profile_form = RegistrationProfileForm(request_dict,
instance=profile)
@@ -499,16 +491,18 @@ def registration(request):
for club in clubs_form.cleaned_data['clubs']:
club.membres.add(member)
club.save()
- success = True
- # Messages
- if success:
- msg = ("L'inscription de {:s} ({:s}) a été "
- "enregistrée avec succès"
- .format(member.get_full_name(), member.email))
- if member.profile.is_cof:
- msg += "Il est désormais membre du COF n°{:d} !".format(
- member.profile.num)
- messages.success(request, msg, extra_tags='safe')
+
+ # ---
+ # Success
+ # ---
+
+ msg = ("L'inscription de {:s} ({:s}) a été "
+ "enregistrée avec succès."
+ .format(member.get_full_name(), member.email))
+ if profile.is_cof:
+ msg += "\nIl est désormais membre du COF n°{:d} !".format(
+ member.profile.id)
+ messages.success(request, msg, extra_tags='safe')
return render(request, "gestioncof/registration_post.html",
{"user_form": user_form,
"profile_form": profile_form,
@@ -572,10 +566,10 @@ def export_members(request):
writer = unicodecsv.writer(response)
for profile in CofProfile.objects.filter(is_cof=True).all():
user = profile.user
- bits = [profile.num, user.username, user.first_name, user.last_name,
+ bits = [profile.id, user.username, user.first_name, user.last_name,
user.email, profile.phone, profile.occupation,
profile.departement, profile.type_cotiz]
- writer.writerow([six.text_type(bit) for bit in bits])
+ writer.writerow([str(bit) for bit in bits])
return response
@@ -591,10 +585,10 @@ def csv_export_mega(filename, qs):
comments = "---".join(
[comment.content for comment in reg.comments.all()])
bits = [user.username, user.first_name, user.last_name, user.email,
- profile.phone, profile.num,
+ profile.phone, profile.id,
profile.comments if profile.comments else "", comments]
- writer.writerow([six.text_type(bit) for bit in bits])
+ writer.writerow([str(bit) for bit in bits])
return response
@@ -613,8 +607,8 @@ def export_mega_remarksonly(request):
user = reg.user
profile = user.profile
bits = [user.username, user.first_name, user.last_name, user.email,
- profile.phone, profile.num, profile.comments, val.content]
- writer.writerow([six.text_type(bit) for bit in bits])
+ profile.phone, profile.id, profile.comments, val.content]
+ writer.writerow([str(bit) for bit in bits])
return response
diff --git a/kfet/tests/test_views.py b/kfet/tests/test_views.py
new file mode 100644
index 00000000..65b953a1
--- /dev/null
+++ b/kfet/tests/test_views.py
@@ -0,0 +1,57 @@
+from decimal import Decimal
+
+from django.test import TestCase, Client
+from django.contrib.auth.models import User
+from django.utils import timezone
+
+from ..models import Account, OperationGroup, Checkout, Operation
+
+
+class AccountTests(TestCase):
+ """Account related views"""
+
+ def setUp(self):
+ # A user and its account
+ self.user = User.objects.create_user(username="foobar", password="foo")
+ acc = Account.objects.create(
+ trigramme="FOO", cofprofile=self.user.profile
+ )
+
+ # Dummy operations and operation groups
+ checkout = Checkout.objects.create(
+ created_by=acc, name="checkout",
+ valid_from=timezone.now(),
+ valid_to=timezone.now() + timezone.timedelta(days=365)
+ )
+ opeg_data = [
+ (timezone.now(), Decimal('10')),
+ (timezone.now() - timezone.timedelta(days=3), Decimal('3')),
+ ]
+ OperationGroup.objects.bulk_create([
+ OperationGroup(
+ on_acc=acc, checkout=checkout, at=at, is_cof=False,
+ amount=amount
+ )
+ for (at, amount) in opeg_data
+ ])
+ self.operation_groups = OperationGroup.objects.order_by("-amount")
+ Operation.objects.create(
+ group=self.operation_groups[0],
+ type=Operation.PURCHASE,
+ amount=Decimal('10')
+ )
+ Operation.objects.create(
+ group=self.operation_groups[1],
+ type=Operation.PURCHASE,
+ amount=Decimal('3')
+ )
+
+ def test_account_read(self):
+ """We can query the "Accout - Read" page."""
+ client = Client()
+ self.assertTrue(client.login(
+ username="foobar",
+ password="foo"
+ ))
+ resp = client.get("/k-fet/accounts/FOO")
+ self.assertEqual(200, resp.status_code)