diff --git a/accounting/admin.py b/accounting/admin.py index 67409ea7..93960568 100644 --- a/accounting/admin.py +++ b/accounting/admin.py @@ -35,5 +35,3 @@ admin.site.register(SimplifiedAccountingType) admin.site.register(Operation) admin.site.register(Label) admin.site.register(Company) - - diff --git a/accounting/models.py b/accounting/models.py index c6b65551..97258270 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -25,7 +25,6 @@ from django.core.urlresolvers import reverse from django.core.exceptions import ValidationError from django.core import validators -from django.db.models import Count from django.db import models from django.conf import settings from django.utils.translation import ugettext_lazy as _ @@ -37,6 +36,7 @@ from decimal import Decimal from core.models import User, SithFile from club.models import Club + class CurrencyField(models.DecimalField): """ This is a custom database field used for currency @@ -50,12 +50,13 @@ class CurrencyField(models.DecimalField): def to_python(self, value): try: - return super(CurrencyField, self).to_python(value).quantize(Decimal("0.01")) + return super(CurrencyField, self).to_python(value).quantize(Decimal("0.01")) except AttributeError: - return None + return None # Accounting classes + class Company(models.Model): name = models.CharField(_('name'), max_length=60) street = models.CharField(_('street'), max_length=60, blank=True) @@ -104,6 +105,7 @@ class Company(models.Model): def __str__(self): return self.name + class BankAccount(models.Model): name = models.CharField(_('name'), max_length=30) iban = models.CharField(_('iban'), max_length=255, blank=True) @@ -131,6 +133,7 @@ class BankAccount(models.Model): def __str__(self): return self.name + class ClubAccount(models.Model): name = models.CharField(_('name'), max_length=30) club = models.ForeignKey(Club, related_name="club_account", verbose_name=_("club")) @@ -244,6 +247,7 @@ class GeneralJournal(models.Model): self.amount -= o.amount self.save() + class Operation(models.Model): """ An operation is a line in the journal, a debit or a credit @@ -258,17 +262,17 @@ class Operation(models.Model): invoice = models.ForeignKey(SithFile, related_name='operations', verbose_name=_("invoice"), null=True, blank=True) done = models.BooleanField(_('is done'), default=False) simpleaccounting_type = models.ForeignKey('SimplifiedAccountingType', related_name="operations", - verbose_name=_("simple type"), null=True, blank=True) + verbose_name=_("simple type"), null=True, blank=True) accounting_type = models.ForeignKey('AccountingType', related_name="operations", - verbose_name=_("accounting type"), null=True, blank=True) + verbose_name=_("accounting type"), null=True, blank=True) label = models.ForeignKey('Label', related_name="operations", - verbose_name=_("label"), null=True, blank=True, on_delete=models.SET_NULL) + verbose_name=_("label"), null=True, blank=True, on_delete=models.SET_NULL) target_type = models.CharField(_('target type'), max_length=10, - choices=[('USER', _('User')), ('CLUB', _('Club')), ('ACCOUNT', _('Account')), ('COMPANY', _('Company')), ('OTHER', _('Other'))]) + choices=[('USER', _('User')), ('CLUB', _('Club')), ('ACCOUNT', _('Account')), ('COMPANY', _('Company')), ('OTHER', _('Other'))]) target_id = models.IntegerField(_('target id'), null=True, blank=True) target_label = models.CharField(_('target label'), max_length=32, default="", blank=True) linked_operation = models.OneToOneField('self', related_name='operation_linked_to', verbose_name=_("linked operation"), - null=True, blank=True, default=None) + null=True, blank=True, default=None) class Meta: unique_together = ('number', 'journal') @@ -349,8 +353,9 @@ class Operation(models.Model): def __str__(self): return "%d € | %s | %s | %s" % ( - self.amount, self.date, self.accounting_type, self.done, - ) + self.amount, self.date, self.accounting_type, self.done, + ) + class AccountingType(models.Model): """ @@ -359,13 +364,13 @@ class AccountingType(models.Model): Thoses are numbers used in accounting to classify operations """ code = models.CharField(_('code'), max_length=16, - validators=[ - validators.RegexValidator(r'^[0-9]*$', _('An accounting type code contains only numbers')), - ], - ) + validators=[ + validators.RegexValidator(r'^[0-9]*$', _('An accounting type code contains only numbers')), + ], + ) label = models.CharField(_('label'), max_length=128) movement_type = models.CharField(_('movement type'), choices=[('CREDIT', _('Credit')), ('DEBIT', _('Debit')), - ('NEUTRAL', _('Neutral'))], max_length=12) + ('NEUTRAL', _('Neutral'))], max_length=12) class Meta: verbose_name = _("accounting type") @@ -383,7 +388,8 @@ class AccountingType(models.Model): return reverse('accounting:type_list') def __str__(self): - return self.code+" - "+self.get_movement_type_display()+" - "+self.label + return self.code + " - " + self.get_movement_type_display() + " - " + self.label + class SimplifiedAccountingType(models.Model): """ @@ -391,7 +397,7 @@ class SimplifiedAccountingType(models.Model): """ label = models.CharField(_('label'), max_length=128) accounting_type = models.ForeignKey(AccountingType, related_name="simplified_types", - verbose_name=_("simplified accounting types")) + verbose_name=_("simplified accounting types")) class Meta: verbose_name = _("simplified type") @@ -408,7 +414,8 @@ class SimplifiedAccountingType(models.Model): return reverse('accounting:simple_type_list') def __str__(self): - return self.get_movement_type_display()+" - "+self.accounting_type.code+" - "+self.label + return self.get_movement_type_display() + " - " + self.accounting_type.code + " - " + self.label + class Label(models.Model): """Label allow a club to sort its operations""" @@ -432,4 +439,3 @@ class Label(models.Model): def can_be_viewed_by(self, user): return self.club_account.can_be_viewed_by(user) - diff --git a/accounting/tests.py b/accounting/tests.py index d54efe34..a2c3ab38 100644 --- a/accounting/tests.py +++ b/accounting/tests.py @@ -22,15 +22,12 @@ # # -from django.test import Client, TestCase +from django.test import TestCase from django.core.urlresolvers import reverse -from django.contrib.auth.models import Group from django.core.management import call_command -from django.conf import settings -from datetime import date, datetime +from datetime import date from core.models import User -from counter.models import Counter from accounting.models import GeneralJournal, Operation, Label, AccountingType, SimplifiedAccountingType @@ -72,10 +69,11 @@ class RefoundAccountTest(TestCase): self.assertFalse(response_post.status_code == 403) self.assertTrue(self.skia.customer.amount == 0) + class JournalTest(TestCase): def setUp(self): call_command("populate") - self.journal = GeneralJournal.objects.filter(id = 1).first() + self.journal = GeneralJournal.objects.filter(id=1).first() def test_permission_granted(self): self.client.login(username='comptable', password='plop') @@ -91,115 +89,116 @@ class JournalTest(TestCase): self.assertTrue(response_get.status_code == 403) self.assertFalse('M\xc3\xa9thode de paiement' in str(response_get.content)) + class OperationTest(TestCase): def setUp(self): call_command("populate") - self.journal = GeneralJournal.objects.filter(id = 1).first() + self.journal = GeneralJournal.objects.filter(id=1).first() self.skia = User.objects.filter(username='skia').first() at = AccountingType(code='443', label="Ce code n'existe pas", movement_type='CREDIT') at.save() - l = Label(club_account= self.journal.club_account, name='bob') + l = Label(club_account=self.journal.club_account, name='bob') l.save() self.client.login(username='comptable', password='plop') - self.op1 = Operation(journal=self.journal, date=date.today(), amount=1, - remark="Test bilan", mode='CASH', done=True, label=l, - accounting_type=at, target_type='USER', target_id=self.skia.id) + self.op1 = Operation(journal=self.journal, date=date.today(), amount=1, + remark="Test bilan", mode='CASH', done=True, label=l, + accounting_type=at, target_type='USER', target_id=self.skia.id) self.op1.save() - self.op2 = Operation(journal=self.journal, date=date.today(), amount=2, - remark="Test bilan", mode='CASH', done=True, label=l, - accounting_type=at, target_type='USER', target_id=self.skia.id) + self.op2 = Operation(journal=self.journal, date=date.today(), amount=2, + remark="Test bilan", mode='CASH', done=True, label=l, + accounting_type=at, target_type='USER', target_id=self.skia.id) self.op2.save() def test_new_operation(self): - self.client.login(username='comptable', password='plop') - at = AccountingType.objects.filter(code = '604').first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 30, - 'remark' : "Un gros test", - 'journal' : self.journal.id, - 'target_type' : 'OTHER', - 'target_id' : '', - 'target_label' : "Le fantome de la nuit", - 'date' : '04/12/2020', - 'mode' : 'CASH', - 'cheque_number' : '', - 'invoice' : '', - 'simpleaccounting_type' : '', - 'accounting_type': at.id, - 'label' : '', - 'done' : False, - }) + self.client.login(username='comptable', password='plop') + at = AccountingType.objects.filter(code='604').first() + response = self.client.post(reverse('accounting:op_new', + args=[self.journal.id]), + {'amount': 30, + 'remark': "Un gros test", + 'journal': self.journal.id, + 'target_type': 'OTHER', + 'target_id': '', + 'target_label': "Le fantome de la nuit", + 'date': '04/12/2020', + 'mode': 'CASH', + 'cheque_number': '', + 'invoice': '', + 'simpleaccounting_type': '', + 'accounting_type': at.id, + 'label': '', + 'done': False, + }) self.assertFalse(response.status_code == 403) - self.assertTrue(self.journal.operations.filter(target_label = "Le fantome de la nuit").exists()) + self.assertTrue(self.journal.operations.filter(target_label="Le fantome de la nuit").exists()) response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) self.assertTrue('Le fantome de la nuit' in str(response_get.content)) def test_bad_new_operation(self): self.client.login(username='comptable', password='plop') - at = AccountingType.objects.filter(code = '604').first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 30, - 'remark' : "Un gros test", - 'journal' : self.journal.id, - 'target_type' : 'OTHER', - 'target_id' : '', - 'target_label' : "Le fantome de la nuit", - 'date' : '04/12/2020', - 'mode' : 'CASH', - 'cheque_number' : '', - 'invoice' : '', - 'simpleaccounting_type' : '', - 'accounting_type': '', - 'label' : '', - 'done' : False, - }) + AccountingType.objects.filter(code='604').first() + response = self.client.post(reverse('accounting:op_new', + args=[self.journal.id]), + {'amount': 30, + 'remark': "Un gros test", + 'journal': self.journal.id, + 'target_type': 'OTHER', + 'target_id': '', + 'target_label': "Le fantome de la nuit", + 'date': '04/12/2020', + 'mode': 'CASH', + 'cheque_number': '', + 'invoice': '', + 'simpleaccounting_type': '', + 'accounting_type': '', + 'label': '', + 'done': False, + }) self.assertTrue('Vous devez fournir soit un type comptable simplifi\\xc3\\xa9 ou un type comptable standard' in str(response.content)) def test_new_operation_not_authorized(self): self.client.login(username='skia', password='plop') - at = AccountingType.objects.filter(code = '604').first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 30, - 'remark' : "Un gros test", - 'journal' : self.journal.id, - 'target_type' : 'OTHER', - 'target_id' : '', - 'target_label' : "Le fantome du jour", - 'date' : '04/12/2020', - 'mode' : 'CASH', - 'cheque_number' : '', - 'invoice' : '', - 'simpleaccounting_type' : '', - 'accounting_type': at.id, - 'label' : '', - 'done' : False, - }) + at = AccountingType.objects.filter(code='604').first() + response = self.client.post(reverse('accounting:op_new', + args=[self.journal.id]), + {'amount': 30, + 'remark': "Un gros test", + 'journal': self.journal.id, + 'target_type': 'OTHER', + 'target_id': '', + 'target_label': "Le fantome du jour", + 'date': '04/12/2020', + 'mode': 'CASH', + 'cheque_number': '', + 'invoice': '', + 'simpleaccounting_type': '', + 'accounting_type': at.id, + 'label': '', + 'done': False, + }) self.assertTrue(response.status_code == 403) - self.assertFalse(self.journal.operations.filter(target_label = "Le fantome du jour").exists()) + self.assertFalse(self.journal.operations.filter(target_label="Le fantome du jour").exists()) def test__operation_simple_accounting(self): self.client.login(username='comptable', password='plop') sat = SimplifiedAccountingType.objects.all().first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 23, - 'remark' : "Un gros test", - 'journal' : self.journal.id, - 'target_type' : 'OTHER', - 'target_id' : '', - 'target_label' : "Le fantome de l'aurore", - 'date' : '04/12/2020', - 'mode' : 'CASH', - 'cheque_number' : '', - 'invoice' : '', - 'simpleaccounting_type' : sat.id, - 'accounting_type': '', - 'label' : '', - 'done' : False, - }) + response = self.client.post(reverse('accounting:op_new', + args=[self.journal.id]), + {'amount': 23, + 'remark': "Un gros test", + 'journal': self.journal.id, + 'target_type': 'OTHER', + 'target_id': '', + 'target_label': "Le fantome de l'aurore", + 'date': '04/12/2020', + 'mode': 'CASH', + 'cheque_number': '', + 'invoice': '', + 'simpleaccounting_type': sat.id, + 'accounting_type': '', + 'label': '', + 'done': False, + }) self.assertFalse(response.status_code == 403) self.assertTrue(self.journal.operations.filter(amount=23).exists()) response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) @@ -216,8 +215,7 @@ class OperationTest(TestCase): response_get = self.client.get(reverse("accounting:journal_person_statement", args=[self.journal.id])) self.assertTrue("S' Kia\\n \\n 3.00" in str(response_get.content)) - def test_accounting_statement(self): self.client.login(username='comptable', password='plop') response_get = self.client.get(reverse("accounting:journal_accounting_statement", args=[self.journal.id])) - self.assertTrue("443 - Cr\\xc3\\xa9dit - Ce code n'existe pas\\n 3.00" in str(response_get.content)) \ No newline at end of file + self.assertTrue("443 - Cr\\xc3\\xa9dit - Ce code n'existe pas\\n 3.00" in str(response_get.content)) diff --git a/accounting/urls.py b/accounting/urls.py index 7085fc30..70e6a3ea 100644 --- a/accounting/urls.py +++ b/accounting/urls.py @@ -22,7 +22,7 @@ # # -from django.conf.urls import url, include +from django.conf.urls import url from accounting.views import * @@ -71,5 +71,3 @@ urlpatterns = [ # User account url(r'^refound/account$', RefoundAccountView.as_view(), name='refound_account'), ] - - diff --git a/accounting/views.py b/accounting/views.py index b15ea4e5..9ce9af47 100644 --- a/accounting/views.py +++ b/accounting/views.py @@ -22,25 +22,21 @@ # # -from django.views.generic import ListView, DetailView, RedirectView +from django.views.generic import ListView, DetailView from django.views.generic.edit import UpdateView, CreateView, DeleteView, FormView -from django.shortcuts import render from django.core.urlresolvers import reverse_lazy, reverse from django.utils.translation import ugettext_lazy as _ from django.forms.models import modelform_factory from django.core.exceptions import PermissionDenied, ValidationError -from django.forms import HiddenInput, TextInput +from django.forms import HiddenInput from django.db import transaction from django.db.models import Sum from django.conf import settings from django import forms -from django.http import HttpResponseRedirect, HttpResponse -from django.utils.translation import ugettext as _ -from django.conf import settings - +from django.http import HttpResponse import collections -from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField +from ajax_select.fields import AutoCompleteSelectField from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, TabedViewMixin from core.views.forms import SelectFile, SelectDate @@ -49,6 +45,7 @@ from counter.models import Counter, Selling, Product # Main accounting view + class BankAccountListView(CanViewMixin, ListView): """ A list view for the admins @@ -57,6 +54,7 @@ class BankAccountListView(CanViewMixin, ListView): template_name = 'accounting/bank_account_list.jinja' ordering = ['name'] + # Simplified accounting types class SimplifiedAccountingTypeListView(CanViewMixin, ListView): @@ -66,6 +64,7 @@ class SimplifiedAccountingTypeListView(CanViewMixin, ListView): model = SimplifiedAccountingType template_name = 'accounting/simplifiedaccountingtype_list.jinja' + class SimplifiedAccountingTypeEditView(CanViewMixin, UpdateView): """ An edit view for the admins @@ -75,6 +74,7 @@ class SimplifiedAccountingTypeEditView(CanViewMixin, UpdateView): fields = ['label', 'accounting_type'] template_name = 'core/edit.jinja' + class SimplifiedAccountingTypeCreateView(CanCreateMixin, CreateView): """ Create an accounting type (for the admins) @@ -83,6 +83,7 @@ class SimplifiedAccountingTypeCreateView(CanCreateMixin, CreateView): fields = ['label', 'accounting_type'] template_name = 'core/create.jinja' + # Accounting types class AccountingTypeListView(CanViewMixin, ListView): @@ -92,6 +93,7 @@ class AccountingTypeListView(CanViewMixin, ListView): model = AccountingType template_name = 'accounting/accountingtype_list.jinja' + class AccountingTypeEditView(CanViewMixin, UpdateView): """ An edit view for the admins @@ -101,6 +103,7 @@ class AccountingTypeEditView(CanViewMixin, UpdateView): fields = ['code', 'label', 'movement_type'] template_name = 'core/edit.jinja' + class AccountingTypeCreateView(CanCreateMixin, CreateView): """ Create an accounting type (for the admins) @@ -109,6 +112,7 @@ class AccountingTypeCreateView(CanCreateMixin, CreateView): fields = ['code', 'label', 'movement_type'] template_name = 'core/create.jinja' + # BankAccount views class BankAccountEditView(CanViewMixin, UpdateView): @@ -120,6 +124,7 @@ class BankAccountEditView(CanViewMixin, UpdateView): fields = ['name', 'iban', 'number', 'club'] template_name = 'core/edit.jinja' + class BankAccountDetailView(CanViewMixin, DetailView): """ A detail view, listing every club account @@ -128,6 +133,7 @@ class BankAccountDetailView(CanViewMixin, DetailView): pk_url_kwarg = "b_account_id" template_name = 'accounting/bank_account_details.jinja' + class BankAccountCreateView(CanCreateMixin, CreateView): """ Create a bank account (for the admins) @@ -136,7 +142,8 @@ class BankAccountCreateView(CanCreateMixin, CreateView): fields = ['name', 'club', 'iban', 'number'] template_name = 'core/create.jinja' -class BankAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete to Close + +class BankAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete to Close """ Delete a bank account (for the admins) """ @@ -145,6 +152,7 @@ class BankAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete template_name = 'core/delete_confirm.jinja' success_url = reverse_lazy('accounting:bank_list') + # ClubAccount views class ClubAccountEditView(CanViewMixin, UpdateView): @@ -156,6 +164,7 @@ class ClubAccountEditView(CanViewMixin, UpdateView): fields = ['name', 'club', 'bank_account'] template_name = 'core/edit.jinja' + class ClubAccountDetailView(CanViewMixin, DetailView): """ A detail view, listing every journal @@ -164,6 +173,7 @@ class ClubAccountDetailView(CanViewMixin, DetailView): pk_url_kwarg = "c_account_id" template_name = 'accounting/club_account_details.jinja' + class ClubAccountCreateView(CanCreateMixin, CreateView): """ Create a club account (for the admins) @@ -180,7 +190,8 @@ class ClubAccountCreateView(CanCreateMixin, CreateView): ret['bank_account'] = obj.id return ret -class ClubAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete to Close + +class ClubAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete to Close """ Delete a club account (for the admins) """ @@ -189,6 +200,7 @@ class ClubAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete template_name = 'core/delete_confirm.jinja' success_url = reverse_lazy('accounting:bank_list') + # Journal views class JournalTabsMixin(TabedViewMixin): @@ -198,34 +210,35 @@ class JournalTabsMixin(TabedViewMixin): def get_list_of_tabs(self): tab_list = [] tab_list.append({ - 'url': reverse('accounting:journal_details', kwargs={'j_id': self.object.id}), - 'slug': 'journal', + 'url': reverse('accounting:journal_details', kwargs={'j_id': self.object.id}), + 'slug': 'journal', 'name': _("Journal"), - }) + }) tab_list.append({ - 'url': reverse('accounting:journal_nature_statement', kwargs={'j_id': self.object.id}), - 'slug': 'nature_statement', + 'url': reverse('accounting:journal_nature_statement', kwargs={'j_id': self.object.id}), + 'slug': 'nature_statement', 'name': _("Statement by nature"), - }) + }) tab_list.append({ - 'url': reverse('accounting:journal_person_statement', kwargs={'j_id': self.object.id}), - 'slug': 'person_statement', + 'url': reverse('accounting:journal_person_statement', kwargs={'j_id': self.object.id}), + 'slug': 'person_statement', 'name': _("Statement by person"), - }) + }) tab_list.append({ - 'url': reverse('accounting:journal_accounting_statement', kwargs={'j_id': self.object.id}), - 'slug': 'accounting_statement', + 'url': reverse('accounting:journal_accounting_statement', kwargs={'j_id': self.object.id}), + 'slug': 'accounting_statement', 'name': _("Accounting statement"), - }) + }) return tab_list + class JournalCreateView(CanCreateMixin, CreateView): """ Create a general journal """ model = GeneralJournal form_class = modelform_factory(GeneralJournal, fields=['name', 'start_date', 'club_account'], - widgets={ 'start_date': SelectDate, }) + widgets={'start_date': SelectDate, }) template_name = 'core/create.jinja' def get_initial(self): @@ -236,6 +249,7 @@ class JournalCreateView(CanCreateMixin, CreateView): ret['club_account'] = obj.id return ret + class JournalDetailView(JournalTabsMixin, CanViewMixin, DetailView): """ A detail view, listing every operation @@ -245,6 +259,7 @@ class JournalDetailView(JournalTabsMixin, CanViewMixin, DetailView): template_name = 'accounting/journal_details.jinja' current_tab = 'journal' + class JournalEditView(CanEditMixin, UpdateView): """ Update a general journal @@ -254,6 +269,7 @@ class JournalEditView(CanEditMixin, UpdateView): fields = ['name', 'start_date', 'end_date', 'club_account', 'closed'] template_name = 'core/edit.jinja' + class JournalDeleteView(CanEditPropMixin, DeleteView): """ Delete a club account (for the admins) @@ -264,11 +280,12 @@ class JournalDeleteView(CanEditPropMixin, DeleteView): success_url = reverse_lazy('accounting:club_details') def dispatch(self, request, *args, **kwargs): - self.object = self.get_object() - if self.object.operations.count() == 0: - return super(JournalDeleteView, self).dispatch(request, *args, **kwargs) - else: - raise PermissionDenied + self.object = self.get_object() + if self.object.operations.count() == 0: + return super(JournalDeleteView, self).dispatch(request, *args, **kwargs) + else: + raise PermissionDenied + # Operation views @@ -276,13 +293,13 @@ class OperationForm(forms.ModelForm): class Meta: model = Operation fields = ['amount', 'remark', 'journal', 'target_type', 'target_id', 'target_label', 'date', 'mode', - 'cheque_number', 'invoice', 'simpleaccounting_type', 'accounting_type', 'label', 'done' ] + 'cheque_number', 'invoice', 'simpleaccounting_type', 'accounting_type', 'label', 'done'] widgets = { - 'journal': HiddenInput, - 'target_id': HiddenInput, - 'date': SelectDate, - 'invoice': SelectFile, - } + 'journal': HiddenInput, + 'target_id': HiddenInput, + 'date': SelectDate, + 'invoice': SelectFile, + } user = AutoCompleteSelectField('users', help_text=None, required=False) club_account = AutoCompleteSelectField('club_accounts', help_text=None, required=False) club = AutoCompleteSelectField('clubs', help_text=None, required=False) @@ -328,28 +345,29 @@ class OperationForm(forms.ModelForm): inst = self.instance club_account = inst.target acc_type = AccountingType.objects.exclude(movement_type="NEUTRAL").exclude( - movement_type=inst.accounting_type.movement_type).order_by('code').first() # Select a random opposite accounting type + movement_type=inst.accounting_type.movement_type).order_by('code').first() # Select a random opposite accounting type op = Operation( - journal=club_account.get_open_journal(), - amount=inst.amount, - date=inst.date, - remark=inst.remark, - mode=inst.mode, - cheque_number=inst.cheque_number, - invoice=inst.invoice, - done=False, # Has to be checked by hand - simpleaccounting_type=None, - accounting_type=acc_type, - target_type="ACCOUNT", - target_id=inst.journal.club_account.id, - target_label="", - linked_operation=inst, - ) + journal=club_account.get_open_journal(), + amount=inst.amount, + date=inst.date, + remark=inst.remark, + mode=inst.mode, + cheque_number=inst.cheque_number, + invoice=inst.invoice, + done=False, # Has to be checked by hand + simpleaccounting_type=None, + accounting_type=acc_type, + target_type="ACCOUNT", + target_id=inst.journal.club_account.id, + target_label="", + linked_operation=inst, + ) op.save() self.instance.linked_operation = op self.save() return ret + class OperationCreateView(CanCreateMixin, CreateView): """ Create an operation @@ -376,6 +394,7 @@ class OperationCreateView(CanCreateMixin, CreateView): kwargs['object'] = self.journal return kwargs + class OperationEditView(CanEditMixin, UpdateView): """ An edit view, working as detail for the moment @@ -391,6 +410,7 @@ class OperationEditView(CanEditMixin, UpdateView): kwargs['object'] = self.object.journal return kwargs + class OperationPDFView(CanViewMixin, DetailView): """ Display the PDF of a given operation @@ -402,11 +422,10 @@ class OperationPDFView(CanViewMixin, DetailView): def get(self, request, *args, **kwargs): from reportlab.pdfgen import canvas from reportlab.lib.units import cm - from reportlab.platypus import SimpleDocTemplate, Table, TableStyle + from reportlab.platypus import Table, TableStyle from reportlab.lib import colors from reportlab.lib.pagesizes import letter from reportlab.lib.utils import ImageReader - from reportlab.graphics.shapes import Drawing from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfbase import pdfmetrics @@ -419,7 +438,6 @@ class OperationPDFView(CanViewMixin, DetailView): num = self.object.number date = self.object.date mode = self.object.mode - cheque_number = self.object.cheque_number club_name = self.object.journal.club_account.name ti = self.object.journal.name op_label = self.object.label @@ -432,7 +450,7 @@ class OperationPDFView(CanViewMixin, DetailView): target = self.object.target.get_display_name() response = HttpResponse(content_type='application/pdf') - response['Content-Disposition'] = 'filename="op-%d(%s_on_%s).pdf"' %(num, ti, club_name) + response['Content-Disposition'] = 'filename="op-%d(%s_on_%s).pdf"' % (num, ti, club_name) p = canvas.Canvas(response) p.setFont('DejaVu', 12) @@ -441,21 +459,21 @@ class OperationPDFView(CanViewMixin, DetailView): width, height = letter im = ImageReader("core/static/core/img/logo.jpg") iw, ih = im.getSize() - p.drawImage(im, 40, height - 50, width=iw/2, height=ih/2) + p.drawImage(im, 40, height - 50, width=iw / 2, height=ih / 2) labelStr = [["%s %s - %s %s" % (_("Journal"), ti, _("Operation"), num)]] label = Table(labelStr, colWidths=[150], rowHeights=[20]) label.setStyle(TableStyle([ - ('ALIGN',(0,0),(-1,-1),'RIGHT'), - ])) + ('ALIGN', (0, 0), (-1, -1), 'RIGHT'), + ])) w, h = label.wrapOn(label, 0, 0) - label.drawOn(p, width-180, height) + label.drawOn(p, width - 180, height) - p.drawString(90, height - 100, _("Financial proof: ") + "OP%010d" % (id_op)) #Justificatif du libellé + p.drawString(90, height - 100, _("Financial proof: ") + "OP%010d" % (id_op)) # Justificatif du libellé p.drawString(90, height - 130, _("Club: %(club_name)s") % ({"club_name": club_name})) - p.drawString(90, height - 160, _("Label: %(op_label)s") % {"op_label": op_label if op_label != None else ""}) + p.drawString(90, height - 160, _("Label: %(op_label)s") % {"op_label": op_label if op_label is not None else ""}) p.drawString(90, height - 190, _("Date: %(date)s") % {"date": date}) data = [] @@ -470,7 +488,7 @@ class OperationPDFView(CanViewMixin, DetailView): payment_mode += "[\u00D7]" else: payment_mode += "[ ]" - payment_mode += " %s\n" %(m[1]) + payment_mode += " %s\n" % (m[1]) data += [[payment_mode]] @@ -478,29 +496,29 @@ class OperationPDFView(CanViewMixin, DetailView): data += [["%s \n%s" % (_("Comment:"), remark)]] - t = Table(data, colWidths=[(width-90*2)/2]*2, rowHeights=[20, 20, 70, 20, 80]) + t = Table(data, colWidths=[(width - 90 * 2) / 2] * 2, rowHeights=[20, 20, 70, 20, 80]) t.setStyle(TableStyle([ - ('ALIGN',(0,0),(-1,-1),'CENTER'), - ('VALIGN',(-2,-1),(-1,-1),'TOP'), - ('VALIGN',(0,0),(-1,-2),'MIDDLE'), - ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), - ('SPAN', (0, 0), (1, 0)), # line DEBIT/CREDIT - ('SPAN', (0, 1), (1, 1)), # line amount - ('SPAN',(-2, -1), (-1,-1)), # line comment - ('SPAN', (0, -2), (-1, -2)), # line creditor/debtor - ('SPAN', (0, 2), (1, 2)), # line payment_mode - ('ALIGN',(0, 2), (1, 2),'LEFT'), # line payment_mode - ('ALIGN', (-2, -1), (-1, -1), 'LEFT'), - ('BOX', (0,0), (-1,-1), 0.25, colors.black), - ])) + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('VALIGN', (-2, -1), (-1, -1), 'TOP'), + ('VALIGN', (0, 0), (-1, -2), 'MIDDLE'), + ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.black), + ('SPAN', (0, 0), (1, 0)), # line DEBIT/CREDIT + ('SPAN', (0, 1), (1, 1)), # line amount + ('SPAN', (-2, -1), (-1, -1)), # line comment + ('SPAN', (0, -2), (-1, -2)), # line creditor/debtor + ('SPAN', (0, 2), (1, 2)), # line payment_mode + ('ALIGN', (0, 2), (1, 2), 'LEFT'), # line payment_mode + ('ALIGN', (-2, -1), (-1, -1), 'LEFT'), + ('BOX', (0, 0), (-1, -1), 0.25, colors.black), + ])) signature = [] signature += [[_("Signature:")]] - tSig = Table(signature, colWidths=[(width-90*2)], rowHeights=[80]) + tSig = Table(signature, colWidths=[(width - 90 * 2)], rowHeights=[80]) tSig.setStyle(TableStyle([ - ('VALIGN',(0,0),(-1,-1),'TOP'), - ('BOX', (0,0), (-1,-1), 0.25, colors.black)])) + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('BOX', (0, 0), (-1, -1), 0.25, colors.black)])) w, h = tSig.wrapOn(p, 0, 0) tSig.drawOn(p, 90, 200) @@ -516,14 +534,15 @@ class OperationPDFView(CanViewMixin, DetailView): p.save() return response + class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): """ Display a statement sorted by labels """ model = GeneralJournal pk_url_kwarg = "j_id" - template_name='accounting/journal_statement_nature.jinja' - current_tab='nature_statement' + template_name = 'accounting/journal_statement_nature.jinja' + current_tab = 'nature_statement' def statement(self, queryset, movement_type): ret = collections.OrderedDict() @@ -531,14 +550,16 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): total_sum = 0 for sat in [None] + list(SimplifiedAccountingType.objects.order_by('label').all()): sum = queryset.filter(accounting_type__movement_type=movement_type, - simpleaccounting_type=sat).aggregate(amount_sum=Sum('amount'))['amount_sum'] - if sat: sat = sat.label - else: sat = "" + simpleaccounting_type=sat).aggregate(amount_sum=Sum('amount'))['amount_sum'] + if sat: + sat = sat.label + else: + sat = "" if sum: total_sum += sum statement[sat] = sum ret[movement_type] = statement - ret[movement_type+"_sum"] = total_sum + ret[movement_type + "_sum"] = total_sum return ret def big_statement(self): @@ -566,23 +587,24 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): kwargs['statement'] = self.big_statement() return kwargs + class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView): """ Calculate a dictionary with operation target and sum of operations """ model = GeneralJournal pk_url_kwarg = "j_id" - template_name='accounting/journal_statement_person.jinja' - current_tab='person_statement' + template_name = 'accounting/journal_statement_person.jinja' + current_tab = 'person_statement' def sum_by_target(self, target_id, target_type, movement_type): return self.object.operations.filter(accounting_type__movement_type=movement_type, - target_id=target_id, target_type=target_type).aggregate(amount_sum=Sum('amount'))['amount_sum'] + target_id=target_id, target_type=target_type).aggregate(amount_sum=Sum('amount'))['amount_sum'] def statement(self, movement_type): statement = collections.OrderedDict() for op in self.object.operations.filter(accounting_type__movement_type=movement_type).order_by('target_type', - 'target_id').distinct(): + 'target_id').distinct(): statement[op.target] = self.sum_by_target(op.target_id, op.target_type, movement_type) return statement @@ -598,13 +620,14 @@ class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView): kwargs['total_debit'] = self.total("DEBIT") return kwargs + class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView): """ Calculate a dictionary with operation type and sum of operations """ model = GeneralJournal pk_url_kwarg = "j_id" - template_name='accounting/journal_statement_accounting.jinja' + template_name = 'accounting/journal_statement_accounting.jinja' current_tab = "accounting_statement" def statement(self): @@ -624,10 +647,12 @@ class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView) # Company views + class CompanyListView(CanViewMixin, ListView): model = Company template_name = 'accounting/co_list.jinja' + class CompanyCreateView(CanCreateMixin, CreateView): """ Create a company @@ -648,6 +673,7 @@ class CompanyEditView(CanCreateMixin, UpdateView): template_name = 'core/edit.jinja' success_url = reverse_lazy('accounting:co_list') + # Label views class LabelListView(CanViewMixin, DetailView): @@ -655,11 +681,12 @@ class LabelListView(CanViewMixin, DetailView): pk_url_kwarg = "clubaccount_id" template_name = 'accounting/label_list.jinja' -class LabelCreateView(CanCreateMixin, CreateView): # FIXME we need to check the rights before creating the object + +class LabelCreateView(CanCreateMixin, CreateView): # FIXME we need to check the rights before creating the object model = Label form_class = modelform_factory(Label, fields=['name', 'club_account'], widgets={ 'club_account': HiddenInput, - }) + }) template_name = 'core/create.jinja' def get_initial(self): @@ -670,12 +697,14 @@ class LabelCreateView(CanCreateMixin, CreateView): # FIXME we need to check the ret['club_account'] = obj.id return ret + class LabelEditView(CanEditMixin, UpdateView): model = Label pk_url_kwarg = "label_id" fields = ['name'] template_name = 'core/edit.jinja' + class LabelDeleteView(CanEditMixin, DeleteView): model = Label pk_url_kwarg = "label_id" @@ -684,9 +713,11 @@ class LabelDeleteView(CanEditMixin, DeleteView): def get_success_url(self): return self.object.get_absolute_url() + class CloseCustomerAccountForm(forms.Form): user = AutoCompleteSelectField('users', label=_('Refound this account'), help_text=None, required=True) + class RefoundAccountView(FormView): """ Create a selling with the same amount than the current user money diff --git a/club/admin.py b/club/admin.py index c9ae46a4..10de0e67 100644 --- a/club/admin.py +++ b/club/admin.py @@ -29,4 +29,3 @@ from club.models import Club, Membership admin.site.register(Club) admin.site.register(Membership) - diff --git a/club/models.py b/club/models.py index 4f1f4852..242250f5 100644 --- a/club/models.py +++ b/club/models.py @@ -27,12 +27,13 @@ from django.core import validators from django.conf import settings from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import ValidationError -from django.db import IntegrityError, transaction +from django.db import transaction from django.core.urlresolvers import reverse from django.utils import timezone from core.models import User, MetaGroup, Group, SithFile + # Create your models here. class Club(models.Model): @@ -43,17 +44,17 @@ class Club(models.Model): name = models.CharField(_('name'), max_length=64) parent = models.ForeignKey('Club', related_name='children', null=True, blank=True) unix_name = models.CharField(_('unix name'), max_length=30, unique=True, - validators=[ - validators.RegexValidator( - r'^[a-z0-9][a-z0-9._-]*[a-z0-9]$', - _('Enter a valid unix name. This value may contain only ' - 'letters, numbers ./-/_ characters.') - ), - ], - error_messages={ - 'unique': _("A club with that unix name already exists."), - }, - ) + validators=[ + validators.RegexValidator( + r'^[a-z0-9][a-z0-9._-]*[a-z0-9]$', + _('Enter a valid unix name. This value may contain only ' + 'letters, numbers ./-/_ characters.') + ), + ], + error_messages={ + 'unique': _("A club with that unix name already exists."), + }, + ) address = models.CharField(_('address'), max_length=254) # email = models.EmailField(_('email address'), unique=True) # This should, and will be generated automatically owner_group = models.ForeignKey(Group, related_name="owned_club", @@ -61,7 +62,7 @@ class Club(models.Model): edit_groups = models.ManyToManyField(Group, related_name="editable_club", blank=True) view_groups = models.ManyToManyField(Group, related_name="viewable_club", blank=True) home = models.OneToOneField(SithFile, related_name='home_of_club', verbose_name=_("home"), null=True, blank=True, - on_delete=models.SET_NULL) + on_delete=models.SET_NULL) class Meta: ordering = ['name', 'unix_name'] @@ -109,9 +110,9 @@ class Club(models.Model): self._change_unixname(self.unix_name) super(Club, self).save(*args, **kwargs) if creation: - board = MetaGroup(name=self.unix_name+settings.SITH_BOARD_SUFFIX) + board = MetaGroup(name=self.unix_name + settings.SITH_BOARD_SUFFIX) board.save() - member = MetaGroup(name=self.unix_name+settings.SITH_MEMBER_SUFFIX) + member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX) member.save() subscribers = Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first() self.make_home() @@ -153,6 +154,7 @@ class Club(models.Model): return sub.is_subscribed _memberships = {} + def get_membership_for(self, user): """ Returns the current membership the given user @@ -168,6 +170,7 @@ class Club(models.Model): Club._memberships[self.id][user.id] = m return m + class Membership(models.Model): """ The Membership class makes the connection between User and Clubs @@ -184,7 +187,7 @@ class Membership(models.Model): start_date = models.DateField(_('start date'), default=timezone.now) end_date = models.DateField(_('end date'), null=True, blank=True) role = models.IntegerField(_('role'), choices=sorted(settings.SITH_CLUB_ROLES.items()), - default=sorted(settings.SITH_CLUB_ROLES.items())[0][0]) + default=sorted(settings.SITH_CLUB_ROLES.items())[0][0]) description = models.CharField(_('description'), max_length=128, null=False, blank=True) def clean(self): @@ -195,9 +198,9 @@ class Membership(models.Model): raise ValidationError(_('User is already member of that club')) def __str__(self): - return self.club.name+' - '+self.user.username+' - '+str(settings.SITH_CLUB_ROLES[self.role])+str( - " - "+str(_('past member')) if self.end_date is not None else "" - ) + return self.club.name + ' - ' + self.user.username + ' - ' + str(settings.SITH_CLUB_ROLES[self.role]) + str( + " - " + str(_('past member')) if self.end_date is not None else "" + ) def is_owned_by(self, user): """ @@ -216,4 +219,3 @@ class Membership(models.Model): def get_absolute_url(self): return reverse('club:club_members', kwargs={'club_id': self.club.id}) - diff --git a/club/tests.py b/club/tests.py index 88020b14..87a2009f 100644 --- a/club/tests.py +++ b/club/tests.py @@ -31,6 +31,7 @@ from club.models import Club # Create your tests here. + class ClubTest(TestCase): def setUp(self): call_command("populate") @@ -41,34 +42,34 @@ class ClubTest(TestCase): def test_create_add_user_to_club_from_root_ok(self): self.client.login(username='root', password='plop') - self.client.post(reverse("club:club_members", kwargs={"club_id":self.bdf.id}), { + self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { "user": self.skia.id, "start_date": "12/06/2016", "role": 3}) - response = self.client.get(reverse("club:club_members", kwargs={"club_id":self.bdf.id})) + response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id})) self.assertTrue(response.status_code == 200) self.assertTrue("S' Kia\\n Responsable info" in str(response.content)) def test_create_add_user_to_club_from_root_fail_not_subscriber(self): self.client.login(username='root', password='plop') - response = self.client.post(reverse("club:club_members", kwargs={"club_id":self.bdf.id}), { + response = self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { "user": self.guy.id, "start_date": "12/06/2016", "role": 3}) self.assertTrue(response.status_code == 200) self.assertTrue('\\n \\n" + " 15.00 \\xe2\\x82\\xac" in str(response.content)) def test_buy_subscribe_product_with_credit_card(self): self.client.login(username='old_subscriber', password='plop') @@ -151,11 +151,11 @@ class EbouticTest(TestCase): "action": "add_product", "product_id": self.cotis.id}) self.assertTrue("\\n" - " \\n" - "\\n Cotis 1 semestre: 15.00 \\xe2\\x82\\xac" in str(response.content)) + " \\n" + "\\n Cotis 1 semestre: 15.00 \\xe2\\x82\\xac" in str(response.content)) response = self.client.post(reverse("eboutic:command")) self.assertTrue("\\n Cotis 1 semestre\\n 1\\n" - " 15.00 \\xe2\\x82\\xac\\n " in str(response.content)) + " 15.00 \\xe2\\x82\\xac\\n " in str(response.content)) response = self.generate_bank_valid_answer_from_page_content(response.content) @@ -163,14 +163,11 @@ class EbouticTest(TestCase): "user_id": self.old_subscriber.id, "year": datetime.now().year, "month": datetime.now().month, - })) + })) self.assertTrue("class=\"selected_tab\">Compte (0.00 \\xe2\\x82\\xac)" in str(response.content)) self.assertTrue("\\n \\n \\n" - " 15.00 \\xe2\\x82\\xac" in str(response.content)) + "
  • 1 x Cotis 1 semestre - 15.00 \\xe2\\x82\\xac
  • \\n" + " \\n \\n \\n" + " 15.00 \\xe2\\x82\\xac" in str(response.content)) response = self.client.get(reverse("core:user_profile", kwargs={"user_id": self.old_subscriber.id})) self.assertTrue("Cotisant jusqu\\'au" in str(response.content)) - - - diff --git a/eboutic/urls.py b/eboutic/urls.py index bb964d1e..d4ddb486 100644 --- a/eboutic/urls.py +++ b/eboutic/urls.py @@ -22,7 +22,7 @@ # # -from django.conf.urls import url, include +from django.conf.urls import url from eboutic.views import * @@ -33,6 +33,3 @@ urlpatterns = [ url(r'^pay$', EbouticPayWithSith.as_view(), name='pay_with_sith'), url(r'^et_autoanswer$', EtransactionAutoAnswer.as_view(), name='etransation_autoanswer'), ] - - - diff --git a/eboutic/views.py b/eboutic/views.py index 58e518cb..7d841d26 100644 --- a/eboutic/views.py +++ b/eboutic/views.py @@ -24,29 +24,27 @@ from collections import OrderedDict from datetime import datetime -import pytz import hmac import base64 from OpenSSL import crypto -from django.shortcuts import render from django.core.urlresolvers import reverse_lazy from django.views.generic import TemplateView, View from django.http import HttpResponse, HttpResponseRedirect from django.core.exceptions import SuspiciousOperation -from django.shortcuts import render from django.db import transaction, DataError from django.utils.translation import ugettext as _ from django.conf import settings from counter.models import Customer, Counter, ProductType, Selling -from eboutic.models import Basket, Invoice, BasketItem, InvoiceItem +from eboutic.models import Basket, Invoice, InvoiceItem + class EbouticMain(TemplateView): template_name = 'eboutic/eboutic_main.jinja' def make_basket(self, request): - if 'basket_id' not in request.session.keys(): # Init the basket session entry + if 'basket_id' not in request.session.keys(): # Init the basket session entry self.basket = Basket(user=request.user) self.basket.save() else: @@ -60,7 +58,7 @@ class EbouticMain(TemplateView): def get(self, request, *args, **kwargs): if not request.user.is_authenticated(): return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + - request.path) + request.path) self.object = Counter.objects.filter(type="EBOUTIC").first() self.make_basket(request) return super(EbouticMain, self).get(request, *args, **kwargs) @@ -68,7 +66,7 @@ class EbouticMain(TemplateView): def post(self, request, *args, **kwargs): if not request.user.is_authenticated(): return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + - request.path) + request.path) self.object = Counter.objects.filter(type="EBOUTIC").first() self.make_basket(request) if 'add_product' in request.POST['action']: @@ -77,7 +75,6 @@ class EbouticMain(TemplateView): self.del_product(request) return self.render_to_response(self.get_context_data(**kwargs)) - def add_product(self, request): """ Add a product to the basket """ try: @@ -108,19 +105,20 @@ class EbouticMain(TemplateView): kwargs['categories'] = kwargs['categories'].exclude(id=settings.SITH_PRODUCTTYPE_SUBSCRIPTION) return kwargs + class EbouticCommand(TemplateView): template_name = 'eboutic/eboutic_makecommand.jinja' def get(self, request, *args, **kwargs): if not request.user.is_authenticated(): return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + - request.path) + request.path) return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) def post(self, request, *args, **kwargs): if not request.user.is_authenticated(): return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + - request.path) + request.path) if 'basket_id' not in request.session.keys(): return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) self.basket = Basket.objects.filter(id=request.session['basket_id']).first() @@ -136,8 +134,8 @@ class EbouticCommand(TemplateView): kwargs['et_request']['PBX_SITE'] = settings.SITH_EBOUTIC_PBX_SITE kwargs['et_request']['PBX_RANG'] = settings.SITH_EBOUTIC_PBX_RANG kwargs['et_request']['PBX_IDENTIFIANT'] = settings.SITH_EBOUTIC_PBX_IDENTIFIANT - kwargs['et_request']['PBX_TOTAL'] = int(self.basket.get_total()*100) - kwargs['et_request']['PBX_DEVISE'] = 978 # This is Euro. ET support only this value anyway + kwargs['et_request']['PBX_TOTAL'] = int(self.basket.get_total() * 100) + kwargs['et_request']['PBX_DEVISE'] = 978 # This is Euro. ET support only this value anyway kwargs['et_request']['PBX_CMD'] = self.basket.id kwargs['et_request']['PBX_PORTEUR'] = self.basket.user.email kwargs['et_request']['PBX_RETOUR'] = "Amount:M;BasketID:R;Auto:A;Error:E;Sig:K" @@ -146,10 +144,11 @@ class EbouticCommand(TemplateView): kwargs['et_request']['PBX_TYPECARTE'] = "CB" kwargs['et_request']['PBX_TIME'] = str(datetime.now().replace(microsecond=0).isoformat('T')) kwargs['et_request']['PBX_HMAC'] = hmac.new(settings.SITH_EBOUTIC_HMAC_KEY, - bytes("&".join(["%s=%s"%(k,v) for k,v in kwargs['et_request'].items()]), 'utf-8'), - "sha512").hexdigest().upper() + bytes("&".join(["%s=%s" % (k, v) for k, v in kwargs['et_request'].items()]), 'utf-8'), + "sha512").hexdigest().upper() return kwargs + class EbouticPayWithSith(TemplateView): template_name = 'eboutic/eboutic_payment_result.jinja' @@ -172,30 +171,31 @@ class EbouticPayWithSith(TemplateView): for it in b.items.all(): product = eboutic.products.filter(id=it.product_id).first() Selling( - label=it.product_name, - counter=eboutic, - club=product.club, - product=product, - seller=c.user, - customer=c, - unit_price=it.product_unit_price, - quantity=it.quantity, - payment_method="SITH_ACCOUNT", - ).save() + label=it.product_name, + counter=eboutic, + club=product.club, + product=product, + seller=c.user, + customer=c, + unit_price=it.product_unit_price, + quantity=it.quantity, + payment_method="SITH_ACCOUNT", + ).save() b.delete() kwargs['not_enough'] = False request.session.pop('basket_id', None) except DataError as e: - kwargs['not_enough'] = True + kwargs['not_enough'] = True return self.render_to_response(self.get_context_data(**kwargs)) + class EtransactionAutoAnswer(View): def get(self, request, *args, **kwargs): if (not 'Amount' in request.GET.keys() or not 'BasketID' in request.GET.keys() or not 'Auto' in request.GET.keys() or not 'Error' in request.GET.keys() or - not 'Sig' in request.GET.keys()): + not 'Sig' in request.GET.keys()): return HttpResponse("Bad arguments", status=400) key = crypto.load_publickey(crypto.FILETYPE_PEM, settings.SITH_EBOUTIC_PUB_KEY) cert = crypto.X509() @@ -217,12 +217,11 @@ class EtransactionAutoAnswer(View): i.save() for it in b.items.all(): InvoiceItem(invoice=i, product_id=it.product_id, product_name=it.product_name, type_id=it.type_id, - product_unit_price=it.product_unit_price, quantity=it.quantity).save() + product_unit_price=it.product_unit_price, quantity=it.quantity).save() i.validate() b.delete() except Exception as e: - return HttpResponse("Payment failed with error: "+repr(e), status=400) + return HttpResponse("Payment failed with error: " + repr(e), status=400) return HttpResponse() else: - return HttpResponse("Payment failed with error: "+request.GET['Error'], status=400) - + return HttpResponse("Payment failed with error: " + request.GET['Error'], status=400) diff --git a/forum/models.py b/forum/models.py index b7f0c8d8..296b5a99 100644 --- a/forum/models.py +++ b/forum/models.py @@ -23,11 +23,9 @@ # from django.db import models -from django.core import validators from django.conf import settings from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import ValidationError -from django.db import IntegrityError, transaction from django.core.urlresolvers import reverse from django.utils import timezone from django.utils.functional import cached_property @@ -35,9 +33,10 @@ from django.utils.functional import cached_property from datetime import datetime import pytz -from core.models import User, MetaGroup, Group, SithFile +from core.models import User, Group from club.models import Club + class Forum(models.Model): """ The Forum class, made as a tree to allow nice tidy organization @@ -52,14 +51,14 @@ class Forum(models.Model): is_category = models.BooleanField(_('is a category'), default=False) parent = models.ForeignKey('Forum', related_name='children', null=True, blank=True) owner_club = models.ForeignKey(Club, related_name="owned_forums", verbose_name=_("owner club"), - default=settings.SITH_MAIN_CLUB_ID) + default=settings.SITH_MAIN_CLUB_ID) edit_groups = models.ManyToManyField(Group, related_name="editable_forums", blank=True, - default=[settings.SITH_GROUP_OLD_SUBSCRIBERS_ID]) + default=[settings.SITH_GROUP_OLD_SUBSCRIBERS_ID]) view_groups = models.ManyToManyField(Group, related_name="viewable_forums", blank=True, - default=[settings.SITH_GROUP_PUBLIC_ID]) + default=[settings.SITH_GROUP_PUBLIC_ID]) number = models.IntegerField(_("number to choose a specific forum ordering"), default=1) _last_message = models.ForeignKey('ForumMessage', related_name="forums_where_its_last", - verbose_name=_("the last message"), null=True, on_delete=models.SET_NULL) + verbose_name=_("the last message"), null=True, on_delete=models.SET_NULL) _topic_number = models.IntegerField(_("number of topics"), default=0) class Meta: @@ -112,16 +111,18 @@ class Forum(models.Model): self.view_groups = self.parent.view_groups.all() self.save() - _club_memberships = {} # This cache is particularly efficient: - # divided by 3 the number of requests on the main forum page - # after the first load + _club_memberships = {} # This cache is particularly efficient: + # divided by 3 the number of requests on the main forum page + # after the first load def is_owned_by(self, user): if user.is_in_group(settings.SITH_GROUP_FORUM_ADMIN_ID): return True - try: m = Forum._club_memberships[self.id][user.id] + try: + m = Forum._club_memberships[self.id][user.id] except: m = self.owner_club.get_membership_for(user) - try: Forum._club_memberships[self.id][user.id] = m + try: + Forum._club_memberships[self.id][user.id] = m except: Forum._club_memberships[self.id] = {} Forum._club_memberships[self.id][user.id] = m @@ -178,12 +179,13 @@ class Forum(models.Model): l += c.get_children_list() return l + class ForumTopic(models.Model): forum = models.ForeignKey(Forum, related_name='topics') author = models.ForeignKey(User, related_name='forum_topics') description = models.CharField(_('description'), max_length=256, default="") _last_message = models.ForeignKey('ForumMessage', related_name="+", verbose_name=_("the last message"), - null=True, on_delete=models.SET_NULL) + null=True, on_delete=models.SET_NULL) _title = models.CharField(_('title'), max_length=64, blank=True) _message_number = models.IntegerField(_("number of messages"), default=0) @@ -192,7 +194,7 @@ class ForumTopic(models.Model): def save(self, *args, **kwargs): super(ForumTopic, self).save(*args, **kwargs) - self.forum.set_topic_number() # Recompute the cached value + self.forum.set_topic_number() # Recompute the cached value self.forum.set_last_message() def is_owned_by(self, user): @@ -225,6 +227,7 @@ class ForumTopic(models.Model): def title(self): return self._title + class ForumMessage(models.Model): """ "A ForumMessage object represents a message in the forum" -- Cpt. Obvious @@ -244,7 +247,7 @@ class ForumMessage(models.Model): return "%s (%s) - %s" % (self.id, self.author, self.title) def save(self, *args, **kwargs): - self._deleted = self.is_deleted() # Recompute the cached value + self._deleted = self.is_deleted() # Recompute the cached value super(ForumMessage, self).save(*args, **kwargs) if self.is_last_in_topic(): self.topic._last_message_id = self.id @@ -259,15 +262,15 @@ class ForumMessage(models.Model): def is_last_in_topic(self): return bool(self.id == self.topic.messages.order_by('date').last().id) - def is_owned_by(self, user): # Anyone can create a topic: it's better to - # check the rights at the forum level, since it's more controlled + def is_owned_by(self, user): # Anyone can create a topic: it's better to + # check the rights at the forum level, since it's more controlled return self.topic.forum.is_owned_by(user) or user.id == self.author.id def can_be_edited_by(self, user): return user.can_edit(self.topic.forum) def can_be_viewed_by(self, user): - return not self._deleted # No need to check the real rights since it's already done by the Topic view + return not self._deleted # No need to check the real rights since it's already done by the Topic view def can_be_moderated_by(self, user): return self.topic.forum.is_owned_by(user) or user.id == self.author.id @@ -282,10 +285,11 @@ class ForumMessage(models.Model): return int(self.topic.messages.filter(id__lt=self.id).count() / settings.SITH_FORUM_PAGE_LENGTH) + 1 def mark_as_read(self, user): - try: # Need the try/except because of AnonymousUser + try: # Need the try/except because of AnonymousUser if not self.is_read(user): self.readers.add(user) - except: pass + except: + pass def is_read(self, user): return (self.date < user.forum_infos.last_read_date) or (user in self.readers.all()) @@ -296,11 +300,13 @@ class ForumMessage(models.Model): return meta.action == "DELETE" return False + MESSAGE_META_ACTIONS = [ - ('EDIT', _("Message edited by")), - ('DELETE', _("Message deleted by")), - ('UNDELETE', _("Message undeleted by")), - ] + ('EDIT', _("Message edited by")), + ('DELETE', _("Message deleted by")), + ('UNDELETE', _("Message undeleted by")), +] + class ForumMessageMeta(models.Model): user = models.ForeignKey(User, related_name="forum_message_metas") @@ -322,8 +328,7 @@ class ForumUserInfo(models.Model): """ user = models.OneToOneField(User, related_name="_forum_infos") last_read_date = models.DateTimeField(_('last read date'), default=datetime(year=settings.SITH_SCHOOL_START_YEAR, - month=1, day=1, tzinfo=pytz.UTC)) + month=1, day=1, tzinfo=pytz.UTC)) def __str__(self): return str(self.user) - diff --git a/forum/urls.py b/forum/urls.py index 9096e3cb..43d7c90f 100644 --- a/forum/urls.py +++ b/forum/urls.py @@ -22,7 +22,7 @@ # # -from django.conf.urls import url, include +from django.conf.urls import url from forum.views import * diff --git a/forum/views.py b/forum/views.py index e90f9d9f..1c539e70 100644 --- a/forum/views.py +++ b/forum/views.py @@ -22,30 +22,30 @@ # # -from django.shortcuts import render, get_object_or_404 +from django.shortcuts import get_object_or_404 from django.views.generic import ListView, DetailView, RedirectView from django.views.generic.edit import UpdateView, CreateView, DeleteView from django.views.generic.detail import SingleObjectMixin from django.utils.translation import ugettext_lazy as _ -from django.core.urlresolvers import reverse, reverse_lazy +from django.core.urlresolvers import reverse_lazy from django.utils import timezone from django.conf import settings from django import forms -from django.db import models from django.core.exceptions import PermissionDenied from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger -from ajax_select import make_ajax_form, make_ajax_field +from ajax_select import make_ajax_field -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, TabedViewMixin +from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin from core.views.forms import MarkdownInput -from core.models import Page from forum.models import Forum, ForumMessage, ForumTopic, ForumMessageMeta + class ForumMainView(ListView): queryset = Forum.objects.filter(parent=None).prefetch_related("children___last_message__author", "children___last_message__topic") template_name = "forum/main.jinja" + class ForumMarkAllAsRead(RedirectView): permanent = False url = reverse_lazy('forum:last_unread') @@ -56,10 +56,12 @@ class ForumMarkAllAsRead(RedirectView): fi.last_read_date = timezone.now() fi.save() for m in request.user.read_messages.filter(date__lt=fi.last_read_date): - m.readers.remove(request.user) # Clean up to keep table low in data - except: pass + m.readers.remove(request.user) # Clean up to keep table low in data + except: + pass return super(ForumMarkAllAsRead, self).get(request, *args, **kwargs) + class ForumLastUnread(ListView): model = ForumTopic template_name = "forum/last_unread.jinja" @@ -67,12 +69,13 @@ class ForumLastUnread(ListView): def get_queryset(self): topic_list = self.model.objects.filter(_last_message__date__gt=self.request.user.forum_infos.last_read_date)\ - .exclude(_last_message__readers=self.request.user)\ - .order_by('-_last_message__date')\ - .select_related('_last_message__author', 'author')\ - .prefetch_related('forum__edit_groups') + .exclude(_last_message__readers=self.request.user)\ + .order_by('-_last_message__date')\ + .select_related('_last_message__author', 'author')\ + .prefetch_related('forum__edit_groups') return topic_list + class ForumForm(forms.ModelForm): class Meta: model = Forum @@ -80,6 +83,7 @@ class ForumForm(forms.ModelForm): edit_groups = make_ajax_field(Forum, 'edit_groups', 'groups', help_text="") view_groups = make_ajax_field(Forum, 'view_groups', 'groups', help_text="") + class ForumCreateView(CanCreateMixin, CreateView): model = Forum form_class = ForumForm @@ -93,12 +97,15 @@ class ForumCreateView(CanCreateMixin, CreateView): init['owner_club'] = parent.owner_club init['edit_groups'] = parent.edit_groups.all() init['view_groups'] = parent.view_groups.all() - except: pass + except: + pass return init + class ForumEditForm(ForumForm): recursive = forms.BooleanField(label=_("Apply rights and club owner recursively"), required=False) + class ForumEditView(CanEditPropMixin, UpdateView): model = Forum pk_url_kwarg = "forum_id" @@ -112,12 +119,14 @@ class ForumEditView(CanEditPropMixin, UpdateView): self.object.apply_rights_recursively() return ret + class ForumDeleteView(CanEditPropMixin, DeleteView): model = Forum pk_url_kwarg = "forum_id" template_name = "core/delete_confirm.jinja" success_url = reverse_lazy('forum:main') + class ForumDetailView(CanViewMixin, DetailView): model = Forum template_name = "forum/forum.jinja" @@ -126,10 +135,10 @@ class ForumDetailView(CanViewMixin, DetailView): def get_context_data(self, **kwargs): kwargs = super(ForumDetailView, self).get_context_data(**kwargs) qs = self.object.topics.order_by('-_last_message__date')\ - .select_related('_last_message__author', 'author')\ - .prefetch_related("forum__edit_groups") + .select_related('_last_message__author', 'author')\ + .prefetch_related("forum__edit_groups") paginator = Paginator(qs, - settings.SITH_FORUM_PAGE_LENGTH) + settings.SITH_FORUM_PAGE_LENGTH) page = self.request.GET.get('topic_page') try: kwargs["topics"] = paginator.page(page) @@ -139,15 +148,17 @@ class ForumDetailView(CanViewMixin, DetailView): kwargs["topics"] = paginator.page(paginator.num_pages) return kwargs + class TopicForm(forms.ModelForm): class Meta: model = ForumMessage fields = ['title', 'message'] widgets = { 'message': MarkdownInput, - } + } title = forms.CharField(required=True, label=_("Title")) + class ForumTopicCreateView(CanCreateMixin, CreateView): model = ForumMessage form_class = TopicForm @@ -166,12 +177,14 @@ class ForumTopicCreateView(CanCreateMixin, CreateView): form.instance.author = self.request.user return super(ForumTopicCreateView, self).form_valid(form) + class ForumTopicEditView(CanEditMixin, UpdateView): model = ForumTopic fields = ['forum'] pk_url_kwarg = "topic_id" template_name = "core/edit.jinja" + class ForumTopicDetailView(CanViewMixin, DetailView): model = ForumTopic pk_url_kwarg = "topic_id" @@ -186,9 +199,9 @@ class ForumTopicDetailView(CanViewMixin, DetailView): kwargs['first_unread_message_id'] = msg.id except: kwargs['first_unread_message_id'] = float("inf") - paginator = Paginator(self.object.messages.select_related('author__avatar_pict')\ - .prefetch_related('topic__forum__edit_groups', 'readers').order_by('date'), - settings.SITH_FORUM_PAGE_LENGTH) + paginator = Paginator(self.object.messages.select_related('author__avatar_pict') + .prefetch_related('topic__forum__edit_groups', 'readers').order_by('date'), + settings.SITH_FORUM_PAGE_LENGTH) page = self.request.GET.get('page') try: kwargs["msgs"] = paginator.page(page) @@ -198,6 +211,7 @@ class ForumTopicDetailView(CanViewMixin, DetailView): kwargs["msgs"] = paginator.page(paginator.num_pages) return kwargs + class ForumMessageView(SingleObjectMixin, RedirectView): model = ForumMessage pk_url_kwarg = "message_id" @@ -207,9 +221,10 @@ class ForumMessageView(SingleObjectMixin, RedirectView): self.object = self.get_object() return self.object.get_url() + class ForumMessageEditView(CanEditMixin, UpdateView): model = ForumMessage - form_class = forms.modelform_factory(model=ForumMessage, fields=['title', 'message',], widgets={'message': MarkdownInput}) + form_class = forms.modelform_factory(model=ForumMessage, fields=['title', 'message', ], widgets={'message': MarkdownInput}) template_name = "forum/reply.jinja" pk_url_kwarg = "message_id" @@ -222,6 +237,7 @@ class ForumMessageEditView(CanEditMixin, UpdateView): kwargs['topic'] = self.object.topic return kwargs + class ForumMessageDeleteView(SingleObjectMixin, RedirectView): model = ForumMessage pk_url_kwarg = "message_id" @@ -233,6 +249,7 @@ class ForumMessageDeleteView(SingleObjectMixin, RedirectView): ForumMessageMeta(message=self.object, user=self.request.user, action="DELETE").save() return self.object.get_absolute_url() + class ForumMessageUndeleteView(SingleObjectMixin, RedirectView): model = ForumMessage pk_url_kwarg = "message_id" @@ -244,9 +261,10 @@ class ForumMessageUndeleteView(SingleObjectMixin, RedirectView): ForumMessageMeta(message=self.object, user=self.request.user, action="UNDELETE").save() return self.object.get_absolute_url() + class ForumMessageCreateView(CanCreateMixin, CreateView): model = ForumMessage - form_class = forms.modelform_factory(model=ForumMessage, fields=['title', 'message',], widgets={'message': MarkdownInput}) + form_class = forms.modelform_factory(model=ForumMessage, fields=['title', 'message', ], widgets={'message': MarkdownInput}) template_name = "forum/reply.jinja" def dispatch(self, request, *args, **kwargs): @@ -262,7 +280,7 @@ class ForumMessageCreateView(CanCreateMixin, CreateView): init['message'] = "> ##### %s\n" % (_("%(author)s said") % {'author': message.author.get_short_name()}) init['message'] += "\n".join([ "> " + line for line in message.message.split('\n') - ]) + ]) init['message'] += "\n\n" except Exception as e: print(repr(e)) @@ -277,4 +295,3 @@ class ForumMessageCreateView(CanCreateMixin, CreateView): kwargs = super(ForumMessageCreateView, self).get_context_data(**kwargs) kwargs['topic'] = self.topic return kwargs - diff --git a/launderette/models.py b/launderette/models.py index 9ad0f35e..9528db70 100644 --- a/launderette/models.py +++ b/launderette/models.py @@ -27,12 +27,13 @@ from django.utils.translation import ugettext_lazy as _ from django.conf import settings from django.core.urlresolvers import reverse -from counter.models import Counter, Product +from counter.models import Counter from core.models import User from club.models import Club # Create your models here. + class Launderette(models.Model): name = models.CharField(_('name'), max_length=30) counter = models.OneToOneField(Counter, verbose_name=_('counter'), related_name='launderette') @@ -78,6 +79,7 @@ class Launderette(models.Model): def token_list(self): return [t.id for t in self.get_token_list()] + class Machine(models.Model): name = models.CharField(_('name'), max_length=30) launderette = models.ForeignKey(Launderette, related_name='machines', verbose_name=_('launderette')) @@ -103,6 +105,7 @@ class Machine(models.Model): def get_absolute_url(self): return reverse('launderette:launderette_admin', kwargs={"launderette_id": self.launderette.id}) + class Token(models.Model): name = models.CharField(_('name'), max_length=5) launderette = models.ForeignKey(Launderette, related_name='tokens', verbose_name=_('launderette')) @@ -140,6 +143,7 @@ class Token(models.Model): else: return False + class Slot(models.Model): start_date = models.DateTimeField(_('start date')) type = models.CharField(_('type'), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES) @@ -156,6 +160,4 @@ class Slot(models.Model): def __str__(self): return "User: %s - Date: %s - Type: %s - Machine: %s - Token: %s" % (self.user, self.start_date, self.get_type_display(), - self.machine.name, self.token) - - + self.machine.name, self.token) diff --git a/launderette/urls.py b/launderette/urls.py index ccd09415..844f1385 100644 --- a/launderette/urls.py +++ b/launderette/urls.py @@ -22,7 +22,7 @@ # # -from django.conf.urls import url, include +from django.conf.urls import url from launderette.views import * diff --git a/launderette/views.py b/launderette/views.py index 340f6607..da7e6219 100644 --- a/launderette/views.py +++ b/launderette/views.py @@ -26,11 +26,8 @@ from datetime import datetime, timedelta from collections import OrderedDict import pytz -from django.shortcuts import render -from django.views.generic import ListView, DetailView, RedirectView, TemplateView +from django.views.generic import ListView, DetailView, TemplateView from django.views.generic.edit import UpdateView, CreateView, DeleteView, BaseFormView -from django.forms.models import modelform_factory -from django.forms import CheckboxSelectMultiple from django.utils.translation import ugettext as _ from django.utils import dateparse, timezone from django.core.urlresolvers import reverse_lazy @@ -38,7 +35,6 @@ from django.conf import settings from django.db import transaction, DataError from django import forms from django.template import defaultfilters -from django.utils import formats from core.models import Page, User from club.models import Club @@ -49,6 +45,7 @@ from counter.views import GetUserForm # For users + class LaunderetteMainView(TemplateView): """Main presentation view""" template_name = 'launderette/launderette_main.jinja' @@ -59,11 +56,13 @@ class LaunderetteMainView(TemplateView): kwargs['page'] = Page.objects.filter(name='launderette').first() return kwargs + class LaunderetteBookMainView(CanViewMixin, ListView): """Choose which launderette to book""" model = Launderette template_name = 'launderette/launderette_book_choose.jinja' + class LaunderetteBookView(CanViewMixin, DetailView): """Display the launderette schedule""" model = Launderette @@ -96,11 +95,12 @@ class LaunderetteBookView(CanViewMixin, DetailView): if self.check_slot("WASHING") and self.check_slot("DRYING", self.date + timedelta(hours=1)): Slot(user=self.subscriber, start_date=self.date, machine=self.machines["WASHING"], type="WASHING").save() Slot(user=self.subscriber, start_date=self.date + timedelta(hours=1), - machine=self.machines["DRYING"], type="DRYING").save() + machine=self.machines["DRYING"], type="DRYING").save() return super(LaunderetteBookView, self).get(request, *args, **kwargs) def check_slot(self, type, date=None): - if date is None: date = self.date + if date is None: + date = self.date for m in self.object.machines.filter(is_working=True, type=type).all(): slot = Slot.objects.filter(start_date=date, machine=m).first() if slot is None: @@ -121,9 +121,9 @@ class LaunderetteBookView(CanViewMixin, DetailView): kwargs['planning'] = OrderedDict() kwargs['slot_type'] = self.slot_type start_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=pytz.UTC) - for date in LaunderetteBookView.date_iterator(start_date, start_date+timedelta(days=6), timedelta(days=1)): + for date in LaunderetteBookView.date_iterator(start_date, start_date + timedelta(days=6), timedelta(days=1)): kwargs['planning'][date] = [] - for h in LaunderetteBookView.date_iterator(date, date+timedelta(days=1), timedelta(hours=1)): + for h in LaunderetteBookView.date_iterator(date, date + timedelta(days=1), timedelta(hours=1)): free = False if self.slot_type == "BOTH" and self.check_slot("WASHING", h) and self.check_slot("DRYING", h + timedelta(hours=1)): free = True @@ -137,6 +137,7 @@ class LaunderetteBookView(CanViewMixin, DetailView): kwargs['planning'][date].append(None) return kwargs + class SlotDeleteView(CanEditPropMixin, DeleteView): """Delete a slot""" model = Slot @@ -154,6 +155,7 @@ class LaunderetteListView(CanEditPropMixin, ListView): model = Launderette template_name = 'launderette/launderette_list.jinja' + class LaunderetteEditView(CanEditPropMixin, UpdateView): """Edit a launderette""" model = Launderette @@ -161,6 +163,7 @@ class LaunderetteEditView(CanEditPropMixin, UpdateView): fields = ['name'] template_name = 'core/edit.jinja' + class LaunderetteCreateView(CanCreateMixin, CreateView): """Create a new launderette""" model = Launderette @@ -174,11 +177,12 @@ class LaunderetteCreateView(CanCreateMixin, CreateView): form.instance.counter = c return super(LaunderetteCreateView, self).form_valid(form) + class ManageTokenForm(forms.Form): action = forms.ChoiceField(choices=[("BACK", _("Back")), ("ADD", _("Add")), ("DEL", _("Delete"))], initial="BACK", - label=_("Action"), widget=forms.RadioSelect) + label=_("Action"), widget=forms.RadioSelect) token_type = forms.ChoiceField(choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES, label=_("Type"), initial="WASHING", - widget=forms.RadioSelect) + widget=forms.RadioSelect) tokens = forms.CharField(max_length=512, widget=forms.widgets.Textarea, label=_("Tokens, separated by spaces")) def process(self, launderette): @@ -210,6 +214,7 @@ class ManageTokenForm(forms.Form): except: self.add_error(None, _("Token %(token_name)s does not exists") % {'token_name': t}) + class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView): """The admin page of the launderette""" model = Launderette @@ -253,6 +258,7 @@ class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView): def get_success_url(self): return reverse_lazy('launderette:launderette_admin', args=self.args, kwargs=self.kwargs) + class GetLaunderetteUserForm(GetUserForm): def clean(self): cleaned_data = super(GetLaunderetteUserForm, self).clean() @@ -261,12 +267,13 @@ class GetLaunderetteUserForm(GetUserForm): raise forms.ValidationError(_("User has booked no slot")) return cleaned_data + class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView): """The click page of the launderette""" model = Launderette pk_url_kwarg = "launderette_id" template_name = 'counter/counter_main.jinja' - form_class = GetLaunderetteUserForm # Form to enter a client code and get the corresponding user id + form_class = GetLaunderetteUserForm # Form to enter a client code and get the corresponding user id def get(self, request, *args, **kwargs): self.object = self.get_object() @@ -301,6 +308,7 @@ class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView): def get_success_url(self): return reverse_lazy('launderette:click', args=self.args, kwargs=self.kwargs) + class ClickTokenForm(forms.BaseForm): def clean(self): with transaction.atomic(): @@ -309,11 +317,11 @@ class ClickTokenForm(forms.BaseForm): counter = Counter.objects.filter(id=self.counter_id).first() subscriber = customer.user self.last_basket = { - 'last_basket': [], - 'last_customer': customer.user.get_display_name(), - } + 'last_basket': [], + 'last_customer': customer.user.get_display_name(), + } total = 0 - for k,t in self.cleaned_data.items(): + for k, t in self.cleaned_data.items(): if t is not None: slot_id = int(k[5:]) slot = Slot.objects.filter(id=slot_id).first() @@ -323,15 +331,16 @@ class ClickTokenForm(forms.BaseForm): t.borrow_date = datetime.now().replace(tzinfo=pytz.UTC) t.save() price = settings.SITH_LAUNDERETTE_PRICES[t.type] - s = Selling(label="Jeton "+t.get_type_display()+" N°"+t.name, club=counter.club, product=None, counter=counter, unit_price=price, - quantity=1, seller=operator, customer=customer) + s = Selling(label="Jeton " + t.get_type_display() + " N°" + t.name, club=counter.club, product=None, counter=counter, unit_price=price, + quantity=1, seller=operator, customer=customer) s.save() total += price - self.last_basket['last_basket'].append("Jeton "+t.get_type_display()+" N°"+t.name) + self.last_basket['last_basket'].append("Jeton " + t.get_type_display() + " N°" + t.name) self.last_basket['new_customer_amount'] = str(customer.amount) self.last_basket['last_total'] = str(total) return self.cleaned_data + class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView): """The click page of the launderette""" model = Launderette @@ -346,7 +355,7 @@ class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView): t_name = str(self2.data[field_name]) if t_name != "": t = Token.objects.filter(name=str(self2.data[field_name]), type=slot.type, launderette=self.object, - user=None).first() + user=None).first() if t is None: raise forms.ValidationError(_("Token not found")) return t @@ -354,9 +363,9 @@ class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView): for s in self.subscriber.slots.filter(token=None, start_date__gte=timezone.now().replace(tzinfo=None)).all(): field_name = "slot-%s" % (str(s.id)) fields[field_name] = forms.CharField(max_length=5, required=False, - label="%s - %s" % (s.get_type_display(), defaultfilters.date(s.start_date, "j N Y H:i"))) + label="%s - %s" % (s.get_type_display(), defaultfilters.date(s.start_date, "j N Y H:i"))) # XXX l10n settings.DATETIME_FORMAT didn't work here :/ - kwargs["clean_"+field_name] = clean_field_factory(field_name, s) + kwargs["clean_" + field_name] = clean_field_factory(field_name, s) kwargs['subscriber_id'] = self.subscriber.id kwargs['counter_id'] = self.object.counter.id kwargs['operator_id'] = self.operator.id @@ -401,7 +410,6 @@ class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView): return reverse_lazy('launderette:main_click', args=self.args, kwargs=self.kwargs) - class MachineEditView(CanEditPropMixin, UpdateView): """Edit a machine""" model = Machine @@ -409,6 +417,7 @@ class MachineEditView(CanEditPropMixin, UpdateView): fields = ['name', 'launderette', 'type', 'is_working'] template_name = 'core/edit.jinja' + class MachineDeleteView(CanEditPropMixin, DeleteView): """Edit a machine""" model = Machine @@ -416,6 +425,7 @@ class MachineDeleteView(CanEditPropMixin, DeleteView): template_name = 'core/delete_confirm.jinja' success_url = reverse_lazy('launderette:launderette_list') + class MachineCreateView(CanCreateMixin, CreateView): """Create a new machine""" model = Machine @@ -429,6 +439,3 @@ class MachineCreateView(CanCreateMixin, CreateView): if obj is not None: ret['launderette'] = obj.id return ret - - - diff --git a/sas/admin.py b/sas/admin.py index f504eec4..add09607 100644 --- a/sas/admin.py +++ b/sas/admin.py @@ -30,5 +30,3 @@ from sas.models import * admin.site.register(Album) # admin.site.register(Picture) admin.site.register(PeoplePictureRelation) - - diff --git a/sas/models.py b/sas/models.py index 10412b9e..b88d599d 100644 --- a/sas/models.py +++ b/sas/models.py @@ -23,11 +23,9 @@ # from django.db import models -from django.core.urlresolvers import reverse_lazy, reverse -from django.conf import settings from django.core.urlresolvers import reverse +from django.conf import settings from django.utils.translation import ugettext_lazy as _ -from django.core.files.base import ContentFile from PIL import Image from io import BytesIO @@ -36,6 +34,7 @@ import os from core.models import SithFile, User from core.utils import resize_image, exif_auto_rotate + class Picture(SithFile): class Meta: proxy = True @@ -50,12 +49,12 @@ class Picture(SithFile): def can_be_edited_by(self, user): # file = SithFile.objects.filter(id=self.id).first() - return user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID)# or user.can_edit(file) + return user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) # or user.can_edit(file) def can_be_viewed_by(self, user): # file = SithFile.objects.filter(id=self.id).first() return self.can_be_edited_by(user) or (self.is_in_sas and self.is_moderated and - user.was_subscribed)# or user.can_view(file) + user.was_subscribed) # or user.can_view(file) def get_download_url(self): return reverse('sas:download', kwargs={'picture_id': self.id}) @@ -73,7 +72,8 @@ class Picture(SithFile): im = Image.open(BytesIO(self.file.read())) try: im = exif_auto_rotate(im) - except: pass + except: + pass file = resize_image(im, max(im.size), self.mime_type.split('/')[-1]) thumb = resize_image(im, 200, self.mime_type.split('/')[-1]) compressed = resize_image(im, 1200, self.mime_type.split('/')[-1]) @@ -102,17 +102,18 @@ class Picture(SithFile): def get_next(self): if self.is_moderated: return self.parent.children.filter(is_moderated=True, asked_for_removal=False, is_folder=False, - id__gt=self.id).order_by('id').first() + id__gt=self.id).order_by('id').first() else: return Picture.objects.filter(id__gt=self.id, is_moderated=False, is_in_sas=True).order_by('id').first() def get_previous(self): if self.is_moderated: return self.parent.children.filter(is_moderated=True, asked_for_removal=False, is_folder=False, - id__lt=self.id).order_by('id').last() + id__lt=self.id).order_by('id').last() else: return Picture.objects.filter(id__lt=self.id, is_moderated=False, is_in_sas=True).order_by('-id').first() + class Album(SithFile): class Meta: proxy = True @@ -127,12 +128,12 @@ class Album(SithFile): def can_be_edited_by(self, user): # file = SithFile.objects.filter(id=self.id).first() - return user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID)# or user.can_edit(file) + return user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) # or user.can_edit(file) def can_be_viewed_by(self, user): # file = SithFile.objects.filter(id=self.id).first() return self.can_be_edited_by(user) or (self.is_in_sas and self.is_moderated and - user.was_subscribed)# or user.can_view(file) + user.was_subscribed) # or user.can_view(file) def get_absolute_url(self): return reverse('sas:album', kwargs={'album_id': self.id}) @@ -148,6 +149,7 @@ class Album(SithFile): self.file.name = self.name + '/thumb.jpg' self.save() + class PeoplePictureRelation(models.Model): """ The PeoplePictureRelation class makes the connection between User and Picture diff --git a/sas/urls.py b/sas/urls.py index 40f5d3b7..0a204e69 100644 --- a/sas/urls.py +++ b/sas/urls.py @@ -22,7 +22,7 @@ # # -from django.conf.urls import url, include +from django.conf.urls import url from sas.views import * @@ -40,4 +40,3 @@ urlpatterns = [ # url(r'^album/new$', AlbumCreateView.as_view(), name='album_new'), # url(r'^(?P[0-9]+)/$', ClubView.as_view(), name='club_view'), ] - diff --git a/sas/views.py b/sas/views.py index 38ab54dc..1dc5b202 100644 --- a/sas/views.py +++ b/sas/views.py @@ -22,35 +22,31 @@ # # -from django.shortcuts import render, redirect -from django.http import HttpResponseRedirect, HttpResponse +from django.shortcuts import redirect +from django.http import HttpResponse from django.core.urlresolvers import reverse_lazy, reverse -from django.views.generic import ListView, DetailView, RedirectView, TemplateView -from django.views.generic.edit import UpdateView, CreateView, DeleteView, ProcessFormView, FormMixin, FormView +from core.views.forms import SelectDate +from django.views.generic import DetailView, TemplateView +from django.views.generic.edit import UpdateView, FormMixin, FormView from django.utils.translation import ugettext_lazy as _ -from django.utils import timezone from django.conf import settings -from django.forms.models import modelform_factory from django import forms from django.core.exceptions import PermissionDenied -from ajax_select import make_ajax_form, make_ajax_field -from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField +from ajax_select import make_ajax_field +from ajax_select.fields import AutoCompleteSelectMultipleField -from io import BytesIO -from PIL import Image - -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, TabedViewMixin -from core.views.forms import SelectUser, LoginForm, SelectDate, SelectDateTime +from core.views import CanViewMixin, CanEditMixin from core.views.files import send_file, FileView from core.models import SithFile, User, Notification, RealGroup from sas.models import Picture, Album, PeoplePictureRelation + class SASForm(forms.Form): album_name = forms.CharField(label=_("Add a new album"), max_length=30, required=False) images = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}), label=_("Upload images"), - required=False) + required=False) def process(self, parent, owner, files, automodere=False): notif = False @@ -62,10 +58,10 @@ class SASForm(forms.Form): notif = not automodere except Exception as e: self.add_error(None, _("Error creating album %(album)s: %(msg)s") % - {'album': self.cleaned_data['album_name'], 'msg': repr(e)}) + {'album': self.cleaned_data['album_name'], 'msg': repr(e)}) for f in files: new_file = Picture(parent=parent, name=f.name, file=f, owner=owner, mime_type=f.content_type, size=f._size, - is_folder=False, is_moderated=automodere) + is_folder=False, is_moderated=automodere) if automodere: new_file.moderator = owner try: @@ -80,6 +76,7 @@ class SASForm(forms.Form): if not u.notifications.filter(type="SAS_MODERATION", viewed=False).exists(): Notification(user=u, url=reverse("sas:moderation"), type="SAS_MODERATION").save() + class RelationForm(forms.ModelForm): class Meta: model = PeoplePictureRelation @@ -87,6 +84,7 @@ class RelationForm(forms.ModelForm): widgets = {'picture': forms.HiddenInput} users = AutoCompleteSelectMultipleField('users', show_help_text=False, help_text="", label=_("Add user"), required=False) + class SASMainView(FormView): form_class = SASForm template_name = "sas/main.jinja" @@ -112,6 +110,7 @@ class SASMainView(FormView): kwargs['latest'] = SithFile.objects.filter(is_in_sas=True, is_folder=True, is_moderated=True).order_by('-id')[:5] return kwargs + class PictureView(CanViewMixin, DetailView, FormMixin): model = Picture form_class = RelationForm @@ -132,8 +131,9 @@ class PictureView(CanViewMixin, DetailView, FormMixin): try: user = User.objects.filter(id=int(request.GET['remove_user'])).first() if user.id == request.user.id or request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID): - r = PeoplePictureRelation.objects.filter(user=user, picture=self.object).delete() - except: pass + PeoplePictureRelation.objects.filter(user=user, picture=self.object).delete() + except: + pass if 'ask_removal' in request.GET.keys(): self.object.is_moderated = False self.object.asked_for_removal = True @@ -149,7 +149,7 @@ class PictureView(CanViewMixin, DetailView, FormMixin): for uid in self.form.cleaned_data['users']: u = User.objects.filter(id=uid).first() PeoplePictureRelation(user=u, - picture=self.form.cleaned_data['picture']).save() + picture=self.form.cleaned_data['picture']).save() if not u.notifications.filter(type="NEW_PICTURES", viewed=False).exists(): Notification(user=u, url=reverse("core:user_pictures", kwargs={'user_id': u.id}), type="NEW_PICTURES").save() return super(PictureView, self).form_valid(self.form) @@ -165,15 +165,19 @@ class PictureView(CanViewMixin, DetailView, FormMixin): def get_success_url(self): return reverse('sas:picture', kwargs={'picture_id': self.object.id}) + def send_pict(request, picture_id): return send_file(request, picture_id, Picture) + def send_compressed(request, picture_id): return send_file(request, picture_id, Picture, "compressed") + def send_thumb(request, picture_id): return send_file(request, picture_id, Picture, "thumbnail") + class AlbumUploadView(CanViewMixin, DetailView, FormMixin): model = Album form_class = SASForm @@ -189,7 +193,7 @@ class AlbumUploadView(CanViewMixin, DetailView, FormMixin): if request.user.is_authenticated() and request.user.is_subscribed: if self.form.is_valid(): self.form.process(parent=parent, owner=request.user, files=files, - automodere=request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID)) + automodere=request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID)) if self.form.is_valid(): return HttpResponse(str(self.form.errors), status=200) return HttpResponse(str(self.form.errors), status=500) @@ -214,14 +218,14 @@ class AlbumView(CanViewMixin, DetailView, FormMixin): self.form = self.get_form() if 'clipboard' not in request.session.keys(): request.session['clipboard'] = [] - if request.user.can_edit(self.object): # Handle the copy-paste functions + if request.user.can_edit(self.object): # Handle the copy-paste functions FileView.handle_clipboard(request, self.object) parent = SithFile.objects.filter(id=self.object.id).first() files = request.FILES.getlist('images') if request.user.is_authenticated() and request.user.is_subscribed: if self.form.is_valid(): self.form.process(parent=parent, owner=request.user, files=files, - automodere=request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID)) + automodere=request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID)) if self.form.is_valid(): return super(AlbumView, self).form_valid(self.form) else: @@ -239,6 +243,7 @@ class AlbumView(CanViewMixin, DetailView, FormMixin): # Admin views + class ModerationView(TemplateView): template_name = "sas/moderation.jinja" @@ -257,23 +262,26 @@ class ModerationView(TemplateView): a.save() elif 'delete' in request.POST.keys(): a.delete() - except: pass + except: + pass return super(ModerationView, self).get(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs = super(ModerationView, self).get_context_data(**kwargs) kwargs['albums_to_moderate'] = Album.objects.filter(is_moderated=False, is_in_sas=True, - is_folder=True).order_by('id') + is_folder=True).order_by('id') kwargs['pictures'] = Picture.objects.filter(is_moderated=False, is_in_sas=True, is_folder=False) kwargs['albums'] = Album.objects.filter(id__in=kwargs['pictures'].values('parent').distinct('parent')) return kwargs + class PictureEditForm(forms.ModelForm): class Meta: model = Picture - fields=['name', 'parent'] + fields = ['name', 'parent'] parent = make_ajax_field(Picture, 'parent', 'files', help_text="") + class AlbumEditForm(forms.ModelForm): class Meta: model = Album @@ -283,16 +291,18 @@ class AlbumEditForm(forms.ModelForm): edit_groups = make_ajax_field(Album, 'edit_groups', 'groups', help_text="") recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False) + class PictureEditView(CanEditMixin, UpdateView): - model=Picture - form_class=PictureEditForm - template_name='core/edit.jinja' + model = Picture + form_class = PictureEditForm + template_name = 'core/edit.jinja' pk_url_kwarg = "picture_id" + class AlbumEditView(CanEditMixin, UpdateView): - model=Album - form_class=AlbumEditForm - template_name='core/edit.jinja' + model = Album + form_class = AlbumEditForm + template_name = 'core/edit.jinja' pk_url_kwarg = "album_id" def form_valid(self, form): @@ -300,4 +310,3 @@ class AlbumEditView(CanEditMixin, UpdateView): if form.cleaned_data['recursive']: self.object.apply_rights_recursively(True) return ret - diff --git a/sith/settings.py b/sith/settings.py index 05568325..e3fe254d 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -114,14 +114,14 @@ TEMPLATES = [ "app_dirname": "templates", "newstyle_gettext": True, "context_processors": [ - "django.contrib.auth.context_processors.auth", - "django.template.context_processors.debug", - "django.template.context_processors.i18n", - "django.template.context_processors.media", - "django.template.context_processors.static", - "django.template.context_processors.tz", - "django.contrib.messages.context_processors.messages", - ], + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.debug", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.contrib.messages.context_processors.messages", + ], "extensions": [ "jinja2.ext.do", "jinja2.ext.loopcontrols", @@ -176,11 +176,11 @@ TEMPLATES = [ ] HAYSTACK_CONNECTIONS = { - 'default': { - 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', - 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'), - }, - } + 'default': { + 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', + 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'), + }, +} HAYSTACK_SIGNAL_PROCESSOR = 'core.search_indexes.UserOnlySignalProcessor' @@ -206,9 +206,9 @@ DATABASES = { LANGUAGE_CODE = 'fr-FR' LANGUAGES = [ - ('en', _('English')), - ('fr', _('French')), - ] + ('en', _('English')), + ('fr', _('French')), +] TIME_ZONE = 'Europe/Paris' @@ -247,55 +247,55 @@ AUTH_ANONYMOUS_MODEL = 'core.models.AnonymousUser' LOGIN_URL = '/login' LOGOUT_URL = '/logout' LOGIN_REDIRECT_URL = '/' -DEFAULT_FROM_EMAIL="bibou@git.an" -SITH_COM_EMAIL="bibou_com@git.an" +DEFAULT_FROM_EMAIL = "bibou@git.an" +SITH_COM_EMAIL = "bibou_com@git.an" # Email EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' -EMAIL_HOST="localhost" -EMAIL_PORT=25 +EMAIL_HOST = "localhost" +EMAIL_PORT = 25 # Below this line, only Sith-specific variables are defined IS_OLD_MYSQL_PRESENT = False OLD_MYSQL_INFOS = { - 'host': 'ae-db', - 'user': "my_user", - 'passwd': "password", - 'db': "ae2-db", - 'charset': 'utf8', - 'use_unicode': True, - } + 'host': 'ae-db', + 'user': "my_user", + 'passwd': "password", + 'db': "ae2-db", + 'charset': 'utf8', + 'use_unicode': True, +} SITH_URL = "my.url.git.an" SITH_NAME = "Sith website" # AE configuration -SITH_MAIN_CLUB_ID = 1 # TODO: keep only that first setting, with the ID, and do the same for the other clubs +SITH_MAIN_CLUB_ID = 1 # TODO: keep only that first setting, with the ID, and do the same for the other clubs SITH_MAIN_CLUB = { - 'name': "AE", - 'unix_name': "ae", - 'address': "6 Boulevard Anatole France, 90000 Belfort" - } + 'name': "AE", + 'unix_name': "ae", + 'address': "6 Boulevard Anatole France, 90000 Belfort" +} # Bar managers SITH_BAR_MANAGER = { - 'name': "BdF", - 'unix_name': "bdf", - 'address': "6 Boulevard Anatole France, 90000 Belfort" - } + 'name': "BdF", + 'unix_name': "bdf", + 'address': "6 Boulevard Anatole France, 90000 Belfort" +} # Launderette managers SITH_LAUNDERETTE_MANAGER = { - 'name': "Laverie", - 'unix_name': "laverie", - 'address': "6 Boulevard Anatole France, 90000 Belfort" - } + 'name': "Laverie", + 'unix_name': "laverie", + 'address': "6 Boulevard Anatole France, 90000 Belfort" +} # Define the date in the year serving as reference for the subscriptions calendar # (month, day) -SITH_START_DATE = (8, 15) # 15th August +SITH_START_DATE = (8, 15) # 15th August # Used to determine the valid promos SITH_SCHOOL_START_YEAR = 1999 @@ -328,74 +328,74 @@ SITH_FORUM_PAGE_LENGTH = 30 # SAS variables SITH_SAS_ROOT_DIR_ID = 4 -SITH_BOARD_SUFFIX="-bureau" -SITH_MEMBER_SUFFIX="-membres" +SITH_BOARD_SUFFIX = "-bureau" +SITH_MEMBER_SUFFIX = "-membres" -SITH_MAIN_BOARD_GROUP=SITH_MAIN_CLUB['unix_name']+SITH_BOARD_SUFFIX -SITH_MAIN_MEMBERS_GROUP=SITH_MAIN_CLUB['unix_name']+SITH_MEMBER_SUFFIX +SITH_MAIN_BOARD_GROUP = SITH_MAIN_CLUB['unix_name'] + SITH_BOARD_SUFFIX +SITH_MAIN_MEMBERS_GROUP = SITH_MAIN_CLUB['unix_name'] + SITH_MEMBER_SUFFIX SITH_PROFILE_DEPARTMENTS = [ - ("TC", _("TC")), - ("IMSI", _("IMSI")), - ("IMAP", _("IMAP")), - ("INFO", _("INFO")), - ("GI", _("GI")), - ("E", _("E")), - ("EE", _("EE")), - ("GESC", _("GESC")), - ("GMC", _("GMC")), - ("MC", _("MC")), - ("EDIM", _("EDIM")), - ("HUMA", _("Humanities")), - ("NA", _("N/A")), - ] + ("TC", _("TC")), + ("IMSI", _("IMSI")), + ("IMAP", _("IMAP")), + ("INFO", _("INFO")), + ("GI", _("GI")), + ("E", _("E")), + ("EE", _("EE")), + ("GESC", _("GESC")), + ("GMC", _("GMC")), + ("MC", _("MC")), + ("EDIM", _("EDIM")), + ("HUMA", _("Humanities")), + ("NA", _("N/A")), +] SITH_ACCOUNTING_PAYMENT_METHOD = [ - ('CHECK', _('Check')), - ('CASH', _('Cash')), - ('TRANSFERT', _('Transfert')), - ('CARD', _('Credit card')), - ] + ('CHECK', _('Check')), + ('CASH', _('Cash')), + ('TRANSFERT', _('Transfert')), + ('CARD', _('Credit card')), +] SITH_SUBSCRIPTION_PAYMENT_METHOD = [ - ('CHECK', _('Check')), - ('CARD', _('Credit card')), - ('CASH', _('Cash')), - ('EBOUTIC', _('Eboutic')), - ('OTHER', _('Other')), - ] + ('CHECK', _('Check')), + ('CARD', _('Credit card')), + ('CASH', _('Cash')), + ('EBOUTIC', _('Eboutic')), + ('OTHER', _('Other')), +] SITH_SUBSCRIPTION_LOCATIONS = [ - ('BELFORT', _('Belfort')), - ('SEVENANS', _('Sevenans')), - ('MONTBELIARD', _('Montbéliard')), - ('EBOUTIC', _('Eboutic')), - ] + ('BELFORT', _('Belfort')), + ('SEVENANS', _('Sevenans')), + ('MONTBELIARD', _('Montbéliard')), + ('EBOUTIC', _('Eboutic')), +] SITH_COUNTER_BARS = [ - (1, "MDE"), - (2, "Foyer"), - (35, "La Gommette"), - ] + (1, "MDE"), + (2, "Foyer"), + (35, "La Gommette"), +] SITH_COUNTER_PAYMENT_METHOD = [ - ('CHECK', _('Check')), - ('CASH', _('Cash')), - ('CARD', _('Credit card')), - ] + ('CHECK', _('Check')), + ('CASH', _('Cash')), + ('CARD', _('Credit card')), +] SITH_COUNTER_BANK = [ - ('OTHER', 'Autre'), - ('SOCIETE-GENERALE', 'Société générale'), - ('BANQUE-POPULAIRE', 'Banque populaire'), - ('BNP', 'BNP'), - ('CAISSE-EPARGNE', 'Caisse d\'épargne'), - ('CIC', 'CIC'), - ('CREDIT-AGRICOLE', 'Crédit Agricole'), - ('CREDIT-MUTUEL', 'Credit Mutuel'), - ('CREDIT-LYONNAIS', 'Credit Lyonnais'), - ('LA-POSTE', 'La Poste'), - ] + ('OTHER', 'Autre'), + ('SOCIETE-GENERALE', 'Société générale'), + ('BANQUE-POPULAIRE', 'Banque populaire'), + ('BNP', 'BNP'), + ('CAISSE-EPARGNE', 'Caisse d\'épargne'), + ('CIC', 'CIC'), + ('CREDIT-AGRICOLE', 'Crédit Agricole'), + ('CREDIT-MUTUEL', 'Credit Mutuel'), + ('CREDIT-LYONNAIS', 'Credit Lyonnais'), + ('LA-POSTE', 'La Poste'), +] # Defines pagination for cash summary SITH_COUNTER_CASH_SUMMARY_LENGTH = 50 @@ -466,7 +466,7 @@ SITH_SUBSCRIPTIONS = { 'price': 15, 'duration': 2, }, -# To be completed.... + # To be completed.... } SITH_CLUB_ROLES = {} @@ -497,16 +497,16 @@ SITH_CLUB_ROLES = { # This corresponds to the maximum role a user can freely subscribe to # In this case, SITH_MAXIMUM_FREE_ROLE=1 means that a user can set himself as "Membre actif" or "Curieux", but not higher -SITH_MAXIMUM_FREE_ROLE=1 +SITH_MAXIMUM_FREE_ROLE = 1 # Minutes to timeout the logged barmen -SITH_BARMAN_TIMEOUT=20 +SITH_BARMAN_TIMEOUT = 20 # Minutes to delete the last operations -SITH_LAST_OPERATIONS_LIMIT=10 +SITH_LAST_OPERATIONS_LIMIT = 10 # Minutes for a counter to be inactive -SITH_COUNTER_MINUTE_INACTIVE=10 +SITH_COUNTER_MINUTE_INACTIVE = 10 # ET variables SITH_EBOUTIC_ET_URL = "https://preprod-tpeweb.e-transactions.fr/cgi/MYchoix_pagepaiement.cgi" @@ -521,27 +521,27 @@ with open('./sith/et_keys/pubkey.pem') as f: # Launderette variables SITH_LAUNDERETTE_MACHINE_TYPES = [('WASHING', _('Washing')), ('DRYING', _('Drying'))] SITH_LAUNDERETTE_PRICES = { - 'WASHING': 1.0, - 'DRYING': 0.75, - } + 'WASHING': 1.0, + 'DRYING': 0.75, +} SITH_NOTIFICATIONS = [ - ('NEWS_MODERATION', _("A fresh new to be moderated")), - ('FILE_MODERATION', _("New files to be moderated")), - ('SAS_MODERATION', _("New pictures/album to be moderated in the SAS")), - ('NEW_PICTURES', _("You've been identified on some pictures")), - ('REFILLING', _("You just refilled of %s €")), - ('SELLING', _("You just bought %s")), - ('GENERIC', _("You have a notification")), - ] + ('NEWS_MODERATION', _("A fresh new to be moderated")), + ('FILE_MODERATION', _("New files to be moderated")), + ('SAS_MODERATION', _("New pictures/album to be moderated in the SAS")), + ('NEW_PICTURES', _("You've been identified on some pictures")), + ('REFILLING', _("You just refilled of %s €")), + ('SELLING', _("You just bought %s")), + ('GENERIC', _("You have a notification")), +] SITH_QUICK_NOTIF = { - 'qn_success': _("Success!"), - 'qn_fail': _("Fail!"), - 'qn_weekmail_new_article': _("You successfully posted an article in the Weekmail"), - 'qn_weekmail_article_edit': _("You successfully edited an article in the Weekmail"), - 'qn_weekmail_send_success': _("You successfully sent the Weekmail"), - } + 'qn_success': _("Success!"), + 'qn_fail': _("Fail!"), + 'qn_weekmail_new_article': _("You successfully posted an article in the Weekmail"), + 'qn_weekmail_article_edit': _("You successfully edited an article in the Weekmail"), + 'qn_weekmail_send_success': _("You successfully sent the Weekmail"), +} try: from .settings_custom import * diff --git a/sith/toolbar_debug.py b/sith/toolbar_debug.py index feae0669..9fb29ef3 100644 --- a/sith/toolbar_debug.py +++ b/sith/toolbar_debug.py @@ -24,6 +24,7 @@ from debug_toolbar.panels.templates import TemplatesPanel as BaseTemplatesPanel + class TemplatesPanel(BaseTemplatesPanel): def generate_stats(self, *args): template = self.templates[0]['template'] diff --git a/sith/urls.py b/sith/urls.py index 2e414e29..9623ca16 100644 --- a/sith/urls.py +++ b/sith/urls.py @@ -80,4 +80,3 @@ if settings.DEBUG: urlpatterns += [ url(r'^__debug__/', include(debug_toolbar.urls)), ] - diff --git a/subscription/admin.py b/subscription/admin.py index 2bb80007..985a0691 100644 --- a/subscription/admin.py +++ b/subscription/admin.py @@ -26,6 +26,4 @@ from django.contrib import admin from subscription.models import Subscription - - admin.site.register(Subscription) diff --git a/subscription/models.py b/subscription/models.py index d189d0ab..e88bb696 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -35,37 +35,38 @@ from core.models import User from core.utils import get_start_of_semester - def validate_type(value): if value not in settings.SITH_SUBSCRIPTIONS.keys(): raise ValidationError(_('Bad subscription type')) + def validate_payment(value): if value not in settings.SITH_SUBSCRIPTION_PAYMENT_METHOD: raise ValidationError(_('Bad payment method')) + class Subscription(models.Model): member = models.ForeignKey(User, related_name='subscriptions') subscription_type = models.CharField(_('subscription type'), max_length=255, - choices=((k, v['name']) for k,v in sorted(settings.SITH_SUBSCRIPTIONS.items()))) + choices=((k, v['name']) for k, v in sorted(settings.SITH_SUBSCRIPTIONS.items()))) subscription_start = models.DateField(_('subscription start')) subscription_end = models.DateField(_('subscription end')) payment_method = models.CharField(_('payment method'), - max_length=255, - choices=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD) + max_length=255, + choices=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD) location = models.CharField(choices=settings.SITH_SUBSCRIPTION_LOCATIONS, - max_length=20, verbose_name=_('location')) + max_length=20, verbose_name=_('location')) class Meta: - ordering = ['subscription_start',] + ordering = ['subscription_start', ] def clean(self): try: for s in Subscription.objects.filter(member=self.member).exclude(pk=self.pk).all(): if s.is_valid_now(): raise ValidationError(_("You can not subscribe many time for the same period")) - except: # This should not happen, because the form should have handled the data before, but sadly, it still + except: # This should not happen, because the form should have handled the data before, but sadly, it still # calls the model validation :'( # TODO see SubscriptionForm's clean method raise ValidationError(_("Subscription error")) @@ -74,53 +75,53 @@ class Subscription(models.Model): super(Subscription, self).save() from counter.models import Customer if not Customer.objects.filter(user=self.member).exists(): - last_id = Customer.objects.count() + 1504 # Number to keep a continuity with the old site - Customer(user=self.member, account_id=Customer.generate_account_id(last_id+1), amount=0).save() + last_id = Customer.objects.count() + 1504 # Number to keep a continuity with the old site + Customer(user=self.member, account_id=Customer.generate_account_id(last_id + 1), amount=0).save() form = PasswordResetForm({'email': self.member.email}) if form.is_valid(): form.save(use_https=True, email_template_name='core/new_user_email.jinja', - subject_template_name='core/new_user_email_subject.jinja', from_email="ae@utbm.fr") + subject_template_name='core/new_user_email_subject.jinja', from_email="ae@utbm.fr") self.member.make_home() if settings.IS_OLD_MYSQL_PRESENT: import MySQLdb - try: # Create subscription on the old site: TODO remove me! + try: # Create subscription on the old site: TODO remove me! LOCATION = { - "SEVENANS": 5, - "BELFORT": 6, - "MONTBELIARD": 9, - "EBOUTIC": 5, - } + "SEVENANS": 5, + "BELFORT": 6, + "MONTBELIARD": 9, + "EBOUTIC": 5, + } TYPE = { - 'un-semestre' : 0, - 'deux-semestres' : 1, - 'cursus-tronc-commun' : 2, - 'cursus-branche' : 3, - 'membre-honoraire' : 4, - 'assidu' : 5, - 'amicale/doceo' : 6, - 'reseau-ut' : 7, - 'crous' : 8, - 'sbarro/esta' : 9, - 'cursus-alternant' : 10, - } + 'un-semestre': 0, + 'deux-semestres': 1, + 'cursus-tronc-commun': 2, + 'cursus-branche': 3, + 'membre-honoraire': 4, + 'assidu': 5, + 'amicale/doceo': 6, + 'reseau-ut': 7, + 'crous': 8, + 'sbarro/esta': 9, + 'cursus-alternant': 10, + } PAYMENT = { - "CHECK" : 1, - "CARD" : 2, - "CASH" : 3, - "OTHER" : 4, - "EBOUTIC" : 5, - "OTHER" : 0, - } + "CHECK": 1, + "CARD": 2, + "CASH": 3, + "OTHER": 4, + "EBOUTIC": 5, + "OTHER": 0, + } db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS) c = db.cursor() c.execute("""INSERT INTO ae_cotisations (id_utilisateur, date_cotis, date_fin_cotis, mode_paiement_cotis, type_cotis, id_comptoir) VALUES (%s, %s, %s, %s, %s, %s)""", (self.member.id, self.subscription_start, - self.subscription_end, PAYMENT[self.payment_method], TYPE[self.subscription_type], - LOCATION[self.location])) + self.subscription_end, PAYMENT[self.payment_method], TYPE[self.subscription_type], + LOCATION[self.location])) db.commit() except Exception as e: - with open(settings.BASE_DIR+"/subscription_fail.log", "a") as f: + with open(settings.BASE_DIR + "/subscription_fail.log", "a") as f: print("FAIL to add subscription to %s to old site" % (self.member), file=f) print("Reason: %s" % (repr(e)), file=f) db.rollback() @@ -130,10 +131,9 @@ class Subscription(models.Model): def __str__(self): if hasattr(self, "member") and self.member is not None: - return self.member.username+' - '+str(self.pk) + return self.member.username + ' - ' + str(self.pk) else: - return 'No user - '+str(self.pk) - + return 'No user - ' + str(self.pk) @staticmethod def compute_start(d=date.today(), duration=1): @@ -146,7 +146,7 @@ class Subscription(models.Model): 2015-03-17 -> 2015-02-15 2015-01-11 -> 2014-08-15 """ - if duration <= 2: # Sliding subscriptions for 1 or 2 semesters + if duration <= 2: # Sliding subscriptions for 1 or 2 semesters return d return get_start_of_semester(d) @@ -165,12 +165,11 @@ class Subscription(models.Model): start = Subscription.compute_start(duration=duration) # This can certainly be simplified, but it works like this try: - return start.replace(month=(start.month-1+6*duration)%12+1, - year=start.year+int(duration/2)+(1 if start.month > 6 and duration%2 == 1 else 0)) + return start.replace(month=(start.month - 1 + 6 * duration) % 12 + 1, + year=start.year + int(duration / 2) + (1 if start.month > 6 and duration % 2 == 1 else 0)) except ValueError as e: - return start.replace(day=1, month=(start.month+6*duration)%12+1, - year=start.year+int(duration/2)+(1 if start.month > 6 and duration%2 == 1 else 0)) - + return start.replace(day=1, month=(start.month + 6 * duration) % 12 + 1, + year=start.year + int(duration / 2) + (1 if start.month > 6 and duration % 2 == 1 else 0)) def can_be_edited_by(self, user): return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user.is_root @@ -178,10 +177,15 @@ class Subscription(models.Model): def is_valid_now(self): return self.subscription_start <= date.today() and date.today() <= self.subscription_end + def guy_test(date, duration=4): - print(str(date)+" - "+str(duration)+" -> "+str(Subscription.compute_start(date, duration))) + print(str(date) + " - " + str(duration) + " -> " + str(Subscription.compute_start(date, duration))) + + def bibou_test(duration, date=date.today()): - print(str(date)+" - "+str(duration)+" -> "+str(Subscription.compute_end(duration, Subscription.compute_start(date, duration)))) + print(str(date) + " - " + str(duration) + " -> " + str(Subscription.compute_end(duration, Subscription.compute_start(date, duration)))) + + def guy(): guy_test(date(2015, 7, 11)) guy_test(date(2015, 8, 11)) @@ -191,7 +195,7 @@ def guy(): guy_test(date(2015, 2, 11)) guy_test(date(2015, 8, 17)) guy_test(date(2015, 9, 17)) - print('='*80) + print('=' * 80) guy_test(date(2015, 7, 11), 1) guy_test(date(2015, 8, 11), 2) guy_test(date(2015, 2, 17), 3) @@ -200,7 +204,7 @@ def guy(): guy_test(date(2015, 2, 11), 2) guy_test(date(2015, 8, 17), 3) guy_test(date(2015, 9, 17), 4) - print('='*80) + print('=' * 80) bibou_test(1, date(2015, 2, 18)) bibou_test(2, date(2015, 2, 18)) bibou_test(3, date(2015, 2, 18)) @@ -209,7 +213,7 @@ def guy(): bibou_test(2, date(2015, 9, 18)) bibou_test(3, date(2015, 9, 18)) bibou_test(4, date(2015, 9, 18)) - print('='*80) + print('=' * 80) bibou_test(1, date(2000, 2, 29)) bibou_test(2, date(2000, 2, 29)) bibou_test(1, date(2000, 5, 31)) @@ -219,5 +223,6 @@ def guy(): bibou_test(3) bibou_test(4) + if __name__ == "__main__": guy() diff --git a/subscription/urls.py b/subscription/urls.py index 718f45db..4d31199d 100644 --- a/subscription/urls.py +++ b/subscription/urls.py @@ -31,6 +31,3 @@ urlpatterns = [ url(r'^$', NewSubscription.as_view(), name='subscription'), url(r'stats', SubscriptionsStatsView.as_view(), name='stats'), ] - - - diff --git a/subscription/views.py b/subscription/views.py index 762acf5a..a2716ed4 100644 --- a/subscription/views.py +++ b/subscription/views.py @@ -26,16 +26,13 @@ from django.views.generic.edit import CreateView, FormView from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import PermissionDenied, ValidationError from django.core.urlresolvers import reverse_lazy -from django.db import IntegrityError from django import forms -from django.forms import Select from django.conf import settings from ajax_select.fields import AutoCompleteSelectField import random from subscription.models import Subscription -from core.views import CanEditMixin, CanEditPropMixin, CanViewMixin from core.views.forms import SelectDateTime from core.models import User @@ -85,9 +82,9 @@ class SubscriptionForm(forms.ModelForm): if User.objects.filter(email=cleaned_data.get("email")).first() is not None: self.add_error("email", ValidationError(_("A user with that email address already exists"))) else: - u = User(last_name = self.cleaned_data.get("last_name"), - first_name = self.cleaned_data.get("first_name"), - email = self.cleaned_data.get("email")) + u = User(last_name=self.cleaned_data.get("last_name"), + first_name=self.cleaned_data.get("first_name"), + email=self.cleaned_data.get("email")) u.generate_username() u.set_password(str(random.randrange(1000000, 10000000))) u.save() @@ -102,6 +99,7 @@ class SubscriptionForm(forms.ModelForm): raise ValidationError(_("You must either choose an existing user or create a new one properly")) return cleaned_data + class NewSubscription(CreateView): template_name = 'subscription/subscription.jinja' form_class = SubscriptionForm @@ -119,11 +117,11 @@ class NewSubscription(CreateView): def form_valid(self, form): form.instance.subscription_start = Subscription.compute_start( - duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type]['duration']) + duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type]['duration']) form.instance.subscription_end = Subscription.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type]['duration'], - start=form.instance.subscription_start - ) + duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type]['duration'], + start=form.instance.subscription_start + ) return super(NewSubscription, self).form_valid(form) diff --git a/trombi/models.py b/trombi/models.py index bfe9ca3d..c7533560 100644 --- a/trombi/models.py +++ b/trombi/models.py @@ -34,14 +34,17 @@ from core.models import User from core.utils import get_start_of_semester, get_semester from club.models import Club + class TrombiManager(models.Manager): def get_queryset(self): return super(TrombiManager, self).get_queryset() + class AvailableTrombiManager(models.Manager): def get_queryset(self): return super(AvailableTrombiManager, - self).get_queryset().filter(subscription_deadline__gte=date.today()) + self).get_queryset().filter(subscription_deadline__gte=date.today()) + class Trombi(models.Model): """ @@ -50,14 +53,14 @@ class Trombi(models.Model): its Trombi. """ subscription_deadline = models.DateField(_('subscription deadline'), - default=date.today, help_text=_("Before this date, users are " - "allowed to subscribe to this Trombi. " - "After this date, users subscribed will be allowed to comment on each other.")) + default=date.today, help_text=_("Before this date, users are " + "allowed to subscribe to this Trombi. " + "After this date, users subscribed will be allowed to comment on each other.")) comments_deadline = models.DateField(_('comments deadline'), - default=date.today, help_text=_("After this date, users won't be " - "able to make comments anymore.")) + default=date.today, help_text=_("After this date, users won't be " + "able to make comments anymore.")) max_chars = models.IntegerField(_('maximum characters'), default=400, - help_text=_('Maximum number of characters allowed in a comment.')) + help_text=_('Maximum number of characters allowed in a comment.')) show_profiles = models.BooleanField(_("show users profiles to each other"), default=True) club = models.OneToOneField(Club, related_name='trombi') @@ -70,7 +73,7 @@ class Trombi(models.Model): def clean(self): if self.subscription_deadline > self.comments_deadline: raise ValidationError(_("Closing the subscriptions after the " - "comments is definitively not a good idea.")) + "comments is definitively not a good idea.")) def get_absolute_url(self): return reverse('trombi:detail', kwargs={'trombi_id': self.id}) @@ -81,6 +84,7 @@ class Trombi(models.Model): def can_be_viewed_by(self, user): return user.id in [u.user.id for u in self.users.all()] + class TrombiUser(models.Model): """ This class is only here to avoid cross references between the core, club, @@ -92,9 +96,9 @@ class TrombiUser(models.Model): user = models.OneToOneField(User, verbose_name=_("trombi user"), related_name='trombi_user') trombi = models.ForeignKey(Trombi, verbose_name=_("trombi"), related_name='users', blank=True, null=True, on_delete=models.SET_NULL) profile_pict = models.ImageField(upload_to='trombi', verbose_name=_("profile pict"), null=True, blank=True, - help_text=_("The profile picture you want in the trombi (warning: this picture may be published)")) + help_text=_("The profile picture you want in the trombi (warning: this picture may be published)")) scrub_pict = models.ImageField(upload_to='trombi', verbose_name=_("scrub pict"), null=True, blank=True, - help_text=_("The scrub picture you want in the trombi (warning: this picture may be published)")) + help_text=_("The scrub picture you want in the trombi (warning: this picture may be published)")) def __str__(self): return str(self.user) @@ -113,12 +117,13 @@ class TrombiUser(models.Model): else: end_date = "" TrombiClubMembership( - user=self, - club=str(m.club), - role=role[:64], - start=get_semester(m.start_date), - end=end_date, - ).save() + user=self, + club=str(m.club), + role=role[:64], + start=get_semester(m.start_date), + end=end_date, + ).save() + class TrombiComment(models.Model): """ @@ -135,6 +140,7 @@ class TrombiComment(models.Model): return False return user.id == self.author.user.id or user.can_edit(self.author.trombi) + class TrombiClubMembership(models.Model): """ This represent a membership to a club @@ -156,4 +162,3 @@ class TrombiClubMembership(models.Model): def get_absolute_url(self): return reverse('trombi:profile') - diff --git a/trombi/urls.py b/trombi/urls.py index 8a41fc66..d1a44b92 100644 --- a/trombi/urls.py +++ b/trombi/urls.py @@ -22,7 +22,7 @@ # # -from django.conf.urls import url, include +from django.conf.urls import url from trombi.views import * @@ -43,4 +43,3 @@ urlpatterns = [ url(r'^membership/(?P[0-9]+)/edit$', UserTrombiEditMembershipView.as_view(), name='edit_membership'), url(r'^membership/(?P[0-9]+)/delete$', UserTrombiDeleteMembershipView.as_view(), name='delete_membership'), ] - diff --git a/trombi/views.py b/trombi/views.py index 7e854f91..cf44a8f4 100644 --- a/trombi/views.py +++ b/trombi/views.py @@ -23,10 +23,10 @@ # from django.http import Http404 -from django.shortcuts import render, get_object_or_404, redirect +from django.shortcuts import get_object_or_404, redirect from django.core.urlresolvers import reverse_lazy, reverse -from django.views.generic import ListView, DetailView, RedirectView, TemplateView -from django.views.generic.edit import UpdateView, CreateView, DeleteView, FormView, SingleObjectMixin +from django.views.generic import DetailView, RedirectView, TemplateView +from django.views.generic.edit import UpdateView, CreateView, DeleteView from django.utils.translation import ugettext_lazy as _ from django import forms from django.conf import settings @@ -35,11 +35,12 @@ from django.forms.models import modelform_factory from datetime import date from trombi.models import Trombi, TrombiUser, TrombiComment, TrombiClubMembership -from core.views.forms import SelectFile, SelectDate -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, CanCreateMixin, QuickNotifMixin +from core.views.forms import SelectDate +from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, QuickNotifMixin from core.models import User from club.models import Club + class TrombiTabsMixin(TabedViewMixin): def get_tabs_title(self): return _("Trombi") @@ -47,39 +48,42 @@ class TrombiTabsMixin(TabedViewMixin): def get_list_of_tabs(self): tab_list = [] tab_list.append({ - 'url': reverse('trombi:user_tools'), - 'slug': 'tools', + 'url': reverse('trombi:user_tools'), + 'slug': 'tools', 'name': _("Tools"), - }) + }) tab_list.append({ - 'url': reverse('trombi:profile'), - 'slug': 'profile', + 'url': reverse('trombi:profile'), + 'slug': 'profile', 'name': _("My profile"), - }) + }) tab_list.append({ - 'url': reverse('trombi:pictures'), - 'slug': 'pictures', + 'url': reverse('trombi:pictures'), + 'slug': 'pictures', 'name': _("My pictures"), - }) + }) try: trombi = self.request.user.trombi_user.trombi if self.request.user.is_owner(trombi): tab_list.append({ - 'url': reverse('trombi:detail', kwargs={'trombi_id': trombi.id}), - 'slug': 'admin_tools', - 'name': _("Admin tools"), - }) - except: pass + 'url': reverse('trombi:detail', kwargs={'trombi_id': trombi.id}), + 'slug': 'admin_tools', + 'name': _("Admin tools"), + }) + except: + pass return tab_list + class TrombiForm(forms.ModelForm): class Meta: model = Trombi fields = ['subscription_deadline', 'comments_deadline', 'max_chars', 'show_profiles'] widgets = { - 'subscription_deadline': SelectDate, - 'comments_deadline': SelectDate, - } + 'subscription_deadline': SelectDate, + 'comments_deadline': SelectDate, + } + class TrombiCreateView(CanCreateMixin, CreateView): """ @@ -102,6 +106,7 @@ class TrombiCreateView(CanCreateMixin, CreateView): else: return self.form_invalid(form) + class TrombiEditView(CanEditPropMixin, TrombiTabsMixin, UpdateView): model = Trombi form_class = TrombiForm @@ -110,7 +115,8 @@ class TrombiEditView(CanEditPropMixin, TrombiTabsMixin, UpdateView): current_tab = "admin_tools" def get_success_url(self): - return super(TrombiEditView, self).get_success_url()+"?qn_success" + return super(TrombiEditView, self).get_success_url() + "?qn_success" + class TrombiDetailView(CanEditMixin, QuickNotifMixin, TrombiTabsMixin, DetailView): model = Trombi @@ -118,6 +124,7 @@ class TrombiDetailView(CanEditMixin, QuickNotifMixin, TrombiTabsMixin, DetailVie pk_url_kwarg = 'trombi_id' current_tab = "admin_tools" + class TrombiDeleteUserView(CanEditPropMixin, TrombiTabsMixin, DeleteView): model = TrombiUser pk_url_kwarg = 'user_id' @@ -127,6 +134,7 @@ class TrombiDeleteUserView(CanEditPropMixin, TrombiTabsMixin, DeleteView): def get_success_url(self): return reverse('trombi:detail', kwargs={'trombi_id': self.object.trombi.id}) + "?qn_success" + class TrombiModerateCommentsView(CanEditPropMixin, QuickNotifMixin, TrombiTabsMixin, DetailView): model = Trombi template_name = 'trombi/comment_moderation.jinja' @@ -136,13 +144,15 @@ class TrombiModerateCommentsView(CanEditPropMixin, QuickNotifMixin, TrombiTabsMi def get_context_data(self, **kwargs): kwargs = super(TrombiModerateCommentsView, self).get_context_data(**kwargs) kwargs['comments'] = TrombiComment.objects.filter(is_moderated=False, - author__trombi__id=self.object.id).exclude(target__user__id=self.request.user.id) + author__trombi__id=self.object.id).exclude(target__user__id=self.request.user.id) return kwargs + class TrombiModerateForm(forms.Form): reason = forms.CharField(help_text=_("Explain why you rejected the comment")) action = forms.CharField(initial="delete", widget=forms.widgets.HiddenInput) + class TrombiModerateCommentView(DetailView): model = TrombiComment template_name = 'core/edit.jinja' @@ -159,42 +169,45 @@ class TrombiModerateCommentView(DetailView): if request.POST['action'] == "accept": self.object.is_moderated = True self.object.save() - return redirect(reverse('trombi:moderate_comments', kwargs={'trombi_id': self.object.author.trombi.id})+"?qn_success") + return redirect(reverse('trombi:moderate_comments', kwargs={'trombi_id': self.object.author.trombi.id}) + "?qn_success") elif request.POST['action'] == "reject": return super(TrombiModerateCommentView, self).get(request, *args, **kwargs) elif request.POST['action'] == "delete" and "reason" in request.POST.keys(): self.object.author.user.email_user( - subject="[%s] %s" % (settings.SITH_NAME, _("Rejected comment")), - message=_("Your comment to %(target)s on the Trombi \"%(trombi)s\" was rejected for the following " - "reason: %(reason)s\n\n" - "Your comment was:\n\n%(content)s" - ) % { - 'target': self.object.target.user.get_display_name(), - 'trombi': self.object.author.trombi, - 'reason': request.POST["reason"], - 'content': self.object.content, - }, - ) + subject="[%s] %s" % (settings.SITH_NAME, _("Rejected comment")), + message=_("Your comment to %(target)s on the Trombi \"%(trombi)s\" was rejected for the following " + "reason: %(reason)s\n\n" + "Your comment was:\n\n%(content)s" + ) % { + 'target': self.object.target.user.get_display_name(), + 'trombi': self.object.author.trombi, + 'reason': request.POST["reason"], + 'content': self.object.content, + }, + ) self.object.delete() - return redirect(reverse('trombi:moderate_comments', kwargs={'trombi_id': self.object.author.trombi.id})+"?qn_success") + return redirect(reverse('trombi:moderate_comments', kwargs={'trombi_id': self.object.author.trombi.id}) + "?qn_success") raise Http404 - def get_context_data(self, **kwargs): kwargs = super(TrombiModerateCommentView, self).get_context_data(**kwargs) kwargs['form'] = TrombiModerateForm() return kwargs # User side + + class TrombiModelChoiceField(forms.ModelChoiceField): def label_from_instance(self, obj): return _("%(name)s (deadline: %(date)s)") % {'name': str(obj), 'date': str(obj.subscription_deadline)} + class UserTrombiForm(forms.Form): trombi = TrombiModelChoiceField(Trombi.availables.all(), required=False, label=_("Select trombi"), - help_text=_("This allows you to subscribe to a Trombi. " - "Be aware that you can subscribe only once, so don't play with that, " - "or you will expose yourself to the admins' wrath!")) + help_text=_("This allows you to subscribe to a Trombi. " + "Be aware that you can subscribe only once, so don't play with that, " + "or you will expose yourself to the admins' wrath!")) + class UserTrombiToolsView(QuickNotifMixin, TrombiTabsMixin, TemplateView): """ @@ -207,7 +220,7 @@ class UserTrombiToolsView(QuickNotifMixin, TrombiTabsMixin, TemplateView): self.form = UserTrombiForm(request.POST) if self.form.is_valid(): trombi_user = TrombiUser(user=request.user, - trombi=self.form.cleaned_data['trombi']) + trombi=self.form.cleaned_data['trombi']) trombi_user.save() self.quick_notif_list += ['qn_success'] return super(UserTrombiToolsView, self).get(request, *args, **kwargs) @@ -222,6 +235,7 @@ class UserTrombiToolsView(QuickNotifMixin, TrombiTabsMixin, TemplateView): kwargs['date'] = date return kwargs + class UserTrombiEditPicturesView(TrombiTabsMixin, UpdateView): model = TrombiUser fields = ['profile_pict', 'scrub_pict'] @@ -232,18 +246,19 @@ class UserTrombiEditPicturesView(TrombiTabsMixin, UpdateView): return self.request.user.trombi_user def get_success_url(self): - return reverse('trombi:user_tools')+"?qn_success" + return reverse('trombi:user_tools') + "?qn_success" + class UserTrombiEditProfileView(QuickNotifMixin, TrombiTabsMixin, UpdateView): model = User form_class = modelform_factory(User, - fields=['second_email', 'phone', 'department', 'dpt_option', - 'quote', 'parent_address'], - labels={ - 'second_email': _("Personal email (not UTBM)"), - 'phone': _("Phone"), - 'parent_address': _("Native town"), - }) + fields=['second_email', 'phone', 'department', 'dpt_option', + 'quote', 'parent_address'], + labels={ + 'second_email': _("Personal email (not UTBM)"), + 'phone': _("Phone"), + 'parent_address': _("Native town"), + }) template_name = "trombi/edit_profile.jinja" current_tab = "profile" @@ -251,7 +266,8 @@ class UserTrombiEditProfileView(QuickNotifMixin, TrombiTabsMixin, UpdateView): return self.request.user def get_success_url(self): - return reverse('trombi:user_tools')+"?qn_success" + return reverse('trombi:user_tools') + "?qn_success" + class UserTrombiResetClubMembershipsView(RedirectView): permanent = False @@ -262,7 +278,8 @@ class UserTrombiResetClubMembershipsView(RedirectView): return redirect(self.get_success_url()) def get_success_url(self): - return reverse('trombi:profile')+"?qn_success" + return reverse('trombi:profile') + "?qn_success" + class UserTrombiDeleteMembershipView(TrombiTabsMixin, CanEditMixin, DeleteView): model = TrombiClubMembership @@ -274,6 +291,7 @@ class UserTrombiDeleteMembershipView(TrombiTabsMixin, CanEditMixin, DeleteView): def get_success_url(self): return super(UserTrombiDeleteMembershipView, self).get_success_url() + "?qn_success" + class UserTrombiEditMembershipView(CanEditMixin, TrombiTabsMixin, UpdateView): model = TrombiClubMembership pk_url_kwarg = "membership_id" @@ -300,6 +318,7 @@ class UserTrombiProfileView(TrombiTabsMixin, DetailView): raise Http404() return super(UserTrombiProfileView, self).get(request, *args, **kwargs) + class TrombiCommentFormView(): """ Create/edit a trombi comment @@ -312,20 +331,20 @@ class TrombiCommentFormView(): self.trombi = self.request.user.trombi_user.trombi if date.today() <= self.trombi.subscription_deadline: raise Http404(_("You can not yet write comment, you must wait for " - "the subscription deadline to be passed.")) + "the subscription deadline to be passed.")) if self.trombi.comments_deadline < date.today(): raise Http404(_("You can not write comment anymore, the deadline is " - "already passed.")) + "already passed.")) return modelform_factory(self.model, fields=self.fields, - widgets={ - 'content': forms.widgets.Textarea(attrs={'maxlength': self.trombi.max_chars}) - }, - help_texts={ - 'content': _("Maximum characters: %(max_length)s") % {'max_length': self.trombi.max_chars} - }) + widgets={ + 'content': forms.widgets.Textarea(attrs={'maxlength': self.trombi.max_chars}) + }, + help_texts={ + 'content': _("Maximum characters: %(max_length)s") % {'max_length': self.trombi.max_chars} + }) def get_success_url(self): - return reverse('trombi:user_tools')+"?qn_success" + return reverse('trombi:user_tools') + "?qn_success" def get_context_data(self, **kwargs): kwargs = super(TrombiCommentFormView, self).get_context_data(**kwargs) @@ -335,6 +354,7 @@ class TrombiCommentFormView(): kwargs['target'] = self.object.target return kwargs + class TrombiCommentCreateView(TrombiCommentFormView, CreateView): def form_valid(self, form): target = get_object_or_404(TrombiUser, id=self.kwargs['user_id']) @@ -342,11 +362,10 @@ class TrombiCommentCreateView(TrombiCommentFormView, CreateView): form.instance.target = target return super(TrombiCommentCreateView, self).form_valid(form) + class TrombiCommentEditView(TrombiCommentFormView, CanViewMixin, UpdateView): pk_url_kwarg = "comment_id" def form_valid(self, form): form.instance.is_moderated = False return super(TrombiCommentEditView, self).form_valid(form) - -