Merge branch 'pep8' into 'master'

Pep8

See merge request !81
This commit is contained in:
Skia 2017-06-13 11:39:38 +02:00
commit fbcf525378
64 changed files with 1756 additions and 1474 deletions

View File

@ -35,5 +35,3 @@ admin.site.register(SimplifiedAccountingType)
admin.site.register(Operation) admin.site.register(Operation)
admin.site.register(Label) admin.site.register(Label)
admin.site.register(Company) admin.site.register(Company)

View File

@ -25,7 +25,6 @@
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core import validators from django.core import validators
from django.db.models import Count
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -37,6 +36,7 @@ from decimal import Decimal
from core.models import User, SithFile from core.models import User, SithFile
from club.models import Club from club.models import Club
class CurrencyField(models.DecimalField): class CurrencyField(models.DecimalField):
""" """
This is a custom database field used for currency This is a custom database field used for currency
@ -50,12 +50,13 @@ class CurrencyField(models.DecimalField):
def to_python(self, value): def to_python(self, value):
try: 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: except AttributeError:
return None return None
# Accounting classes # Accounting classes
class Company(models.Model): class Company(models.Model):
name = models.CharField(_('name'), max_length=60) name = models.CharField(_('name'), max_length=60)
street = models.CharField(_('street'), max_length=60, blank=True) street = models.CharField(_('street'), max_length=60, blank=True)
@ -104,6 +105,7 @@ class Company(models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
class BankAccount(models.Model): class BankAccount(models.Model):
name = models.CharField(_('name'), max_length=30) name = models.CharField(_('name'), max_length=30)
iban = models.CharField(_('iban'), max_length=255, blank=True) iban = models.CharField(_('iban'), max_length=255, blank=True)
@ -131,6 +133,7 @@ class BankAccount(models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
class ClubAccount(models.Model): class ClubAccount(models.Model):
name = models.CharField(_('name'), max_length=30) name = models.CharField(_('name'), max_length=30)
club = models.ForeignKey(Club, related_name="club_account", verbose_name=_("club")) club = models.ForeignKey(Club, related_name="club_account", verbose_name=_("club"))
@ -244,6 +247,7 @@ class GeneralJournal(models.Model):
self.amount -= o.amount self.amount -= o.amount
self.save() self.save()
class Operation(models.Model): class Operation(models.Model):
""" """
An operation is a line in the journal, a debit or a credit 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) invoice = models.ForeignKey(SithFile, related_name='operations', verbose_name=_("invoice"), null=True, blank=True)
done = models.BooleanField(_('is done'), default=False) done = models.BooleanField(_('is done'), default=False)
simpleaccounting_type = models.ForeignKey('SimplifiedAccountingType', related_name="operations", 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", 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", 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, 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_id = models.IntegerField(_('target id'), null=True, blank=True)
target_label = models.CharField(_('target label'), max_length=32, default="", 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"), 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: class Meta:
unique_together = ('number', 'journal') unique_together = ('number', 'journal')
@ -349,8 +353,9 @@ class Operation(models.Model):
def __str__(self): def __str__(self):
return "%d € | %s | %s | %s" % ( 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): class AccountingType(models.Model):
""" """
@ -359,13 +364,13 @@ class AccountingType(models.Model):
Thoses are numbers used in accounting to classify operations Thoses are numbers used in accounting to classify operations
""" """
code = models.CharField(_('code'), max_length=16, code = models.CharField(_('code'), max_length=16,
validators=[ validators=[
validators.RegexValidator(r'^[0-9]*$', _('An accounting type code contains only numbers')), validators.RegexValidator(r'^[0-9]*$', _('An accounting type code contains only numbers')),
], ],
) )
label = models.CharField(_('label'), max_length=128) label = models.CharField(_('label'), max_length=128)
movement_type = models.CharField(_('movement type'), choices=[('CREDIT', _('Credit')), ('DEBIT', _('Debit')), movement_type = models.CharField(_('movement type'), choices=[('CREDIT', _('Credit')), ('DEBIT', _('Debit')),
('NEUTRAL', _('Neutral'))], max_length=12) ('NEUTRAL', _('Neutral'))], max_length=12)
class Meta: class Meta:
verbose_name = _("accounting type") verbose_name = _("accounting type")
@ -383,7 +388,8 @@ class AccountingType(models.Model):
return reverse('accounting:type_list') return reverse('accounting:type_list')
def __str__(self): 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): class SimplifiedAccountingType(models.Model):
""" """
@ -391,7 +397,7 @@ class SimplifiedAccountingType(models.Model):
""" """
label = models.CharField(_('label'), max_length=128) label = models.CharField(_('label'), max_length=128)
accounting_type = models.ForeignKey(AccountingType, related_name="simplified_types", accounting_type = models.ForeignKey(AccountingType, related_name="simplified_types",
verbose_name=_("simplified accounting types")) verbose_name=_("simplified accounting types"))
class Meta: class Meta:
verbose_name = _("simplified type") verbose_name = _("simplified type")
@ -408,7 +414,8 @@ class SimplifiedAccountingType(models.Model):
return reverse('accounting:simple_type_list') return reverse('accounting:simple_type_list')
def __str__(self): 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): class Label(models.Model):
"""Label allow a club to sort its operations""" """Label allow a club to sort its operations"""
@ -432,4 +439,3 @@ class Label(models.Model):
def can_be_viewed_by(self, user): def can_be_viewed_by(self, user):
return self.club_account.can_be_viewed_by(user) return self.club_account.can_be_viewed_by(user)

View File

@ -22,15 +22,12 @@
# #
# #
from django.test import Client, TestCase from django.test import TestCase
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.models import Group
from django.core.management import call_command from django.core.management import call_command
from django.conf import settings from datetime import date
from datetime import date, datetime
from core.models import User from core.models import User
from counter.models import Counter
from accounting.models import GeneralJournal, Operation, Label, AccountingType, SimplifiedAccountingType from accounting.models import GeneralJournal, Operation, Label, AccountingType, SimplifiedAccountingType
@ -72,10 +69,11 @@ class RefoundAccountTest(TestCase):
self.assertFalse(response_post.status_code == 403) self.assertFalse(response_post.status_code == 403)
self.assertTrue(self.skia.customer.amount == 0) self.assertTrue(self.skia.customer.amount == 0)
class JournalTest(TestCase): class JournalTest(TestCase):
def setUp(self): def setUp(self):
call_command("populate") call_command("populate")
self.journal = GeneralJournal.objects.filter(id = 1).first() self.journal = GeneralJournal.objects.filter(id=1).first()
def test_permission_granted(self): def test_permission_granted(self):
self.client.login(username='comptable', password='plop') self.client.login(username='comptable', password='plop')
@ -91,115 +89,116 @@ class JournalTest(TestCase):
self.assertTrue(response_get.status_code == 403) self.assertTrue(response_get.status_code == 403)
self.assertFalse('<td>M\xc3\xa9thode de paiement</td>' in str(response_get.content)) self.assertFalse('<td>M\xc3\xa9thode de paiement</td>' in str(response_get.content))
class OperationTest(TestCase): class OperationTest(TestCase):
def setUp(self): def setUp(self):
call_command("populate") 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() self.skia = User.objects.filter(username='skia').first()
at = AccountingType(code='443', label="Ce code n'existe pas", movement_type='CREDIT') at = AccountingType(code='443', label="Ce code n'existe pas", movement_type='CREDIT')
at.save() at.save()
l = Label(club_account= self.journal.club_account, name='bob') l = Label(club_account=self.journal.club_account, name='bob')
l.save() l.save()
self.client.login(username='comptable', password='plop') self.client.login(username='comptable', password='plop')
self.op1 = Operation(journal=self.journal, date=date.today(), amount=1, self.op1 = Operation(journal=self.journal, date=date.today(), amount=1,
remark="Test bilan", mode='CASH', done=True, label=l, remark="Test bilan", mode='CASH', done=True, label=l,
accounting_type=at, target_type='USER', target_id=self.skia.id) accounting_type=at, target_type='USER', target_id=self.skia.id)
self.op1.save() self.op1.save()
self.op2 = Operation(journal=self.journal, date=date.today(), amount=2, self.op2 = Operation(journal=self.journal, date=date.today(), amount=2,
remark="Test bilan", mode='CASH', done=True, label=l, remark="Test bilan", mode='CASH', done=True, label=l,
accounting_type=at, target_type='USER', target_id=self.skia.id) accounting_type=at, target_type='USER', target_id=self.skia.id)
self.op2.save() self.op2.save()
def test_new_operation(self): def test_new_operation(self):
self.client.login(username='comptable', password='plop') self.client.login(username='comptable', password='plop')
at = AccountingType.objects.filter(code = '604').first() at = AccountingType.objects.filter(code='604').first()
response = self.client.post(reverse('accounting:op_new', response = self.client.post(reverse('accounting:op_new',
args=[self.journal.id]), args=[self.journal.id]),
{'amount': 30, {'amount': 30,
'remark' : "Un gros test", 'remark': "Un gros test",
'journal' : self.journal.id, 'journal': self.journal.id,
'target_type' : 'OTHER', 'target_type': 'OTHER',
'target_id' : '', 'target_id': '',
'target_label' : "Le fantome de la nuit", 'target_label': "Le fantome de la nuit",
'date' : '04/12/2020', 'date': '04/12/2020',
'mode' : 'CASH', 'mode': 'CASH',
'cheque_number' : '', 'cheque_number': '',
'invoice' : '', 'invoice': '',
'simpleaccounting_type' : '', 'simpleaccounting_type': '',
'accounting_type': at.id, 'accounting_type': at.id,
'label' : '', 'label': '',
'done' : False, 'done': False,
}) })
self.assertFalse(response.status_code == 403) 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])) response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id]))
self.assertTrue('<td>Le fantome de la nuit</td>' in str(response_get.content)) self.assertTrue('<td>Le fantome de la nuit</td>' in str(response_get.content))
def test_bad_new_operation(self): def test_bad_new_operation(self):
self.client.login(username='comptable', password='plop') self.client.login(username='comptable', password='plop')
at = AccountingType.objects.filter(code = '604').first() AccountingType.objects.filter(code='604').first()
response = self.client.post(reverse('accounting:op_new', response = self.client.post(reverse('accounting:op_new',
args=[self.journal.id]), args=[self.journal.id]),
{'amount': 30, {'amount': 30,
'remark' : "Un gros test", 'remark': "Un gros test",
'journal' : self.journal.id, 'journal': self.journal.id,
'target_type' : 'OTHER', 'target_type': 'OTHER',
'target_id' : '', 'target_id': '',
'target_label' : "Le fantome de la nuit", 'target_label': "Le fantome de la nuit",
'date' : '04/12/2020', 'date': '04/12/2020',
'mode' : 'CASH', 'mode': 'CASH',
'cheque_number' : '', 'cheque_number': '',
'invoice' : '', 'invoice': '',
'simpleaccounting_type' : '', 'simpleaccounting_type': '',
'accounting_type': '', 'accounting_type': '',
'label' : '', 'label': '',
'done' : False, 'done': False,
}) })
self.assertTrue('Vous devez fournir soit un type comptable simplifi\\xc3\\xa9 ou un type comptable standard' in str(response.content)) 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): def test_new_operation_not_authorized(self):
self.client.login(username='skia', password='plop') self.client.login(username='skia', password='plop')
at = AccountingType.objects.filter(code = '604').first() at = AccountingType.objects.filter(code='604').first()
response = self.client.post(reverse('accounting:op_new', response = self.client.post(reverse('accounting:op_new',
args=[self.journal.id]), args=[self.journal.id]),
{'amount': 30, {'amount': 30,
'remark' : "Un gros test", 'remark': "Un gros test",
'journal' : self.journal.id, 'journal': self.journal.id,
'target_type' : 'OTHER', 'target_type': 'OTHER',
'target_id' : '', 'target_id': '',
'target_label' : "Le fantome du jour", 'target_label': "Le fantome du jour",
'date' : '04/12/2020', 'date': '04/12/2020',
'mode' : 'CASH', 'mode': 'CASH',
'cheque_number' : '', 'cheque_number': '',
'invoice' : '', 'invoice': '',
'simpleaccounting_type' : '', 'simpleaccounting_type': '',
'accounting_type': at.id, 'accounting_type': at.id,
'label' : '', 'label': '',
'done' : False, 'done': False,
}) })
self.assertTrue(response.status_code == 403) 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): def test__operation_simple_accounting(self):
self.client.login(username='comptable', password='plop') self.client.login(username='comptable', password='plop')
sat = SimplifiedAccountingType.objects.all().first() sat = SimplifiedAccountingType.objects.all().first()
response = self.client.post(reverse('accounting:op_new', response = self.client.post(reverse('accounting:op_new',
args=[self.journal.id]), args=[self.journal.id]),
{'amount': 23, {'amount': 23,
'remark' : "Un gros test", 'remark': "Un gros test",
'journal' : self.journal.id, 'journal': self.journal.id,
'target_type' : 'OTHER', 'target_type': 'OTHER',
'target_id' : '', 'target_id': '',
'target_label' : "Le fantome de l'aurore", 'target_label': "Le fantome de l'aurore",
'date' : '04/12/2020', 'date': '04/12/2020',
'mode' : 'CASH', 'mode': 'CASH',
'cheque_number' : '', 'cheque_number': '',
'invoice' : '', 'invoice': '',
'simpleaccounting_type' : sat.id, 'simpleaccounting_type': sat.id,
'accounting_type': '', 'accounting_type': '',
'label' : '', 'label': '',
'done' : False, 'done': False,
}) })
self.assertFalse(response.status_code == 403) self.assertFalse(response.status_code == 403)
self.assertTrue(self.journal.operations.filter(amount=23).exists()) self.assertTrue(self.journal.operations.filter(amount=23).exists())
response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) 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])) response_get = self.client.get(reverse("accounting:journal_person_statement", args=[self.journal.id]))
self.assertTrue("S&#39; Kia</a></td>\\n \\n <td>3.00</td>" in str(response_get.content)) self.assertTrue("S&#39; Kia</a></td>\\n \\n <td>3.00</td>" in str(response_get.content))
def test_accounting_statement(self): def test_accounting_statement(self):
self.client.login(username='comptable', password='plop') self.client.login(username='comptable', password='plop')
response_get = self.client.get(reverse("accounting:journal_accounting_statement", args=[self.journal.id])) response_get = self.client.get(reverse("accounting:journal_accounting_statement", args=[self.journal.id]))
self.assertTrue("<td>443 - Cr\\xc3\\xa9dit - Ce code n&#39;existe pas</td>\\n <td>3.00</td>" in str(response_get.content)) self.assertTrue("<td>443 - Cr\\xc3\\xa9dit - Ce code n&#39;existe pas</td>\\n <td>3.00</td>" in str(response_get.content))

View File

@ -22,7 +22,7 @@
# #
# #
from django.conf.urls import url, include from django.conf.urls import url
from accounting.views import * from accounting.views import *
@ -71,5 +71,3 @@ urlpatterns = [
# User account # User account
url(r'^refound/account$', RefoundAccountView.as_view(), name='refound_account'), url(r'^refound/account$', RefoundAccountView.as_view(), name='refound_account'),
] ]

View File

@ -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.views.generic.edit import UpdateView, CreateView, DeleteView, FormView
from django.shortcuts import render
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.core.exceptions import PermissionDenied, ValidationError 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 import transaction
from django.db.models import Sum from django.db.models import Sum
from django.conf import settings from django.conf import settings
from django import forms from django import forms
from django.http import HttpResponseRedirect, HttpResponse from django.http import HttpResponse
from django.utils.translation import ugettext as _
from django.conf import settings
import collections 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 import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, TabedViewMixin
from core.views.forms import SelectFile, SelectDate from core.views.forms import SelectFile, SelectDate
@ -49,6 +45,7 @@ from counter.models import Counter, Selling, Product
# Main accounting view # Main accounting view
class BankAccountListView(CanViewMixin, ListView): class BankAccountListView(CanViewMixin, ListView):
""" """
A list view for the admins A list view for the admins
@ -57,6 +54,7 @@ class BankAccountListView(CanViewMixin, ListView):
template_name = 'accounting/bank_account_list.jinja' template_name = 'accounting/bank_account_list.jinja'
ordering = ['name'] ordering = ['name']
# Simplified accounting types # Simplified accounting types
class SimplifiedAccountingTypeListView(CanViewMixin, ListView): class SimplifiedAccountingTypeListView(CanViewMixin, ListView):
@ -66,6 +64,7 @@ class SimplifiedAccountingTypeListView(CanViewMixin, ListView):
model = SimplifiedAccountingType model = SimplifiedAccountingType
template_name = 'accounting/simplifiedaccountingtype_list.jinja' template_name = 'accounting/simplifiedaccountingtype_list.jinja'
class SimplifiedAccountingTypeEditView(CanViewMixin, UpdateView): class SimplifiedAccountingTypeEditView(CanViewMixin, UpdateView):
""" """
An edit view for the admins An edit view for the admins
@ -75,6 +74,7 @@ class SimplifiedAccountingTypeEditView(CanViewMixin, UpdateView):
fields = ['label', 'accounting_type'] fields = ['label', 'accounting_type']
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
class SimplifiedAccountingTypeCreateView(CanCreateMixin, CreateView): class SimplifiedAccountingTypeCreateView(CanCreateMixin, CreateView):
""" """
Create an accounting type (for the admins) Create an accounting type (for the admins)
@ -83,6 +83,7 @@ class SimplifiedAccountingTypeCreateView(CanCreateMixin, CreateView):
fields = ['label', 'accounting_type'] fields = ['label', 'accounting_type']
template_name = 'core/create.jinja' template_name = 'core/create.jinja'
# Accounting types # Accounting types
class AccountingTypeListView(CanViewMixin, ListView): class AccountingTypeListView(CanViewMixin, ListView):
@ -92,6 +93,7 @@ class AccountingTypeListView(CanViewMixin, ListView):
model = AccountingType model = AccountingType
template_name = 'accounting/accountingtype_list.jinja' template_name = 'accounting/accountingtype_list.jinja'
class AccountingTypeEditView(CanViewMixin, UpdateView): class AccountingTypeEditView(CanViewMixin, UpdateView):
""" """
An edit view for the admins An edit view for the admins
@ -101,6 +103,7 @@ class AccountingTypeEditView(CanViewMixin, UpdateView):
fields = ['code', 'label', 'movement_type'] fields = ['code', 'label', 'movement_type']
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
class AccountingTypeCreateView(CanCreateMixin, CreateView): class AccountingTypeCreateView(CanCreateMixin, CreateView):
""" """
Create an accounting type (for the admins) Create an accounting type (for the admins)
@ -109,6 +112,7 @@ class AccountingTypeCreateView(CanCreateMixin, CreateView):
fields = ['code', 'label', 'movement_type'] fields = ['code', 'label', 'movement_type']
template_name = 'core/create.jinja' template_name = 'core/create.jinja'
# BankAccount views # BankAccount views
class BankAccountEditView(CanViewMixin, UpdateView): class BankAccountEditView(CanViewMixin, UpdateView):
@ -120,6 +124,7 @@ class BankAccountEditView(CanViewMixin, UpdateView):
fields = ['name', 'iban', 'number', 'club'] fields = ['name', 'iban', 'number', 'club']
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
class BankAccountDetailView(CanViewMixin, DetailView): class BankAccountDetailView(CanViewMixin, DetailView):
""" """
A detail view, listing every club account A detail view, listing every club account
@ -128,6 +133,7 @@ class BankAccountDetailView(CanViewMixin, DetailView):
pk_url_kwarg = "b_account_id" pk_url_kwarg = "b_account_id"
template_name = 'accounting/bank_account_details.jinja' template_name = 'accounting/bank_account_details.jinja'
class BankAccountCreateView(CanCreateMixin, CreateView): class BankAccountCreateView(CanCreateMixin, CreateView):
""" """
Create a bank account (for the admins) Create a bank account (for the admins)
@ -136,7 +142,8 @@ class BankAccountCreateView(CanCreateMixin, CreateView):
fields = ['name', 'club', 'iban', 'number'] fields = ['name', 'club', 'iban', 'number']
template_name = 'core/create.jinja' 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) Delete a bank account (for the admins)
""" """
@ -145,6 +152,7 @@ class BankAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete
template_name = 'core/delete_confirm.jinja' template_name = 'core/delete_confirm.jinja'
success_url = reverse_lazy('accounting:bank_list') success_url = reverse_lazy('accounting:bank_list')
# ClubAccount views # ClubAccount views
class ClubAccountEditView(CanViewMixin, UpdateView): class ClubAccountEditView(CanViewMixin, UpdateView):
@ -156,6 +164,7 @@ class ClubAccountEditView(CanViewMixin, UpdateView):
fields = ['name', 'club', 'bank_account'] fields = ['name', 'club', 'bank_account']
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
class ClubAccountDetailView(CanViewMixin, DetailView): class ClubAccountDetailView(CanViewMixin, DetailView):
""" """
A detail view, listing every journal A detail view, listing every journal
@ -164,6 +173,7 @@ class ClubAccountDetailView(CanViewMixin, DetailView):
pk_url_kwarg = "c_account_id" pk_url_kwarg = "c_account_id"
template_name = 'accounting/club_account_details.jinja' template_name = 'accounting/club_account_details.jinja'
class ClubAccountCreateView(CanCreateMixin, CreateView): class ClubAccountCreateView(CanCreateMixin, CreateView):
""" """
Create a club account (for the admins) Create a club account (for the admins)
@ -180,7 +190,8 @@ class ClubAccountCreateView(CanCreateMixin, CreateView):
ret['bank_account'] = obj.id ret['bank_account'] = obj.id
return ret 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) Delete a club account (for the admins)
""" """
@ -189,6 +200,7 @@ class ClubAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete
template_name = 'core/delete_confirm.jinja' template_name = 'core/delete_confirm.jinja'
success_url = reverse_lazy('accounting:bank_list') success_url = reverse_lazy('accounting:bank_list')
# Journal views # Journal views
class JournalTabsMixin(TabedViewMixin): class JournalTabsMixin(TabedViewMixin):
@ -198,34 +210,35 @@ class JournalTabsMixin(TabedViewMixin):
def get_list_of_tabs(self): def get_list_of_tabs(self):
tab_list = [] tab_list = []
tab_list.append({ tab_list.append({
'url': reverse('accounting:journal_details', kwargs={'j_id': self.object.id}), 'url': reverse('accounting:journal_details', kwargs={'j_id': self.object.id}),
'slug': 'journal', 'slug': 'journal',
'name': _("Journal"), 'name': _("Journal"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('accounting:journal_nature_statement', kwargs={'j_id': self.object.id}), 'url': reverse('accounting:journal_nature_statement', kwargs={'j_id': self.object.id}),
'slug': 'nature_statement', 'slug': 'nature_statement',
'name': _("Statement by nature"), 'name': _("Statement by nature"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('accounting:journal_person_statement', kwargs={'j_id': self.object.id}), 'url': reverse('accounting:journal_person_statement', kwargs={'j_id': self.object.id}),
'slug': 'person_statement', 'slug': 'person_statement',
'name': _("Statement by person"), 'name': _("Statement by person"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('accounting:journal_accounting_statement', kwargs={'j_id': self.object.id}), 'url': reverse('accounting:journal_accounting_statement', kwargs={'j_id': self.object.id}),
'slug': 'accounting_statement', 'slug': 'accounting_statement',
'name': _("Accounting statement"), 'name': _("Accounting statement"),
}) })
return tab_list return tab_list
class JournalCreateView(CanCreateMixin, CreateView): class JournalCreateView(CanCreateMixin, CreateView):
""" """
Create a general journal Create a general journal
""" """
model = GeneralJournal model = GeneralJournal
form_class = modelform_factory(GeneralJournal, fields=['name', 'start_date', 'club_account'], form_class = modelform_factory(GeneralJournal, fields=['name', 'start_date', 'club_account'],
widgets={ 'start_date': SelectDate, }) widgets={'start_date': SelectDate, })
template_name = 'core/create.jinja' template_name = 'core/create.jinja'
def get_initial(self): def get_initial(self):
@ -236,6 +249,7 @@ class JournalCreateView(CanCreateMixin, CreateView):
ret['club_account'] = obj.id ret['club_account'] = obj.id
return ret return ret
class JournalDetailView(JournalTabsMixin, CanViewMixin, DetailView): class JournalDetailView(JournalTabsMixin, CanViewMixin, DetailView):
""" """
A detail view, listing every operation A detail view, listing every operation
@ -245,6 +259,7 @@ class JournalDetailView(JournalTabsMixin, CanViewMixin, DetailView):
template_name = 'accounting/journal_details.jinja' template_name = 'accounting/journal_details.jinja'
current_tab = 'journal' current_tab = 'journal'
class JournalEditView(CanEditMixin, UpdateView): class JournalEditView(CanEditMixin, UpdateView):
""" """
Update a general journal Update a general journal
@ -254,6 +269,7 @@ class JournalEditView(CanEditMixin, UpdateView):
fields = ['name', 'start_date', 'end_date', 'club_account', 'closed'] fields = ['name', 'start_date', 'end_date', 'club_account', 'closed']
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
class JournalDeleteView(CanEditPropMixin, DeleteView): class JournalDeleteView(CanEditPropMixin, DeleteView):
""" """
Delete a club account (for the admins) Delete a club account (for the admins)
@ -264,11 +280,12 @@ class JournalDeleteView(CanEditPropMixin, DeleteView):
success_url = reverse_lazy('accounting:club_details') success_url = reverse_lazy('accounting:club_details')
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if self.object.operations.count() == 0: if self.object.operations.count() == 0:
return super(JournalDeleteView, self).dispatch(request, *args, **kwargs) return super(JournalDeleteView, self).dispatch(request, *args, **kwargs)
else: else:
raise PermissionDenied raise PermissionDenied
# Operation views # Operation views
@ -276,13 +293,13 @@ class OperationForm(forms.ModelForm):
class Meta: class Meta:
model = Operation model = Operation
fields = ['amount', 'remark', 'journal', 'target_type', 'target_id', 'target_label', 'date', 'mode', 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 = { widgets = {
'journal': HiddenInput, 'journal': HiddenInput,
'target_id': HiddenInput, 'target_id': HiddenInput,
'date': SelectDate, 'date': SelectDate,
'invoice': SelectFile, 'invoice': SelectFile,
} }
user = AutoCompleteSelectField('users', help_text=None, required=False) user = AutoCompleteSelectField('users', help_text=None, required=False)
club_account = AutoCompleteSelectField('club_accounts', help_text=None, required=False) club_account = AutoCompleteSelectField('club_accounts', help_text=None, required=False)
club = AutoCompleteSelectField('clubs', help_text=None, required=False) club = AutoCompleteSelectField('clubs', help_text=None, required=False)
@ -328,28 +345,29 @@ class OperationForm(forms.ModelForm):
inst = self.instance inst = self.instance
club_account = inst.target club_account = inst.target
acc_type = AccountingType.objects.exclude(movement_type="NEUTRAL").exclude( 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( op = Operation(
journal=club_account.get_open_journal(), journal=club_account.get_open_journal(),
amount=inst.amount, amount=inst.amount,
date=inst.date, date=inst.date,
remark=inst.remark, remark=inst.remark,
mode=inst.mode, mode=inst.mode,
cheque_number=inst.cheque_number, cheque_number=inst.cheque_number,
invoice=inst.invoice, invoice=inst.invoice,
done=False, # Has to be checked by hand done=False, # Has to be checked by hand
simpleaccounting_type=None, simpleaccounting_type=None,
accounting_type=acc_type, accounting_type=acc_type,
target_type="ACCOUNT", target_type="ACCOUNT",
target_id=inst.journal.club_account.id, target_id=inst.journal.club_account.id,
target_label="", target_label="",
linked_operation=inst, linked_operation=inst,
) )
op.save() op.save()
self.instance.linked_operation = op self.instance.linked_operation = op
self.save() self.save()
return ret return ret
class OperationCreateView(CanCreateMixin, CreateView): class OperationCreateView(CanCreateMixin, CreateView):
""" """
Create an operation Create an operation
@ -376,6 +394,7 @@ class OperationCreateView(CanCreateMixin, CreateView):
kwargs['object'] = self.journal kwargs['object'] = self.journal
return kwargs return kwargs
class OperationEditView(CanEditMixin, UpdateView): class OperationEditView(CanEditMixin, UpdateView):
""" """
An edit view, working as detail for the moment An edit view, working as detail for the moment
@ -391,6 +410,7 @@ class OperationEditView(CanEditMixin, UpdateView):
kwargs['object'] = self.object.journal kwargs['object'] = self.object.journal
return kwargs return kwargs
class OperationPDFView(CanViewMixin, DetailView): class OperationPDFView(CanViewMixin, DetailView):
""" """
Display the PDF of a given operation Display the PDF of a given operation
@ -402,11 +422,10 @@ class OperationPDFView(CanViewMixin, DetailView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
from reportlab.pdfgen import canvas from reportlab.pdfgen import canvas
from reportlab.lib.units import cm 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 import colors
from reportlab.lib.pagesizes import letter from reportlab.lib.pagesizes import letter
from reportlab.lib.utils import ImageReader from reportlab.lib.utils import ImageReader
from reportlab.graphics.shapes import Drawing
from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase import pdfmetrics
@ -419,7 +438,6 @@ class OperationPDFView(CanViewMixin, DetailView):
num = self.object.number num = self.object.number
date = self.object.date date = self.object.date
mode = self.object.mode mode = self.object.mode
cheque_number = self.object.cheque_number
club_name = self.object.journal.club_account.name club_name = self.object.journal.club_account.name
ti = self.object.journal.name ti = self.object.journal.name
op_label = self.object.label op_label = self.object.label
@ -432,7 +450,7 @@ class OperationPDFView(CanViewMixin, DetailView):
target = self.object.target.get_display_name() target = self.object.target.get_display_name()
response = HttpResponse(content_type='application/pdf') 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 = canvas.Canvas(response)
p.setFont('DejaVu', 12) p.setFont('DejaVu', 12)
@ -441,21 +459,21 @@ class OperationPDFView(CanViewMixin, DetailView):
width, height = letter width, height = letter
im = ImageReader("core/static/core/img/logo.jpg") im = ImageReader("core/static/core/img/logo.jpg")
iw, ih = im.getSize() 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)]] labelStr = [["%s %s - %s %s" % (_("Journal"), ti, _("Operation"), num)]]
label = Table(labelStr, colWidths=[150], rowHeights=[20]) label = Table(labelStr, colWidths=[150], rowHeights=[20])
label.setStyle(TableStyle([ label.setStyle(TableStyle([
('ALIGN',(0,0),(-1,-1),'RIGHT'), ('ALIGN', (0, 0), (-1, -1), 'RIGHT'),
])) ]))
w, h = label.wrapOn(label, 0, 0) 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 - 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}) p.drawString(90, height - 190, _("Date: %(date)s") % {"date": date})
data = [] data = []
@ -470,7 +488,7 @@ class OperationPDFView(CanViewMixin, DetailView):
payment_mode += "[\u00D7]" payment_mode += "[\u00D7]"
else: else:
payment_mode += "[ ]" payment_mode += "[ ]"
payment_mode += " %s\n" %(m[1]) payment_mode += " %s\n" % (m[1])
data += [[payment_mode]] data += [[payment_mode]]
@ -478,29 +496,29 @@ class OperationPDFView(CanViewMixin, DetailView):
data += [["%s \n%s" % (_("Comment:"), remark)]] 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([ t.setStyle(TableStyle([
('ALIGN',(0,0),(-1,-1),'CENTER'), ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('VALIGN',(-2,-1),(-1,-1),'TOP'), ('VALIGN', (-2, -1), (-1, -1), 'TOP'),
('VALIGN',(0,0),(-1,-2),'MIDDLE'), ('VALIGN', (0, 0), (-1, -2), 'MIDDLE'),
('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.black),
('SPAN', (0, 0), (1, 0)), # line DEBIT/CREDIT ('SPAN', (0, 0), (1, 0)), # line DEBIT/CREDIT
('SPAN', (0, 1), (1, 1)), # line amount ('SPAN', (0, 1), (1, 1)), # line amount
('SPAN',(-2, -1), (-1,-1)), # line comment ('SPAN', (-2, -1), (-1, -1)), # line comment
('SPAN', (0, -2), (-1, -2)), # line creditor/debtor ('SPAN', (0, -2), (-1, -2)), # line creditor/debtor
('SPAN', (0, 2), (1, 2)), # line payment_mode ('SPAN', (0, 2), (1, 2)), # line payment_mode
('ALIGN',(0, 2), (1, 2),'LEFT'), # line payment_mode ('ALIGN', (0, 2), (1, 2), 'LEFT'), # line payment_mode
('ALIGN', (-2, -1), (-1, -1), 'LEFT'), ('ALIGN', (-2, -1), (-1, -1), 'LEFT'),
('BOX', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0, 0), (-1, -1), 0.25, colors.black),
])) ]))
signature = [] signature = []
signature += [[_("Signature:")]] signature += [[_("Signature:")]]
tSig = Table(signature, colWidths=[(width-90*2)], rowHeights=[80]) tSig = Table(signature, colWidths=[(width - 90 * 2)], rowHeights=[80])
tSig.setStyle(TableStyle([ tSig.setStyle(TableStyle([
('VALIGN',(0,0),(-1,-1),'TOP'), ('VALIGN', (0, 0), (-1, -1), 'TOP'),
('BOX', (0,0), (-1,-1), 0.25, colors.black)])) ('BOX', (0, 0), (-1, -1), 0.25, colors.black)]))
w, h = tSig.wrapOn(p, 0, 0) w, h = tSig.wrapOn(p, 0, 0)
tSig.drawOn(p, 90, 200) tSig.drawOn(p, 90, 200)
@ -516,14 +534,15 @@ class OperationPDFView(CanViewMixin, DetailView):
p.save() p.save()
return response return response
class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
""" """
Display a statement sorted by labels Display a statement sorted by labels
""" """
model = GeneralJournal model = GeneralJournal
pk_url_kwarg = "j_id" pk_url_kwarg = "j_id"
template_name='accounting/journal_statement_nature.jinja' template_name = 'accounting/journal_statement_nature.jinja'
current_tab='nature_statement' current_tab = 'nature_statement'
def statement(self, queryset, movement_type): def statement(self, queryset, movement_type):
ret = collections.OrderedDict() ret = collections.OrderedDict()
@ -531,14 +550,16 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
total_sum = 0 total_sum = 0
for sat in [None] + list(SimplifiedAccountingType.objects.order_by('label').all()): for sat in [None] + list(SimplifiedAccountingType.objects.order_by('label').all()):
sum = queryset.filter(accounting_type__movement_type=movement_type, sum = queryset.filter(accounting_type__movement_type=movement_type,
simpleaccounting_type=sat).aggregate(amount_sum=Sum('amount'))['amount_sum'] simpleaccounting_type=sat).aggregate(amount_sum=Sum('amount'))['amount_sum']
if sat: sat = sat.label if sat:
else: sat = "" sat = sat.label
else:
sat = ""
if sum: if sum:
total_sum += sum total_sum += sum
statement[sat] = sum statement[sat] = sum
ret[movement_type] = statement ret[movement_type] = statement
ret[movement_type+"_sum"] = total_sum ret[movement_type + "_sum"] = total_sum
return ret return ret
def big_statement(self): def big_statement(self):
@ -566,23 +587,24 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
kwargs['statement'] = self.big_statement() kwargs['statement'] = self.big_statement()
return kwargs return kwargs
class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView): class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView):
""" """
Calculate a dictionary with operation target and sum of operations Calculate a dictionary with operation target and sum of operations
""" """
model = GeneralJournal model = GeneralJournal
pk_url_kwarg = "j_id" pk_url_kwarg = "j_id"
template_name='accounting/journal_statement_person.jinja' template_name = 'accounting/journal_statement_person.jinja'
current_tab='person_statement' current_tab = 'person_statement'
def sum_by_target(self, target_id, target_type, movement_type): def sum_by_target(self, target_id, target_type, movement_type):
return self.object.operations.filter(accounting_type__movement_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): def statement(self, movement_type):
statement = collections.OrderedDict() statement = collections.OrderedDict()
for op in self.object.operations.filter(accounting_type__movement_type=movement_type).order_by('target_type', 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) statement[op.target] = self.sum_by_target(op.target_id, op.target_type, movement_type)
return statement return statement
@ -598,13 +620,14 @@ class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView):
kwargs['total_debit'] = self.total("DEBIT") kwargs['total_debit'] = self.total("DEBIT")
return kwargs return kwargs
class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView): class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView):
""" """
Calculate a dictionary with operation type and sum of operations Calculate a dictionary with operation type and sum of operations
""" """
model = GeneralJournal model = GeneralJournal
pk_url_kwarg = "j_id" pk_url_kwarg = "j_id"
template_name='accounting/journal_statement_accounting.jinja' template_name = 'accounting/journal_statement_accounting.jinja'
current_tab = "accounting_statement" current_tab = "accounting_statement"
def statement(self): def statement(self):
@ -624,10 +647,12 @@ class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView)
# Company views # Company views
class CompanyListView(CanViewMixin, ListView): class CompanyListView(CanViewMixin, ListView):
model = Company model = Company
template_name = 'accounting/co_list.jinja' template_name = 'accounting/co_list.jinja'
class CompanyCreateView(CanCreateMixin, CreateView): class CompanyCreateView(CanCreateMixin, CreateView):
""" """
Create a company Create a company
@ -648,6 +673,7 @@ class CompanyEditView(CanCreateMixin, UpdateView):
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
success_url = reverse_lazy('accounting:co_list') success_url = reverse_lazy('accounting:co_list')
# Label views # Label views
class LabelListView(CanViewMixin, DetailView): class LabelListView(CanViewMixin, DetailView):
@ -655,11 +681,12 @@ class LabelListView(CanViewMixin, DetailView):
pk_url_kwarg = "clubaccount_id" pk_url_kwarg = "clubaccount_id"
template_name = 'accounting/label_list.jinja' 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 model = Label
form_class = modelform_factory(Label, fields=['name', 'club_account'], widgets={ form_class = modelform_factory(Label, fields=['name', 'club_account'], widgets={
'club_account': HiddenInput, 'club_account': HiddenInput,
}) })
template_name = 'core/create.jinja' template_name = 'core/create.jinja'
def get_initial(self): def get_initial(self):
@ -670,12 +697,14 @@ class LabelCreateView(CanCreateMixin, CreateView): # FIXME we need to check the
ret['club_account'] = obj.id ret['club_account'] = obj.id
return ret return ret
class LabelEditView(CanEditMixin, UpdateView): class LabelEditView(CanEditMixin, UpdateView):
model = Label model = Label
pk_url_kwarg = "label_id" pk_url_kwarg = "label_id"
fields = ['name'] fields = ['name']
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
class LabelDeleteView(CanEditMixin, DeleteView): class LabelDeleteView(CanEditMixin, DeleteView):
model = Label model = Label
pk_url_kwarg = "label_id" pk_url_kwarg = "label_id"
@ -684,9 +713,11 @@ class LabelDeleteView(CanEditMixin, DeleteView):
def get_success_url(self): def get_success_url(self):
return self.object.get_absolute_url() return self.object.get_absolute_url()
class CloseCustomerAccountForm(forms.Form): class CloseCustomerAccountForm(forms.Form):
user = AutoCompleteSelectField('users', label=_('Refound this account'), help_text=None, required=True) user = AutoCompleteSelectField('users', label=_('Refound this account'), help_text=None, required=True)
class RefoundAccountView(FormView): class RefoundAccountView(FormView):
""" """
Create a selling with the same amount than the current user money Create a selling with the same amount than the current user money

View File

@ -29,4 +29,3 @@ from club.models import Club, Membership
admin.site.register(Club) admin.site.register(Club)
admin.site.register(Membership) admin.site.register(Membership)

View File

@ -27,12 +27,13 @@ from django.core import validators
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError 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.core.urlresolvers import reverse
from django.utils import timezone from django.utils import timezone
from core.models import User, MetaGroup, Group, SithFile from core.models import User, MetaGroup, Group, SithFile
# Create your models here. # Create your models here.
class Club(models.Model): class Club(models.Model):
@ -43,17 +44,17 @@ class Club(models.Model):
name = models.CharField(_('name'), max_length=64) name = models.CharField(_('name'), max_length=64)
parent = models.ForeignKey('Club', related_name='children', null=True, blank=True) parent = models.ForeignKey('Club', related_name='children', null=True, blank=True)
unix_name = models.CharField(_('unix name'), max_length=30, unique=True, unix_name = models.CharField(_('unix name'), max_length=30, unique=True,
validators=[ validators=[
validators.RegexValidator( validators.RegexValidator(
r'^[a-z0-9][a-z0-9._-]*[a-z0-9]$', r'^[a-z0-9][a-z0-9._-]*[a-z0-9]$',
_('Enter a valid unix name. This value may contain only ' _('Enter a valid unix name. This value may contain only '
'letters, numbers ./-/_ characters.') 'letters, numbers ./-/_ characters.')
), ),
], ],
error_messages={ error_messages={
'unique': _("A club with that unix name already exists."), 'unique': _("A club with that unix name already exists."),
}, },
) )
address = models.CharField(_('address'), max_length=254) address = models.CharField(_('address'), max_length=254)
# email = models.EmailField(_('email address'), unique=True) # This should, and will be generated automatically # email = models.EmailField(_('email address'), unique=True) # This should, and will be generated automatically
owner_group = models.ForeignKey(Group, related_name="owned_club", 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) edit_groups = models.ManyToManyField(Group, related_name="editable_club", blank=True)
view_groups = models.ManyToManyField(Group, related_name="viewable_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, 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: class Meta:
ordering = ['name', 'unix_name'] ordering = ['name', 'unix_name']
@ -109,9 +110,9 @@ class Club(models.Model):
self._change_unixname(self.unix_name) self._change_unixname(self.unix_name)
super(Club, self).save(*args, **kwargs) super(Club, self).save(*args, **kwargs)
if creation: if creation:
board = MetaGroup(name=self.unix_name+settings.SITH_BOARD_SUFFIX) board = MetaGroup(name=self.unix_name + settings.SITH_BOARD_SUFFIX)
board.save() board.save()
member = MetaGroup(name=self.unix_name+settings.SITH_MEMBER_SUFFIX) member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX)
member.save() member.save()
subscribers = Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first() subscribers = Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()
self.make_home() self.make_home()
@ -153,6 +154,7 @@ class Club(models.Model):
return sub.is_subscribed return sub.is_subscribed
_memberships = {} _memberships = {}
def get_membership_for(self, user): def get_membership_for(self, user):
""" """
Returns the current membership the given user Returns the current membership the given user
@ -168,6 +170,7 @@ class Club(models.Model):
Club._memberships[self.id][user.id] = m Club._memberships[self.id][user.id] = m
return m return m
class Membership(models.Model): class Membership(models.Model):
""" """
The Membership class makes the connection between User and Clubs 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) start_date = models.DateField(_('start date'), default=timezone.now)
end_date = models.DateField(_('end date'), null=True, blank=True) end_date = models.DateField(_('end date'), null=True, blank=True)
role = models.IntegerField(_('role'), choices=sorted(settings.SITH_CLUB_ROLES.items()), 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) description = models.CharField(_('description'), max_length=128, null=False, blank=True)
def clean(self): def clean(self):
@ -195,9 +198,9 @@ class Membership(models.Model):
raise ValidationError(_('User is already member of that club')) raise ValidationError(_('User is already member of that club'))
def __str__(self): def __str__(self):
return self.club.name+' - '+self.user.username+' - '+str(settings.SITH_CLUB_ROLES[self.role])+str( 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 "" " - " + str(_('past member')) if self.end_date is not None else ""
) )
def is_owned_by(self, user): def is_owned_by(self, user):
""" """
@ -216,4 +219,3 @@ class Membership(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('club:club_members', kwargs={'club_id': self.club.id}) return reverse('club:club_members', kwargs={'club_id': self.club.id})

View File

@ -31,6 +31,7 @@ from club.models import Club
# Create your tests here. # Create your tests here.
class ClubTest(TestCase): class ClubTest(TestCase):
def setUp(self): def setUp(self):
call_command("populate") call_command("populate")
@ -41,34 +42,34 @@ class ClubTest(TestCase):
def test_create_add_user_to_club_from_root_ok(self): def test_create_add_user_to_club_from_root_ok(self):
self.client.login(username='root', password='plop') 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, "user": self.skia.id,
"start_date": "12/06/2016", "start_date": "12/06/2016",
"role": 3}) "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(response.status_code == 200)
self.assertTrue("S&#39; Kia</a></td>\\n <td>Responsable info</td>" in str(response.content)) self.assertTrue("S&#39; Kia</a></td>\\n <td>Responsable info</td>" in str(response.content))
def test_create_add_user_to_club_from_root_fail_not_subscriber(self): def test_create_add_user_to_club_from_root_fail_not_subscriber(self):
self.client.login(username='root', password='plop') 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, "user": self.guy.id,
"start_date": "12/06/2016", "start_date": "12/06/2016",
"role": 3}) "role": 3})
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('<ul class="errorlist nonfield"><li>' in str(response.content)) self.assertTrue('<ul class="errorlist nonfield"><li>' in str(response.content))
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.assertFalse("Guy Carlier</a></td>\\n <td>Responsable info</td>" in str(response.content)) self.assertFalse("Guy Carlier</a></td>\\n <td>Responsable info</td>" in str(response.content))
def test_create_add_user_to_club_from_root_fail_already_in_club(self): def test_create_add_user_to_club_from_root_fail_already_in_club(self):
self.client.login(username='root', password='plop') 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, "user": self.skia.id,
"start_date": "12/06/2016", "start_date": "12/06/2016",
"role": 3}) "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("S&#39; Kia</a></td>\\n <td>Responsable info</td>" in str(response.content)) self.assertTrue("S&#39; Kia</a></td>\\n <td>Responsable info</td>" in str(response.content))
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.skia.id, "user": self.skia.id,
"start_date": "12/06/2016", "start_date": "12/06/2016",
"role": 4}) "role": 4})
@ -77,30 +78,29 @@ class ClubTest(TestCase):
def test_create_add_user_to_club_from_skia_ok(self): def test_create_add_user_to_club_from_skia_ok(self):
self.client.login(username='root', password='plop') 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, "user": self.skia.id,
"start_date": "12/06/2016", "start_date": "12/06/2016",
"role": 10}) "role": 10})
self.client.login(username='skia', password='plop') self.client.login(username='skia', 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.rbatsbak.id, "user": self.rbatsbak.id,
"start_date": "12/06/2016", "start_date": "12/06/2016",
"role": 9}) "role": 9})
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(response.status_code == 200)
self.assertTrue("""Richard Batsbak</a></td>\\n <td>Vice-Pr\\xc3\\xa9sident</td>""" in str(response.content)) self.assertTrue("""Richard Batsbak</a></td>\\n <td>Vice-Pr\\xc3\\xa9sident</td>""" in str(response.content))
def test_create_add_user_to_club_from_richard_fail(self): def test_create_add_user_to_club_from_richard_fail(self):
self.client.login(username='root', password='plop') 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.rbatsbak.id, "user": self.rbatsbak.id,
"start_date": "12/06/2016", "start_date": "12/06/2016",
"role": 3}) "role": 3})
self.client.login(username='rbatsbak', password='plop') self.client.login(username='rbatsbak', 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.skia.id, "user": self.skia.id,
"start_date": "12/06/2016", "start_date": "12/06/2016",
"role": 10}) "role": 10})
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue("<li>Vous n&#39;avez pas la permission de faire cela</li>" in str(response.content)) self.assertTrue("<li>Vous n&#39;avez pas la permission de faire cela</li>" in str(response.content))

View File

@ -22,7 +22,7 @@
# #
# #
from django.conf.urls import url, include from django.conf.urls import url
from club.views import * from club.views import *
@ -40,4 +40,3 @@ urlpatterns = [
url(r'^(?P<club_id>[0-9]+)/tools$', ClubToolsView.as_view(), name='tools'), url(r'^(?P<club_id>[0-9]+)/tools$', ClubToolsView.as_view(), name='tools'),
url(r'^membership/(?P<membership_id>[0-9]+)/set_old$', MembershipSetOldView.as_view(), name='membership_set_old'), url(r'^membership/(?P<membership_id>[0-9]+)/set_old$', MembershipSetOldView.as_view(), name='membership_set_old'),
] ]

View File

@ -23,27 +23,21 @@
# #
from django import forms from django import forms
from django.shortcuts import render
from django.views.generic import ListView, DetailView, TemplateView from django.views.generic import ListView, DetailView, TemplateView
from django.views.generic.edit import UpdateView, CreateView from django.views.generic.edit import UpdateView, CreateView
from django.forms import CheckboxSelectMultiple
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect, HttpResponse from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext as _t from django.utils.translation import ugettext as _t
from django.conf import settings
from ajax_select.fields import AutoCompleteSelectField from ajax_select.fields import AutoCompleteSelectField
from datetime import timedelta
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin
from core.views.forms import SelectDate, SelectSingle, SelectDateTime from core.views.forms import SelectDate, SelectDateTime
from club.models import Club, Membership from club.models import Club, Membership
from core.models import User from sith.settings import SITH_MAXIMUM_FREE_ROLE
from sith.settings import SITH_MAXIMUM_FREE_ROLE, SITH_MAIN_BOARD_GROUP from counter.models import Selling, Counter
from counter.models import Product, Selling, Counter
class ClubTabsMixin(TabedViewMixin): class ClubTabsMixin(TabedViewMixin):
def get_tabs_title(self): def get_tabs_title(self):
@ -52,45 +46,46 @@ class ClubTabsMixin(TabedViewMixin):
def get_list_of_tabs(self): def get_list_of_tabs(self):
tab_list = [] tab_list = []
tab_list.append({ tab_list.append({
'url': reverse('club:club_view', kwargs={'club_id': self.object.id}), 'url': reverse('club:club_view', kwargs={'club_id': self.object.id}),
'slug': 'infos', 'slug': 'infos',
'name': _("Infos"), 'name': _("Infos"),
}) })
if self.request.user.can_view(self.object): if self.request.user.can_view(self.object):
tab_list.append({ tab_list.append({
'url': reverse('club:club_members', kwargs={'club_id': self.object.id}), 'url': reverse('club:club_members', kwargs={'club_id': self.object.id}),
'slug': 'members', 'slug': 'members',
'name': _("Members"), 'name': _("Members"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('club:club_old_members', kwargs={'club_id': self.object.id}), 'url': reverse('club:club_old_members', kwargs={'club_id': self.object.id}),
'slug': 'elderlies', 'slug': 'elderlies',
'name': _("Old members"), 'name': _("Old members"),
}) })
if self.request.user.can_edit(self.object): if self.request.user.can_edit(self.object):
tab_list.append({ tab_list.append({
'url': reverse('club:tools', kwargs={'club_id': self.object.id}), 'url': reverse('club:tools', kwargs={'club_id': self.object.id}),
'slug': 'tools', 'slug': 'tools',
'name': _("Tools"), 'name': _("Tools"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('club:club_edit', kwargs={'club_id': self.object.id}), 'url': reverse('club:club_edit', kwargs={'club_id': self.object.id}),
'slug': 'edit', 'slug': 'edit',
'name': _("Edit"), 'name': _("Edit"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('club:club_sellings', kwargs={'club_id': self.object.id}), 'url': reverse('club:club_sellings', kwargs={'club_id': self.object.id}),
'slug': 'sellings', 'slug': 'sellings',
'name': _("Sellings"), 'name': _("Sellings"),
}) })
if self.request.user.is_owner(self.object): if self.request.user.is_owner(self.object):
tab_list.append({ tab_list.append({
'url': reverse('club:club_prop', kwargs={'club_id': self.object.id}), 'url': reverse('club:club_prop', kwargs={'club_id': self.object.id}),
'slug': 'props', 'slug': 'props',
'name': _("Props"), 'name': _("Props"),
}) })
return tab_list return tab_list
class ClubListView(ListView): class ClubListView(ListView):
""" """
List the Clubs List the Clubs
@ -98,6 +93,7 @@ class ClubListView(ListView):
model = Club model = Club
template_name = 'club/club_list.jinja' template_name = 'club/club_list.jinja'
class ClubView(ClubTabsMixin, DetailView): class ClubView(ClubTabsMixin, DetailView):
""" """
Front page of a Club Front page of a Club
@ -107,6 +103,7 @@ class ClubView(ClubTabsMixin, DetailView):
template_name = 'club/club_detail.jinja' template_name = 'club/club_detail.jinja'
current_tab = "infos" current_tab = "infos"
class ClubToolsView(ClubTabsMixin, CanEditMixin, DetailView): class ClubToolsView(ClubTabsMixin, CanEditMixin, DetailView):
""" """
Tools page of a Club Tools page of a Club
@ -116,27 +113,30 @@ class ClubToolsView(ClubTabsMixin, CanEditMixin, DetailView):
template_name = 'club/club_tools.jinja' template_name = 'club/club_tools.jinja'
current_tab = "tools" current_tab = "tools"
class ClubMemberForm(forms.ModelForm): class ClubMemberForm(forms.ModelForm):
""" """
Form handling the members of a club Form handling the members of a club
""" """
error_css_class = 'error' error_css_class = 'error'
required_css_class = 'required' required_css_class = 'required'
class Meta: class Meta:
model = Membership model = Membership
fields = ['user', 'role', 'start_date', 'description'] fields = ['user', 'role', 'start_date', 'description']
widgets = { widgets = {
'start_date': SelectDate 'start_date': SelectDate
} }
user = AutoCompleteSelectField('users', required=True, label=_("Select user"), help_text=None) user = AutoCompleteSelectField('users', required=True, label=_("Select user"), help_text=None)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" """
Overloaded to return the club, and not to a Membership object that has no view Overloaded to return the club, and not to a Membership object that has no view
""" """
ret = super(ClubMemberForm, self).save(*args, **kwargs) super(ClubMemberForm, self).save(*args, **kwargs)
return self.instance.club return self.instance.club
class ClubMembersView(ClubTabsMixin, CanViewMixin, UpdateView): class ClubMembersView(ClubTabsMixin, CanViewMixin, UpdateView):
""" """
View of a club's members View of a club's members
@ -153,9 +153,9 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, UpdateView):
That's why the save method of ClubMemberForm is overridden. That's why the save method of ClubMemberForm is overridden.
""" """
form = super(ClubMembersView, self).get_form() form = super(ClubMembersView, self).get_form()
if 'user' in form.data and form.data.get('user') != '': # Load an existing membership if possible if 'user' in form.data and form.data.get('user') != '': # Load an existing membership if possible
form.instance = Membership.objects.filter(club=self.object).filter(user=form.data.get('user')).filter(end_date=None).first() form.instance = Membership.objects.filter(club=self.object).filter(user=form.data.get('user')).filter(end_date=None).first()
if form.instance is None: # Instanciate a new membership if form.instance is None: # Instanciate a new membership
form.instance = Membership(club=self.object, user=self.request.user) form.instance = Membership(club=self.object, user=self.request.user)
if not self.request.user.is_root: if not self.request.user.is_root:
form.fields.pop('start_date', None) form.fields.pop('start_date', None)
@ -174,7 +174,7 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, UpdateView):
if (form.cleaned_data['role'] <= SITH_MAXIMUM_FREE_ROLE or if (form.cleaned_data['role'] <= SITH_MAXIMUM_FREE_ROLE or
(ms is not None and ms.role >= form.cleaned_data['role']) or (ms is not None and ms.role >= form.cleaned_data['role']) or
request.user.is_board_member or request.user.is_board_member or
request.user.is_root): request.user.is_root):
return self.form_valid(form) return self.form_valid(form)
else: else:
form.add_error(None, _("You do not have the permission to do that")) form.add_error(None, _("You do not have the permission to do that"))
@ -182,6 +182,7 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, UpdateView):
else: else:
return self.form_invalid(form) return self.form_invalid(form)
class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView): class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView):
""" """
Old members of a club Old members of a club
@ -191,11 +192,13 @@ class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView):
template_name = 'club/club_old_members.jinja' template_name = 'club/club_old_members.jinja'
current_tab = "elderlies" current_tab = "elderlies"
class SellingsFormBase(forms.Form): class SellingsFormBase(forms.Form):
begin_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Begin date"), required=False, widget=SelectDateTime) begin_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Begin date"), required=False, widget=SelectDateTime)
end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), required=False, widget=SelectDateTime) end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), required=False, widget=SelectDateTime)
counter = forms.ModelChoiceField(Counter.objects.order_by('name').all(), label=_("Counter"), required=False) counter = forms.ModelChoiceField(Counter.objects.order_by('name').all(), label=_("Counter"), required=False)
class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView): class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView):
""" """
Sellings of a club Sellings of a club
@ -207,8 +210,8 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView):
def get_form_class(self): def get_form_class(self):
kwargs = { kwargs = {
'product': forms.ModelChoiceField(self.object.products.order_by('name').all(), label=_("Product"), required=False) 'product': forms.ModelChoiceField(self.object.products.order_by('name').all(), label=_("Product"), required=False)
} }
return type('SellingsForm', (SellingsFormBase,), kwargs) return type('SellingsForm', (SellingsFormBase,), kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -235,6 +238,7 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView):
kwargs['form'] = form kwargs['form'] = form
return kwargs return kwargs
class ClubSellingCSVView(ClubSellingView): class ClubSellingCSVView(ClubSellingView):
""" """
Generate sellings in csv for a given period Generate sellings in csv for a given period
@ -252,27 +256,31 @@ class ClubSellingCSVView(ClubSellingView):
writer.writerow([_t('Quantity'), kwargs['total_quantity']]) writer.writerow([_t('Quantity'), kwargs['total_quantity']])
writer.writerow([_t('Total'), kwargs['total']]) writer.writerow([_t('Total'), kwargs['total']])
writer.writerow([_t('Benefit'), kwargs['benefit']]) writer.writerow([_t('Benefit'), kwargs['benefit']])
writer.writerow([_t('Date'),_t('Counter'),_t('Barman'),_t('Customer'),_t('Label'), writer.writerow([_t('Date'), _t('Counter'), _t('Barman'), _t('Customer'), _t('Label'),
_t('Quantity'), _t('Total'),_t('Payment method'), _t('Selling price'), _t('Purchase price'), _t('Benefit')]) _t('Quantity'), _t('Total'), _t('Payment method'), _t('Selling price'), _t('Purchase price'), _t('Benefit')])
for o in kwargs['result']: for o in kwargs['result']:
row = [o.date, o.counter] row = [o.date, o.counter]
if o.seller: if o.seller:
row.append(o.seller.get_display_name()) row.append(o.seller.get_display_name())
else: row.append('') else:
row.append('')
if o.customer: if o.customer:
row.append(o.customer.user.get_display_name()) row.append(o.customer.user.get_display_name())
else: row.append('') else:
row = row +[o.label, o.quantity, o.quantity * o.unit_price, row.append('')
o.get_payment_method_display()] row = row + [o.label, o.quantity, o.quantity * o.unit_price,
o.get_payment_method_display()]
if o.product: if o.product:
row.append(o.product.selling_price) row.append(o.product.selling_price)
row.append(o.product.purchase_price) row.append(o.product.purchase_price)
row.append(o.product.selling_price - o.product.purchase_price) row.append(o.product.selling_price - o.product.purchase_price)
else: row = row + ['', '', ''] else:
row = row + ['', '', '']
writer.writerow(row) writer.writerow(row)
return response return response
class ClubEditView(ClubTabsMixin, CanEditMixin, UpdateView): class ClubEditView(ClubTabsMixin, CanEditMixin, UpdateView):
""" """
Edit a Club's main informations (for the club's members) Edit a Club's main informations (for the club's members)
@ -283,6 +291,7 @@ class ClubEditView(ClubTabsMixin, CanEditMixin, UpdateView):
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
current_tab = "edit" current_tab = "edit"
class ClubEditPropView(ClubTabsMixin, CanEditPropMixin, UpdateView): class ClubEditPropView(ClubTabsMixin, CanEditPropMixin, UpdateView):
""" """
Edit the properties of a Club object (for the Sith admins) Edit the properties of a Club object (for the Sith admins)
@ -293,6 +302,7 @@ class ClubEditPropView(ClubTabsMixin, CanEditPropMixin, UpdateView):
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
current_tab = "props" current_tab = "props"
class ClubCreateView(CanEditPropMixin, CreateView): class ClubCreateView(CanEditPropMixin, CreateView):
""" """
Create a club (for the Sith admin) Create a club (for the Sith admin)
@ -302,6 +312,7 @@ class ClubCreateView(CanEditPropMixin, CreateView):
fields = ['name', 'unix_name', 'parent'] fields = ['name', 'unix_name', 'parent']
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
class MembershipSetOldView(CanEditMixin, DetailView): class MembershipSetOldView(CanEditMixin, DetailView):
""" """
Set a membership as beeing old Set a membership as beeing old
@ -319,8 +330,9 @@ class MembershipSetOldView(CanEditMixin, DetailView):
self.object = self.get_object() self.object = self.get_object()
return HttpResponseRedirect(reverse('club:club_members', args=self.args, kwargs={'club_id': self.object.club.id})) return HttpResponseRedirect(reverse('club:club_members', args=self.args, kwargs={'club_id': self.object.club.id}))
class ClubStatView(TemplateView): class ClubStatView(TemplateView):
template_name="club/stats.jinja" template_name = "club/stats.jinja"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(ClubStatView, self).get_context_data(**kwargs) kwargs = super(ClubStatView, self).get_context_data(**kwargs)

View File

@ -30,5 +30,3 @@ admin.site.register(Sith)
admin.site.register(News) admin.site.register(News)
admin.site.register(Weekmail) admin.site.register(Weekmail)

View File

@ -25,15 +25,14 @@
from django.shortcuts import render from django.shortcuts import render
from django.db import models, transaction from django.db import models, transaction
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse
from django.conf import settings from django.conf import settings
from django.contrib.staticfiles.templatetags.staticfiles import static from django.contrib.staticfiles.templatetags.staticfiles import static
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
from django.core.exceptions import ValidationError
from core.models import User, Preferences from core.models import User, Preferences
from club.models import Club from club.models import Club
import os
class Sith(models.Model): class Sith(models.Model):
"""A one instance class storing all the modifiable infos""" """A one instance class storing all the modifiable infos"""
@ -48,12 +47,14 @@ class Sith(models.Model):
def __str__(self): def __str__(self):
return "⛩ Sith ⛩" return "⛩ Sith ⛩"
NEWS_TYPES = [ NEWS_TYPES = [
('NOTICE', _('Notice')), ('NOTICE', _('Notice')),
('EVENT', _('Event')), ('EVENT', _('Event')),
('WEEKLY', _('Weekly')), ('WEEKLY', _('Weekly')),
('CALL', _('Call')), ('CALL', _('Call')),
] ]
class News(models.Model): class News(models.Model):
"""The news class""" """The news class"""
@ -81,6 +82,7 @@ class News(models.Model):
def __str__(self): def __str__(self):
return "%s: %s" % (self.type, self.title) return "%s: %s" % (self.type, self.title)
class NewsDate(models.Model): class NewsDate(models.Model):
""" """
A date class, useful for weekly events, or for events that just have no date A date class, useful for weekly events, or for events that just have no date
@ -95,6 +97,7 @@ class NewsDate(models.Model):
def __str__(self): def __str__(self):
return "%s: %s - %s" % (self.news.title, self.start_date, self.end_date) return "%s: %s - %s" % (self.news.title, self.start_date, self.end_date)
class Weekmail(models.Model): class Weekmail(models.Model):
""" """
The weekmail class The weekmail class
@ -113,12 +116,12 @@ class Weekmail(models.Model):
dest = [i[0] for i in Preferences.objects.filter(receive_weekmail=True).values_list('user__email')] dest = [i[0] for i in Preferences.objects.filter(receive_weekmail=True).values_list('user__email')]
with transaction.atomic(): with transaction.atomic():
email = EmailMultiAlternatives( email = EmailMultiAlternatives(
subject=self.title, subject=self.title,
body=self.render_text(), body=self.render_text(),
from_email=settings.SITH_COM_EMAIL, from_email=settings.SITH_COM_EMAIL,
to=Sith.objects.first().weekmail_destinations.split(' '), to=Sith.objects.first().weekmail_destinations.split(' '),
bcc=dest, bcc=dest,
) )
email.attach_alternative(self.render_html(), "text/html") email.attach_alternative(self.render_html(), "text/html")
email.send() email.send()
self.sent = True self.sent = True
@ -128,12 +131,12 @@ class Weekmail(models.Model):
def render_text(self): def render_text(self):
return render(None, "com/weekmail_renderer_text.jinja", context={ return render(None, "com/weekmail_renderer_text.jinja", context={
'weekmail': self, 'weekmail': self,
}).content.decode('utf-8') }).content.decode('utf-8')
def render_html(self): def render_html(self):
return render(None, "com/weekmail_renderer_html.jinja", context={ return render(None, "com/weekmail_renderer_html.jinja", context={
'weekmail': self, 'weekmail': self,
}).content.decode('utf-8') }).content.decode('utf-8')
def get_banner(self): def get_banner(self):
return "http://" + settings.SITH_URL + static("com/img/weekmail_banner.png") return "http://" + settings.SITH_URL + static("com/img/weekmail_banner.png")
@ -144,6 +147,7 @@ class Weekmail(models.Model):
def is_owned_by(self, user): def is_owned_by(self, user):
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
class WeekmailArticle(models.Model): class WeekmailArticle(models.Model):
weekmail = models.ForeignKey(Weekmail, related_name="articles", verbose_name=_("weekmail"), null=True) weekmail = models.ForeignKey(Weekmail, related_name="articles", verbose_name=_("weekmail"), null=True)
title = models.CharField(_("title"), max_length=64) title = models.CharField(_("title"), max_length=64)

View File

@ -28,7 +28,7 @@ from django.core.urlresolvers import reverse
from django.core.management import call_command from django.core.management import call_command
from core.models import User, RealGroup from core.models import User, RealGroup
from com.models import Sith
class ComTest(TestCase): class ComTest(TestCase):
def setUp(self): def setUp(self):
@ -56,4 +56,3 @@ class ComTest(TestCase):
r = self.client.get(reverse("core:index")) r = self.client.get(reverse("core:index"))
self.assertTrue(r.status_code == 200) self.assertTrue(r.status_code == 200)
self.assertTrue("""<div id="info_box">\\n <div class="markdown"><h3>INFO: <strong>Caaaataaaapuuuulte!!!!</strong></h3>""" in str(r.content)) self.assertTrue("""<div id="info_box">\\n <div class="markdown"><h3>INFO: <strong>Caaaataaaapuuuulte!!!!</strong></h3>""" in str(r.content))

View File

@ -22,7 +22,7 @@
# #
# #
from django.conf.urls import url, include from django.conf.urls import url
from com.views import * from com.views import *

View File

@ -22,9 +22,9 @@
# #
# #
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import redirect, get_object_or_404
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.views.generic import ListView, DetailView, RedirectView from django.views.generic import ListView, DetailView
from django.views.generic.edit import UpdateView, CreateView, DeleteView from django.views.generic.edit import UpdateView, CreateView, DeleteView
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -49,6 +49,7 @@ from club.models import Club
sith = Sith.objects.first sith = Sith.objects.first
class ComTabsMixin(TabedViewMixin): class ComTabsMixin(TabedViewMixin):
def get_tabs_title(self): def get_tabs_title(self):
return _("Communication administration") return _("Communication administration")
@ -56,32 +57,33 @@ class ComTabsMixin(TabedViewMixin):
def get_list_of_tabs(self): def get_list_of_tabs(self):
tab_list = [] tab_list = []
tab_list.append({ tab_list.append({
'url': reverse('com:weekmail'), 'url': reverse('com:weekmail'),
'slug': 'weekmail', 'slug': 'weekmail',
'name': _("Weekmail"), 'name': _("Weekmail"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('com:weekmail_destinations'), 'url': reverse('com:weekmail_destinations'),
'slug': 'weekmail_destinations', 'slug': 'weekmail_destinations',
'name': _("Weekmail destinations"), 'name': _("Weekmail destinations"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('com:index_edit'), 'url': reverse('com:index_edit'),
'slug': 'index', 'slug': 'index',
'name': _("Index page"), 'name': _("Index page"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('com:info_edit'), 'url': reverse('com:info_edit'),
'slug': 'info', 'slug': 'info',
'name': _("Info message"), 'name': _("Info message"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('com:alert_edit'), 'url': reverse('com:alert_edit'),
'slug': 'alert', 'slug': 'alert',
'name': _("Alert message"), 'name': _("Alert message"),
}) })
return tab_list return tab_list
class ComEditView(ComTabsMixin, CanEditPropMixin, UpdateView): class ComEditView(ComTabsMixin, CanEditPropMixin, UpdateView):
model = Sith model = Sith
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
@ -89,21 +91,25 @@ class ComEditView(ComTabsMixin, CanEditPropMixin, UpdateView):
def get_object(self, queryset=None): def get_object(self, queryset=None):
return Sith.objects.first() return Sith.objects.first()
class AlertMsgEditView(ComEditView): class AlertMsgEditView(ComEditView):
fields = ['alert_msg'] fields = ['alert_msg']
current_tab = "alert" current_tab = "alert"
success_url = reverse_lazy('com:alert_edit') success_url = reverse_lazy('com:alert_edit')
class InfoMsgEditView(ComEditView): class InfoMsgEditView(ComEditView):
fields = ['info_msg'] fields = ['info_msg']
current_tab = "info" current_tab = "info"
success_url = reverse_lazy('com:info_edit') success_url = reverse_lazy('com:info_edit')
class IndexEditView(ComEditView): class IndexEditView(ComEditView):
fields = ['index_page'] fields = ['index_page']
current_tab = "index" current_tab = "index"
success_url = reverse_lazy('com:index_edit') success_url = reverse_lazy('com:index_edit')
class WeekmailDestinationEditView(ComEditView): class WeekmailDestinationEditView(ComEditView):
fields = ['weekmail_destinations'] fields = ['weekmail_destinations']
current_tab = "weekmail_destinations" current_tab = "weekmail_destinations"
@ -111,14 +117,15 @@ class WeekmailDestinationEditView(ComEditView):
# News # News
class NewsForm(forms.ModelForm): class NewsForm(forms.ModelForm):
class Meta: class Meta:
model = News model = News
fields = ['type', 'title', 'club', 'summary', 'content', 'author'] fields = ['type', 'title', 'club', 'summary', 'content', 'author']
widgets = { widgets = {
'author': forms.HiddenInput, 'author': forms.HiddenInput,
'type': forms.RadioSelect, 'type': forms.RadioSelect,
} }
start_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start date"), widget=SelectDateTime, required=False) start_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start date"), widget=SelectDateTime, required=False)
end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), widget=SelectDateTime, required=False) end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), widget=SelectDateTime, required=False)
until = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Until"), widget=SelectDateTime, required=False) until = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Until"), widget=SelectDateTime, required=False)
@ -142,19 +149,20 @@ class NewsForm(forms.ModelForm):
self.instance.dates.all().delete() self.instance.dates.all().delete()
if self.instance.type == "EVENT" or self.instance.type == "CALL": if self.instance.type == "EVENT" or self.instance.type == "CALL":
NewsDate(start_date=self.cleaned_data['start_date'], NewsDate(start_date=self.cleaned_data['start_date'],
end_date=self.cleaned_data['end_date'], end_date=self.cleaned_data['end_date'],
news=self.instance).save() news=self.instance).save()
elif self.instance.type == "WEEKLY": elif self.instance.type == "WEEKLY":
start_date = self.cleaned_data['start_date'] start_date = self.cleaned_data['start_date']
end_date = self.cleaned_data['end_date'] end_date = self.cleaned_data['end_date']
while start_date <= self.cleaned_data['until']: while start_date <= self.cleaned_data['until']:
NewsDate(start_date=start_date, NewsDate(start_date=start_date,
end_date=end_date, end_date=end_date,
news=self.instance).save() news=self.instance).save()
start_date += timedelta(days=7) start_date += timedelta(days=7)
end_date += timedelta(days=7) end_date += timedelta(days=7)
return ret return ret
class NewsEditView(CanEditMixin, UpdateView): class NewsEditView(CanEditMixin, UpdateView):
model = News model = News
form_class = NewsForm form_class = NewsForm
@ -165,15 +173,17 @@ class NewsEditView(CanEditMixin, UpdateView):
init = {} init = {}
try: try:
init['start_date'] = self.object.dates.order_by('id').first().start_date.strftime('%Y-%m-%d %H:%M:%S') init['start_date'] = self.object.dates.order_by('id').first().start_date.strftime('%Y-%m-%d %H:%M:%S')
except: pass except:
pass
try: try:
init['end_date'] = self.object.dates.order_by('id').first().end_date.strftime('%Y-%m-%d %H:%M:%S') init['end_date'] = self.object.dates.order_by('id').first().end_date.strftime('%Y-%m-%d %H:%M:%S')
except: pass except:
pass
return init return init
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.get_form() form = self.get_form()
if form.is_valid() and not 'preview' in request.POST.keys(): if form.is_valid() and 'preview' not in request.POST.keys():
return self.form_valid(form) return self.form_valid(form)
else: else:
return self.form_invalid(form) return self.form_invalid(form)
@ -192,6 +202,7 @@ class NewsEditView(CanEditMixin, UpdateView):
Notification(user=u, url=reverse("com:news_detail", kwargs={'news_id': self.object.id}), type="NEWS_MODERATION").save() Notification(user=u, url=reverse("com:news_detail", kwargs={'news_id': self.object.id}), type="NEWS_MODERATION").save()
return super(NewsEditView, self).form_valid(form) return super(NewsEditView, self).form_valid(form)
class NewsCreateView(CanCreateMixin, CreateView): class NewsCreateView(CanCreateMixin, CreateView):
model = News model = News
form_class = NewsForm form_class = NewsForm
@ -201,12 +212,13 @@ class NewsCreateView(CanCreateMixin, CreateView):
init = {'author': self.request.user} init = {'author': self.request.user}
try: try:
init['club'] = Club.objects.filter(id=self.request.GET['club']).first() init['club'] = Club.objects.filter(id=self.request.GET['club']).first()
except: pass except:
pass
return init return init
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.get_form() form = self.get_form()
if form.is_valid() and not 'preview' in request.POST.keys(): if form.is_valid() and 'preview' not in request.POST.keys():
return self.form_valid(form) return self.form_valid(form)
else: else:
self.object = form.instance self.object = form.instance
@ -224,6 +236,7 @@ class NewsCreateView(CanCreateMixin, CreateView):
Notification(user=u, url=reverse("com:news_detail", kwargs={'news_id': self.object.id}), type="NEWS_MODERATION").save() Notification(user=u, url=reverse("com:news_detail", kwargs={'news_id': self.object.id}), type="NEWS_MODERATION").save()
return super(NewsCreateView, self).form_valid(form) return super(NewsCreateView, self).form_valid(form)
class NewsModerateView(CanEditMixin, SingleObjectMixin): class NewsModerateView(CanEditMixin, SingleObjectMixin):
model = News model = News
pk_url_kwarg = 'news_id' pk_url_kwarg = 'news_id'
@ -240,11 +253,13 @@ class NewsModerateView(CanEditMixin, SingleObjectMixin):
return redirect(self.request.GET['next']) return redirect(self.request.GET['next'])
return redirect('com:news_admin_list') return redirect('com:news_admin_list')
class NewsAdminListView(CanEditMixin, ListView): class NewsAdminListView(CanEditMixin, ListView):
model = News model = News
template_name = 'com/news_admin_list.jinja' template_name = 'com/news_admin_list.jinja'
queryset = News.objects.filter(dates__end_date__gte=timezone.now()).distinct().order_by('id') queryset = News.objects.filter(dates__end_date__gte=timezone.now()).distinct().order_by('id')
class NewsListView(CanViewMixin, ListView): class NewsListView(CanViewMixin, ListView):
model = News model = News
template_name = 'com/news_list.jinja' template_name = 'com/news_list.jinja'
@ -255,6 +270,7 @@ class NewsListView(CanViewMixin, ListView):
kwargs['timedelta'] = timedelta kwargs['timedelta'] = timedelta
return kwargs return kwargs
class NewsDetailView(CanViewMixin, DetailView): class NewsDetailView(CanViewMixin, DetailView):
model = News model = News
template_name = 'com/news_detail.jinja' template_name = 'com/news_detail.jinja'
@ -262,6 +278,7 @@ class NewsDetailView(CanViewMixin, DetailView):
# Weekmail # Weekmail
class WeekmailPreviewView(ComTabsMixin, CanEditPropMixin, DetailView): class WeekmailPreviewView(ComTabsMixin, CanEditPropMixin, DetailView):
model = Weekmail model = Weekmail
template_name = 'com/weekmail_preview.jinja' template_name = 'com/weekmail_preview.jinja'
@ -274,7 +291,8 @@ class WeekmailPreviewView(ComTabsMixin, CanEditPropMixin, DetailView):
if request.POST['send'] == "validate": if request.POST['send'] == "validate":
self.object.send() self.object.send()
return HttpResponseRedirect(reverse('com:weekmail') + "?qn_weekmail_send_success") return HttpResponseRedirect(reverse('com:weekmail') + "?qn_weekmail_send_success")
except: pass except:
pass
return super(WeekmailEditView, self).get(request, *args, **kwargs) return super(WeekmailEditView, self).get(request, *args, **kwargs)
def get_object(self, queryset=None): def get_object(self, queryset=None):
@ -286,11 +304,12 @@ class WeekmailPreviewView(ComTabsMixin, CanEditPropMixin, DetailView):
kwargs['weekmail_rendered'] = self.object.render_html() kwargs['weekmail_rendered'] = self.object.render_html()
return kwargs return kwargs
class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView): class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView):
model = Weekmail model = Weekmail
template_name = 'com/weekmail.jinja' template_name = 'com/weekmail.jinja'
form_class = modelform_factory(Weekmail, fields=['title', 'intro', 'joke', 'protip', 'conclusion'], form_class = modelform_factory(Weekmail, fields=['title', 'intro', 'joke', 'protip', 'conclusion'],
help_texts={'title': _("Delete and save to regenerate")}) help_texts={'title': _("Delete and save to regenerate")})
success_url = reverse_lazy('com:weekmail') success_url = reverse_lazy('com:weekmail')
current_tab = "weekmail" current_tab = "weekmail"
@ -341,6 +360,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
kwargs['orphans'] = WeekmailArticle.objects.filter(weekmail=None) kwargs['orphans'] = WeekmailArticle.objects.filter(weekmail=None)
return kwargs return kwargs
class WeekmailArticleEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView): class WeekmailArticleEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView):
"""Edit an article""" """Edit an article"""
model = WeekmailArticle model = WeekmailArticle
@ -351,6 +371,7 @@ class WeekmailArticleEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, U
quick_notif_url_arg = "qn_weekmail_article_edit" quick_notif_url_arg = "qn_weekmail_article_edit"
current_tab = "weekmail" current_tab = "weekmail"
class WeekmailArticleCreateView(QuickNotifMixin, CreateView): class WeekmailArticleCreateView(QuickNotifMixin, CreateView):
"""Post an article""" """Post an article"""
model = WeekmailArticle model = WeekmailArticle
@ -363,13 +384,14 @@ class WeekmailArticleCreateView(QuickNotifMixin, CreateView):
init = {} init = {}
try: try:
init['club'] = Club.objects.filter(id=self.request.GET['club']).first() init['club'] = Club.objects.filter(id=self.request.GET['club']).first()
except: pass except:
pass
return init return init
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.get_form() form = self.get_form()
self.object = form.instance self.object = form.instance
form.is_valid() # Valid a first time to populate club field form.is_valid() #  Valid a first time to populate club field
try: try:
m = form.instance.club.get_membership_for(request.user) m = form.instance.club.get_membership_for(request.user)
if m.role <= settings.SITH_MAXIMUM_FREE_ROLE: if m.role <= settings.SITH_MAXIMUM_FREE_ROLE:
@ -385,14 +407,10 @@ class WeekmailArticleCreateView(QuickNotifMixin, CreateView):
form.instance.author = self.request.user form.instance.author = self.request.user
return super(WeekmailArticleCreateView, self).form_valid(form) return super(WeekmailArticleCreateView, self).form_valid(form)
class WeekmailArticleDeleteView(CanEditPropMixin, DeleteView): class WeekmailArticleDeleteView(CanEditPropMixin, DeleteView):
"""Delete an article""" """Delete an article"""
model = WeekmailArticle model = WeekmailArticle
template_name = 'core/delete_confirm.jinja' template_name = 'core/delete_confirm.jinja'
success_url = reverse_lazy('com:weekmail') success_url = reverse_lazy('com:weekmail')
pk_url_kwarg = "article_id" pk_url_kwarg = "article_id"

View File

@ -33,8 +33,9 @@ admin.site.unregister(AuthGroup)
admin.site.register(RealGroup) admin.site.register(RealGroup)
admin.site.register(Page) admin.site.register(Page)
@admin.register(SithFile) @admin.register(SithFile)
class SithFileAdmin(admin.ModelAdmin): class SithFileAdmin(admin.ModelAdmin):
form = make_ajax_form(SithFile, { form = make_ajax_form(SithFile, {
'parent': 'files', # ManyToManyField 'parent': 'files', # ManyToManyField
}) })

View File

@ -23,9 +23,9 @@
# #
from django.apps import AppConfig from django.apps import AppConfig
from django.dispatch import receiver
from django.core.signals import request_started from django.core.signals import request_started
class SithConfig(AppConfig): class SithConfig(AppConfig):
name = 'core' name = 'core'
verbose_name = "Core app of the Sith" verbose_name = "Core app of the Sith"
@ -48,4 +48,3 @@ class SithConfig(AppConfig):
request_started.connect(clear_cached_groups, weak=False, dispatch_uid="clear_cached_groups") request_started.connect(clear_cached_groups, weak=False, dispatch_uid="clear_cached_groups")
request_started.connect(clear_cached_memberships, weak=False, dispatch_uid="clear_cached_memberships") request_started.connect(clear_cached_memberships, weak=False, dispatch_uid="clear_cached_memberships")
# TODO: there may be a need to add more cache clearing # TODO: there may be a need to add more cache clearing

View File

@ -31,16 +31,19 @@ from club.models import Club
from counter.models import Product, Counter from counter.models import Product, Counter
from accounting.models import ClubAccount, Company from accounting.models import ClubAccount, Company
def check_token(request): def check_token(request):
return ('counter_token' in request.session.keys() and return ('counter_token' in request.session.keys() and
request.session['counter_token'] and request.session['counter_token'] and
Counter.objects.filter(token=request.session['counter_token']).exists()) Counter.objects.filter(token=request.session['counter_token']).exists())
class RightManagedLookupChannel(LookupChannel): class RightManagedLookupChannel(LookupChannel):
def check_auth(self, request): def check_auth(self, request):
if not request.user.was_subscribed and not check_token(request): if not request.user.was_subscribed and not check_token(request):
raise PermissionDenied raise PermissionDenied
@register('users') @register('users')
class UsersLookup(RightManagedLookupChannel): class UsersLookup(RightManagedLookupChannel):
model = User model = User
@ -54,6 +57,7 @@ class UsersLookup(RightManagedLookupChannel):
def format_item_display(self, item): def format_item_display(self, item):
return item.get_display_name() return item.get_display_name()
@register('groups') @register('groups')
class GroupsLookup(RightManagedLookupChannel): class GroupsLookup(RightManagedLookupChannel):
model = Group model = Group
@ -67,6 +71,7 @@ class GroupsLookup(RightManagedLookupChannel):
def format_item_display(self, item): def format_item_display(self, item):
return item.name return item.name
@register('clubs') @register('clubs')
class ClubLookup(RightManagedLookupChannel): class ClubLookup(RightManagedLookupChannel):
model = Club model = Club
@ -80,6 +85,7 @@ class ClubLookup(RightManagedLookupChannel):
def format_item_display(self, item): def format_item_display(self, item):
return item.name return item.name
@register('counters') @register('counters')
class CountersLookup(RightManagedLookupChannel): class CountersLookup(RightManagedLookupChannel):
model = Counter model = Counter
@ -90,6 +96,7 @@ class CountersLookup(RightManagedLookupChannel):
def format_item_display(self, item): def format_item_display(self, item):
return item.name return item.name
@register('products') @register('products')
class ProductsLookup(RightManagedLookupChannel): class ProductsLookup(RightManagedLookupChannel):
model = Product model = Product
@ -101,6 +108,7 @@ class ProductsLookup(RightManagedLookupChannel):
def format_item_display(self, item): def format_item_display(self, item):
return "%s (%s)" % (item.name, item.code) return "%s (%s)" % (item.name, item.code)
@register('files') @register('files')
class SithFileLookup(RightManagedLookupChannel): class SithFileLookup(RightManagedLookupChannel):
model = SithFile model = SithFile
@ -108,6 +116,7 @@ class SithFileLookup(RightManagedLookupChannel):
def get_query(self, q, request): def get_query(self, q, request):
return self.model.objects.filter(name__icontains=q)[:50] return self.model.objects.filter(name__icontains=q)[:50]
@register('club_accounts') @register('club_accounts')
class ClubAccountLookup(RightManagedLookupChannel): class ClubAccountLookup(RightManagedLookupChannel):
model = ClubAccount model = ClubAccount
@ -118,6 +127,7 @@ class ClubAccountLookup(RightManagedLookupChannel):
def format_item_display(self, item): def format_item_display(self, item):
return item.name return item.name
@register('companies') @register('companies')
class CompaniesLookup(RightManagedLookupChannel): class CompaniesLookup(RightManagedLookupChannel):
model = Company model = Company

View File

@ -21,4 +21,3 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -44,7 +44,6 @@ class Command(BaseCommand):
args['precision'] = settings.SASS_PRECISION args['precision'] = settings.SASS_PRECISION
return sass.compile(**args) return sass.compile(**args)
def is_compilable(self, file, ext_list): def is_compilable(self, file, ext_list):
path, ext = os.path.splitext(file) path, ext = os.path.splitext(file)
return ext in ext_list return ext in ext_list

View File

@ -26,7 +26,7 @@ import os
from datetime import date, datetime from datetime import date, datetime
from io import StringIO, BytesIO from io import StringIO, BytesIO
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand
from django.core.management import call_command from django.core.management import call_command
from django.conf import settings from django.conf import settings
from django.db import connection from django.db import connection
@ -42,7 +42,7 @@ from subscription.models import Subscription
from counter.models import Customer, ProductType, Product, Counter from counter.models import Customer, ProductType, Product, Counter
from com.models import Sith, Weekmail from com.models import Sith, Weekmail
from election.models import Election, Role, Candidature, ElectionList from election.models import Election, Role, Candidature, ElectionList
from forum.models import Forum, ForumMessage, ForumTopic from forum.models import Forum, ForumTopic
class Command(BaseCommand): class Command(BaseCommand):
@ -75,9 +75,9 @@ class Command(BaseCommand):
Group(name="Forum admin").save() Group(name="Forum admin").save()
self.reset_index("core", "auth") self.reset_index("core", "auth")
root = User(id=0, username='root', last_name="", first_name="Bibou", root = User(id=0, username='root', last_name="", first_name="Bibou",
email="ae.info@utbm.fr", email="ae.info@utbm.fr",
date_of_birth="1942-06-12", date_of_birth="1942-06-12",
is_superuser=True, is_staff=True) is_superuser=True, is_staff=True)
root.set_password("plop") root.set_password("plop")
root.save() root.save()
profiles_root = SithFile(parent=None, name="profiles", is_folder=True, owner=root) profiles_root = SithFile(parent=None, name="profiles", is_folder=True, owner=root)
@ -88,18 +88,18 @@ class Command(BaseCommand):
club_root.save() club_root.save()
SithFile(parent=None, name="SAS", is_folder=True, owner=root).save() SithFile(parent=None, name="SAS", is_folder=True, owner=root).save()
main_club = Club(id=1, name=settings.SITH_MAIN_CLUB['name'], unix_name=settings.SITH_MAIN_CLUB['unix_name'], main_club = Club(id=1, name=settings.SITH_MAIN_CLUB['name'], unix_name=settings.SITH_MAIN_CLUB['unix_name'],
address=settings.SITH_MAIN_CLUB['address']) address=settings.SITH_MAIN_CLUB['address'])
main_club.save() main_club.save()
bar_club = Club(id=2, name=settings.SITH_BAR_MANAGER['name'], unix_name=settings.SITH_BAR_MANAGER['unix_name'], bar_club = Club(id=2, name=settings.SITH_BAR_MANAGER['name'], unix_name=settings.SITH_BAR_MANAGER['unix_name'],
address=settings.SITH_BAR_MANAGER['address']) address=settings.SITH_BAR_MANAGER['address'])
bar_club.save() bar_club.save()
launderette_club = Club(id=84, name=settings.SITH_LAUNDERETTE_MANAGER['name'], launderette_club = Club(id=84, name=settings.SITH_LAUNDERETTE_MANAGER['name'],
unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name'], unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name'],
address=settings.SITH_LAUNDERETTE_MANAGER['address']) address=settings.SITH_LAUNDERETTE_MANAGER['address'])
launderette_club.save() launderette_club.save()
self.reset_index("club") self.reset_index("club")
for b in settings.SITH_COUNTER_BARS: for b in settings.SITH_COUNTER_BARS:
g = Group(name=b[1]+" admin") g = Group(name=b[1] + " admin")
g.save() g.save()
c = Counter(id=b[0], name=b[1], club=bar_club, type='BAR') c = Counter(id=b[0], name=b[1], club=bar_club, type='BAR')
c.save() c.save()
@ -120,7 +120,7 @@ class Command(BaseCommand):
p = Page(name='Index') p = Page(name='Index')
p.set_lock(root) p.set_lock(root)
p.save() p.save()
p.view_groups=[settings.SITH_GROUP_PUBLIC_ID] p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
p.set_lock(root) p.set_lock(root)
p.save() p.save()
PageRev(page=p, title="Wiki index", author=root, content=""" PageRev(page=p, title="Wiki index", author=root, content="""
@ -130,7 +130,7 @@ Welcome to the wiki page!
p = Page(name="services") p = Page(name="services")
p.set_lock(root) p.set_lock(root)
p.save() p.save()
p.view_groups=[settings.SITH_GROUP_PUBLIC_ID] p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
p.set_lock(root) p.set_lock(root)
PageRev(page=p, title="Services", author=root, content=""" PageRev(page=p, title="Services", author=root, content="""
| | | | | | | |
@ -150,18 +150,18 @@ Welcome to the wiki page!
if not options['prod']: if not options['prod']:
# Adding user Skia # Adding user Skia
skia = User(username='skia', last_name="Kia", first_name="S'", skia = User(username='skia', last_name="Kia", first_name="S'",
email="skia@git.an", email="skia@git.an",
date_of_birth="1942-06-12") date_of_birth="1942-06-12")
skia.set_password("plop") skia.set_password("plop")
skia.save() skia.save()
skia.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] skia.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
skia.save() skia.save()
skia_profile_path = os.path.join(root_path, 'core/fixtures/images/3.jpg') skia_profile_path = os.path.join(root_path, 'core/fixtures/images/3.jpg')
with open(skia_profile_path, 'rb') as f: with open(skia_profile_path, 'rb') as f:
name = str(skia.id) + "_profile.jpg" name = str(skia.id) + "_profile.jpg"
skia_profile = SithFile(parent=profiles_root, name=name, skia_profile = SithFile(parent=profiles_root, name=name,
file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'),
owner=skia, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(skia_profile_path)) owner=skia, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(skia_profile_path))
skia_profile.file.name = name skia_profile.file.name = name
skia_profile.save() skia_profile.save()
skia.profile_pict = skia_profile skia.profile_pict = skia_profile
@ -169,50 +169,50 @@ Welcome to the wiki page!
# Adding user public # Adding user public
public = User(username='public', last_name="Not subscribed", first_name="Public", public = User(username='public', last_name="Not subscribed", first_name="Public",
email="public@git.an", email="public@git.an",
date_of_birth="1942-06-12", date_of_birth="1942-06-12",
is_superuser=False, is_staff=False) is_superuser=False, is_staff=False)
public.set_password("plop") public.set_password("plop")
public.save() public.save()
public.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] public.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
public.save() public.save()
# Adding user Subscriber # Adding user Subscriber
subscriber = User(username='subscriber', last_name="User", first_name="Subscribed", subscriber = User(username='subscriber', last_name="User", first_name="Subscribed",
email="Subscribed@git.an", email="Subscribed@git.an",
date_of_birth="1942-06-12", date_of_birth="1942-06-12",
is_superuser=False, is_staff=False) is_superuser=False, is_staff=False)
subscriber.set_password("plop") subscriber.set_password("plop")
subscriber.save() subscriber.save()
subscriber.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] subscriber.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
subscriber.save() subscriber.save()
# Adding user old Subscriber # Adding user old Subscriber
old_subscriber = User(username='old_subscriber', last_name="Subscriber", first_name="Old", old_subscriber = User(username='old_subscriber', last_name="Subscriber", first_name="Old",
email="old_subscriber@git.an", email="old_subscriber@git.an",
date_of_birth="1942-06-12", date_of_birth="1942-06-12",
is_superuser=False, is_staff=False) is_superuser=False, is_staff=False)
old_subscriber.set_password("plop") old_subscriber.set_password("plop")
old_subscriber.save() old_subscriber.save()
old_subscriber.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] old_subscriber.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
old_subscriber.save() old_subscriber.save()
# Adding user Counter admin # Adding user Counter admin
counter = User(username='counter', last_name="Ter", first_name="Coun", counter = User(username='counter', last_name="Ter", first_name="Coun",
email="counter@git.an", email="counter@git.an",
date_of_birth="1942-06-12", date_of_birth="1942-06-12",
is_superuser=False, is_staff=False) is_superuser=False, is_staff=False)
counter.set_password("plop") counter.set_password("plop")
counter.save() counter.save()
counter.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] counter.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
counter.groups=[Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID).first().id] counter.groups = [Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID).first().id]
counter.save() counter.save()
# Adding user Comptable # Adding user Comptable
comptable = User(username='comptable', last_name="Able", first_name="Compte", comptable = User(username='comptable', last_name="Able", first_name="Compte",
email="compta@git.an", email="compta@git.an",
date_of_birth="1942-06-12", date_of_birth="1942-06-12",
is_superuser=False, is_staff=False) is_superuser=False, is_staff=False)
comptable.set_password("plop") comptable.set_password("plop")
comptable.save() comptable.save()
comptable.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] comptable.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
comptable.groups=[Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID).first().id] comptable.groups = [Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID).first().id]
comptable.save() comptable.save()
# Adding user Guy # Adding user Guy
u = User(username='guy', last_name="Carlier", first_name="Guy", u = User(username='guy', last_name="Carlier", first_name="Guy",
@ -221,7 +221,7 @@ Welcome to the wiki page!
is_superuser=False, is_staff=False) is_superuser=False, is_staff=False)
u.set_password("plop") u.set_password("plop")
u.save() u.save()
u.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] u.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
u.save() u.save()
# Adding user Richard Batsbak # Adding user Richard Batsbak
r = User(username='rbatsbak', last_name="Batsbak", first_name="Richard", r = User(username='rbatsbak', last_name="Batsbak", first_name="Richard",
@ -229,18 +229,18 @@ Welcome to the wiki page!
date_of_birth="1982-06-12") date_of_birth="1982-06-12")
r.set_password("plop") r.set_password("plop")
r.save() r.save()
r.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] r.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
r.save() r.save()
# Adding syntax help page # Adding syntax help page
p = Page(name='Aide_sur_la_syntaxe') p = Page(name='Aide_sur_la_syntaxe')
p.save(force_lock=True) p.save(force_lock=True)
with open(os.path.join(root_path)+'/doc/SYNTAX.md', 'r') as rm: with open(os.path.join(root_path) + '/doc/SYNTAX.md', 'r') as rm:
PageRev(page=p, title="Aide sur la syntaxe", author=skia, content=rm.read()).save() PageRev(page=p, title="Aide sur la syntaxe", author=skia, content=rm.read()).save()
p.view_groups=[settings.SITH_GROUP_PUBLIC_ID] p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
p.save(force_lock=True) p.save(force_lock=True)
p = Page(name='Services') p = Page(name='Services')
p.save(force_lock=True) p.save(force_lock=True)
p.view_groups=[settings.SITH_GROUP_PUBLIC_ID] p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
p.save(force_lock=True) p.save(force_lock=True)
PageRev(page=p, title="Services", author=skia, content=""" PageRev(page=p, title="Services", author=skia, content="""
| | | | | | | |
@ -252,83 +252,83 @@ Welcome to the wiki page!
# Adding README # Adding README
p = Page(name='README') p = Page(name='README')
p.save(force_lock=True) p.save(force_lock=True)
p.view_groups=[settings.SITH_GROUP_PUBLIC_ID] p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
p.save(force_lock=True) p.save(force_lock=True)
with open(os.path.join(root_path)+'/README.md', 'r') as rm: with open(os.path.join(root_path) + '/README.md', 'r') as rm:
PageRev(page=p, title="README", author=skia, content=rm.read()).save() PageRev(page=p, title="README", author=skia, content=rm.read()).save()
# Subscription # Subscription
## Root # Root
s = Subscription(member=User.objects.filter(pk=root.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=root.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
## Skia # Skia
s = Subscription(member=User.objects.filter(pk=skia.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=skia.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
## Counter admin # Counter admin
s = Subscription(member=User.objects.filter(pk=counter.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=counter.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
## Comptable # Comptable
s = Subscription(member=User.objects.filter(pk=comptable.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=comptable.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
## Richard # Richard
s = Subscription(member=User.objects.filter(pk=r.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=r.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
## User # User
s = Subscription(member=User.objects.filter(pk=subscriber.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=subscriber.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
## Old subscriber # Old subscriber
s = Subscription(member=User.objects.filter(pk=old_subscriber.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=old_subscriber.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start(datetime(year=2012, month=9, day=4)) s.subscription_start = s.compute_start(datetime(year=2012, month=9, day=4))
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
# Clubs # Clubs
Club(name="Bibo'UT", unix_name="bibout", Club(name="Bibo'UT", unix_name="bibout",
address="46 de la Boustifaille", parent=main_club).save() address="46 de la Boustifaille", parent=main_club).save()
guyut = Club(name="Guy'UT", unix_name="guyut", guyut = Club(name="Guy'UT", unix_name="guyut",
address="42 de la Boustifaille", parent=main_club) address="42 de la Boustifaille", parent=main_club)
guyut.save() guyut.save()
Club(name="Woenzel'UT", unix_name="woenzel", Club(name="Woenzel'UT", unix_name="woenzel",
address="Woenzel", parent=guyut).save() address="Woenzel", parent=guyut).save()
Membership(user=skia, club=main_club, role=3, description="").save() Membership(user=skia, club=main_club, role=3, description="").save()
troll = Club(name="Troll Penché", unix_name="troll", troll = Club(name="Troll Penché", unix_name="troll",
address="Terre Du Milieu", parent=main_club) address="Terre Du Milieu", parent=main_club)
troll.save() troll.save()
refound = Club(name="Carte AE", unix_name="carte_ae", refound = Club(name="Carte AE", unix_name="carte_ae",
address="Jamais imprimée", parent=main_club) address="Jamais imprimée", parent=main_club)
refound.save() refound.save()
# Counters # Counters
@ -341,19 +341,19 @@ Welcome to the wiki page!
r = ProductType(name="Rechargements") r = ProductType(name="Rechargements")
r.save() r.save()
cotis = Product(name="Cotis 1 semestre", code="1SCOTIZ", product_type=c, purchase_price="15", selling_price="15", cotis = Product(name="Cotis 1 semestre", code="1SCOTIZ", product_type=c, purchase_price="15", selling_price="15",
special_selling_price="15", club=main_club) special_selling_price="15", club=main_club)
cotis.save() cotis.save()
cotis2 = Product(name="Cotis 2 semestres", code="2SCOTIZ", product_type=c, purchase_price="28", selling_price="28", cotis2 = Product(name="Cotis 2 semestres", code="2SCOTIZ", product_type=c, purchase_price="28", selling_price="28",
special_selling_price="28", club=main_club) special_selling_price="28", club=main_club)
cotis2.save() cotis2.save()
refill = Product(name="Rechargement 15 €", code="15REFILL", product_type=r, purchase_price="15", selling_price="15", refill = Product(name="Rechargement 15 €", code="15REFILL", product_type=r, purchase_price="15", selling_price="15",
special_selling_price="15", club=main_club) special_selling_price="15", club=main_club)
refill.save() refill.save()
barb = Product(name="Barbar", code="BARB", product_type=p, purchase_price="1.50", selling_price="1.7", barb = Product(name="Barbar", code="BARB", product_type=p, purchase_price="1.50", selling_price="1.7",
special_selling_price="1.6", club=main_club) special_selling_price="1.6", club=main_club)
barb.save() barb.save()
cble = Product(name="Chimay Bleue", code="CBLE", product_type=p, purchase_price="1.50", selling_price="1.7", cble = Product(name="Chimay Bleue", code="CBLE", product_type=p, purchase_price="1.50", selling_price="1.7",
special_selling_price="1.6", club=main_club) special_selling_price="1.6", club=main_club)
cble.save() cble.save()
Product(name="Corsendonk", code="CORS", product_type=p, purchase_price="1.50", selling_price="1.7", Product(name="Corsendonk", code="CORS", product_type=p, purchase_price="1.50", selling_price="1.7",
special_selling_price="1.6", club=main_club).save() special_selling_price="1.6", club=main_club).save()
@ -375,7 +375,7 @@ Welcome to the wiki page!
refound_counter = Counter(name="Carte AE", club=refound, type='OFFICE') refound_counter = Counter(name="Carte AE", club=refound, type='OFFICE')
refound_counter.save() refound_counter.save()
refound_product = Product(name="remboursement", code="REMBOURS", purchase_price="0", selling_price="0", refound_product = Product(name="remboursement", code="REMBOURS", purchase_price="0", selling_price="0",
special_selling_price="0", club=refound) special_selling_price="0", club=refound)
refound_product.save() refound_product.save()
# Accounting test values: # Accounting test values:
@ -397,28 +397,28 @@ Welcome to the wiki page!
buying.save() buying.save()
comptes = AccountingType(code='6', label="Comptes de charge", movement_type='DEBIT') comptes = AccountingType(code='6', label="Comptes de charge", movement_type='DEBIT')
comptes.save() comptes.save()
simple = SimplifiedAccountingType(label = 'Je fais du simple 6', accounting_type = comptes, movement_type='DEBIT') simple = SimplifiedAccountingType(label='Je fais du simple 6', accounting_type=comptes, movement_type='DEBIT')
simple.save() simple.save()
woenzco = Company(name="Woenzel & co") woenzco = Company(name="Woenzel & co")
woenzco.save() woenzco.save()
operation_list = [ operation_list = [
(27, "J'avais trop de bière", 'CASH', None, buying, 'USER', skia.id, "", None), (27, "J'avais trop de bière", 'CASH', None, buying, 'USER', skia.id, "", None),
(4000, "Ceci n'est pas une opération... en fait si mais non", 'CHECK', None, debit,'COMPANY', woenzco.id, "", 23), (4000, "Ceci n'est pas une opération... en fait si mais non", 'CHECK', None, debit, 'COMPANY', woenzco.id, "", 23),
(22, "C'est de l'argent ?", 'CARD', None, credit, 'CLUB', troll.id, "", None), (22, "C'est de l'argent ?", 'CARD', None, credit, 'CLUB', troll.id, "", None),
(37, "Je paye CASH", 'CASH', None, debit2, 'OTHER', None, "tous les étudiants <3", None), (37, "Je paye CASH", 'CASH', None, debit2, 'OTHER', None, "tous les étudiants <3", None),
(300, "Paiement Guy", 'CASH', None, buying, 'USER', skia.id, "", None), (300, "Paiement Guy", 'CASH', None, buying, 'USER', skia.id, "", None),
(32.3, "Essence", 'CASH', None, buying, 'OTHER', None, "station", None), (32.3, "Essence", 'CASH', None, buying, 'OTHER', None, "station", None),
(46.42, "Allumette", 'CHECK', None, credit, 'CLUB', main_club.id, "", 57), (46.42, "Allumette", 'CHECK', None, credit, 'CLUB', main_club.id, "", 57),
(666.42, "Subvention de far far away", 'CASH', None, comptes, 'CLUB', main_club.id, "", None), (666.42, "Subvention de far far away", 'CASH', None, comptes, 'CLUB', main_club.id, "", None),
(496, "Ça, c'est un 6", 'CARD', simple, None, 'USER', skia.id, "", None), (496, "Ça, c'est un 6", 'CARD', simple, None, 'USER', skia.id, "", None),
(17, "La Gargotte du Korrigan", 'CASH', None, debit2, 'CLUB', bar_club.id, "", None), (17, "La Gargotte du Korrigan", 'CASH', None, debit2, 'CLUB', bar_club.id, "", None),
] ]
for op in operation_list: for op in operation_list:
operation = Operation(journal=gj, date=date.today(), amount=op[0], operation = Operation(journal=gj, date=date.today(), amount=op[0],
remark=op[1], mode=op[2], done=True, simpleaccounting_type=op[3], remark=op[1], mode=op[2], done=True, simpleaccounting_type=op[3],
accounting_type=op[4], target_type=op[5], target_id=op[6], accounting_type=op[4], target_type=op[5], target_id=op[6],
target_label=op[7], cheque_number=op[8]) target_label=op[7], cheque_number=op[8])
operation.clean() operation.clean()
operation.save() operation.save()
@ -428,14 +428,14 @@ Welcome to the wiki page!
date_of_birth="1942-06-12") date_of_birth="1942-06-12")
sli.set_password("plop") sli.set_password("plop")
sli.save() sli.save()
sli.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] sli.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
sli.save() sli.save()
sli_profile_path = os.path.join(root_path, 'core/fixtures/images/5.jpg') sli_profile_path = os.path.join(root_path, 'core/fixtures/images/5.jpg')
with open(sli_profile_path, 'rb') as f: with open(sli_profile_path, 'rb') as f:
name = str(sli.id) + "_profile.jpg" name = str(sli.id) + "_profile.jpg"
sli_profile = SithFile(parent=profiles_root, name=name, sli_profile = SithFile(parent=profiles_root, name=name,
file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'),
owner=sli, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(sli_profile_path)) owner=sli, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(sli_profile_path))
sli_profile.file.name = name sli_profile.file.name = name
sli_profile.save() sli_profile.save()
sli.profile_pict = sli_profile sli.profile_pict = sli_profile
@ -450,27 +450,27 @@ Welcome to the wiki page!
with open(krophil_profile_path, 'rb') as f: with open(krophil_profile_path, 'rb') as f:
name = str(krophil.id) + "_profile.jpg" name = str(krophil.id) + "_profile.jpg"
krophil_profile = SithFile(parent=profiles_root, name=name, krophil_profile = SithFile(parent=profiles_root, name=name,
file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'),
owner=krophil, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(krophil_profile_path)) owner=krophil, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(krophil_profile_path))
krophil_profile.file.name = name krophil_profile.file.name = name
krophil_profile.save() krophil_profile.save()
krophil.profile_pict = krophil_profile krophil.profile_pict = krophil_profile
krophil.save() krophil.save()
## Adding subscription for sli # Adding subscription for sli
s = Subscription(member=User.objects.filter(pk=sli.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=sli.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
## Adding subscription for Krophil # Adding subscription for Krophil
s = Subscription(member=User.objects.filter(pk=krophil.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=krophil.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
# Add barman to counter # Add barman to counter
@ -483,8 +483,8 @@ Welcome to the wiki page!
subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP) subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP)
ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP) ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP)
el = Election(title="Élection 2017", description="La roue tourne", start_candidature='1942-06-12 10:28:45+01', el = Election(title="Élection 2017", description="La roue tourne", start_candidature='1942-06-12 10:28:45+01',
end_candidature='2042-06-12 10:28:45+01',start_date='1942-06-12 10:28:45+01', end_candidature='2042-06-12 10:28:45+01', start_date='1942-06-12 10:28:45+01',
end_date='7942-06-12 10:28:45+01') end_date='7942-06-12 10:28:45+01')
el.save() el.save()
el.view_groups.add(public_group) el.view_groups.add(public_group)
el.edit_groups.add(ae_board_group) el.edit_groups.add(ae_board_group)
@ -519,4 +519,3 @@ Welcome to the wiki page!
various.save() various.save()
Forum(name="Promos", description="Réservé aux Promos", parent=various).save() Forum(name="Promos", description="Réservé aux Promos", parent=various).save()
ForumTopic(forum=hall) ForumTopic(forum=hall)

View File

@ -23,9 +23,8 @@
# #
import os import os
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand
from django.core.management import call_command from django.core.management import call_command
from django.conf import settings
class Command(BaseCommand): class Command(BaseCommand):
@ -37,7 +36,7 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
try: try:
os.mkdir(os.path.join(root_path)+'/data') os.mkdir(os.path.join(root_path) + '/data')
print("Data dir created") print("Data dir created")
except Exception as e: except Exception as e:
repr(e) repr(e)

View File

@ -24,7 +24,7 @@
import re import re
from mistune import Renderer, InlineGrammar, InlineLexer, Markdown, escape, escape_link from mistune import Renderer, InlineGrammar, InlineLexer, Markdown, escape, escape_link
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse
class SithRenderer(Renderer): class SithRenderer(Renderer):
@ -54,13 +54,16 @@ class SithRenderer(Renderer):
src = original_src src = original_src
else: else:
width = m.group(1) width = m.group(1)
if not width.endswith('%'): width += "px" if not width.endswith('%'):
width += "px"
style = "width: %s; " % width style = "width: %s; " % width
try: try:
height = m.group(3) height = m.group(3)
if not height.endswith('%'): height += "px" if not height.endswith('%'):
height += "px"
style += "height: %s; " % height style += "height: %s; " % height
except: pass except:
pass
else: else:
params = None params = None
src = original_src src = original_src
@ -77,6 +80,7 @@ class SithRenderer(Renderer):
return '%s />' % html return '%s />' % html
return '%s>' % html return '%s>' % html
class SithInlineGrammar(InlineGrammar): class SithInlineGrammar(InlineGrammar):
double_emphasis = re.compile( double_emphasis = re.compile(
r'^\*{2}([\s\S]+?)\*{2}(?!\*)' # **word** r'^\*{2}([\s\S]+?)\*{2}(?!\*)' # **word**
@ -94,6 +98,7 @@ class SithInlineGrammar(InlineGrammar):
r'^<sub>([\s\S]+?)</sub>' # <sub>text</sub> r'^<sub>([\s\S]+?)</sub>' # <sub>text</sub>
) )
class SithInlineLexer(InlineLexer): class SithInlineLexer(InlineLexer):
grammar_class = SithInlineGrammar grammar_class = SithInlineGrammar
@ -159,15 +164,16 @@ class SithInlineLexer(InlineLexer):
return self.renderer.emphasis(text) return self.renderer.emphasis(text)
def _process_link(self, m, link, title=None): def _process_link(self, m, link, title=None):
try: # Add page:// support for links try: # Add page:// support for links
page = re.compile( page = re.compile(
r'^page://(\S*)' # page://nom_de_ma_page r'^page://(\S*)' # page://nom_de_ma_page
) )
match = page.search(link) match = page.search(link)
page = match.group(1) or "" page = match.group(1) or ""
link = reverse('core:page', kwargs={'page_name': page}) link = reverse('core:page', kwargs={'page_name': page})
except: pass except:
try: # Add file:// support for links pass
try: # Add file:// support for links
file_link = re.compile( file_link = re.compile(
r'^file://(\d*)/?(\S*)?' # file://4000/download r'^file://(\d*)/?(\S*)?' # file://4000/download
) )
@ -175,9 +181,11 @@ class SithInlineLexer(InlineLexer):
id = match.group(1) id = match.group(1)
suffix = match.group(2) or "" suffix = match.group(2) or ""
link = reverse('core:file_detail', kwargs={'file_id': id}) + suffix link = reverse('core:file_detail', kwargs={'file_id': id}) + suffix
except: pass except:
pass
return super(SithInlineLexer, self)._process_link(m, link, title) return super(SithInlineLexer, self)._process_link(m, link, title)
renderer = SithRenderer(escape=True) renderer = SithRenderer(escape=True)
inline = SithInlineLexer(renderer) inline = SithInlineLexer(renderer)
@ -222,4 +230,3 @@ Petit *test* _sur_ ^une^ **seule** ^ligne pour voir^
""" """
print(markdown(text)) print(markdown(text))

View File

@ -52,4 +52,3 @@ class AuthenticationMiddleware(DjangoAuthenticationMiddleware):
"'account.middleware.AuthenticationMiddleware'." "'account.middleware.AuthenticationMiddleware'."
) )
request.user = SimpleLazyObject(lambda: get_cached_user(request)) request.user = SimpleLazyObject(lambda: get_cached_user(request))

View File

@ -44,14 +44,17 @@ from datetime import datetime, timedelta, date
import unicodedata import unicodedata
class RealGroupManager(AuthGroupManager): class RealGroupManager(AuthGroupManager):
def get_queryset(self): def get_queryset(self):
return super(RealGroupManager, self).get_queryset().filter(is_meta=False) return super(RealGroupManager, self).get_queryset().filter(is_meta=False)
class MetaGroupManager(AuthGroupManager): class MetaGroupManager(AuthGroupManager):
def get_queryset(self): def get_queryset(self):
return super(MetaGroupManager, self).get_queryset().filter(is_meta=True) return super(MetaGroupManager, self).get_queryset().filter(is_meta=True)
class Group(AuthGroup): class Group(AuthGroup):
is_meta = models.BooleanField( is_meta = models.BooleanField(
_('meta group status'), _('meta group status'),
@ -69,8 +72,10 @@ class Group(AuthGroup):
""" """
return reverse('core:group_list') return reverse('core:group_list')
class MetaGroup(Group): class MetaGroup(Group):
objects = MetaGroupManager() objects = MetaGroupManager()
class Meta: class Meta:
proxy = True proxy = True
@ -78,20 +83,24 @@ class MetaGroup(Group):
super(MetaGroup, self).__init__(*args, **kwargs) super(MetaGroup, self).__init__(*args, **kwargs)
self.is_meta = True self.is_meta = True
class RealGroup(Group): class RealGroup(Group):
objects = RealGroupManager() objects = RealGroupManager()
class Meta: class Meta:
proxy = True proxy = True
def validate_promo(value): def validate_promo(value):
start_year = settings.SITH_SCHOOL_START_YEAR start_year = settings.SITH_SCHOOL_START_YEAR
delta = (date.today()+timedelta(days=180)).year - start_year delta = (date.today() + timedelta(days=180)).year - start_year
if value < 0 or delta < value: if value < 0 or delta < value:
raise ValidationError( raise ValidationError(
_('%(value)s is not a valid promo (between 0 and %(end)s)'), _('%(value)s is not a valid promo (between 0 and %(end)s)'),
params={'value': value, 'end': delta}, params={'value': value, 'end': delta},
) )
class User(AbstractBaseUser): class User(AbstractBaseUser):
""" """
Defines the base user class, useable in every app Defines the base user class, useable in every app
@ -148,13 +157,13 @@ class User(AbstractBaseUser):
) )
groups = models.ManyToManyField(RealGroup, related_name='users', blank=True) groups = models.ManyToManyField(RealGroup, related_name='users', blank=True)
home = models.OneToOneField('SithFile', related_name='home_of', verbose_name=_("home"), null=True, blank=True, home = models.OneToOneField('SithFile', related_name='home_of', verbose_name=_("home"), null=True, blank=True,
on_delete=models.SET_NULL) on_delete=models.SET_NULL)
profile_pict = models.OneToOneField('SithFile', related_name='profile_of', verbose_name=_("profile"), null=True, profile_pict = models.OneToOneField('SithFile', related_name='profile_of', verbose_name=_("profile"), null=True,
blank=True, on_delete=models.SET_NULL) blank=True, on_delete=models.SET_NULL)
avatar_pict = models.OneToOneField('SithFile', related_name='avatar_of', verbose_name=_("avatar"), null=True, avatar_pict = models.OneToOneField('SithFile', related_name='avatar_of', verbose_name=_("avatar"), null=True,
blank=True, on_delete=models.SET_NULL) blank=True, on_delete=models.SET_NULL)
scrub_pict = models.OneToOneField('SithFile', related_name='scrub_of', verbose_name=_("scrub"), null=True, scrub_pict = models.OneToOneField('SithFile', related_name='scrub_of', verbose_name=_("scrub"), null=True,
blank=True, on_delete=models.SET_NULL) blank=True, on_delete=models.SET_NULL)
sex = models.CharField(_("sex"), max_length=10, choices=[("MAN", _("Man")), ("WOMAN", _("Woman"))], default="MAN") sex = models.CharField(_("sex"), max_length=10, choices=[("MAN", _("Man")), ("WOMAN", _("Woman"))], default="MAN")
tshirt_size = models.CharField(_("tshirt size"), max_length=5, choices=[ tshirt_size = models.CharField(_("tshirt size"), max_length=5, choices=[
("-", _("-")), ("-", _("-")),
@ -165,7 +174,7 @@ class User(AbstractBaseUser):
("XL", _("XL")), ("XL", _("XL")),
("XXL", _("XXL")), ("XXL", _("XXL")),
("XXXL", _("XXXL")), ("XXXL", _("XXXL")),
], default="-") ], default="-")
role = models.CharField(_("role"), max_length=15, choices=[ role = models.CharField(_("role"), max_length=15, choices=[
("STUDENT", _("Student")), ("STUDENT", _("Student")),
("ADMINISTRATIVE", _("Administrative agent")), ("ADMINISTRATIVE", _("Administrative agent")),
@ -174,9 +183,9 @@ class User(AbstractBaseUser):
("DOCTOR", _("Doctor")), ("DOCTOR", _("Doctor")),
("FORMER STUDENT", _("Former student")), ("FORMER STUDENT", _("Former student")),
("SERVICE", _("Service")), ("SERVICE", _("Service")),
], blank=True, default="") ], blank=True, default="")
department = models.CharField(_("department"), max_length=15, choices=settings.SITH_PROFILE_DEPARTMENTS, department = models.CharField(_("department"), max_length=15, choices=settings.SITH_PROFILE_DEPARTMENTS,
default="NA", blank=True) default="NA", blank=True)
dpt_option = models.CharField(_("dpt option"), max_length=32, blank=True, default="") dpt_option = models.CharField(_("dpt option"), max_length=32, blank=True, default="")
semester = models.CharField(_("semester"), max_length=5, blank=True, default="") semester = models.CharField(_("semester"), max_length=5, blank=True, default="")
quote = models.CharField(_("quote"), max_length=256, blank=True, default="") quote = models.CharField(_("quote"), max_length=256, blank=True, default="")
@ -226,11 +235,12 @@ class User(AbstractBaseUser):
_club_memberships = {} _club_memberships = {}
_group_names = {} _group_names = {}
_group_ids = {} _group_ids = {}
def is_in_group(self, group_name): def is_in_group(self, group_name):
"""If the user is in the group passed in argument (as string or by id)""" """If the user is in the group passed in argument (as string or by id)"""
group_id = 0 group_id = 0
g = None g = None
if isinstance(group_name, int): # Handle the case where group_name is an ID if isinstance(group_name, int): # Handle the case where group_name is an ID
if group_name in User._group_ids.keys(): if group_name in User._group_ids.keys():
g = User._group_ids[group_name] g = User._group_ids[group_name]
else: else:
@ -253,7 +263,7 @@ class User(AbstractBaseUser):
return self.is_subscribed return self.is_subscribed
if group_id == settings.SITH_GROUP_OLD_SUBSCRIBERS_ID: if group_id == settings.SITH_GROUP_OLD_SUBSCRIBERS_ID:
return self.was_subscribed return self.was_subscribed
if group_name == settings.SITH_MAIN_MEMBERS_GROUP: # We check the subscription if asked if group_name == settings.SITH_MAIN_MEMBERS_GROUP: # We check the subscription if asked
return self.is_subscribed return self.is_subscribed
if group_name[-len(settings.SITH_BOARD_SUFFIX):] == settings.SITH_BOARD_SUFFIX: if group_name[-len(settings.SITH_BOARD_SUFFIX):] == settings.SITH_BOARD_SUFFIX:
name = group_name[:-len(settings.SITH_BOARD_SUFFIX)] name = group_name[:-len(settings.SITH_BOARD_SUFFIX)]
@ -315,7 +325,7 @@ class User(AbstractBaseUser):
else: else:
create = True create = True
super(User, self).save(*args, **kwargs) super(User, self).save(*args, **kwargs)
if create and settings.IS_OLD_MYSQL_PRESENT: # Create user on the old site: TODO remove me! if create and settings.IS_OLD_MYSQL_PRESENT: # Create user on the old site: TODO remove me!
import MySQLdb import MySQLdb
try: try:
db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS) db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS)
@ -324,9 +334,9 @@ class User(AbstractBaseUser):
(%s, %s, %s, %s, %s, %s)""", (self.id, self.last_name, self.first_name, self.email, "valid", "0")) (%s, %s, %s, %s, %s, %s)""", (self.id, self.last_name, self.first_name, self.email, "valid", "0"))
db.commit() db.commit()
except Exception as e: except Exception as e:
with open(settings.BASE_DIR+"/user_fail.log", "a") as f: with open(settings.BASE_DIR + "/user_fail.log", "a") as f:
print("FAIL to add user %s (%s %s - %s) to old site" % (self.id, self.first_name, self.last_name, print("FAIL to add user %s (%s %s - %s) to old site" % (self.id, self.first_name, self.last_name,
self.email), file=f) self.email), file=f)
print("Reason: %s" % (repr(e)), file=f) print("Reason: %s" % (repr(e)), file=f)
db.rollback() db.rollback()
@ -401,13 +411,13 @@ class User(AbstractBaseUser):
Returns the generated username Returns the generated username
""" """
def remove_accents(data): def remove_accents(data):
return ''.join(x for x in unicodedata.normalize('NFKD', data) if \ return ''.join(x for x in unicodedata.normalize('NFKD', data) if
unicodedata.category(x)[0] == 'L').lower() unicodedata.category(x)[0] == 'L').lower()
user_name = remove_accents(self.first_name[0]+self.last_name).encode('ascii', 'ignore').decode('utf-8') user_name = remove_accents(self.first_name[0] + self.last_name).encode('ascii', 'ignore').decode('utf-8')
un_set = [u.username for u in User.objects.all()] un_set = [u.username for u in User.objects.all()]
if user_name in un_set: if user_name in un_set:
i = 1 i = 1
while user_name+str(i) in un_set: while user_name + str(i) in un_set:
i += 1 i += 1
user_name += str(i) user_name += str(i)
self.username = user_name self.username = user_name
@ -473,7 +483,7 @@ class User(AbstractBaseUser):
self.profile_pict.get_download_url() if self.profile_pict else staticfiles_storage.url("core/img/unknown.jpg"), self.profile_pict.get_download_url() if self.profile_pict else staticfiles_storage.url("core/img/unknown.jpg"),
_("Profile"), _("Profile"),
escape(self.get_display_name()), escape(self.get_display_name()),
) )
@cached_property @cached_property
def subscribed(self): def subscribed(self):
@ -489,6 +499,7 @@ class User(AbstractBaseUser):
infos.save() infos.save()
return infos return infos
class AnonymousUser(AuthAnonymousUser): class AnonymousUser(AuthAnonymousUser):
def __init__(self, request): def __init__(self, request):
super(AnonymousUser, self).__init__() super(AnonymousUser, self).__init__()
@ -530,7 +541,7 @@ class AnonymousUser(AuthAnonymousUser):
The anonymous user is only the public group The anonymous user is only the public group
""" """
group_id = 0 group_id = 0
if isinstance(group_name, int): # Handle the case where group_name is an ID if isinstance(group_name, int): # Handle the case where group_name is an ID
g = Group.objects.filter(id=group_name).first() g = Group.objects.filter(id=group_name).first()
if g: if g:
group_name = g.name group_name = g.name
@ -557,6 +568,7 @@ class AnonymousUser(AuthAnonymousUser):
def get_display_name(self): def get_display_name(self):
return _("Visitor") return _("Visitor")
class Preferences(models.Model): class Preferences(models.Model):
user = models.OneToOneField(User, related_name="preferences") user = models.OneToOneField(User, related_name="preferences")
receive_weekmail = models.BooleanField( receive_weekmail = models.BooleanField(
@ -576,15 +588,19 @@ class Preferences(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return self.user.get_absolute_url() return self.user.get_absolute_url()
def get_directory(instance, filename): def get_directory(instance, filename):
return '.{0}/{1}'.format(instance.get_parent_path(), filename) return '.{0}/{1}'.format(instance.get_parent_path(), filename)
def get_compressed_directory(instance, filename): def get_compressed_directory(instance, filename):
return '.{0}/compressed/{1}'.format(instance.get_parent_path(), filename) return '.{0}/compressed/{1}'.format(instance.get_parent_path(), filename)
def get_thumbnail_directory(instance, filename): def get_thumbnail_directory(instance, filename):
return '.{0}/thumbnail/{1}'.format(instance.get_parent_path(), filename) return '.{0}/thumbnail/{1}'.format(instance.get_parent_path(), filename)
class SithFile(models.Model): class SithFile(models.Model):
name = models.CharField(_('file name'), max_length=256, blank=False) name = models.CharField(_('file name'), max_length=256, blank=False)
parent = models.ForeignKey('self', related_name="children", verbose_name=_("parent"), null=True, blank=True) parent = models.ForeignKey('self', related_name="children", verbose_name=_("parent"), null=True, blank=True)
@ -601,7 +617,7 @@ class SithFile(models.Model):
is_moderated = models.BooleanField(_("is moderated"), default=False) is_moderated = models.BooleanField(_("is moderated"), default=False)
moderator = models.ForeignKey(User, related_name="moderated_files", verbose_name=_("owner"), null=True, blank=True) moderator = models.ForeignKey(User, related_name="moderated_files", verbose_name=_("owner"), null=True, blank=True)
asked_for_removal = models.BooleanField(_("asked for removal"), default=False) asked_for_removal = models.BooleanField(_("asked for removal"), default=False)
is_in_sas = models.BooleanField(_("is in the SAS"), default=False) # Allows to query this flag, updated at each call to save() is_in_sas = models.BooleanField(_("is in the SAS"), default=False) # Allows to query this flag, updated at each call to save()
class Meta: class Meta:
verbose_name = _("file") verbose_name = _("file")
@ -763,18 +779,22 @@ class SithFile(models.Model):
def __str__(self): def __str__(self):
return self.get_parent_path() + "/" + self.name return self.get_parent_path() + "/" + self.name
class LockError(Exception): class LockError(Exception):
"""There was a lock error on the object""" """There was a lock error on the object"""
pass pass
class AlreadyLocked(LockError): class AlreadyLocked(LockError):
"""The object is already locked""" """The object is already locked"""
pass pass
class NotLocked(LockError): class NotLocked(LockError):
"""The object is not locked""" """The object is not locked"""
pass pass
class Page(models.Model): class Page(models.Model):
""" """
The page class to build a Wiki The page class to build a Wiki
@ -787,14 +807,13 @@ class Page(models.Model):
query, but don't rely on it when playing with a Page object, use get_full_name() instead! query, but don't rely on it when playing with a Page object, use get_full_name() instead!
""" """
name = models.CharField(_('page unix name'), max_length=30, name = models.CharField(_('page unix name'), max_length=30,
validators=[ validators=[
validators.RegexValidator( validators.RegexValidator(
r'^[A-z.+-]+$', r'^[A-z.+-]+$',
_('Enter a valid page name. This value may contain only ' _('Enter a valid page name. This value may contain only '
'unaccented letters, numbers ' 'and ./+/-/_ characters.') 'unaccented letters, numbers ' 'and ./+/-/_ characters.')
), ), ],
], blank=False)
blank=False)
parent = models.ForeignKey('self', related_name="children", verbose_name=_("parent"), null=True, blank=True, on_delete=models.SET_NULL) parent = models.ForeignKey('self', related_name="children", verbose_name=_("parent"), null=True, blank=True, on_delete=models.SET_NULL)
# Attention: this field may not be valid until you call save(). It's made for fast query, but don't rely on it when # Attention: this field may not be valid until you call save(). It's made for fast query, but don't rely on it when
# playing with a Page object, use get_full_name() instead! # playing with a Page object, use get_full_name() instead!
@ -854,12 +873,13 @@ class Page(models.Model):
Performs some needed actions before and after saving a page in database Performs some needed actions before and after saving a page in database
""" """
locked = kwargs.pop('force_lock', False) locked = kwargs.pop('force_lock', False)
if not locked: locked = self.is_locked() if not locked:
locked = self.is_locked()
if not locked: if not locked:
raise NotLocked("The page is not locked and thus can not be saved") raise NotLocked("The page is not locked and thus can not be saved")
self.full_clean() self.full_clean()
if not self.id: if not self.id:
super(Page, self).save(*args, **kwargs) # Save a first time to correctly set _full_name super(Page, self).save(*args, **kwargs) # Save a first time to correctly set _full_name
# This reset the _full_name just before saving to maintain a coherent field quicker for queries than the # This reset the _full_name just before saving to maintain a coherent field quicker for queries than the
# recursive method # recursive method
# It also update all the children to maintain correct names # It also update all the children to maintain correct names
@ -973,7 +993,7 @@ class PageRev(models.Model):
page = models.ForeignKey(Page, related_name='revisions') page = models.ForeignKey(Page, related_name='revisions')
class Meta: class Meta:
ordering = ['date',] ordering = ['date', ]
def get_absolute_url(self): def get_absolute_url(self):
""" """
@ -1003,6 +1023,7 @@ class PageRev(models.Model):
# Don't forget to unlock, otherwise, people will have to wait for the page's timeout # Don't forget to unlock, otherwise, people will have to wait for the page's timeout
self.page.unset_lock() self.page.unset_lock()
class Notification(models.Model): class Notification(models.Model):
user = models.ForeignKey(User, related_name='notifications') user = models.ForeignKey(User, related_name='notifications')
url = models.CharField(_("url"), max_length=255) url = models.CharField(_("url"), max_length=255)
@ -1015,4 +1036,3 @@ class Notification(models.Model):
if self.param: if self.param:
return self.get_type_display() % self.param return self.get_type_display() % self.param
return self.get_type_display() return self.get_type_display()

View File

@ -27,30 +27,32 @@ from django import template
from django.template.defaultfilters import stringfilter from django.template.defaultfilters import stringfilter
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from core.scss.processor import ScssProcessor from core.scss.processor import ScssProcessor
from django.utils.html import escape
from core.markdown import markdown as md from core.markdown import markdown as md
register = template.Library() register = template.Library()
@register.filter(is_safe=False) @register.filter(is_safe=False)
@stringfilter @stringfilter
def markdown(text): def markdown(text):
return mark_safe("<div class=\"markdown\">%s</div>" % md(text)) return mark_safe("<div class=\"markdown\">%s</div>" % md(text))
@register.filter() @register.filter()
@stringfilter @stringfilter
def datetime_format_python_to_PHP(python_format_string): def datetime_format_python_to_PHP(python_format_string):
""" """
Given a python datetime format string, attempts to convert it to the nearest PHP datetime format string possible. Given a python datetime format string, attempts to convert it to the nearest PHP datetime format string possible.
""" """
python2PHP = {"%a": "D", "%a": "D", "%A": "l", "%b": "M", "%B": "F", "%c": "", "%d": "d", "%H": "H", "%I": "h", "%j": "z", "%m": "m", "%M": "i", "%p": "A", "%S": "s", "%U": "", "%w": "w", "%W": "W", "%x": "", "%X": "", "%y": "y", "%Y": "Y", "%Z": "e" } python2PHP = {"%a": "D", "%a": "D", "%A": "l", "%b": "M", "%B": "F", "%c": "", "%d": "d", "%H": "H", "%I": "h", "%j": "z", "%m": "m", "%M": "i", "%p": "A", "%S": "s", "%U": "", "%w": "w", "%W": "W", "%x": "", "%X": "", "%y": "y", "%Y": "Y", "%Z": "e"}
php_format_string = python_format_string php_format_string = python_format_string
for py, php in python2PHP.items(): for py, php in python2PHP.items():
php_format_string = php_format_string.replace(py, php) php_format_string = php_format_string.replace(py, php)
return php_format_string return php_format_string
@register.simple_tag() @register.simple_tag()
def scss(path): def scss(path):
""" """

View File

@ -24,7 +24,6 @@
from django.test import Client, TestCase from django.test import Client, TestCase
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.models import Group
from django.core.management import call_command from django.core.management import call_command
from core.models import User, Group, Page from core.models import User, Group, Page
@ -34,6 +33,7 @@ to run these tests :
python3 manage.py test python3 manage.py test
""" """
class UserRegistrationTest(TestCase): class UserRegistrationTest(TestCase):
def setUp(self): def setUp(self):
try: try:
@ -52,7 +52,7 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_OK' in str(response.content)) self.assertTrue('TEST_REGISTER_USER_FORM_OK' in str(response.content))
@ -67,7 +67,7 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop2', 'password2': 'plop2',
}) })
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content))
@ -82,7 +82,7 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content))
@ -97,7 +97,7 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content))
@ -112,7 +112,7 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '', 'date_of_birth': '',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content))
@ -127,7 +127,7 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content))
@ -142,14 +142,14 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
response = c.post(reverse('core:register'), {'first_name': 'Bibou', response = c.post(reverse('core:register'), {'first_name': 'Bibou',
'last_name': 'Carlier', 'last_name': 'Carlier',
'email': 'bibou@git.an', 'email': 'bibou@git.an',
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content))
@ -164,7 +164,7 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
response = c.post(reverse('core:login'), {'username': 'gcarlier', 'password': 'plop'}) response = c.post(reverse('core:login'), {'username': 'gcarlier', 'password': 'plop'})
self.assertTrue(response.status_code == 302) self.assertTrue(response.status_code == 302)
#self.assertTrue('Hello, world' in str(response.content)) #self.assertTrue('Hello, world' in str(response.content))
@ -180,11 +180,12 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
response = c.post(reverse('core:login'), {'username': 'gcarlier', 'password': 'guy'}) response = c.post(reverse('core:login'), {'username': 'gcarlier', 'password': 'guy'})
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue("""<p>Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.</p>""" in str(response.content)) self.assertTrue("""<p>Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.</p>""" in str(response.content))
class PageHandlingTest(TestCase): class PageHandlingTest(TestCase):
def setUp(self): def setUp(self):
try: try:
@ -207,7 +208,7 @@ class PageHandlingTest(TestCase):
'parent': '', 'parent': '',
'name': 'guy', 'name': 'guy',
'owner_group': 1, 'owner_group': 1,
}) })
response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'})) response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'}))
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('<a href="/page/guy/hist">' in str(response.content)) self.assertTrue('<a href="/page/guy/hist">' in str(response.content))
@ -220,12 +221,12 @@ class PageHandlingTest(TestCase):
'parent': '', 'parent': '',
'name': 'guy', 'name': 'guy',
'owner_group': '1', 'owner_group': '1',
}) })
response = self.client.post(reverse('core:page_new'), { response = self.client.post(reverse('core:page_new'), {
'parent': '1', 'parent': '1',
'name': 'bibou', 'name': 'bibou',
'owner_group': '1', 'owner_group': '1',
}) })
response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy/bibou'})) response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy/bibou'}))
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('<a href="/page/guy/bibou/">' in str(response.content)) self.assertTrue('<a href="/page/guy/bibou/">' in str(response.content))
@ -259,11 +260,11 @@ class PageHandlingTest(TestCase):
'parent': '', 'parent': '',
'name': 'guy', 'name': 'guy',
'owner_group': '1', 'owner_group': '1',
}) })
r = self.client.post(reverse('core:page_edit', kwargs={'page_name': 'guy'}), { self.client.post(reverse('core:page_edit', kwargs={'page_name': 'guy'}), {
'title': 'Bibou', 'title': 'Bibou',
'content': 'content':
'''Guy *bibou* '''Guy *bibou*
http://git.an http://git.an
@ -273,18 +274,19 @@ http://git.an
<script>alert('Guy');</script> <script>alert('Guy');</script>
''', ''',
}) })
response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'})) response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'}))
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('<p>Guy <em>bibou</em></p>\\n<p><a href="http://git.an">http://git.an</a></p>\\n' + self.assertTrue('<p>Guy <em>bibou</em></p>\\n<p><a href="http://git.an">http://git.an</a></p>\\n' +
'<h1>Swag</h1>\\n&lt;guy&gt;Bibou&lt;/guy&gt;' + '<h1>Swag</h1>\\n&lt;guy&gt;Bibou&lt;/guy&gt;' +
"&lt;script&gt;alert(\\'Guy\\');&lt;/script&gt;" in str(response.content)) "&lt;script&gt;alert(\\'Guy\\');&lt;/script&gt;" in str(response.content))
#TODO: many tests on the pages: # TODO: many tests on the pages:
# - renaming a page # - renaming a page
# - changing a page's parent --> check that page's children's full_name # - changing a page's parent --> check that page's children's full_name
# - changing the different groups of the page # - changing the different groups of the page
class FileHandlingTest(TestCase): class FileHandlingTest(TestCase):
def setUp(self): def setUp(self):
try: try:
@ -295,19 +297,18 @@ class FileHandlingTest(TestCase):
print(e) print(e)
def test_create_folder_home(self): def test_create_folder_home(self):
response = self.client.post(reverse("core:file_detail", kwargs={"file_id":self.subscriber.home.id}), response = self.client.post(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}),
{"folder_name": "GUY_folder_test"}) {"folder_name": "GUY_folder_test"})
self.assertTrue(response.status_code == 302) self.assertTrue(response.status_code == 302)
response = self.client.get(reverse("core:file_detail", kwargs={"file_id":self.subscriber.home.id})) response = self.client.get(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}))
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue("GUY_folder_test</a>" in str(response.content)) self.assertTrue("GUY_folder_test</a>" in str(response.content))
def test_upload_file_home(self): def test_upload_file_home(self):
with open("/bin/ls", "rb") as f: with open("/bin/ls", "rb") as f:
response = self.client.post(reverse("core:file_detail", kwargs={"file_id":self.subscriber.home.id}), response = self.client.post(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}),
{"file_field": f}) {"file_field": f})
self.assertTrue(response.status_code == 302) self.assertTrue(response.status_code == 302)
response = self.client.get(reverse("core:file_detail", kwargs={"file_id":self.subscriber.home.id})) response = self.client.get(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}))
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue("ls</a>" in str(response.content)) self.assertTrue("ls</a>" in str(response.content))

View File

@ -22,7 +22,7 @@
# #
# #
from django.conf.urls import url, include from django.conf.urls import url
from core.views import * from core.views import *

View File

@ -27,9 +27,10 @@ import re
# Image utils # Image utils
from io import BytesIO from io import BytesIO
from datetime import datetime, timezone, date from datetime import date
from PIL import Image, ExifTags from PIL import ExifTags
# from exceptions import IOError
import PIL import PIL
from django.conf import settings from django.conf import settings
@ -49,16 +50,17 @@ def get_start_of_semester(d=date.today()):
today = d today = d
year = today.year year = today.year
start = date(year, settings.SITH_START_DATE[0], settings.SITH_START_DATE[1]) start = date(year, settings.SITH_START_DATE[0], settings.SITH_START_DATE[1])
start2 = start.replace(month=(start.month+6)%12) start2 = start.replace(month=(start.month + 6) % 12)
if start > start2: if start > start2:
start, start2 = start2, start start, start2 = start2, start
if today < start: if today < start:
return start2.replace(year=year-1) return start2.replace(year=year - 1)
elif today < start2: elif today < start2:
return start return start
else: else:
return start2 return start2
def get_semester(d=date.today()): def get_semester(d=date.today()):
start = get_start_of_semester(d) start = get_start_of_semester(d)
if start.month <= 6: if start.month <= 6:
@ -66,6 +68,7 @@ def get_semester(d=date.today()):
else: else:
return "A" + str(start.year)[-2:] return "A" + str(start.year)[-2:]
def scale_dimension(width, height, long_edge): def scale_dimension(width, height, long_edge):
if width > height: if width > height:
ratio = long_edge * 1. / width ratio = long_edge * 1. / width
@ -73,6 +76,7 @@ def scale_dimension(width, height, long_edge):
ratio = long_edge * 1. / height ratio = long_edge * 1. / height
return int(width * ratio), int(height * ratio) return int(width * ratio), int(height * ratio)
def resize_image(im, edge, format): def resize_image(im, edge, format):
(w, h) = im.size (w, h) = im.size
(width, height) = scale_dimension(w, h, long_edge=edge) (width, height) = scale_dimension(w, h, long_edge=edge)
@ -85,28 +89,31 @@ def resize_image(im, edge, format):
im.save(fp=content, format=format.upper(), quality=90, optimize=True, progressive=True) im.save(fp=content, format=format.upper(), quality=90, optimize=True, progressive=True)
return ContentFile(content.getvalue()) return ContentFile(content.getvalue())
def exif_auto_rotate(image):
for orientation in ExifTags.TAGS.keys() :
if ExifTags.TAGS[orientation]=='Orientation' : break
exif=dict(image._getexif().items())
if exif[orientation] == 3 : def exif_auto_rotate(image):
image=image.rotate(180, expand=True) for orientation in ExifTags.TAGS.keys():
elif exif[orientation] == 6 : if ExifTags.TAGS[orientation] == 'Orientation':
image=image.rotate(270, expand=True) break
elif exif[orientation] == 8 : exif = dict(image._getexif().items())
image=image.rotate(90, expand=True)
if exif[orientation] == 3:
image = image.rotate(180, expand=True)
elif exif[orientation] == 6:
image = image.rotate(270, expand=True)
elif exif[orientation] == 8:
image = image.rotate(90, expand=True)
return image return image
def doku_to_markdown(text): def doku_to_markdown(text):
"""This is a quite correct doku translator""" """This is a quite correct doku translator"""
text = re.sub(r'([^:]|^)\/\/(.*?)\/\/', r'*\2*', text) # Italic (prevents protocol:// conflict) text = re.sub(r'([^:]|^)\/\/(.*?)\/\/', r'*\2*', text) # Italic (prevents protocol:// conflict)
text = re.sub(r'<del>(.*?)<\/del>', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline) text = re.sub(r'<del>(.*?)<\/del>', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline)
text = re.sub(r'<sup>(.*?)<\/sup>', r'^\1^', text) # Superscript (multiline not supported, because almost never used) text = re.sub(r'<sup>(.*?)<\/sup>', r'^\1^', text) # Superscript (multiline not supported, because almost never used)
text = re.sub(r'<sub>(.*?)<\/sub>', r'_\1_', text) # Subscript (idem) text = re.sub(r'<sub>(.*?)<\/sub>', r'_\1_', text) # Subscript (idem)
text = re.sub(r'^======(.*?)======', r'#\1', text, flags=re.MULTILINE) # Titles text = re.sub(r'^======(.*?)======', r'#\1', text, flags=re.MULTILINE) # Titles
text = re.sub(r'^=====(.*?)=====', r'##\1', text, flags=re.MULTILINE) text = re.sub(r'^=====(.*?)=====', r'##\1', text, flags=re.MULTILINE)
text = re.sub(r'^====(.*?)====', r'###\1', text, flags=re.MULTILINE) text = re.sub(r'^====(.*?)====', r'###\1', text, flags=re.MULTILINE)
text = re.sub(r'^===(.*?)===', r'####\1', text, flags=re.MULTILINE) text = re.sub(r'^===(.*?)===', r'####\1', text, flags=re.MULTILINE)
@ -121,93 +128,95 @@ def doku_to_markdown(text):
text = re.sub(r'dfile://', r'file://', text) text = re.sub(r'dfile://', r'file://', text)
i = 1 i = 1
for fn in re.findall(r'\(\((.*?)\)\)', text): # Footnotes for fn in re.findall(r'\(\((.*?)\)\)', text): # Footnotes
text = re.sub(r'\(\((.*?)\)\)', r'[^%s]' % i, text, count=1) text = re.sub(r'\(\((.*?)\)\)', r'[^%s]' % i, text, count=1)
text += "\n[^%s]: %s\n" % (i, fn) text += "\n[^%s]: %s\n" % (i, fn)
i += 1 i += 1
text = re.sub(r'\\{2,}[\s]', r' \n', text) # Carriage return text = re.sub(r'\\{2,}[\s]', r' \n', text) # Carriage return
text = re.sub(r'\[\[(.*?)\|(.*?)\]\]', r'[\2](\1)', text) # Links text = re.sub(r'\[\[(.*?)\|(.*?)\]\]', r'[\2](\1)', text) # Links
text = re.sub(r'\[\[(.*?)\]\]', r'[\1](\1)', text) # Links 2 text = re.sub(r'\[\[(.*?)\]\]', r'[\1](\1)', text) # Links 2
text = re.sub(r'{{(.*?)\|(.*?)}}', r'![\2](\1 "\2")', text) # Images text = re.sub(r'{{(.*?)\|(.*?)}}', r'![\2](\1 "\2")', text) # Images
text = re.sub(r'{{(.*?)(\|(.*?))?}}', r'![\1](\1 "\1")', text) # Images 2 text = re.sub(r'{{(.*?)(\|(.*?))?}}', r'![\1](\1 "\1")', text) # Images 2
text = re.sub(r'{\[(.*?)(\|(.*?))?\]}', r'[\1](\1)', text) # Video (transform to classic links, since we can't integrate them) text = re.sub(r'{\[(.*?)(\|(.*?))?\]}', r'[\1](\1)', text) # Video (transform to classic links, since we can't integrate them)
text = re.sub(r'###(\d*?)###', r'[[[\1]]]', text) # Progress bar text = re.sub(r'###(\d*?)###', r'[[[\1]]]', text) # Progress bar
text = re.sub(r'(\n +[^* -][^\n]*(\n +[^* -][^\n]*)*)', r'```\1\n```', text, flags=re.DOTALL) # Block code without lists text = re.sub(r'(\n +[^* -][^\n]*(\n +[^* -][^\n]*)*)', r'```\1\n```', text, flags=re.DOTALL) # Block code without lists
text = re.sub(r'( +)-(.*)', r'1.\2', text) # Ordered lists text = re.sub(r'( +)-(.*)', r'1.\2', text) # Ordered lists
new_text = [] new_text = []
quote_level = 0 quote_level = 0
for line in text.splitlines(): # Tables and quotes for line in text.splitlines(): # Tables and quotes
enter = re.finditer(r'\[quote(=(.+?))?\]', line) enter = re.finditer(r'\[quote(=(.+?))?\]', line)
quit = re.finditer(r'\[/quote\]', line) quit = re.finditer(r'\[/quote\]', line)
if re.search(r'\A\s*\^(([^\^]*?)\^)*', line): # Table part if re.search(r'\A\s*\^(([^\^]*?)\^)*', line): # Table part
line = line.replace('^', '|') line = line.replace('^', '|')
new_text.append("> " * quote_level + line) new_text.append("> " * quote_level + line)
new_text.append("> " * quote_level + "|---|") # Don't keep the text alignement in tables it's really too complex for what it's worth new_text.append("> " * quote_level + "|---|") # Don't keep the text alignement in tables it's really too complex for what it's worth
elif enter or quit: # Quote part elif enter or quit: # Quote part
for quote in enter: # Enter quotes (support multiple at a time) for quote in enter: # Enter quotes (support multiple at a time)
quote_level += 1 quote_level += 1
try: try:
new_text.append("> " * quote_level + "##### " + quote.group(2)) new_text.append("> " * quote_level + "##### " + quote.group(2))
except: except:
new_text.append("> " * quote_level) new_text.append("> " * quote_level)
line = line.replace(quote.group(0), '') line = line.replace(quote.group(0), '')
final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration
final_newline = False final_newline = False
for quote in quit: # Quit quotes (support multiple at a time) for quote in quit: # Quit quotes (support multiple at a time)
line = line.replace(quote.group(0), '') line = line.replace(quote.group(0), '')
quote_level -= 1 quote_level -= 1
final_newline = True final_newline = True
new_text.append("> " * final_quote_level + line) # Finally append the line new_text.append("> " * final_quote_level + line) # Finally append the line
if final_newline: new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text if final_newline:
new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text
else: else:
new_text.append(line) new_text.append(line)
return "\n".join(new_text) return "\n".join(new_text)
def bbcode_to_markdown(text): def bbcode_to_markdown(text):
"""This is a very basic BBcode translator""" """This is a very basic BBcode translator"""
text = re.sub(r'\[b\](.*?)\[\/b\]', r'**\1**', text, flags=re.DOTALL) # Bold text = re.sub(r'\[b\](.*?)\[\/b\]', r'**\1**', text, flags=re.DOTALL) # Bold
text = re.sub(r'\[i\](.*?)\[\/i\]', r'*\1*', text, flags=re.DOTALL) # Italic text = re.sub(r'\[i\](.*?)\[\/i\]', r'*\1*', text, flags=re.DOTALL) # Italic
text = re.sub(r'\[u\](.*?)\[\/u\]', r'__\1__', text, flags=re.DOTALL) # Underline text = re.sub(r'\[u\](.*?)\[\/u\]', r'__\1__', text, flags=re.DOTALL) # Underline
text = re.sub(r'\[s\](.*?)\[\/s\]', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline) text = re.sub(r'\[s\](.*?)\[\/s\]', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline)
text = re.sub(r'\[strike\](.*?)\[\/strike\]', r'~~\1~~', text, flags=re.DOTALL) # Strike 2 text = re.sub(r'\[strike\](.*?)\[\/strike\]', r'~~\1~~', text, flags=re.DOTALL) # Strike 2
text = re.sub(r'article://', r'page://', text) text = re.sub(r'article://', r'page://', text)
text = re.sub(r'dfile://', r'file://', text) text = re.sub(r'dfile://', r'file://', text)
text = re.sub(r'\[url=(.*?)\](.*)\[\/url\]', r'[\2](\1)', text) # Links text = re.sub(r'\[url=(.*?)\](.*)\[\/url\]', r'[\2](\1)', text) # Links
text = re.sub(r'\[url\](.*)\[\/url\]', r'\1', text) # Links 2 text = re.sub(r'\[url\](.*)\[\/url\]', r'\1', text) # Links 2
text = re.sub(r'\[img\](.*)\[\/img\]', r'![\1](\1 "\1")', text) # Images text = re.sub(r'\[img\](.*)\[\/img\]', r'![\1](\1 "\1")', text) # Images
new_text = [] new_text = []
quote_level = 0 quote_level = 0
for line in text.splitlines(): # Tables and quotes for line in text.splitlines(): # Tables and quotes
enter = re.finditer(r'\[quote(=(.+?))?\]', line) enter = re.finditer(r'\[quote(=(.+?))?\]', line)
quit = re.finditer(r'\[/quote\]', line) quit = re.finditer(r'\[/quote\]', line)
if enter or quit: # Quote part if enter or quit: # Quote part
for quote in enter: # Enter quotes (support multiple at a time) for quote in enter: # Enter quotes (support multiple at a time)
quote_level += 1 quote_level += 1
try: try:
new_text.append("> " * quote_level + "##### " + quote.group(2)) new_text.append("> " * quote_level + "##### " + quote.group(2))
except: except:
new_text.append("> " * quote_level) new_text.append("> " * quote_level)
line = line.replace(quote.group(0), '') line = line.replace(quote.group(0), '')
final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration
final_newline = False final_newline = False
for quote in quit: # Quit quotes (support multiple at a time) for quote in quit: # Quit quotes (support multiple at a time)
line = line.replace(quote.group(0), '') line = line.replace(quote.group(0), '')
quote_level -= 1 quote_level -= 1
final_newline = True final_newline = True
new_text.append("> " * final_quote_level + line) # Finally append the line new_text.append("> " * final_quote_level + line) # Finally append the line
if final_newline: new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text if final_newline:
new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text
else: else:
new_text.append(line) new_text.append(line)
return "\n".join(new_text) return "\n".join(new_text)

View File

@ -23,29 +23,28 @@
# #
# This file contains all the views that concern the page model # This file contains all the views that concern the page model
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import redirect
from django.views.generic import ListView, DetailView, TemplateView from django.views.generic import ListView, DetailView, TemplateView
from django.views.generic.edit import UpdateView, CreateView, FormMixin, DeleteView from django.views.generic.edit import UpdateView, FormMixin, DeleteView
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.contrib.auth.decorators import login_required, permission_required
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.forms import CheckboxSelectMultiple
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.http import HttpResponse from django.http import HttpResponse
from django.core.servers.basehttp import FileWrapper from django.core.servers.basehttp import FileWrapper
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist from django.core.exceptions import PermissionDenied
from django import forms from django import forms
import os import os
from ajax_select import make_ajax_form, make_ajax_field from ajax_select import make_ajax_field
from core.models import SithFile, RealGroup, Notification from core.models import SithFile, RealGroup, Notification
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, can_view, not_found from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, can_view, not_found
from counter.models import Counter from counter.models import Counter
def send_file(request, file_id, file_class=SithFile, file_attr="file"): def send_file(request, file_id, file_class=SithFile, file_attr="file"):
""" """
Send a file through Django without loading the whole file into Send a file through Django without loading the whole file into
@ -57,7 +56,7 @@ def send_file(request, file_id, file_class=SithFile, file_attr="file"):
return not_found(request) return not_found(request)
if not (can_view(f, request.user) or if not (can_view(f, request.user) or
('counter_token' in request.session.keys() and ('counter_token' in request.session.keys() and
request.session['counter_token'] and # check if not null for counters that have no token set request.session['counter_token'] and # check if not null for counters that have no token set
Counter.objects.filter(token=request.session['counter_token']).exists()) Counter.objects.filter(token=request.session['counter_token']).exists())
): ):
raise PermissionDenied raise PermissionDenied
@ -70,10 +69,11 @@ def send_file(request, file_id, file_class=SithFile, file_attr="file"):
response['Content-Disposition'] = ('inline; filename="%s"' % f.name).encode('utf-8') response['Content-Disposition'] = ('inline; filename="%s"' % f.name).encode('utf-8')
return response return response
class AddFilesForm(forms.Form): class AddFilesForm(forms.Form):
folder_name = forms.CharField(label=_("Add a new folder"), max_length=30, required=False) folder_name = forms.CharField(label=_("Add a new folder"), max_length=30, required=False)
file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}), label=_("Files"), file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}), label=_("Files"),
required=False) required=False)
def process(self, parent, owner, files): def process(self, parent, owner, files):
notif = False notif = False
@ -85,10 +85,10 @@ class AddFilesForm(forms.Form):
notif = True notif = True
except Exception as e: except Exception as e:
self.add_error(None, _("Error creating folder %(folder_name)s: %(msg)s") % self.add_error(None, _("Error creating folder %(folder_name)s: %(msg)s") %
{'folder_name': self.cleaned_data['folder_name'], 'msg': repr(e)}) {'folder_name': self.cleaned_data['folder_name'], 'msg': repr(e)})
for f in files: for f in files:
new_file = SithFile(parent=parent, name=f.name, file=f, owner=owner, is_folder=False, new_file = SithFile(parent=parent, name=f.name, file=f, owner=owner, is_folder=False,
mime_type=f.content_type, size=f._size) mime_type=f.content_type, size=f._size)
try: try:
new_file.clean() new_file.clean()
new_file.save() new_file.save()
@ -100,6 +100,7 @@ class AddFilesForm(forms.Form):
if not u.notifications.filter(type="FILE_MODERATION", viewed=False).exists(): if not u.notifications.filter(type="FILE_MODERATION", viewed=False).exists():
Notification(user=u, url=reverse("core:file_moderation"), type="FILE_MODERATION").save() Notification(user=u, url=reverse("core:file_moderation"), type="FILE_MODERATION").save()
class FileListView(ListView): class FileListView(ListView):
template_name = 'core/file_list.jinja' template_name = 'core/file_list.jinja'
context_object_name = "file_list" context_object_name = "file_list"
@ -114,6 +115,7 @@ class FileListView(ListView):
kwargs['popup'] = 'popup' kwargs['popup'] = 'popup'
return kwargs return kwargs
class FileEditView(CanEditMixin, UpdateView): class FileEditView(CanEditMixin, UpdateView):
model = SithFile model = SithFile
pk_url_kwarg = "file_id" pk_url_kwarg = "file_id"
@ -138,6 +140,7 @@ class FileEditView(CanEditMixin, UpdateView):
kwargs['popup'] = 'popup' kwargs['popup'] = 'popup'
return kwargs return kwargs
class FileEditPropForm(forms.ModelForm): class FileEditPropForm(forms.ModelForm):
class Meta: class Meta:
model = SithFile model = SithFile
@ -147,6 +150,7 @@ class FileEditPropForm(forms.ModelForm):
view_groups = make_ajax_field(SithFile, 'view_groups', 'groups', help_text="", label=_("view group")) view_groups = make_ajax_field(SithFile, 'view_groups', 'groups', help_text="", label=_("view group"))
recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False) recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False)
class FileEditPropView(CanEditPropMixin, UpdateView): class FileEditPropView(CanEditPropMixin, UpdateView):
model = SithFile model = SithFile
pk_url_kwarg = "file_id" pk_url_kwarg = "file_id"
@ -175,6 +179,7 @@ class FileEditPropView(CanEditPropMixin, UpdateView):
kwargs['popup'] = 'popup' kwargs['popup'] = 'popup'
return kwargs return kwargs
class FileView(CanViewMixin, DetailView, FormMixin): class FileView(CanViewMixin, DetailView, FormMixin):
"""This class handle the upload of new files into a folder""" """This class handle the upload of new files into a folder"""
model = SithFile model = SithFile
@ -217,7 +222,7 @@ class FileView(CanViewMixin, DetailView, FormMixin):
request.session['clipboard'] = [] request.session['clipboard'] = []
if request.user.can_edit(self.object): if request.user.can_edit(self.object):
FileView.handle_clipboard(request, self.object) FileView.handle_clipboard(request, self.object)
self.form = self.get_form() # The form handle only the file upload self.form = self.get_form() # The form handle only the file upload
files = request.FILES.getlist('file_field') files = request.FILES.getlist('file_field')
if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid(): if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid():
self.form.process(parent=self.object, owner=request.user, files=files) self.form.process(parent=self.object, owner=request.user, files=files)
@ -237,6 +242,7 @@ class FileView(CanViewMixin, DetailView, FormMixin):
kwargs['clipboard'] = SithFile.objects.filter(id__in=self.request.session['clipboard']) kwargs['clipboard'] = SithFile.objects.filter(id__in=self.request.session['clipboard'])
return kwargs return kwargs
class FileDeleteView(CanEditPropMixin, DeleteView): class FileDeleteView(CanEditPropMixin, DeleteView):
model = SithFile model = SithFile
pk_url_kwarg = "file_id" pk_url_kwarg = "file_id"
@ -244,7 +250,7 @@ class FileDeleteView(CanEditPropMixin, DeleteView):
context_object_name = "file" context_object_name = "file"
def get_success_url(self): def get_success_url(self):
self.object.file.delete() # Doing it here or overloading delete() is the same, so let's do it here self.object.file.delete() # Doing it here or overloading delete() is the same, so let's do it here
if 'next' in self.request.GET.keys(): if 'next' in self.request.GET.keys():
return self.request.GET['next'] return self.request.GET['next']
if self.object.parent is None: if self.object.parent is None:
@ -258,6 +264,7 @@ class FileDeleteView(CanEditPropMixin, DeleteView):
kwargs['popup'] = 'popup' kwargs['popup'] = 'popup'
return kwargs return kwargs
class FileModerationView(TemplateView): class FileModerationView(TemplateView):
template_name = "core/file_moderation.jinja" template_name = "core/file_moderation.jinja"
@ -266,6 +273,7 @@ class FileModerationView(TemplateView):
kwargs['files'] = SithFile.objects.filter(is_moderated=False)[:100] kwargs['files'] = SithFile.objects.filter(is_moderated=False)[:100]
return kwargs return kwargs
class FileModerateView(CanEditPropMixin, SingleObjectMixin): class FileModerateView(CanEditPropMixin, SingleObjectMixin):
model = SithFile model = SithFile
pk_url_kwarg = "file_id" pk_url_kwarg = "file_id"
@ -278,4 +286,3 @@ class FileModerateView(CanEditPropMixin, SingleObjectMixin):
if 'next' in self.request.GET.keys(): if 'next' in self.request.GET.keys():
return redirect(self.request.GET['next']) return redirect(self.request.GET['next'])
return redirect('core:file_moderation') return redirect('core:file_moderation')

View File

@ -22,22 +22,24 @@
# #
# #
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, UserChangeForm from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.db import transaction from django.db import transaction
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.contrib.auth import logout, login, authenticate
from django.forms import CheckboxSelectMultiple, Select, DateInput, TextInput, DateTimeInput, Textarea from django.forms import CheckboxSelectMultiple, Select, DateInput, TextInput, DateTimeInput, Textarea
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext from django.utils.translation import ugettext
from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget
from ajax_select.fields import AutoCompleteSelectField from ajax_select.fields import AutoCompleteSelectField
import logging
import re import re
from core.models import User, Page, RealGroup, SithFile from core.models import User, Page, SithFile
from core.utils import resize_image
from io import BytesIO
from PIL import Image
# Widgets # Widgets
@ -50,6 +52,7 @@ class SelectSingle(Select):
attrs = {'class': "select_single"} attrs = {'class': "select_single"}
return super(SelectSingle, self).render(name, value, attrs) return super(SelectSingle, self).render(name, value, attrs)
class SelectMultiple(Select): class SelectMultiple(Select):
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if attrs: if attrs:
@ -58,6 +61,7 @@ class SelectMultiple(Select):
attrs = {'class': "select_multiple"} attrs = {'class': "select_multiple"}
return super(SelectMultiple, self).render(name, value, attrs) return super(SelectMultiple, self).render(name, value, attrs)
class SelectDateTime(DateTimeInput): class SelectDateTime(DateTimeInput):
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if attrs: if attrs:
@ -66,6 +70,7 @@ class SelectDateTime(DateTimeInput):
attrs = {'class': "select_datetime"} attrs = {'class': "select_datetime"}
return super(SelectDateTime, self).render(name, value, attrs) return super(SelectDateTime, self).render(name, value, attrs)
class SelectDate(DateInput): class SelectDate(DateInput):
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if attrs: if attrs:
@ -74,16 +79,18 @@ class SelectDate(DateInput):
attrs = {'class': "select_date"} attrs = {'class': "select_date"}
return super(SelectDate, self).render(name, value, attrs) return super(SelectDate, self).render(name, value, attrs)
class MarkdownInput(Textarea): class MarkdownInput(Textarea):
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
output = '<p><a href="%(syntax_url)s">%(help_text)s</a></p>'\ output = '<p><a href="%(syntax_url)s">%(help_text)s</a></p>'\
'<div class="markdown_editor">%(content)s</div>' % { '<div class="markdown_editor">%(content)s</div>' % {
'syntax_url': Page.get_page_by_full_name(settings.SITH_CORE_PAGE_SYNTAX).get_absolute_url(), 'syntax_url': Page.get_page_by_full_name(settings.SITH_CORE_PAGE_SYNTAX).get_absolute_url(),
'help_text': _("Help on the syntax"), 'help_text': _("Help on the syntax"),
'content': super(MarkdownInput, self).render(name, value, attrs), 'content': super(MarkdownInput, self).render(name, value, attrs),
} }
return output return output
class SelectFile(TextInput): class SelectFile(TextInput):
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if attrs: if attrs:
@ -91,13 +98,14 @@ class SelectFile(TextInput):
else: else:
attrs = {'class': "select_file"} attrs = {'class': "select_file"}
output = '%(content)s<div name="%(name)s" class="choose_file_widget" title="%(title)s"></div>' % { output = '%(content)s<div name="%(name)s" class="choose_file_widget" title="%(title)s"></div>' % {
'content': super(SelectFile, self).render(name, value, attrs), 'content': super(SelectFile, self).render(name, value, attrs),
'title': _("Choose file"), 'title': _("Choose file"),
'name': name, 'name': name,
} }
output += '<span name="' + name + '" class="choose_file_button">' + ugettext("Choose file") + '</span>' output += '<span name="' + name + '" class="choose_file_button">' + ugettext("Choose file") + '</span>'
return output return output
class SelectUser(TextInput): class SelectUser(TextInput):
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if attrs: if attrs:
@ -105,15 +113,16 @@ class SelectUser(TextInput):
else: else:
attrs = {'class': "select_user"} attrs = {'class': "select_user"}
output = '%(content)s<div name="%(name)s" class="choose_user_widget" title="%(title)s"></div>' % { output = '%(content)s<div name="%(name)s" class="choose_user_widget" title="%(title)s"></div>' % {
'content': super(SelectUser, self).render(name, value, attrs), 'content': super(SelectUser, self).render(name, value, attrs),
'title': _("Choose user"), 'title': _("Choose user"),
'name': name, 'name': name,
} }
output += '<span name="' + name + '" class="choose_user_button">' + ugettext("Choose user") + '</span>' output += '<span name="' + name + '" class="choose_user_button">' + ugettext("Choose user") + '</span>'
return output return output
# Forms # Forms
class LoginForm(AuthenticationForm): class LoginForm(AuthenticationForm):
def __init__(self, *arg, **kwargs): def __init__(self, *arg, **kwargs):
if 'data' in kwargs.keys(): if 'data' in kwargs.keys():
@ -128,14 +137,17 @@ class LoginForm(AuthenticationForm):
else: else:
user = User.objects.filter(username=data['username']).first() user = User.objects.filter(username=data['username']).first()
data['username'] = user.username data['username'] = user.username
except: pass except:
pass
kwargs['data'] = data kwargs['data'] = data
super(LoginForm, self).__init__(*arg, **kwargs) super(LoginForm, self).__init__(*arg, **kwargs)
self.fields['username'].label = _("Username, email, or account number") self.fields['username'].label = _("Username, email, or account number")
class RegisteringForm(UserCreationForm): class RegisteringForm(UserCreationForm):
error_css_class = 'error' error_css_class = 'error'
required_css_class = 'required' required_css_class = 'required'
class Meta: class Meta:
model = User model = User
fields = ('first_name', 'last_name', 'email') fields = ('first_name', 'last_name', 'email')
@ -148,9 +160,6 @@ class RegisteringForm(UserCreationForm):
user.save() user.save()
return user return user
from core.utils import resize_image
from io import BytesIO
from PIL import Image
class UserProfileForm(forms.ModelForm): class UserProfileForm(forms.ModelForm):
""" """
@ -161,22 +170,22 @@ class UserProfileForm(forms.ModelForm):
class Meta: class Meta:
model = User model = User
fields = ['first_name', 'last_name', 'nick_name', 'email', 'date_of_birth', 'profile_pict', 'avatar_pict', fields = ['first_name', 'last_name', 'nick_name', 'email', 'date_of_birth', 'profile_pict', 'avatar_pict',
'scrub_pict', 'sex', 'second_email', 'address', 'parent_address', 'phone', 'parent_phone', 'scrub_pict', 'sex', 'second_email', 'address', 'parent_address', 'phone', 'parent_phone',
'tshirt_size', 'role', 'department', 'dpt_option', 'semester', 'quote', 'school', 'promo', 'tshirt_size', 'role', 'department', 'dpt_option', 'semester', 'quote', 'school', 'promo',
'forum_signature', 'is_subscriber_viewable'] 'forum_signature', 'is_subscriber_viewable']
widgets = { widgets = {
'date_of_birth': SelectDate, 'date_of_birth': SelectDate,
'profile_pict': forms.ClearableFileInput, 'profile_pict': forms.ClearableFileInput,
'avatar_pict': forms.ClearableFileInput, 'avatar_pict': forms.ClearableFileInput,
'scrub_pict': forms.ClearableFileInput, 'scrub_pict': forms.ClearableFileInput,
'phone': PhoneNumberInternationalFallbackWidget, 'phone': PhoneNumberInternationalFallbackWidget,
'parent_phone': PhoneNumberInternationalFallbackWidget, 'parent_phone': PhoneNumberInternationalFallbackWidget,
} }
labels = { labels = {
'profile_pict': _("Profile: you need to be visible on the picture, in order to be recognized (e.g. by the barmen)"), 'profile_pict': _("Profile: you need to be visible on the picture, in order to be recognized (e.g. by the barmen)"),
'avatar_pict': _("Avatar: used on the forum"), 'avatar_pict': _("Avatar: used on the forum"),
'scrub_pict': _("Scrub: let other know how your scrub looks like!"), 'scrub_pict': _("Scrub: let other know how your scrub looks like!"),
} }
def __init__(self, *arg, **kwargs): def __init__(self, *arg, **kwargs):
super(UserProfileForm, self).__init__(*arg, **kwargs) super(UserProfileForm, self).__init__(*arg, **kwargs)
@ -197,14 +206,14 @@ class UserProfileForm(forms.ModelForm):
self.cleaned_data['profile_pict'] = profile self.cleaned_data['profile_pict'] = profile
self.cleaned_data['scrub_pict'] = scrub self.cleaned_data['scrub_pict'] = scrub
parent = SithFile.objects.filter(parent=None, name="profiles").first() parent = SithFile.objects.filter(parent=None, name="profiles").first()
for field,f in files: for field, f in files:
with transaction.atomic(): with transaction.atomic():
try: try:
im = Image.open(BytesIO(f.read())) im = Image.open(BytesIO(f.read()))
new_file = SithFile(parent=parent, name=self.generate_name(field, f), new_file = SithFile(parent=parent, name=self.generate_name(field, f),
file=resize_image(im, 400, f.content_type.split('/')[-1]), file=resize_image(im, 400, f.content_type.split('/')[-1]),
owner=self.instance, is_folder=False, mime_type=f.content_type, size=f._size, owner=self.instance, is_folder=False, mime_type=f.content_type, size=f._size,
moderator=self.instance, is_moderated=True) moderator=self.instance, is_moderated=True)
new_file.file.name = new_file.name new_file.file.name = new_file.name
old = SithFile.objects.filter(parent=parent, name=new_file.name).first() old = SithFile.objects.filter(parent=parent, name=new_file.name).first()
if old: if old:
@ -216,16 +225,18 @@ class UserProfileForm(forms.ModelForm):
except ValidationError as e: except ValidationError as e:
self._errors.pop(field, None) self._errors.pop(field, None)
self.add_error(field, _("Error uploading file %(file_name)s: %(msg)s") % self.add_error(field, _("Error uploading file %(file_name)s: %(msg)s") %
{'file_name': f, 'msg': str(e.message)}) {'file_name': f, 'msg': str(e.message)})
except IOError: except IOError:
self._errors.pop(field, None) self._errors.pop(field, None)
self.add_error(field, _("Error uploading file %(file_name)s: %(msg)s") % self.add_error(field, _("Error uploading file %(file_name)s: %(msg)s") %
{'file_name': f, 'msg': _("Bad image format, only jpeg, png, and gif are accepted")}) {'file_name': f, 'msg': _("Bad image format, only jpeg, png, and gif are accepted")})
self._post_clean() self._post_clean()
class UserPropForm(forms.ModelForm): class UserPropForm(forms.ModelForm):
error_css_class = 'error' error_css_class = 'error'
required_css_class = 'required' required_css_class = 'required'
class Meta: class Meta:
model = User model = User
fields = ['groups'] fields = ['groups']
@ -236,13 +247,16 @@ class UserPropForm(forms.ModelForm):
'groups': CheckboxSelectMultiple, 'groups': CheckboxSelectMultiple,
} }
class UserGodfathersForm(forms.Form): class UserGodfathersForm(forms.Form):
type = forms.ChoiceField(choices=[('godfather', _("Godfather")), ('godchild', _("Godchild"))], label=_("Add")) type = forms.ChoiceField(choices=[('godfather', _("Godfather")), ('godchild', _("Godchild"))], label=_("Add"))
user = AutoCompleteSelectField('users', required=True, label=_("Select user"), help_text=None) user = AutoCompleteSelectField('users', required=True, label=_("Select user"), help_text=None)
class PagePropForm(forms.ModelForm): class PagePropForm(forms.ModelForm):
error_css_class = 'error' error_css_class = 'error'
required_css_class = 'required' required_css_class = 'required'
class Meta: class Meta:
model = Page model = Page
fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ] fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ]
@ -255,4 +269,3 @@ class PagePropForm(forms.ModelForm):
super(PagePropForm, self).__init__(*arg, **kwargs) super(PagePropForm, self).__init__(*arg, **kwargs)
self.fields['edit_groups'].required = False self.fields['edit_groups'].required = False
self.fields['view_groups'].required = False self.fields['view_groups'].required = False

View File

@ -29,6 +29,7 @@ from django.core.urlresolvers import reverse_lazy
from core.models import RealGroup from core.models import RealGroup
from core.views import CanEditMixin from core.views import CanEditMixin
class GroupListView(CanEditMixin, ListView): class GroupListView(CanEditMixin, ListView):
""" """
Displays the group list Displays the group list
@ -36,17 +37,20 @@ class GroupListView(CanEditMixin, ListView):
model = RealGroup model = RealGroup
template_name = "core/group_list.jinja" template_name = "core/group_list.jinja"
class GroupEditView(CanEditMixin, UpdateView): class GroupEditView(CanEditMixin, UpdateView):
model = RealGroup model = RealGroup
pk_url_kwarg = "group_id" pk_url_kwarg = "group_id"
template_name = "core/group_edit.jinja" template_name = "core/group_edit.jinja"
fields = ['name', 'description'] fields = ['name', 'description']
class GroupCreateView(CanEditMixin, CreateView): class GroupCreateView(CanEditMixin, CreateView):
model = RealGroup model = RealGroup
template_name = "core/group_edit.jinja" template_name = "core/group_edit.jinja"
fields = ['name', 'description'] fields = ['name', 'description']
class GroupDeleteView(CanEditMixin, DeleteView): class GroupDeleteView(CanEditMixin, DeleteView):
model = RealGroup model = RealGroup
pk_url_kwarg = "group_id" pk_url_kwarg = "group_id"

View File

@ -23,23 +23,22 @@
# #
# This file contains all the views that concern the page model # This file contains all the views that concern the page model
from django.shortcuts import render, redirect, get_object_or_404
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.views.generic import ListView, DetailView from django.views.generic import ListView, DetailView
from django.views.generic.edit import UpdateView, CreateView, DeleteView from django.views.generic.edit import UpdateView, CreateView, DeleteView
from django.contrib.auth.decorators import login_required, permission_required
from django.utils.decorators import method_decorator
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.forms import CheckboxSelectMultiple, modelform_factory from django.forms import CheckboxSelectMultiple
from core.models import Page, PageRev, LockError from core.models import Page, PageRev, LockError
from core.views.forms import PagePropForm, MarkdownInput from core.views.forms import MarkdownInput
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin
class PageListView(CanViewMixin, ListView): class PageListView(CanViewMixin, ListView):
model = Page model = Page
template_name = 'core/page_list.jinja' template_name = 'core/page_list.jinja'
class PageView(CanViewMixin, DetailView): class PageView(CanViewMixin, DetailView):
model = Page model = Page
template_name = 'core/page_detail.jinja' template_name = 'core/page_detail.jinja'
@ -54,6 +53,7 @@ class PageView(CanViewMixin, DetailView):
context['new_page'] = self.kwargs['page_name'] context['new_page'] = self.kwargs['page_name']
return context return context
class PageHistView(CanViewMixin, DetailView): class PageHistView(CanViewMixin, DetailView):
model = Page model = Page
template_name = 'core/page_hist.jinja' template_name = 'core/page_hist.jinja'
@ -62,6 +62,7 @@ class PageHistView(CanViewMixin, DetailView):
self.page = Page.get_page_by_full_name(self.kwargs['page_name']) self.page = Page.get_page_by_full_name(self.kwargs['page_name'])
return self.page return self.page
class PageRevView(CanViewMixin, DetailView): class PageRevView(CanViewMixin, DetailView):
model = Page model = Page
template_name = 'core/page_detail.jinja' template_name = 'core/page_detail.jinja'
@ -78,20 +79,21 @@ class PageRevView(CanViewMixin, DetailView):
rev = self.page.revisions.get(id=self.kwargs['rev']) rev = self.page.revisions.get(id=self.kwargs['rev'])
context['rev'] = rev context['rev'] = rev
except: except:
# By passing, the template will just display the normal page without taking revision into account # By passing, the template will just display the normal page without taking revision into account
pass pass
else: else:
context['new_page'] = self.kwargs['page_name'] context['new_page'] = self.kwargs['page_name']
return context return context
class PageCreateView(CanCreateMixin, CreateView): class PageCreateView(CanCreateMixin, CreateView):
model = Page model = Page
form_class = modelform_factory(Page, form_class = modelform_factory(Page,
fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ], fields=['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ],
widgets={ widgets={
'edit_groups':CheckboxSelectMultiple, 'edit_groups': CheckboxSelectMultiple,
'view_groups':CheckboxSelectMultiple, 'view_groups': CheckboxSelectMultiple,
}) })
template_name = 'core/page_prop.jinja' template_name = 'core/page_prop.jinja'
def get_initial(self): def get_initial(self):
@ -115,14 +117,15 @@ class PageCreateView(CanCreateMixin, CreateView):
ret = super(PageCreateView, self).form_valid(form) ret = super(PageCreateView, self).form_valid(form)
return ret return ret
class PagePropView(CanEditPropMixin, UpdateView): class PagePropView(CanEditPropMixin, UpdateView):
model = Page model = Page
form_class = modelform_factory(Page, form_class = modelform_factory(Page,
fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ], fields=['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ],
widgets={ widgets={
'edit_groups':CheckboxSelectMultiple, 'edit_groups': CheckboxSelectMultiple,
'view_groups':CheckboxSelectMultiple, 'view_groups': CheckboxSelectMultiple,
}) })
template_name = 'core/page_prop.jinja' template_name = 'core/page_prop.jinja'
slug_field = '_full_name' slug_field = '_full_name'
slug_url_kwarg = 'page_name' slug_url_kwarg = 'page_name'
@ -130,7 +133,7 @@ class PagePropView(CanEditPropMixin, UpdateView):
def get_object(self): def get_object(self):
o = super(PagePropView, self).get_object() o = super(PagePropView, self).get_object()
# Create the page if it does not exists # Create the page if it does not exists
#if p == None: # if p == None:
# parent_name = '/'.join(page_name.split('/')[:-1]) # parent_name = '/'.join(page_name.split('/')[:-1])
# name = page_name.split('/')[-1] # name = page_name.split('/')[-1]
# if parent_name == "": # if parent_name == "":
@ -145,9 +148,10 @@ class PagePropView(CanEditPropMixin, UpdateView):
raise e raise e
return self.page return self.page
class PageEditView(CanEditMixin, UpdateView): class PageEditView(CanEditMixin, UpdateView):
model = PageRev model = PageRev
form_class = modelform_factory(model=PageRev, fields=['title', 'content',], widgets={'content': MarkdownInput}) form_class = modelform_factory(model=PageRev, fields=['title', 'content', ], widgets={'content': MarkdownInput})
template_name = 'core/pagerev_edit.jinja' template_name = 'core/pagerev_edit.jinja'
def get_object(self): def get_object(self):

View File

@ -22,17 +22,13 @@
# #
# #
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect
from django.db import models
from django.http import JsonResponse from django.http import JsonResponse
from django.core import serializers from django.core import serializers
from django.db.models import Q
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.views.generic import ListView, TemplateView from django.views.generic import ListView, TemplateView
import os
import json import json
from itertools import chain
from haystack.query import SearchQuerySet from haystack.query import SearchQuerySet
@ -40,9 +36,11 @@ from core.models import User, Notification
from core.utils import doku_to_markdown, bbcode_to_markdown from core.utils import doku_to_markdown, bbcode_to_markdown
from club.models import Club from club.models import Club
def index(request, context=None): def index(request, context=None):
return render(request, "core/index.jinja") return render(request, "core/index.jinja")
class NotificationList(ListView): class NotificationList(ListView):
model = Notification model = Notification
template_name = "core/notification_list.jinja" template_name = "core/notification_list.jinja"
@ -52,6 +50,7 @@ class NotificationList(ListView):
self.request.user.notifications.update(viewed=True) self.request.user.notifications.update(viewed=True)
return self.request.user.notifications.order_by('-id')[:20] return self.request.user.notifications.order_by('-id')[:20]
def notification(request, notif_id): def notification(request, notif_id):
notif = Notification.objects.filter(id=notif_id).first() notif = Notification.objects.filter(id=notif_id).first()
if notif: if notif:
@ -60,44 +59,50 @@ def notification(request, notif_id):
return redirect(notif.url) return redirect(notif.url)
return redirect("/") return redirect("/")
def search_user(query, as_json=False): def search_user(query, as_json=False):
res = SearchQuerySet().models(User).filter(text=query).filter_or(text__contains=query)[:20] res = SearchQuerySet().models(User).filter(text=query).filter_or(text__contains=query)[:20]
return [r.object for r in res] return [r.object for r in res]
def search_club(query, as_json=False): def search_club(query, as_json=False):
clubs = [] clubs = []
if query: if query:
clubs = Club.objects.filter(name__icontains=query).all() clubs = Club.objects.filter(name__icontains=query).all()
clubs = clubs[:5] clubs = clubs[:5]
if as_json: # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers if as_json: # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers
clubs = json.loads(serializers.serialize('json', clubs, fields=('name'))) clubs = json.loads(serializers.serialize('json', clubs, fields=('name')))
else: else:
clubs = list(clubs) clubs = list(clubs)
return clubs return clubs
@login_required @login_required
def search_view(request): def search_view(request):
result = { result = {
'users': search_user(request.GET.get('query', '')), 'users': search_user(request.GET.get('query', '')),
'clubs': search_club(request.GET.get('query', '')), 'clubs': search_club(request.GET.get('query', '')),
} }
return render(request, "core/search.jinja", context={'result': result}) return render(request, "core/search.jinja", context={'result': result})
@login_required @login_required
def search_user_json(request): def search_user_json(request):
result = { result = {
'users': search_user(request.GET.get('query', ''), True), 'users': search_user(request.GET.get('query', ''), True),
} }
return JsonResponse(result) return JsonResponse(result)
@login_required @login_required
def search_json(request): def search_json(request):
result = { result = {
'users': search_user(request.GET.get('query', ''), True), 'users': search_user(request.GET.get('query', ''), True),
'clubs': search_club(request.GET.get('query', ''), True), 'clubs': search_club(request.GET.get('query', ''), True),
} }
return JsonResponse(result) return JsonResponse(result)
class ToMarkdownView(TemplateView): class ToMarkdownView(TemplateView):
template_name = "core/to_markdown.jinja" template_name = "core/to_markdown.jinja"
@ -119,4 +124,3 @@ class ToMarkdownView(TemplateView):
kwargs['text'] = "" kwargs['text'] = ""
kwargs['text_md'] = "" kwargs['text_md'] = ""
return kwargs return kwargs

View File

@ -24,30 +24,29 @@
# This file contains all the views that concern the user model # This file contains all the views that concern the user model
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth import logout as auth_logout, views from django.contrib.auth import views
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.http import Http404 from django.http import Http404
from django.views.generic.edit import UpdateView from django.views.generic.edit import UpdateView
from django.views.generic import ListView, DetailView, TemplateView, DeleteView from django.views.generic import ListView, DetailView, TemplateView
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.forms import CheckboxSelectMultiple from django.forms import CheckboxSelectMultiple
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.conf import settings from django.conf import settings
from django.views.generic.dates import YearMixin, MonthMixin from django.views.generic.dates import YearMixin, MonthMixin
from django.utils import timezone from datetime import timedelta, date
from datetime import timedelta, datetime, date
import logging import logging
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, QuickNotifMixin from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, QuickNotifMixin
from core.views.forms import RegisteringForm, UserPropForm, UserProfileForm, LoginForm, UserGodfathersForm from core.views.forms import RegisteringForm, UserProfileForm, LoginForm, UserGodfathersForm
from core.models import User, SithFile, Preferences from core.models import User, SithFile, Preferences
from club.models import Club
from subscription.models import Subscription from subscription.models import Subscription
from trombi.views import UserTrombiForm from trombi.views import UserTrombiForm
def login(request): def login(request):
""" """
The login view The login view
@ -56,24 +55,28 @@ def login(request):
""" """
return views.login(request, template_name="core/login.jinja", authentication_form=LoginForm) return views.login(request, template_name="core/login.jinja", authentication_form=LoginForm)
def logout(request): def logout(request):
""" """
The logout view The logout view
""" """
return views.logout_then_login(request) return views.logout_then_login(request)
def password_change(request): def password_change(request):
""" """
Allows a user to change its password Allows a user to change its password
""" """
return views.password_change(request, template_name="core/password_change.jinja", post_change_redirect=reverse("core:password_change_done")) return views.password_change(request, template_name="core/password_change.jinja", post_change_redirect=reverse("core:password_change_done"))
def password_change_done(request): def password_change_done(request):
""" """
Allows a user to change its password Allows a user to change its password
""" """
return views.password_change_done(request, template_name="core/password_change_done.jinja") return views.password_change_done(request, template_name="core/password_change_done.jinja")
def password_root_change(request, user_id): def password_root_change(request, user_id):
""" """
Allows a root user to change someone's password Allows a root user to change someone's password
@ -92,6 +95,7 @@ def password_root_change(request, user_id):
form = views.SetPasswordForm(user=user) form = views.SetPasswordForm(user=user)
return TemplateResponse(request, "core/password_change.jinja", {'form': form, 'target': user}) return TemplateResponse(request, "core/password_change.jinja", {'form': form, 'target': user})
def password_reset(request): def password_reset(request):
""" """
Allows someone to enter an email adresse for resetting password Allows someone to enter an email adresse for resetting password
@ -100,7 +104,8 @@ def password_reset(request):
template_name="core/password_reset.jinja", template_name="core/password_reset.jinja",
email_template_name="core/password_reset_email.jinja", email_template_name="core/password_reset_email.jinja",
post_reset_redirect="core:password_reset_done", post_reset_redirect="core:password_reset_done",
) )
def password_reset_done(request): def password_reset_done(request):
""" """
@ -108,6 +113,7 @@ def password_reset_done(request):
""" """
return views.password_reset_done(request, template_name="core/password_reset_done.jinja") return views.password_reset_done(request, template_name="core/password_reset_done.jinja")
def password_reset_confirm(request, uidb64=None, token=None): def password_reset_confirm(request, uidb64=None, token=None):
""" """
Provide a reset password formular Provide a reset password formular
@ -115,7 +121,8 @@ def password_reset_confirm(request, uidb64=None, token=None):
return views.password_reset_confirm(request, uidb64=uidb64, token=token, return views.password_reset_confirm(request, uidb64=uidb64, token=token,
post_reset_redirect="core:password_reset_complete", post_reset_redirect="core:password_reset_complete",
template_name="core/password_reset_confirm.jinja", template_name="core/password_reset_confirm.jinja",
) )
def password_reset_complete(request): def password_reset_complete(request):
""" """
@ -123,14 +130,15 @@ def password_reset_complete(request):
""" """
return views.password_reset_complete(request, return views.password_reset_complete(request,
template_name="core/password_reset_complete.jinja", template_name="core/password_reset_complete.jinja",
) )
def register(request): def register(request):
context = {} context = {}
if request.method == 'POST': if request.method == 'POST':
form = RegisteringForm(request.POST) form = RegisteringForm(request.POST)
if form.is_valid(): if form.is_valid():
logging.debug("Registering "+form.cleaned_data['first_name']+form.cleaned_data['last_name']) logging.debug("Registering " + form.cleaned_data['first_name'] + form.cleaned_data['last_name'])
u = form.save() u = form.save()
context['user_registered'] = u context['user_registered'] = u
context['tests'] = 'TEST_REGISTER_USER_FORM_OK' context['tests'] = 'TEST_REGISTER_USER_FORM_OK'
@ -143,6 +151,7 @@ def register(request):
context['form'] = form.as_p() context['form'] = form.as_p()
return render(request, "core/register.jinja", context) return render(request, "core/register.jinja", context)
class UserTabsMixin(TabedViewMixin): class UserTabsMixin(TabedViewMixin):
def get_tabs_title(self): def get_tabs_title(self):
return self.object.get_display_name() return self.object.get_display_name()
@ -150,67 +159,69 @@ class UserTabsMixin(TabedViewMixin):
def get_list_of_tabs(self): def get_list_of_tabs(self):
tab_list = [] tab_list = []
tab_list.append({ tab_list.append({
'url': reverse('core:user_profile', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_profile', kwargs={'user_id': self.object.id}),
'slug': 'infos', 'slug': 'infos',
'name': _("Infos"), 'name': _("Infos"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('core:user_godfathers', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_godfathers', kwargs={'user_id': self.object.id}),
'slug': 'godfathers', 'slug': 'godfathers',
'name': _("Godfathers"), 'name': _("Godfathers"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('core:user_pictures', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_pictures', kwargs={'user_id': self.object.id}),
'slug': 'pictures', 'slug': 'pictures',
'name': _("Pictures"), 'name': _("Pictures"),
}) })
if self.request.user == self.object: if self.request.user == self.object:
tab_list.append({ tab_list.append({
'url': reverse('core:user_tools'), 'url': reverse('core:user_tools'),
'slug': 'tools', 'slug': 'tools',
'name': _("Tools"), 'name': _("Tools"),
}) })
if self.request.user.can_edit(self.object): if self.request.user.can_edit(self.object):
tab_list.append({ tab_list.append({
'url': reverse('core:user_edit', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_edit', kwargs={'user_id': self.object.id}),
'slug': 'edit', 'slug': 'edit',
'name': _("Edit"), 'name': _("Edit"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('core:user_prefs', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_prefs', kwargs={'user_id': self.object.id}),
'slug': 'prefs', 'slug': 'prefs',
'name': _("Preferences"), 'name': _("Preferences"),
}) })
if self.request.user.can_view(self.object): if self.request.user.can_view(self.object):
tab_list.append({ tab_list.append({
'url': reverse('core:user_clubs', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_clubs', kwargs={'user_id': self.object.id}),
'slug': 'clubs', 'slug': 'clubs',
'name': _("Clubs"), 'name': _("Clubs"),
}) })
if self.request.user.is_owner(self.object): if self.request.user.is_owner(self.object):
tab_list.append({ tab_list.append({
'url': reverse('core:user_groups', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_groups', kwargs={'user_id': self.object.id}),
'slug': 'groups', 'slug': 'groups',
'name': _("Groups"), 'name': _("Groups"),
}) })
try: try:
if (self.object.customer and (self.object == self.request.user if (self.object.customer and (self.object == self.request.user
or self.request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or self.request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
or self.request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name']+settings.SITH_BOARD_SUFFIX) or self.request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name'] + settings.SITH_BOARD_SUFFIX)
or self.request.user.is_root)): or self.request.user.is_root)):
tab_list.append({ tab_list.append({
'url': reverse('core:user_stats', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_stats', kwargs={'user_id': self.object.id}),
'slug': 'stats', 'slug': 'stats',
'name': _("Stats"), 'name': _("Stats"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('core:user_account', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_account', kwargs={'user_id': self.object.id}),
'slug': 'account', 'slug': 'account',
'name': _("Account")+" (%s €)" % self.object.customer.amount, 'name': _("Account") + " (%s €)" % self.object.customer.amount,
}) })
except: pass except:
pass
return tab_list return tab_list
class UserView(UserTabsMixin, CanViewMixin, DetailView): class UserView(UserTabsMixin, CanViewMixin, DetailView):
""" """
Display a user's profile Display a user's profile
@ -225,8 +236,8 @@ class UserView(UserTabsMixin, CanViewMixin, DetailView):
def DeleteUserGodfathers(request, user_id, godfather_id, is_father): def DeleteUserGodfathers(request, user_id, godfather_id, is_father):
user = User.objects.get(id=user_id) user = User.objects.get(id=user_id)
if ((user == request.user) or if ((user == request.user) or
request.user.is_root or request.user.is_root or
request.user.is_board_member): request.user.is_board_member):
ud = get_object_or_404(User, id=godfather_id) ud = get_object_or_404(User, id=godfather_id)
if is_father == "True": if is_father == "True":
user.godfathers.remove(ud) user.godfathers.remove(ud)
@ -236,6 +247,7 @@ def DeleteUserGodfathers(request, user_id, godfather_id, is_father):
raise PermissionDenied raise PermissionDenied
return redirect('core:user_godfathers', user_id=user_id) return redirect('core:user_godfathers', user_id=user_id)
class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView): class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
""" """
Display a user's pictures Display a user's pictures
@ -246,6 +258,7 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
template_name = "core/user_pictures.jinja" template_name = "core/user_pictures.jinja"
current_tab = 'pictures' current_tab = 'pictures'
class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView): class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView):
""" """
Display a user's godfathers Display a user's godfathers
@ -277,6 +290,7 @@ class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView):
kwargs['form'] = UserGodfathersForm() kwargs['form'] = UserGodfathersForm()
return kwargs return kwargs
class UserStatsView(UserTabsMixin, CanViewMixin, DetailView): class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
""" """
Display a user's stats Display a user's stats
@ -295,7 +309,7 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
if not (profile == request.user if not (profile == request.user
or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
or request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name']+settings.SITH_BOARD_SUFFIX) or request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name'] + settings.SITH_BOARD_SUFFIX)
or request.user.is_root): or request.user.is_root):
raise PermissionDenied raise PermissionDenied
@ -303,26 +317,27 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(UserStatsView, self).get_context_data(**kwargs) kwargs = super(UserStatsView, self).get_context_data(**kwargs)
from counter.models import Counter, Product, Selling from counter.models import Counter
from django.db.models import Sum from django.db.models import Sum
foyer = Counter.objects.filter(name="Foyer").first() foyer = Counter.objects.filter(name="Foyer").first()
mde = Counter.objects.filter(name="MDE").first() mde = Counter.objects.filter(name="MDE").first()
gommette = Counter.objects.filter(name="La Gommette").first() gommette = Counter.objects.filter(name="La Gommette").first()
semester_start=Subscription.compute_start(d=date.today(), duration=3) semester_start = Subscription.compute_start(d=date.today(), duration=3)
kwargs['total_perm_time'] = sum([p.end-p.start for p in self.object.permanencies.exclude(end=None)], timedelta()) kwargs['total_perm_time'] = sum([p.end - p.start for p in self.object.permanencies.exclude(end=None)], timedelta())
kwargs['total_foyer_time'] = sum([p.end-p.start for p in self.object.permanencies.filter(counter=foyer).exclude(end=None)], timedelta()) kwargs['total_foyer_time'] = sum([p.end - p.start for p in self.object.permanencies.filter(counter=foyer).exclude(end=None)], timedelta())
kwargs['total_mde_time'] = sum([p.end-p.start for p in self.object.permanencies.filter(counter=mde).exclude(end=None)], timedelta()) kwargs['total_mde_time'] = sum([p.end - p.start for p in self.object.permanencies.filter(counter=mde).exclude(end=None)], timedelta())
kwargs['total_gommette_time'] = sum([p.end-p.start for p in self.object.permanencies.filter(counter=gommette).exclude(end=None)], timedelta()) kwargs['total_gommette_time'] = sum([p.end - p.start for p in self.object.permanencies.filter(counter=gommette).exclude(end=None)], timedelta())
kwargs['total_foyer_buyings'] = sum([b.unit_price*b.quantity for b in kwargs['total_foyer_buyings'] = sum([b.unit_price * b.quantity for b in
self.object.customer.buyings.filter(counter=foyer, date__gte=semester_start)]) self.object.customer.buyings.filter(counter=foyer, date__gte=semester_start)])
kwargs['total_mde_buyings'] = sum([b.unit_price*b.quantity for b in self.object.customer.buyings.filter(counter=mde, kwargs['total_mde_buyings'] = sum([b.unit_price * b.quantity for b in self.object.customer.buyings.filter(counter=mde,
date__gte=semester_start)]) date__gte=semester_start)])
kwargs['total_gommette_buyings'] = sum([b.unit_price*b.quantity for b in kwargs['total_gommette_buyings'] = sum([b.unit_price * b.quantity for b in
self.object.customer.buyings.filter(counter=gommette, date__gte=semester_start)]) self.object.customer.buyings.filter(counter=gommette, date__gte=semester_start)])
kwargs['top_product'] = self.object.customer.buyings.values('product__name').annotate( kwargs['top_product'] = self.object.customer.buyings.values('product__name').annotate(
product_sum=Sum('quantity')).exclude(product_sum=None).order_by('-product_sum').all()[:10] product_sum=Sum('quantity')).exclude(product_sum=None).order_by('-product_sum').all()[:10]
return kwargs return kwargs
class UserMiniView(CanViewMixin, DetailView): class UserMiniView(CanViewMixin, DetailView):
""" """
Display a user's profile Display a user's profile
@ -332,6 +347,7 @@ class UserMiniView(CanViewMixin, DetailView):
context_object_name = "profile" context_object_name = "profile"
template_name = "core/user_mini.jinja" template_name = "core/user_mini.jinja"
class UserListView(ListView, CanEditPropMixin): class UserListView(ListView, CanEditPropMixin):
""" """
Displays the user list Displays the user list
@ -339,6 +355,7 @@ class UserListView(ListView, CanEditPropMixin):
model = User model = User
template_name = "core/user_list.jinja" template_name = "core/user_list.jinja"
class UserUploadProfilePictView(CanEditMixin, DetailView): class UserUploadProfilePictView(CanEditMixin, DetailView):
""" """
Handle the upload of the profile picture taken with webcam in navigator Handle the upload of the profile picture taken with webcam in navigator
@ -356,17 +373,18 @@ class UserUploadProfilePictView(CanEditMixin, DetailView):
raise ValidationError(_("User already has a profile picture")) raise ValidationError(_("User already has a profile picture"))
f = request.FILES['new_profile_pict'] f = request.FILES['new_profile_pict']
parent = SithFile.objects.filter(parent=None, name="profiles").first() parent = SithFile.objects.filter(parent=None, name="profiles").first()
name = str(self.object.id) + "_profile.jpg" # Webcamejs uploads JPGs name = str(self.object.id) + "_profile.jpg" # Webcamejs uploads JPGs
im = Image.open(BytesIO(f.read())) im = Image.open(BytesIO(f.read()))
new_file = SithFile(parent=parent, name=name, new_file = SithFile(parent=parent, name=name,
file=resize_image(im, 400, f.content_type.split('/')[-1]), file=resize_image(im, 400, f.content_type.split('/')[-1]),
owner=self.object, is_folder=False, mime_type=f.content_type, size=f._size) owner=self.object, is_folder=False, mime_type=f.content_type, size=f._size)
new_file.file.name = name new_file.file.name = name
new_file.save() new_file.save()
self.object.profile_pict = new_file self.object.profile_pict = new_file
self.object.save() self.object.save()
return redirect("core:user_edit", user_id=self.object.id) return redirect("core:user_edit", user_id=self.object.id)
class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView): class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView):
""" """
Edit a user's profile Edit a user's profile
@ -412,6 +430,7 @@ class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView):
kwargs['form'] = self.form kwargs['form'] = self.form
return kwargs return kwargs
class UserClubView(UserTabsMixin, CanViewMixin, DetailView): class UserClubView(UserTabsMixin, CanViewMixin, DetailView):
""" """
Display the user's club(s) Display the user's club(s)
@ -422,6 +441,7 @@ class UserClubView(UserTabsMixin, CanViewMixin, DetailView):
template_name = "core/user_clubs.jinja" template_name = "core/user_clubs.jinja"
current_tab = "clubs" current_tab = "clubs"
class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView): class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView):
""" """
Edit a user's preferences Edit a user's preferences
@ -453,6 +473,7 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView):
kwargs['trombi_form'] = UserTrombiForm() kwargs['trombi_form'] = UserTrombiForm()
return kwargs return kwargs
class UserUpdateGroupView(UserTabsMixin, CanEditPropMixin, UpdateView): class UserUpdateGroupView(UserTabsMixin, CanEditPropMixin, UpdateView):
""" """
Edit a user's groups Edit a user's groups
@ -461,10 +482,11 @@ class UserUpdateGroupView(UserTabsMixin, CanEditPropMixin, UpdateView):
pk_url_kwarg = "user_id" pk_url_kwarg = "user_id"
template_name = "core/user_group.jinja" template_name = "core/user_group.jinja"
form_class = modelform_factory(User, fields=['groups'], form_class = modelform_factory(User, fields=['groups'],
widgets={'groups':CheckboxSelectMultiple}) widgets={'groups': CheckboxSelectMultiple})
context_object_name = "profile" context_object_name = "profile"
current_tab = "groups" current_tab = "groups"
class UserToolsView(QuickNotifMixin, UserTabsMixin, TemplateView): class UserToolsView(QuickNotifMixin, UserTabsMixin, TemplateView):
""" """
Displays the logged user's tools Displays the logged user's tools
@ -481,6 +503,7 @@ class UserToolsView(QuickNotifMixin, UserTabsMixin, TemplateView):
kwargs['object'] = self.request.user kwargs['object'] = self.request.user
return kwargs return kwargs
class UserAccountBase(UserTabsMixin, DetailView): class UserAccountBase(UserTabsMixin, DetailView):
""" """
Base class for UserAccount Base class for UserAccount
@ -489,15 +512,16 @@ class UserAccountBase(UserTabsMixin, DetailView):
pk_url_kwarg = "user_id" pk_url_kwarg = "user_id"
current_tab = "account" current_tab = "account"
def dispatch(self, request, *arg, **kwargs): # Manually validates the rights def dispatch(self, request, *arg, **kwargs): # Manually validates the rights
res = super(UserAccountBase, self).dispatch(request, *arg, **kwargs) res = super(UserAccountBase, self).dispatch(request, *arg, **kwargs)
if (self.object == request.user if (self.object == request.user
or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
or request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name']+settings.SITH_BOARD_SUFFIX) or request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name'] + settings.SITH_BOARD_SUFFIX)
or request.user.is_root): or request.user.is_root):
return res return res
raise PermissionDenied raise PermissionDenied
class UserAccountView(UserAccountBase): class UserAccountView(UserAccountBase):
""" """
Display a user's account Display a user's account
@ -511,14 +535,14 @@ class UserAccountView(UserAccountBase):
stats.append([]) stats.append([])
i = 0 i = 0
for month in obj.filter(date__year=year.year).datetimes( for month in obj.filter(date__year=year.year).datetimes(
'date', 'month', order='DESC'): 'date', 'month', order='DESC'):
q = obj.filter( q = obj.filter(
date__year=month.year, date__year=month.year,
date__month=month.month date__month=month.month
) )
stats[i].append({ stats[i].append({
'sum':sum([calc(p) for p in q]), 'sum': sum([calc(p) for p in q]),
'date':month 'date': month
}) })
i += 1 i += 1
return stats return stats
@ -551,6 +575,7 @@ class UserAccountView(UserAccountBase):
print(repr(e)) print(repr(e))
return kwargs return kwargs
class UserAccountDetailView(UserAccountBase, YearMixin, MonthMixin): class UserAccountDetailView(UserAccountBase, YearMixin, MonthMixin):
""" """
Display a user's account for month Display a user's account for month
@ -568,4 +593,3 @@ class UserAccountDetailView(UserAccountBase, YearMixin, MonthMixin):
pass pass
kwargs['tab'] = "account" kwargs['tab'] = "account"
return kwargs return kwargs

View File

@ -36,4 +36,3 @@ admin.site.register(Selling)
admin.site.register(Permanency) admin.site.register(Permanency)
admin.site.register(CashRegisterSummary) admin.site.register(CashRegisterSummary)
admin.site.register(Eticket) admin.site.register(Eticket)

View File

@ -22,13 +22,12 @@
# #
# #
from django.db import models, DataError from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.forms import ValidationError from django.forms import ValidationError
from django.contrib.sites.shortcuts import get_current_site
from django.utils.functional import cached_property from django.utils.functional import cached_property
from datetime import timedelta, date from datetime import timedelta, date
@ -43,6 +42,7 @@ from accounting.models import CurrencyField
from core.models import Group, User, Notification from core.models import Group, User, Notification
from subscription.models import Subscription from subscription.models import Subscription
class Customer(models.Model): class Customer(models.Model):
""" """
This class extends a user to make a customer. It adds some basic customers informations, such as the accound ID, and This class extends a user to make a customer. It adds some basic customers informations, such as the accound ID, and
@ -55,7 +55,7 @@ class Customer(models.Model):
class Meta: class Meta:
verbose_name = _('customer') verbose_name = _('customer')
verbose_name_plural = _('customers') verbose_name_plural = _('customers')
ordering = ['account_id',] ordering = ['account_id', ]
def __str__(self): def __str__(self):
return "%s - %s" % (self.user.username, self.account_id) return "%s - %s" % (self.user.username, self.account_id)
@ -68,9 +68,9 @@ class Customer(models.Model):
def generate_account_id(number): def generate_account_id(number):
number = str(number) number = str(number)
letter = random.choice(string.ascii_lowercase) letter = random.choice(string.ascii_lowercase)
while Customer.objects.filter(account_id=number+letter).exists(): while Customer.objects.filter(account_id=number + letter).exists():
letter = random.choice(string.ascii_lowercase) letter = random.choice(string.ascii_lowercase)
return number+letter return number + letter
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.amount < 0: if self.amount < 0:
@ -118,6 +118,7 @@ class ProductType(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('counter:producttype_list') return reverse('counter:producttype_list')
class Product(models.Model): class Product(models.Model):
""" """
This describes a product, with all its related informations This describes a product, with all its related informations
@ -125,7 +126,7 @@ class Product(models.Model):
name = models.CharField(_('name'), max_length=64) name = models.CharField(_('name'), max_length=64)
description = models.TextField(_('description'), blank=True) description = models.TextField(_('description'), blank=True)
product_type = models.ForeignKey(ProductType, related_name='products', verbose_name=_("product type"), null=True, blank=True, product_type = models.ForeignKey(ProductType, related_name='products', verbose_name=_("product type"), null=True, blank=True,
on_delete=models.SET_NULL) on_delete=models.SET_NULL)
code = models.CharField(_('code'), max_length=16, blank=True) code = models.CharField(_('code'), max_length=16, blank=True)
purchase_price = CurrencyField(_('purchase price')) purchase_price = CurrencyField(_('purchase price'))
selling_price = CurrencyField(_('selling price')) selling_price = CurrencyField(_('selling price'))
@ -135,7 +136,7 @@ class Product(models.Model):
limit_age = models.IntegerField(_('limit age'), default=0) limit_age = models.IntegerField(_('limit age'), default=0)
tray = models.BooleanField(_('tray price'), default=False) tray = models.BooleanField(_('tray price'), default=False)
parent_product = models.ForeignKey('self', related_name='children_products', verbose_name=_("parent product"), null=True, parent_product = models.ForeignKey('self', related_name='children_products', verbose_name=_("parent product"), null=True,
blank=True, on_delete=models.SET_NULL) blank=True, on_delete=models.SET_NULL)
buying_groups = models.ManyToManyField(Group, related_name='products', verbose_name=_("buying groups"), blank=True) buying_groups = models.ManyToManyField(Group, related_name='products', verbose_name=_("buying groups"), blank=True)
archived = models.BooleanField(_("archived"), default=False) archived = models.BooleanField(_("archived"), default=False)
@ -156,13 +157,14 @@ class Product(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('counter:product_list') return reverse('counter:product_list')
class Counter(models.Model): class Counter(models.Model):
name = models.CharField(_('name'), max_length=30) name = models.CharField(_('name'), max_length=30)
club = models.ForeignKey(Club, related_name="counters", verbose_name=_("club")) club = models.ForeignKey(Club, related_name="counters", verbose_name=_("club"))
products = models.ManyToManyField(Product, related_name="counters", verbose_name=_("products"), blank=True) products = models.ManyToManyField(Product, related_name="counters", verbose_name=_("products"), blank=True)
type = models.CharField(_('counter type'), type = models.CharField(_('counter type'),
max_length=255, max_length=255,
choices=[('BAR',_('Bar')), ('OFFICE',_('Office')), ('EBOUTIC',_('Eboutic'))]) choices=[('BAR', _('Bar')), ('OFFICE', _('Office')), ('EBOUTIC', _('Eboutic'))])
sellers = models.ManyToManyField(User, verbose_name=_('sellers'), related_name='counters', blank=True) sellers = models.ManyToManyField(User, verbose_name=_('sellers'), related_name='counters', blank=True)
edit_groups = models.ManyToManyField(Group, related_name="editable_counters", blank=True) edit_groups = models.ManyToManyField(Group, related_name="editable_counters", blank=True)
view_groups = models.ManyToManyField(Group, related_name="viewable_counters", blank=True) view_groups = models.ManyToManyField(Group, related_name="viewable_counters", blank=True)
@ -173,7 +175,7 @@ class Counter(models.Model):
def __getattribute__(self, name): def __getattribute__(self, name):
if name == "edit_groups": if name == "edit_groups":
return Group.objects.filter(name=self.club.unix_name+settings.SITH_BOARD_SUFFIX).all() return Group.objects.filter(name=self.club.unix_name + settings.SITH_BOARD_SUFFIX).all()
return object.__getattribute__(self, name) return object.__getattribute__(self, name)
def __str__(self): def __str__(self):
@ -248,7 +250,7 @@ class Counter(models.Model):
Update the barman activity to prevent timeout Update the barman activity to prevent timeout
""" """
for p in Permanency.objects.filter(counter=self, end=None).all(): for p in Permanency.objects.filter(counter=self, end=None).all():
p.save() # Update activity p.save() # Update activity
def is_open(self): def is_open(self):
return len(self.barmen_list) > 0 return len(self.barmen_list) > 0
@ -265,6 +267,7 @@ class Counter(models.Model):
""" """
return [b.id for b in self.get_barmen_list()] return [b.id for b in self.get_barmen_list()]
class Refilling(models.Model): class Refilling(models.Model):
""" """
Handle the refilling Handle the refilling
@ -275,9 +278,9 @@ class Refilling(models.Model):
customer = models.ForeignKey(Customer, related_name="refillings", blank=False) customer = models.ForeignKey(Customer, related_name="refillings", blank=False)
date = models.DateTimeField(_('date')) date = models.DateTimeField(_('date'))
payment_method = models.CharField(_('payment method'), max_length=255, payment_method = models.CharField(_('payment method'), max_length=255,
choices=settings.SITH_COUNTER_PAYMENT_METHOD, default='CASH') choices=settings.SITH_COUNTER_PAYMENT_METHOD, default='CASH')
bank = models.CharField(_('bank'), max_length=255, bank = models.CharField(_('bank'), max_length=255,
choices=settings.SITH_COUNTER_BANK, default='OTHER') choices=settings.SITH_COUNTER_BANK, default='OTHER')
is_validated = models.BooleanField(_('is validated'), default=False) is_validated = models.BooleanField(_('is validated'), default=False)
class Meta: class Meta:
@ -303,12 +306,13 @@ class Refilling(models.Model):
self.customer.save() self.customer.save()
self.is_validated = True self.is_validated = True
Notification(user=self.customer.user, url=reverse('core:user_account_detail', Notification(user=self.customer.user, url=reverse('core:user_account_detail',
kwargs={'user_id': self.customer.user.id, 'year': self.date.year, 'month': self.date.month}), kwargs={'user_id': self.customer.user.id, 'year': self.date.year, 'month': self.date.month}),
param=str(self.amount), param=str(self.amount),
type="REFILLING", type="REFILLING",
).save() ).save()
super(Refilling, self).save(*args, **kwargs) super(Refilling, self).save(*args, **kwargs)
class Selling(models.Model): class Selling(models.Model):
""" """
Handle the sellings Handle the sellings
@ -323,7 +327,7 @@ class Selling(models.Model):
customer = models.ForeignKey(Customer, related_name="buyings", null=True, blank=False, on_delete=models.SET_NULL) customer = models.ForeignKey(Customer, related_name="buyings", null=True, blank=False, on_delete=models.SET_NULL)
date = models.DateTimeField(_('date')) date = models.DateTimeField(_('date'))
payment_method = models.CharField(_('payment method'), max_length=255, payment_method = models.CharField(_('payment method'), max_length=255,
choices=[('SITH_ACCOUNT', _('Sith account')), ('CARD', _('Credit card'))], default='SITH_ACCOUNT') choices=[('SITH_ACCOUNT', _('Sith account')), ('CARD', _('Credit card'))], default='SITH_ACCOUNT')
is_validated = models.BooleanField(_('is validated'), default=False) is_validated = models.BooleanField(_('is validated'), default=False)
class Meta: class Meta:
@ -331,7 +335,7 @@ class Selling(models.Model):
def __str__(self): def __str__(self):
return "Selling: %d x %s (%f) for %s" % (self.quantity, self.label, return "Selling: %d x %s (%f) for %s" % (self.quantity, self.label,
self.quantity*self.unit_price, self.customer.user.get_display_name()) self.quantity * self.unit_price, self.customer.user.get_display_name())
def is_owned_by(self, user): def is_owned_by(self, user):
return user.is_owner(self.counter) and self.payment_method != "CARD" return user.is_owner(self.counter) and self.payment_method != "CARD"
@ -352,13 +356,13 @@ class Selling(models.Model):
"You bought an eticket for the event %(event)s.\nYou can download it on this page %(url)s." "You bought an eticket for the event %(event)s.\nYou can download it on this page %(url)s."
) % { ) % {
'event': event, 'event': event,
'url':''.join(( 'url': ''.join((
'<a href="', '<a href="',
self.customer.get_full_url(), self.customer.get_full_url(),
'">', '">',
self.customer.get_full_url(), self.customer.get_full_url(),
'</a>' '</a>'
)) ))
} }
message_txt = _( message_txt = _(
"You bought an eticket for the event %(event)s.\nYou can download it on this page %(url)s." "You bought an eticket for the event %(event)s.\nYou can download it on this page %(url)s."
@ -384,46 +388,48 @@ class Selling(models.Model):
if u.was_subscribed: if u.was_subscribed:
if self.product and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER: if self.product and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER:
sub = Subscription( sub = Subscription(
member=u, member=u,
subscription_type='un-semestre', subscription_type='un-semestre',
payment_method="EBOUTIC", payment_method="EBOUTIC",
location="EBOUTIC", location="EBOUTIC",
) )
sub.subscription_start = Subscription.compute_start() sub.subscription_start = Subscription.compute_start()
sub.subscription_start = Subscription.compute_start( sub.subscription_start = Subscription.compute_start(
duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration']) duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'])
sub.subscription_end = Subscription.compute_end( sub.subscription_end = Subscription.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'],
start=sub.subscription_start) start=sub.subscription_start)
sub.save() sub.save()
elif self.product and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS: elif self.product and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS:
u = User.objects.filter(id=self.customer.user.id).first() u = User.objects.filter(id=self.customer.user.id).first()
sub = Subscription( sub = Subscription(
member=u, member=u,
subscription_type='deux-semestres', subscription_type='deux-semestres',
payment_method="EBOUTIC", payment_method="EBOUTIC",
location="EBOUTIC", location="EBOUTIC",
) )
sub.subscription_start = Subscription.compute_start() sub.subscription_start = Subscription.compute_start()
sub.subscription_start = Subscription.compute_start( sub.subscription_start = Subscription.compute_start(
duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration']) duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'])
sub.subscription_end = Subscription.compute_end( sub.subscription_end = Subscription.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'],
start=sub.subscription_start) start=sub.subscription_start)
sub.save() sub.save()
try: try:
if self.product.eticket: if self.product.eticket:
self.send_mail_customer() self.send_mail_customer()
except: pass except:
pass
Notification( Notification(
user=self.customer.user, user=self.customer.user,
url=reverse('core:user_account_detail', url=reverse('core:user_account_detail',
kwargs={'user_id': self.customer.user.id, 'year': self.date.year, 'month': self.date.month}), kwargs={'user_id': self.customer.user.id, 'year': self.date.year, 'month': self.date.month}),
param="%d x %s" % (self.quantity, self.label), param="%d x %s" % (self.quantity, self.label),
type="SELLING", type="SELLING",
).save() ).save()
super(Selling, self).save(*args, **kwargs) super(Selling, self).save(*args, **kwargs)
class Permanency(models.Model): class Permanency(models.Model):
""" """
This class aims at storing a traceability of who was barman where and when This class aims at storing a traceability of who was barman where and when
@ -439,10 +445,11 @@ class Permanency(models.Model):
def __str__(self): def __str__(self):
return "%s in %s from %s (last activity: %s) to %s" % (self.user, self.counter, return "%s in %s from %s (last activity: %s) to %s" % (self.user, self.counter,
self.start.strftime("%Y-%m-%d %H:%M:%S"), self.start.strftime("%Y-%m-%d %H:%M:%S"),
self.activity.strftime("%Y-%m-%d %H:%M:%S"), self.activity.strftime("%Y-%m-%d %H:%M:%S"),
self.end.strftime("%Y-%m-%d %H:%M:%S") if self.end else "", self.end.strftime("%Y-%m-%d %H:%M:%S") if self.end else "",
) )
class CashRegisterSummary(models.Model): class CashRegisterSummary(models.Model):
user = models.ForeignKey(User, related_name="cash_summaries", verbose_name=_("user")) user = models.ForeignKey(User, related_name="cash_summaries", verbose_name=_("user"))
@ -515,6 +522,7 @@ class CashRegisterSummary(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('counter:cash_summary_list') return reverse('counter:cash_summary_list')
class CashRegisterSummaryItem(models.Model): class CashRegisterSummaryItem(models.Model):
cash_summary = models.ForeignKey(CashRegisterSummary, related_name="items", verbose_name=_("cash summary")) cash_summary = models.ForeignKey(CashRegisterSummary, related_name="items", verbose_name=_("cash summary"))
value = CurrencyField(_("value")) value = CurrencyField(_("value"))
@ -524,6 +532,7 @@ class CashRegisterSummaryItem(models.Model):
class Meta: class Meta:
verbose_name = _("cash register summary item") verbose_name = _("cash register summary item")
class Eticket(models.Model): class Eticket(models.Model):
""" """
Eticket can be linked to a product an allows PDF generation Eticket can be linked to a product an allows PDF generation
@ -552,6 +561,6 @@ class Eticket(models.Model):
return user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID) return user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID)
def get_hash(self, string): def get_hash(self, string):
import hashlib, hmac import hashlib
import hmac
return hmac.new(bytes(self.secret, 'utf-8'), bytes(string, 'utf-8'), hashlib.sha1).hexdigest() return hmac.new(bytes(self.secret, 'utf-8'), bytes(string, 'utf-8'), hashlib.sha1).hexdigest()

View File

@ -24,7 +24,6 @@
import re import re
from pprint import pprint
from django.test import TestCase from django.test import TestCase
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.management import call_command from django.core.management import call_command

View File

@ -22,7 +22,7 @@
# #
# #
from django.conf.urls import url, include from django.conf.urls import url
from counter.views import * from counter.views import *

View File

@ -22,7 +22,7 @@
# #
# #
from django.shortcuts import render, get_object_or_404 from django.shortcuts import get_object_or_404
from django.http import Http404 from django.http import Http404
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.views.generic import ListView, DetailView, RedirectView, TemplateView from django.views.generic import ListView, DetailView, RedirectView, TemplateView
@ -31,7 +31,6 @@ from django.views.generic.edit import UpdateView, CreateView, DeleteView, Proces
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.forms import CheckboxSelectMultiple from django.forms import CheckboxSelectMultiple
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect, HttpResponse from django.http import HttpResponseRedirect, HttpResponse
from django.utils import timezone from django.utils import timezone
from django import forms from django import forms
@ -43,16 +42,17 @@ import re
import pytz import pytz
from datetime import date, timedelta, datetime from datetime import date, timedelta, datetime
from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField
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, TabedViewMixin
from core.views.forms import SelectUser, LoginForm, SelectDate, SelectDateTime from core.views.forms import LoginForm, SelectDate, SelectDateTime
from core.models import User from core.models import User
from subscription.models import Subscription from subscription.models import Subscription
from counter.models import Counter, Customer, Product, Selling, Refilling, ProductType, \ from counter.models import Counter, Customer, Product, Selling, Refilling, ProductType, \
CashRegisterSummary, CashRegisterSummaryItem, Eticket, Permanency CashRegisterSummary, CashRegisterSummaryItem, Eticket, Permanency
from accounting.models import CurrencyField from accounting.models import CurrencyField
class CounterAdminMixin(View): class CounterAdminMixin(View):
""" """
This view is made to protect counter admin section This view is made to protect counter admin section
@ -72,13 +72,13 @@ class CounterAdminMixin(View):
return True return True
return False return False
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not (request.user.is_root or self._test_group(request.user) if not (request.user.is_root or self._test_group(request.user)
or self._test_club(request.user)): or self._test_club(request.user)):
raise PermissionDenied raise PermissionDenied
return super(CounterAdminMixin, self).dispatch(request, *args, **kwargs) return super(CounterAdminMixin, self).dispatch(request, *args, **kwargs)
class GetUserForm(forms.Form): class GetUserForm(forms.Form):
""" """
The Form class aims at providing a valid user_id field in its cleaned data, in order to pass it to some view, The Form class aims at providing a valid user_id field in its cleaned data, in order to pass it to some view,
@ -107,51 +107,57 @@ class GetUserForm(forms.Form):
cleaned_data['user'] = cus.user cleaned_data['user'] = cus.user
return cleaned_data return cleaned_data
class RefillForm(forms.ModelForm): class RefillForm(forms.ModelForm):
error_css_class = 'error' error_css_class = 'error'
required_css_class = 'required' required_css_class = 'required'
amount = forms.FloatField(min_value=0, widget=forms.NumberInput(attrs={'class':'focus'})) amount = forms.FloatField(min_value=0, widget=forms.NumberInput(attrs={'class': 'focus'}))
class Meta: class Meta:
model = Refilling model = Refilling
fields = ['amount', 'payment_method', 'bank'] fields = ['amount', 'payment_method', 'bank']
class CounterTabsMixin(TabedViewMixin): class CounterTabsMixin(TabedViewMixin):
def get_tabs_title(self): def get_tabs_title(self):
if hasattr(self.object, 'stock_owner') : if hasattr(self.object, 'stock_owner'):
return self.object.stock_owner.counter return self.object.stock_owner.counter
else: else:
return self.object return self.object
def get_list_of_tabs(self): def get_list_of_tabs(self):
tab_list = [] tab_list = []
tab_list.append({ tab_list.append({
'url': reverse_lazy('counter:details', 'url': reverse_lazy('counter:details',
kwargs={'counter_id': self.object.stock_owner.counter.id if hasattr(self.object, 'stock_owner') else self.object.id }), kwargs={'counter_id': self.object.stock_owner.counter.id if hasattr(self.object, 'stock_owner') else self.object.id}),
'slug': 'counter', 'slug': 'counter',
'name': _("Counter"), 'name': _("Counter"),
}) })
if self.object.stock_owner.counter.type if hasattr(self.object, 'stock_owner') else self.object.type == "BAR": if self.object.stock_owner.counter.type if hasattr(self.object, 'stock_owner') else self.object.type == "BAR":
tab_list.append({ tab_list.append({
'url': reverse_lazy('counter:cash_summary', 'url': reverse_lazy('counter:cash_summary',
kwargs={'counter_id': self.object.stock_owner.counter.id if hasattr(self.object, 'stock_owner') else self.object.id}), kwargs={'counter_id': self.object.stock_owner.counter.id if hasattr(self.object, 'stock_owner') else self.object.id}),
'slug': 'cash_summary', 'slug': 'cash_summary',
'name': _("Cash summary"), 'name': _("Cash summary"),
}) })
tab_list.append({ tab_list.append({
'url': reverse_lazy('counter:last_ops', 'url': reverse_lazy('counter:last_ops',
kwargs={'counter_id': self.object.stock_owner.counter.id if hasattr(self.object, 'stock_owner') else self.object.id}), kwargs={'counter_id': self.object.stock_owner.counter.id if hasattr(self.object, 'stock_owner') else self.object.id}),
'slug': 'last_ops', 'slug': 'last_ops',
'name': _("Last operations"), 'name': _("Last operations"),
}) })
try: try:
tab_list.append({ tab_list.append({
'url': reverse_lazy('stock:take_items', 'url': reverse_lazy('stock:take_items',
kwargs={'stock_id': self.object.stock.id if hasattr(self.object, 'stock') else self.object.stock_owner.id}), kwargs={'stock_id': self.object.stock.id if hasattr(self.object, 'stock') else self.object.stock_owner.id}),
'slug': 'take_items_from_stock', 'slug': 'take_items_from_stock',
'name': _("Take items from stock"), 'name': _("Take items from stock"),
}) })
except: pass # The counter just have no stock except:
pass # The counter just have no stock
return tab_list return tab_list
class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, FormMixin): class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, FormMixin):
""" """
The public (barman) view The public (barman) view
@ -159,15 +165,15 @@ class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, F
model = Counter model = Counter
template_name = 'counter/counter_main.jinja' template_name = 'counter/counter_main.jinja'
pk_url_kwarg = "counter_id" pk_url_kwarg = "counter_id"
form_class = GetUserForm # Form to enter a client code and get the corresponding user id form_class = GetUserForm # Form to enter a client code and get the corresponding user id
current_tab = "counter" current_tab = "counter"
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if self.object.type == "BAR" and not ('counter_token' in self.request.session.keys() and if self.object.type == "BAR" and not ('counter_token' in self.request.session.keys() and
self.request.session['counter_token'] == self.object.token): # Check the token to avoid the bar to be stolen self.request.session['counter_token'] == self.object.token): # Check the token to avoid the bar to be stolen
return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args,
kwargs={'counter_id': self.object.id})+'?bad_location') kwargs={'counter_id': self.object.id}) + '?bad_location')
return super(CounterMain, self).post(request, *args, **kwargs) return super(CounterMain, self).post(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -180,13 +186,13 @@ class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, F
kwargs = super(CounterMain, self).get_context_data(**kwargs) kwargs = super(CounterMain, self).get_context_data(**kwargs)
kwargs['login_form'] = LoginForm() kwargs['login_form'] = LoginForm()
kwargs['login_form'].fields['username'].widget.attrs['autofocus'] = True kwargs['login_form'].fields['username'].widget.attrs['autofocus'] = True
kwargs['login_form'].cleaned_data = {} # add_error fails if there are no cleaned_data kwargs['login_form'].cleaned_data = {} # add_error fails if there are no cleaned_data
if "credentials" in self.request.GET: if "credentials" in self.request.GET:
kwargs['login_form'].add_error(None, _("Bad credentials")) kwargs['login_form'].add_error(None, _("Bad credentials"))
if "sellers" in self.request.GET: if "sellers" in self.request.GET:
kwargs['login_form'].add_error(None, _("User is not barman")) kwargs['login_form'].add_error(None, _("User is not barman"))
kwargs['form'] = self.get_form() kwargs['form'] = self.get_form()
kwargs['form'].cleaned_data = {} # same as above kwargs['form'].cleaned_data = {} # same as above
if "bad_location" in self.request.GET: if "bad_location" in self.request.GET:
kwargs['form'].add_error(None, _("Bad location, someone is already logged in somewhere else")) kwargs['form'].add_error(None, _("Bad location, someone is already logged in somewhere else"))
if self.object.type == 'BAR': if self.object.type == 'BAR':
@ -210,6 +216,7 @@ class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, F
def get_success_url(self): def get_success_url(self):
return reverse_lazy('counter:click', args=self.args, kwargs=self.kwargs) return reverse_lazy('counter:click', args=self.args, kwargs=self.kwargs)
class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
""" """
The click view The click view
@ -228,7 +235,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
raise Http404 raise Http404
if obj.type == "BAR": if obj.type == "BAR":
if not ('counter_token' in request.session.keys() and if not ('counter_token' in request.session.keys() and
request.session['counter_token'] == obj.token) or len(obj.get_barmen_list())<1: request.session['counter_token'] == obj.token) or len(obj.get_barmen_list()) < 1:
raise PermissionDenied raise PermissionDenied
else: else:
if not request.user.is_authenticated(): if not request.user.is_authenticated():
@ -237,10 +244,10 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Simple get view""" """Simple get view"""
if 'basket' not in request.session.keys(): # Init the basket session entry if 'basket' not in request.session.keys(): # Init the basket session entry
request.session['basket'] = {} request.session['basket'] = {}
request.session['basket_total'] = 0 request.session['basket_total'] = 0
request.session['not_enough'] = False # Reset every variable request.session['not_enough'] = False # Reset every variable
request.session['too_young'] = False request.session['too_young'] = False
request.session['not_allowed'] = False request.session['not_allowed'] = False
request.session['no_age'] = False request.session['no_age'] = False
@ -248,8 +255,8 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
ret = super(CounterClick, self).get(request, *args, **kwargs) ret = super(CounterClick, self).get(request, *args, **kwargs)
if ((self.object.type != "BAR" and not request.user.is_authenticated()) or if ((self.object.type != "BAR" and not request.user.is_authenticated()) or
(self.object.type == "BAR" and (self.object.type == "BAR" and
len(self.object.get_barmen_list()) < 1)): # Check that at least one barman is logged in len(self.object.get_barmen_list()) < 1)): # Check that at least one barman is logged in
ret = self.cancel(request) # Otherwise, go to main view ret = self.cancel(request) # Otherwise, go to main view
return ret return ret
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@ -258,16 +265,16 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
self.refill_form = None self.refill_form = None
if ((self.object.type != "BAR" and not request.user.is_authenticated()) or if ((self.object.type != "BAR" and not request.user.is_authenticated()) or
(self.object.type == "BAR" and (self.object.type == "BAR" and
len(self.object.get_barmen_list()) < 1)): # Check that at least one barman is logged in len(self.object.get_barmen_list()) < 1)): # Check that at least one barman is logged in
return self.cancel(request) return self.cancel(request)
if self.object.type == "BAR" and not ('counter_token' in self.request.session.keys() and if self.object.type == "BAR" and not ('counter_token' in self.request.session.keys() and
self.request.session['counter_token'] == self.object.token): # Also check the token to avoid the bar to be stolen self.request.session['counter_token'] == self.object.token): # Also check the token to avoid the bar to be stolen
return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args,
kwargs={'counter_id': self.object.id})+'?bad_location') kwargs={'counter_id': self.object.id}) + '?bad_location')
if 'basket' not in request.session.keys(): if 'basket' not in request.session.keys():
request.session['basket'] = {} request.session['basket'] = {}
request.session['basket_total'] = 0 request.session['basket_total'] = 0
request.session['not_enough'] = False # Reset every variable request.session['not_enough'] = False # Reset every variable
request.session['too_young'] = False request.session['too_young'] = False
request.session['not_allowed'] = False request.session['not_allowed'] = False
request.session['no_age'] = False request.session['no_age'] = False
@ -312,7 +319,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
def sum_basket(self, request): def sum_basket(self, request):
total = 0 total = 0
for pid,infos in request.session['basket'].items(): for pid, infos in request.session['basket'].items():
total += infos['price'] * infos['qty'] total += infos['price'] * infos['qty']
return total / 100 return total / 100
@ -323,7 +330,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
except: except:
return 0 return 0
def add_product(self, request, q = 1, p=None): def add_product(self, request, q=1, p=None):
""" """
Add a product to the basket Add a product to the basket
q is the quantity passed as integer q is the quantity passed as integer
@ -344,12 +351,12 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
if not can_buy: if not can_buy:
request.session['not_allowed'] = True request.session['not_allowed'] = True
return False return False
bq = 0 # Bonus quantity, for trays bq = 0 # Bonus quantity, for trays
if product.tray: # Handle the tray to adjust the quantity q to add and the bonus quantity bq if product.tray: # Handle the tray to adjust the quantity q to add and the bonus quantity bq
total_qty_mod_6 = self.get_total_quantity_for_pid(request, pid) % 6 total_qty_mod_6 = self.get_total_quantity_for_pid(request, pid) % 6
bq = int((total_qty_mod_6 + q) / 6) # Integer division bq = int((total_qty_mod_6 + q) / 6) # Integer division
q -= bq q -= bq
if self.customer.amount < (total + round(q*float(price),2)): # Check for enough money if self.customer.amount < (total + round(q * float(price), 2)): # Check for enough money
request.session['not_enough'] = True request.session['not_enough'] = True
return False return False
if product.limit_age >= 18 and not self.customer.user.date_of_birth: if product.limit_age >= 18 and not self.customer.user.date_of_birth:
@ -361,14 +368,14 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
if self.customer.user.is_banned_counter: if self.customer.user.is_banned_counter:
request.session['not_allowed'] = True request.session['not_allowed'] = True
return False return False
if self.customer.user.date_of_birth and self.customer.user.get_age() < product.limit_age: # Check if affordable if self.customer.user.date_of_birth and self.customer.user.get_age() < product.limit_age: # Check if affordable
request.session['too_young'] = True request.session['too_young'] = True
return False return False
if pid in request.session['basket']: # Add if already in basket if pid in request.session['basket']: # Add if already in basket
request.session['basket'][pid]['qty'] += q request.session['basket'][pid]['qty'] += q
request.session['basket'][pid]['bonus_qty'] += bq request.session['basket'][pid]['bonus_qty'] += bq
else: # or create if not else: # or create if not
request.session['basket'][pid] = {'qty': q, 'price': int(price*100), 'bonus_qty': bq} request.session['basket'][pid] = {'qty': q, 'price': int(price * 100), 'bonus_qty': bq}
request.session.modified = True request.session.modified = True
return True return True
@ -414,7 +421,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
""" Finish the click session, and validate the basket """ """ Finish the click session, and validate the basket """
with transaction.atomic(): with transaction.atomic():
request.session['last_basket'] = [] request.session['last_basket'] = []
for pid,infos in request.session['basket'].items(): for pid, infos in request.session['basket'].items():
# This duplicates code for DB optimization (prevent to load many times the same object) # This duplicates code for DB optimization (prevent to load many times the same object)
p = Product.objects.filter(pk=pid).first() p = Product.objects.filter(pk=pid).first()
if self.is_barman_price(): if self.is_barman_price():
@ -423,13 +430,13 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
uprice = p.selling_price uprice = p.selling_price
if uprice * infos['qty'] > self.customer.amount: if uprice * infos['qty'] > self.customer.amount:
raise DataError(_("You have not enough money to buy all the basket")) raise DataError(_("You have not enough money to buy all the basket"))
request.session['last_basket'].append("%d x %s" % (infos['qty']+infos['bonus_qty'], p.name)) request.session['last_basket'].append("%d x %s" % (infos['qty'] + infos['bonus_qty'], p.name))
s = Selling(label=p.name, product=p, club=p.club, counter=self.object, unit_price=uprice, s = Selling(label=p.name, product=p, club=p.club, counter=self.object, unit_price=uprice,
quantity=infos['qty'], seller=self.operator, customer=self.customer) quantity=infos['qty'], seller=self.operator, customer=self.customer)
s.save() s.save()
if infos['bonus_qty']: if infos['bonus_qty']:
s = Selling(label=p.name + " (Plateau)", product=p, club=p.club, counter=self.object, unit_price=0, s = Selling(label=p.name + " (Plateau)", product=p, club=p.club, counter=self.object, unit_price=0,
quantity=infos['bonus_qty'], seller=self.operator, customer=self.customer) quantity=infos['bonus_qty'], seller=self.operator, customer=self.customer)
s.save() s.save()
request.session['last_customer'] = self.customer.user.get_display_name() request.session['last_customer'] = self.customer.user.get_display_name()
request.session['last_total'] = "%0.2f" % self.sum_basket(request) request.session['last_total'] = "%0.2f" % self.sum_basket(request)
@ -437,8 +444,8 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
del request.session['basket'] del request.session['basket']
request.session.modified = True request.session.modified = True
kwargs = { kwargs = {
'counter_id': self.object.id, 'counter_id': self.object.id,
} }
return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, kwargs=kwargs)) return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, kwargs=kwargs))
def cancel(self, request): def cancel(self, request):
@ -470,6 +477,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
kwargs['categories'] = ProductType.objects.all() kwargs['categories'] = ProductType.objects.all()
return kwargs return kwargs
class CounterLogin(RedirectView): class CounterLogin(RedirectView):
""" """
Handle the login of a barman Handle the login of a barman
@ -477,6 +485,7 @@ class CounterLogin(RedirectView):
Logged barmen are stored in the Permanency model Logged barmen are stored in the Permanency model
""" """
permanent = False permanent = False
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
""" """
Register the logged user as barman for this counter Register the logged user as barman for this counter
@ -499,10 +508,12 @@ class CounterLogin(RedirectView):
return super(CounterLogin, self).post(request, *args, **kwargs) return super(CounterLogin, self).post(request, *args, **kwargs)
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
return reverse_lazy('counter:details', args=args, kwargs=kwargs)+"?"+'&'.join(self.errors) return reverse_lazy('counter:details', args=args, kwargs=kwargs) + "?" + '&'.join(self.errors)
class CounterLogout(RedirectView): class CounterLogout(RedirectView):
permanent = False permanent = False
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
""" """
Unregister the user from the barman Unregister the user from the barman
@ -515,52 +526,54 @@ class CounterLogout(RedirectView):
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
return reverse_lazy('counter:details', args=args, kwargs=kwargs) return reverse_lazy('counter:details', args=args, kwargs=kwargs)
## Counter admin views # Counter admin views
class CounterAdminTabsMixin(TabedViewMixin): class CounterAdminTabsMixin(TabedViewMixin):
tabs_title = _("Counter administration") tabs_title = _("Counter administration")
list_of_tabs = [ list_of_tabs = [
{ {
'url': reverse_lazy('stock:list'), 'url': reverse_lazy('stock:list'),
'slug': 'stocks', 'slug': 'stocks',
'name': _("Stocks"), 'name': _("Stocks"),
}, },
{ {
'url': reverse_lazy('counter:admin_list'), 'url': reverse_lazy('counter:admin_list'),
'slug': 'counters', 'slug': 'counters',
'name': _("Counters"), 'name': _("Counters"),
}, },
{ {
'url': reverse_lazy('counter:product_list'), 'url': reverse_lazy('counter:product_list'),
'slug': 'products', 'slug': 'products',
'name': _("Products"), 'name': _("Products"),
}, },
{ {
'url': reverse_lazy('counter:product_list_archived'), 'url': reverse_lazy('counter:product_list_archived'),
'slug': 'archive', 'slug': 'archive',
'name': _("Archived products"), 'name': _("Archived products"),
}, },
{ {
'url': reverse_lazy('counter:producttype_list'), 'url': reverse_lazy('counter:producttype_list'),
'slug': 'product_types', 'slug': 'product_types',
'name': _("Product types"), 'name': _("Product types"),
}, },
{ {
'url': reverse_lazy('counter:cash_summary_list'), 'url': reverse_lazy('counter:cash_summary_list'),
'slug': 'cash_summary', 'slug': 'cash_summary',
'name': _("Cash register summaries"), 'name': _("Cash register summaries"),
}, },
{ {
'url': reverse_lazy('counter:invoices_call'), 'url': reverse_lazy('counter:invoices_call'),
'slug': 'invoices_call', 'slug': 'invoices_call',
'name': _("Invoices call"), 'name': _("Invoices call"),
}, },
{ {
'url': reverse_lazy('counter:eticket_list'), 'url': reverse_lazy('counter:eticket_list'),
'slug': 'etickets', 'slug': 'etickets',
'name': _("Etickets"), 'name': _("Etickets"),
}, },
] ]
class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView): class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView):
""" """
@ -570,6 +583,7 @@ class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView):
template_name = 'counter/counter_list.jinja' template_name = 'counter/counter_list.jinja'
current_tab = "counters" current_tab = "counters"
class CounterEditForm(forms.ModelForm): class CounterEditForm(forms.ModelForm):
class Meta: class Meta:
model = Counter model = Counter
@ -577,6 +591,7 @@ class CounterEditForm(forms.ModelForm):
sellers = make_ajax_field(Counter, 'sellers', 'users', help_text="") sellers = make_ajax_field(Counter, 'sellers', 'users', help_text="")
products = make_ajax_field(Counter, 'products', 'products', help_text="") products = make_ajax_field(Counter, 'products', 'products', help_text="")
class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
""" """
Edit a counter's main informations (for the counter's manager) Edit a counter's main informations (for the counter's manager)
@ -595,6 +610,7 @@ class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
def get_success_url(self): def get_success_url(self):
return reverse_lazy('counter:admin', kwargs={'counter_id': self.object.id}) return reverse_lazy('counter:admin', kwargs={'counter_id': self.object.id})
class CounterEditPropView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): class CounterEditPropView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
""" """
Edit a counter's main informations (for the counter's admin) Edit a counter's main informations (for the counter's admin)
@ -605,16 +621,18 @@ class CounterEditPropView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
current_tab = "counters" current_tab = "counters"
class CounterCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): class CounterCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
""" """
Create a counter (for the admins) Create a counter (for the admins)
""" """
model = Counter model = Counter
form_class = modelform_factory(Counter, fields=['name', 'club', 'type', 'products'], form_class = modelform_factory(Counter, fields=['name', 'club', 'type', 'products'],
widgets={'products':CheckboxSelectMultiple}) widgets={'products': CheckboxSelectMultiple})
template_name = 'core/create.jinja' template_name = 'core/create.jinja'
current_tab = "counters" current_tab = "counters"
class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView): class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView):
""" """
Delete a counter (for the admins) Delete a counter (for the admins)
@ -627,6 +645,7 @@ class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView):
# Product management # Product management
class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
""" """
A list view for the admins A list view for the admins
@ -635,6 +654,7 @@ class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
template_name = 'counter/producttype_list.jinja' template_name = 'counter/producttype_list.jinja'
current_tab = "product_types" current_tab = "product_types"
class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
""" """
A create view for the admins A create view for the admins
@ -644,6 +664,7 @@ class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView
template_name = 'core/create.jinja' template_name = 'core/create.jinja'
current_tab = "products" current_tab = "products"
class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
""" """
An edit view for the admins An edit view for the admins
@ -654,6 +675,7 @@ class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
pk_url_kwarg = "type_id" pk_url_kwarg = "type_id"
current_tab = "products" current_tab = "products"
class ProductArchivedListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): class ProductArchivedListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
""" """
A list view for the admins A list view for the admins
@ -664,6 +686,7 @@ class ProductArchivedListView(CounterAdminTabsMixin, CounterAdminMixin, ListView
ordering = ['name'] ordering = ['name']
current_tab = "archive" current_tab = "archive"
class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
""" """
A list view for the admins A list view for the admins
@ -674,11 +697,12 @@ class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
ordering = ['name'] ordering = ['name']
current_tab = "products" current_tab = "products"
class ProductEditForm(forms.ModelForm): class ProductEditForm(forms.ModelForm):
class Meta: class Meta:
model = Product model = Product
fields = ['name', 'description', 'product_type', 'code', 'parent_product', 'buying_groups', 'purchase_price', fields = ['name', 'description', 'product_type', 'code', 'parent_product', 'buying_groups', 'purchase_price',
'selling_price', 'special_selling_price', 'icon', 'club', 'limit_age', 'tray', 'archived'] 'selling_price', 'special_selling_price', 'icon', 'club', 'limit_age', 'tray', 'archived']
parent_product = AutoCompleteSelectField('products', show_help_text=False, label=_("Parent product"), required=False) parent_product = AutoCompleteSelectField('products', show_help_text=False, label=_("Parent product"), required=False)
buying_groups = AutoCompleteSelectMultipleField('groups', show_help_text=False, help_text="", label=_("Buying groups"), required=False) buying_groups = AutoCompleteSelectMultipleField('groups', show_help_text=False, help_text="", label=_("Buying groups"), required=False)
club = AutoCompleteSelectField('clubs', show_help_text=False) club = AutoCompleteSelectField('clubs', show_help_text=False)
@ -702,6 +726,7 @@ class ProductEditForm(forms.ModelForm):
c.save() c.save()
return ret return ret
class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
""" """
A create view for the admins A create view for the admins
@ -711,6 +736,7 @@ class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
template_name = 'core/create.jinja' template_name = 'core/create.jinja'
current_tab = "products" current_tab = "products"
class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
""" """
An edit view for the admins An edit view for the admins
@ -721,6 +747,7 @@ class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
current_tab = "products" current_tab = "products"
class RefillingDeleteView(DeleteView): class RefillingDeleteView(DeleteView):
""" """
Delete a refilling (for the admins) Delete a refilling (for the admins)
@ -736,7 +763,7 @@ class RefillingDeleteView(DeleteView):
self.object = self.get_object() self.object = self.get_object()
if (timezone.now() - self.object.date <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) and if (timezone.now() - self.object.date <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) and
'counter_token' in request.session.keys() and 'counter_token' in request.session.keys() and
request.session['counter_token'] and # check if not null for counters that have no token set request.session['counter_token'] and # check if not null for counters that have no token set
Counter.objects.filter(token=request.session['counter_token']).exists()): Counter.objects.filter(token=request.session['counter_token']).exists()):
self.success_url = reverse('counter:details', kwargs={'counter_id': self.object.counter.id}) self.success_url = reverse('counter:details', kwargs={'counter_id': self.object.counter.id})
return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs) return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs)
@ -745,6 +772,7 @@ class RefillingDeleteView(DeleteView):
return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs) return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs)
raise PermissionDenied raise PermissionDenied
class SellingDeleteView(DeleteView): class SellingDeleteView(DeleteView):
""" """
Delete a selling (for the admins) Delete a selling (for the admins)
@ -760,7 +788,7 @@ class SellingDeleteView(DeleteView):
self.object = self.get_object() self.object = self.get_object()
if (timezone.now() - self.object.date <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) and if (timezone.now() - self.object.date <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) and
'counter_token' in request.session.keys() and 'counter_token' in request.session.keys() and
request.session['counter_token'] and # check if not null for counters that have no token set request.session['counter_token'] and # check if not null for counters that have no token set
Counter.objects.filter(token=request.session['counter_token']).exists()): Counter.objects.filter(token=request.session['counter_token']).exists()):
self.success_url = reverse('counter:details', kwargs={'counter_id': self.object.counter.id}) self.success_url = reverse('counter:details', kwargs={'counter_id': self.object.counter.id})
return super(SellingDeleteView, self).dispatch(request, *args, **kwargs) return super(SellingDeleteView, self).dispatch(request, *args, **kwargs)
@ -771,6 +799,7 @@ class SellingDeleteView(DeleteView):
# Cash register summaries # Cash register summaries
class CashRegisterSummaryForm(forms.Form): class CashRegisterSummaryForm(forms.Form):
""" """
Provide the cash summary form Provide the cash summary form
@ -831,38 +860,54 @@ class CashRegisterSummaryForm(forms.Form):
def save(self, counter=None): def save(self, counter=None):
cd = self.cleaned_data cd = self.cleaned_data
summary = self.instance or CashRegisterSummary( summary = self.instance or CashRegisterSummary(
counter=counter, counter=counter,
user=counter.get_random_barman(), user=counter.get_random_barman(),
) )
summary.comment = cd['comment'] summary.comment = cd['comment']
summary.emptied = cd['emptied'] summary.emptied = cd['emptied']
summary.save() summary.save()
summary.items.all().delete() summary.items.all().delete()
# Cash # Cash
if cd['ten_cents']: CashRegisterSummaryItem(cash_summary=summary, value=0.1, quantity=cd['ten_cents']).save() if cd['ten_cents']:
if cd['twenty_cents']: CashRegisterSummaryItem(cash_summary=summary, value=0.2, quantity=cd['twenty_cents']).save() CashRegisterSummaryItem(cash_summary=summary, value=0.1, quantity=cd['ten_cents']).save()
if cd['fifty_cents']: CashRegisterSummaryItem(cash_summary=summary, value=0.5, quantity=cd['fifty_cents']).save() if cd['twenty_cents']:
if cd['one_euro']: CashRegisterSummaryItem(cash_summary=summary, value=1, quantity=cd['one_euro']).save() CashRegisterSummaryItem(cash_summary=summary, value=0.2, quantity=cd['twenty_cents']).save()
if cd['two_euros']: CashRegisterSummaryItem(cash_summary=summary, value=2, quantity=cd['two_euros']).save() if cd['fifty_cents']:
if cd['five_euros']: CashRegisterSummaryItem(cash_summary=summary, value=5, quantity=cd['five_euros']).save() CashRegisterSummaryItem(cash_summary=summary, value=0.5, quantity=cd['fifty_cents']).save()
if cd['ten_euros']: CashRegisterSummaryItem(cash_summary=summary, value=10, quantity=cd['ten_euros']).save() if cd['one_euro']:
if cd['twenty_euros']: CashRegisterSummaryItem(cash_summary=summary, value=20, quantity=cd['twenty_euros']).save() CashRegisterSummaryItem(cash_summary=summary, value=1, quantity=cd['one_euro']).save()
if cd['fifty_euros']: CashRegisterSummaryItem(cash_summary=summary, value=50, quantity=cd['fifty_euros']).save() if cd['two_euros']:
if cd['hundred_euros']: CashRegisterSummaryItem(cash_summary=summary, value=100, quantity=cd['hundred_euros']).save() CashRegisterSummaryItem(cash_summary=summary, value=2, quantity=cd['two_euros']).save()
if cd['five_euros']:
CashRegisterSummaryItem(cash_summary=summary, value=5, quantity=cd['five_euros']).save()
if cd['ten_euros']:
CashRegisterSummaryItem(cash_summary=summary, value=10, quantity=cd['ten_euros']).save()
if cd['twenty_euros']:
CashRegisterSummaryItem(cash_summary=summary, value=20, quantity=cd['twenty_euros']).save()
if cd['fifty_euros']:
CashRegisterSummaryItem(cash_summary=summary, value=50, quantity=cd['fifty_euros']).save()
if cd['hundred_euros']:
CashRegisterSummaryItem(cash_summary=summary, value=100, quantity=cd['hundred_euros']).save()
# Checks # Checks
if cd['check_1_quantity']: CashRegisterSummaryItem(cash_summary=summary, value=cd['check_1_value'], if cd['check_1_quantity']:
quantity=cd['check_1_quantity'], check=True).save() CashRegisterSummaryItem(cash_summary=summary, value=cd['check_1_value'],
if cd['check_2_quantity']: CashRegisterSummaryItem(cash_summary=summary, value=cd['check_2_value'], quantity=cd['check_1_quantity'], check=True).save()
quantity=cd['check_2_quantity'], check=True).save() if cd['check_2_quantity']:
if cd['check_3_quantity']: CashRegisterSummaryItem(cash_summary=summary, value=cd['check_3_value'], CashRegisterSummaryItem(cash_summary=summary, value=cd['check_2_value'],
quantity=cd['check_3_quantity'], check=True).save() quantity=cd['check_2_quantity'], check=True).save()
if cd['check_4_quantity']: CashRegisterSummaryItem(cash_summary=summary, value=cd['check_4_value'], if cd['check_3_quantity']:
quantity=cd['check_4_quantity'], check=True).save() CashRegisterSummaryItem(cash_summary=summary, value=cd['check_3_value'],
if cd['check_5_quantity']: CashRegisterSummaryItem(cash_summary=summary, value=cd['check_5_value'], quantity=cd['check_3_quantity'], check=True).save()
quantity=cd['check_5_quantity'], check=True).save() if cd['check_4_quantity']:
CashRegisterSummaryItem(cash_summary=summary, value=cd['check_4_value'],
quantity=cd['check_4_quantity'], check=True).save()
if cd['check_5_quantity']:
CashRegisterSummaryItem(cash_summary=summary, value=cd['check_5_value'],
quantity=cd['check_5_quantity'], check=True).save()
if summary.items.count() < 1: if summary.items.count() < 1:
summary.delete() summary.delete()
class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView): class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView):
""" """
Provide the last operations to allow barmen to delete them Provide the last operations to allow barmen to delete them
@ -878,10 +923,10 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView):
""" """
self.object = self.get_object() self.object = self.get_object()
if (self.object.get_barmen_list() and 'counter_token' in request.session.keys() and if (self.object.get_barmen_list() and 'counter_token' in request.session.keys() and
request.session['counter_token'] and # check if not null for counters that have no token set request.session['counter_token'] and # check if not null for counters that have no token set
Counter.objects.filter(token=request.session['counter_token']).exists()): Counter.objects.filter(token=request.session['counter_token']).exists()):
return super(CounterLastOperationsView, self).dispatch(request, *args, **kwargs) return super(CounterLastOperationsView, self).dispatch(request, *args, **kwargs)
return HttpResponseRedirect(reverse('counter:details', kwargs={'counter_id': self.object.id})+'?bad_location') return HttpResponseRedirect(reverse('counter:details', kwargs={'counter_id': self.object.id}) + '?bad_location')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Add form to the context """ """Add form to the context """
@ -891,6 +936,7 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView):
kwargs['last_sellings'] = self.object.sellings.filter(date__gte=threshold).order_by('-id')[:20] kwargs['last_sellings'] = self.object.sellings.filter(date__gte=threshold).order_by('-id')[:20]
return kwargs return kwargs
class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView):
""" """
Provide the cash summary form Provide the cash summary form
@ -906,10 +952,10 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView):
""" """
self.object = self.get_object() self.object = self.get_object()
if (self.object.get_barmen_list() and 'counter_token' in request.session.keys() and if (self.object.get_barmen_list() and 'counter_token' in request.session.keys() and
request.session['counter_token'] and # check if not null for counters that have no token set request.session['counter_token'] and # check if not null for counters that have no token set
Counter.objects.filter(token=request.session['counter_token']).exists()): Counter.objects.filter(token=request.session['counter_token']).exists()):
return super(CounterCashSummaryView, self).dispatch(request, *args, **kwargs) return super(CounterCashSummaryView, self).dispatch(request, *args, **kwargs)
return HttpResponseRedirect(reverse('counter:details', kwargs={'counter_id': self.object.id})+'?bad_location') return HttpResponseRedirect(reverse('counter:details', kwargs={'counter_id': self.object.id}) + '?bad_location')
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
@ -933,6 +979,7 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView):
kwargs['form'] = self.form kwargs['form'] = self.form
return kwargs return kwargs
class CounterActivityView(DetailView): class CounterActivityView(DetailView):
""" """
Show the bar activity Show the bar activity
@ -941,6 +988,7 @@ class CounterActivityView(DetailView):
pk_url_kwarg = "counter_id" pk_url_kwarg = "counter_id"
template_name = 'counter/activity.jinja' template_name = 'counter/activity.jinja'
class CounterStatView(DetailView, CounterAdminMixin): class CounterStatView(DetailView, CounterAdminMixin):
""" """
Show the bar stats Show the bar stats
@ -957,40 +1005,40 @@ class CounterStatView(DetailView, CounterAdminMixin):
kwargs['User'] = User kwargs['User'] = User
semester_start = Subscription.compute_start(d=date.today(), duration=3) semester_start = Subscription.compute_start(d=date.today(), duration=3)
kwargs['total_sellings'] = Selling.objects.filter(date__gte=semester_start, kwargs['total_sellings'] = Selling.objects.filter(date__gte=semester_start,
counter=self.object).aggregate(total_sellings=Sum(F('quantity')*F('unit_price'), counter=self.object).aggregate(total_sellings=Sum(F('quantity') * F('unit_price'),
output_field=CurrencyField()))['total_sellings'] output_field=CurrencyField()))['total_sellings']
kwargs['top'] = Selling.objects.values('customer__user').annotate( kwargs['top'] = Selling.objects.values('customer__user').annotate(
selling_sum=Sum( selling_sum=Sum(
Case(When(counter=self.object, Case(When(counter=self.object,
date__gte=semester_start, date__gte=semester_start,
unit_price__gt=0, unit_price__gt=0,
then=F('unit_price')*F('quantity')), then=F('unit_price') * F('quantity')),
output_field=CurrencyField() output_field=CurrencyField()
) )
) )
).exclude(selling_sum=None).order_by('-selling_sum').all()[:100] ).exclude(selling_sum=None).order_by('-selling_sum').all()[:100]
kwargs['top_barman'] = Permanency.objects.values('user').annotate( kwargs['top_barman'] = Permanency.objects.values('user').annotate(
perm_sum=Sum( perm_sum=Sum(
Case(When(counter=self.object, Case(When(counter=self.object,
end__gt=datetime(year=1999, month=1, day=1), end__gt=datetime(year=1999, month=1, day=1),
then=F('end')-F('start')), then=F('end') - F('start')),
output_field=models.DateTimeField() output_field=models.DateTimeField()
) )
) )
).exclude(perm_sum=None).order_by('-perm_sum').all()[:100] ).exclude(perm_sum=None).order_by('-perm_sum').all()[:100]
kwargs['top_barman_semester'] = Permanency.objects.values('user').annotate( kwargs['top_barman_semester'] = Permanency.objects.values('user').annotate(
perm_sum=Sum( perm_sum=Sum(
Case(When(counter=self.object, Case(When(counter=self.object,
start__gt=semester_start, start__gt=semester_start,
end__gt=datetime(year=1999, month=1, day=1), end__gt=datetime(year=1999, month=1, day=1),
then=F('end')-F('start')), then=F('end') - F('start')),
output_field=models.DateTimeField() output_field=models.DateTimeField()
) )
) )
).exclude(perm_sum=None).order_by('-perm_sum').all()[:100] ).exclude(perm_sum=None).order_by('-perm_sum').all()[:100]
kwargs['sith_date']=settings.SITH_START_DATE[0] kwargs['sith_date'] = settings.SITH_START_DATE[0]
kwargs['semester_start']=semester_start kwargs['semester_start'] = semester_start
return kwargs return kwargs
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
@ -999,11 +1047,12 @@ class CounterStatView(DetailView, CounterAdminMixin):
except: except:
if (request.user.is_root if (request.user.is_root
or request.user.is_board_member or request.user.is_board_member
or self.object.is_owned_by(request.user)): or self.object.is_owned_by(request.user)):
return super(CanEditMixin, self).dispatch(request, *args, **kwargs) return super(CanEditMixin, self).dispatch(request, *args, **kwargs)
raise PermissionDenied raise PermissionDenied
class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
"""Edit cash summaries""" """Edit cash summaries"""
model = CashRegisterSummary model = CashRegisterSummary
template_name = 'counter/cash_register_summary.jinja' template_name = 'counter/cash_register_summary.jinja'
@ -1015,10 +1064,12 @@ class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView)
def get_success_url(self): def get_success_url(self):
return reverse('counter:cash_summary_list') return reverse('counter:cash_summary_list')
class CashSummaryFormBase(forms.Form): class CashSummaryFormBase(forms.Form):
begin_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Begin date"), required=False, widget=SelectDateTime) begin_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Begin date"), required=False, widget=SelectDateTime)
end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), required=False, widget=SelectDateTime) end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), required=False, widget=SelectDateTime)
class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
"""Display a list of cash summaries""" """Display a list of cash summaries"""
model = CashRegisterSummary model = CashRegisterSummary
@ -1047,7 +1098,7 @@ class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
refillings = refillings.filter(date__gt=last_summary.date) refillings = refillings.filter(date__gt=last_summary.date)
cashredistersummaries = cashredistersummaries.filter(date__gt=last_summary.date) cashredistersummaries = cashredistersummaries.filter(date__gt=last_summary.date)
else: else:
refillings = refillings.filter(date__gte=datetime(year=1994, month=5, day=17, tzinfo=pytz.UTC)) # My birth date should be old enough refillings = refillings.filter(date__gte=datetime(year=1994, month=5, day=17, tzinfo=pytz.UTC)) # My birth date should be old enough
cashredistersummaries = cashredistersummaries.filter(date__gte=datetime(year=1994, month=5, day=17, tzinfo=pytz.UTC)) cashredistersummaries = cashredistersummaries.filter(date__gte=datetime(year=1994, month=5, day=17, tzinfo=pytz.UTC))
if form.is_valid() and form.cleaned_data['end_date']: if form.is_valid() and form.cleaned_data['end_date']:
refillings = refillings.filter(date__lte=form.cleaned_data['end_date']) refillings = refillings.filter(date__lte=form.cleaned_data['end_date'])
@ -1056,6 +1107,7 @@ class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
kwargs['refilling_sums'][c.name] = sum([s.amount for s in refillings.all()]) kwargs['refilling_sums'][c.name] = sum([s.amount for s in refillings.all()])
return kwargs return kwargs
class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView): class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView):
template_name = 'counter/invoices_call.jinja' template_name = 'counter/invoices_call.jinja'
current_tab = 'invoices_call' current_tab = 'invoices_call'
@ -1069,24 +1121,25 @@ class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView):
try: try:
start_date = datetime.strptime(self.request.GET['month'], '%Y-%m') start_date = datetime.strptime(self.request.GET['month'], '%Y-%m')
except: except:
start_date = datetime(year=timezone.now().year, month=(timezone.now().month+10)%12+1, day=1) start_date = datetime(year=timezone.now().year, month=(timezone.now().month + 10) % 12 + 1, day=1)
start_date = start_date.replace(tzinfo=pytz.UTC) start_date = start_date.replace(tzinfo=pytz.UTC)
end_date = (start_date + timedelta(days=32)).replace(day=1, hour=0, minute=0, microsecond=0) end_date = (start_date + timedelta(days=32)).replace(day=1, hour=0, minute=0, microsecond=0)
from django.db.models import Sum, Case, When, F, DecimalField from django.db.models import Sum, Case, When, F, DecimalField
kwargs['sum_cb'] = sum([r.amount for r in Refilling.objects.filter(payment_method='CARD', is_validated=True, kwargs['sum_cb'] = sum([r.amount for r in Refilling.objects.filter(payment_method='CARD', is_validated=True,
date__gte=start_date, date__lte=end_date)]) date__gte=start_date, date__lte=end_date)])
kwargs['sum_cb'] += sum([s.quantity*s.unit_price for s in Selling.objects.filter(payment_method='CARD', is_validated=True, kwargs['sum_cb'] += sum([s.quantity * s.unit_price for s in Selling.objects.filter(payment_method='CARD', is_validated=True,
date__gte=start_date, date__lte=end_date)]) date__gte=start_date, date__lte=end_date)])
kwargs['start_date'] = start_date kwargs['start_date'] = start_date
kwargs['sums'] = Selling.objects.values('club__name').annotate(selling_sum=Sum( kwargs['sums'] = Selling.objects.values('club__name').annotate(selling_sum=Sum(
Case(When(date__gte=start_date, Case(When(date__gte=start_date,
date__lt=end_date, date__lt=end_date,
then=F('unit_price')*F('quantity')), then=F('unit_price') * F('quantity')),
output_field=CurrencyField() output_field=CurrencyField()
) )
)).exclude(selling_sum=None).order_by('-selling_sum') )).exclude(selling_sum=None).order_by('-selling_sum')
return kwargs return kwargs
class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
""" """
A list view for the admins A list view for the admins
@ -1096,15 +1149,17 @@ class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
ordering = ['id'] ordering = ['id']
current_tab = "etickets" current_tab = "etickets"
class EticketForm(forms.ModelForm): class EticketForm(forms.ModelForm):
class Meta: class Meta:
model = Eticket model = Eticket
fields = ['product', 'banner', 'event_title', 'event_date'] fields = ['product', 'banner', 'event_title', 'event_date']
widgets = { widgets = {
'event_date': SelectDate, 'event_date': SelectDate,
} }
product = AutoCompleteSelectField('products', show_help_text=False, label=_("Product"), required=True) product = AutoCompleteSelectField('products', show_help_text=False, label=_("Product"), required=True)
class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
""" """
Create an eticket Create an eticket
@ -1114,6 +1169,7 @@ class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
form_class = EticketForm form_class = EticketForm
current_tab = "etickets" current_tab = "etickets"
class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
""" """
Edit an eticket Edit an eticket
@ -1124,6 +1180,7 @@ class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
pk_url_kwarg = "eticket_id" pk_url_kwarg = "eticket_id"
current_tab = "etickets" current_tab = "etickets"
class EticketPDFView(CanViewMixin, DetailView): class EticketPDFView(CanViewMixin, DetailView):
""" """
Display the PDF of an eticket Display the PDF of an eticket
@ -1172,7 +1229,7 @@ class EticketPDFView(CanViewMixin, DetailView):
p.drawCentredString(10.5 * cm, 23.6 * cm, eticket.event_title) p.drawCentredString(10.5 * cm, 23.6 * cm, eticket.event_title)
if eticket.event_date: if eticket.event_date:
p.setFont("Helvetica-Bold", 16) p.setFont("Helvetica-Bold", 16)
p.drawCentredString(10.5 * cm, 22.6 * cm, eticket.event_date.strftime("%d %b %Y")) # FIXME with a locale p.drawCentredString(10.5 * cm, 22.6 * cm, eticket.event_date.strftime("%d %b %Y")) # FIXME with a locale
p.setFont("Helvetica-Bold", 14) p.setFont("Helvetica-Bold", 14)
p.drawCentredString(10.5 * cm, 15 * cm, "%s : %d %s" % (user.get_display_name(), self.object.quantity, str(_("people(s)")))) p.drawCentredString(10.5 * cm, 15 * cm, "%s : %d %s" % (user.get_display_name(), self.object.quantity, str(_("people(s)"))))
p.setFont("Courier-Bold", 14) p.setFont("Courier-Bold", 14)
@ -1180,7 +1237,7 @@ class EticketPDFView(CanViewMixin, DetailView):
bounds = qrcode.getBounds() bounds = qrcode.getBounds()
width = bounds[2] - bounds[0] width = bounds[2] - bounds[0]
height = bounds[3] - bounds[1] height = bounds[3] - bounds[1]
d = Drawing(260, 260, transform=[260./width, 0, 0, 260./height, 0, 0]) d = Drawing(260, 260, transform=[260. / width, 0, 0, 260. / height, 0, 0])
d.add(qrcode) d.add(qrcode)
renderPDF.draw(d, p, 10.5 * cm - 130, 6.1 * cm) renderPDF.draw(d, p, 10.5 * cm - 130, 6.1 * cm)
p.drawCentredString(10.5 * cm, 6 * cm, code) p.drawCentredString(10.5 * cm, 6 * cm, code)
@ -1195,4 +1252,3 @@ class EticketPDFView(CanViewMixin, DetailView):
p.showPage() p.showPage()
p.save() p.save()
return response return response

View File

@ -27,9 +27,9 @@ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
from accounting.models import CurrencyField from accounting.models import CurrencyField
from counter.models import Counter, Product, Customer, Selling, Refilling from counter.models import Counter, Product, Selling, Refilling
from core.models import User from core.models import User
from subscription.models import Subscription
class Basket(models.Model): class Basket(models.Model):
""" """
@ -38,16 +38,16 @@ class Basket(models.Model):
user = models.ForeignKey(User, related_name='baskets', verbose_name=_('user'), blank=False) user = models.ForeignKey(User, related_name='baskets', verbose_name=_('user'), blank=False)
date = models.DateTimeField(_('date'), auto_now=True) date = models.DateTimeField(_('date'), auto_now=True)
def add_product(self, p, q = 1): def add_product(self, p, q=1):
item = self.items.filter(product_id=p.id).first() item = self.items.filter(product_id=p.id).first()
if item is None: if item is None:
BasketItem(basket=self, product_id=p.id, product_name=p.name, type_id=p.product_type.id, BasketItem(basket=self, product_id=p.id, product_name=p.name, type_id=p.product_type.id,
quantity=q, product_unit_price=p.selling_price).save() quantity=q, product_unit_price=p.selling_price).save()
else: else:
item.quantity += q item.quantity += q
item.save() item.save()
def del_product(self, p, q = 1): def del_product(self, p, q=1):
item = self.items.filter(product_id=p.id).first() item = self.items.filter(product_id=p.id).first()
if item is not None: if item is not None:
item.quantity -= q item.quantity -= q
@ -64,6 +64,7 @@ class Basket(models.Model):
def __str__(self): def __str__(self):
return "%s's basket (%d items)" % (self.user, self.items.all().count()) return "%s's basket (%d items)" % (self.user, self.items.all().count())
class Invoice(models.Model): class Invoice(models.Model):
""" """
Invoices are generated once the payment has been validated Invoices are generated once the payment has been validated
@ -92,34 +93,35 @@ class Invoice(models.Model):
for i in self.items.all(): for i in self.items.all():
if i.type_id == settings.SITH_COUNTER_PRODUCTTYPE_REFILLING: if i.type_id == settings.SITH_COUNTER_PRODUCTTYPE_REFILLING:
new = Refilling( new = Refilling(
counter=eboutic, counter=eboutic,
customer=self.user.customer, customer=self.user.customer,
operator=self.user, operator=self.user,
amount=i.product_unit_price * i.quantity, amount=i.product_unit_price * i.quantity,
payment_method="CARD", payment_method="CARD",
bank="OTHER", bank="OTHER",
date=self.date, date=self.date,
) )
new.save() new.save()
else: else:
product = Product.objects.filter(id=i.product_id).first() product = Product.objects.filter(id=i.product_id).first()
new = Selling( new = Selling(
label=i.product_name, label=i.product_name,
counter=eboutic, counter=eboutic,
club=product.club, club=product.club,
product=product, product=product,
seller=self.user, seller=self.user,
customer=self.user.customer, customer=self.user.customer,
unit_price=i.product_unit_price, unit_price=i.product_unit_price,
quantity=i.quantity, quantity=i.quantity,
payment_method="CARD", payment_method="CARD",
is_validated=True, is_validated=True,
date=self.date, date=self.date,
) )
new.save() new.save()
self.validated = True self.validated = True
self.save() self.save()
class AbstractBaseItem(models.Model): class AbstractBaseItem(models.Model):
product_id = models.IntegerField(_('product id')) product_id = models.IntegerField(_('product id'))
product_name = models.CharField(_('product name'), max_length=255) product_name = models.CharField(_('product name'), max_length=255)
@ -133,8 +135,10 @@ class AbstractBaseItem(models.Model):
def __str__(self): def __str__(self):
return "Item: %s (%s) x%d" % (self.product_name, self.product_unit_price, self.quantity) return "Item: %s (%s) x%d" % (self.product_name, self.product_unit_price, self.quantity)
class BasketItem(AbstractBaseItem): class BasketItem(AbstractBaseItem):
basket = models.ForeignKey(Basket, related_name='items', verbose_name=_('basket')) basket = models.ForeignKey(Basket, related_name='items', verbose_name=_('basket'))
class InvoiceItem(AbstractBaseItem): class InvoiceItem(AbstractBaseItem):
invoice = models.ForeignKey(Invoice, related_name='items', verbose_name=_('invoice')) invoice = models.ForeignKey(Invoice, related_name='items', verbose_name=_('invoice'))

View File

@ -34,7 +34,8 @@ from django.core.management import call_command
from django.conf import settings from django.conf import settings
from core.models import User from core.models import User
from counter.models import Customer, ProductType, Product, Counter, Refilling from counter.models import Product, Counter, Refilling
class EbouticTest(TestCase): class EbouticTest(TestCase):
def setUp(self): def setUp(self):
@ -74,25 +75,24 @@ class EbouticTest(TestCase):
"action": "add_product", "action": "add_product",
"product_id": self.barbar.id}) "product_id": self.barbar.id})
self.assertTrue("<input type=\"hidden\" name=\"action\" value=\"add_product\">\\n" self.assertTrue("<input type=\"hidden\" name=\"action\" value=\"add_product\">\\n"
" <button type=\"submit\" name=\"product_id\" value=\"4\"> + </button>\\n" " <button type=\"submit\" name=\"product_id\" value=\"4\"> + </button>\\n"
"</form>\\n Barbar: 1.70 \\xe2\\x82\\xac</li>" in str(response.content)) "</form>\\n Barbar: 1.70 \\xe2\\x82\\xac</li>" in str(response.content))
response = self.client.post(reverse("eboutic:command")) response = self.client.post(reverse("eboutic:command"))
self.assertTrue("<tr>\\n <td>Barbar</td>\\n <td>1</td>\\n" self.assertTrue("<tr>\\n <td>Barbar</td>\\n <td>1</td>\\n"
" <td>1.70 \\xe2\\x82\\xac</td>\\n </tr>" in str(response.content)) " <td>1.70 \\xe2\\x82\\xac</td>\\n </tr>" in str(response.content))
response = self.client.post(reverse("eboutic:pay_with_sith"), { response = self.client.post(reverse("eboutic:pay_with_sith"), {
"action": "pay_with_sith_account" "action": "pay_with_sith_account"
}) })
self.assertTrue("Le paiement a \\xc3\\xa9t\\xc3\\xa9 effectu\\xc3\\xa9\\n" in str(response.content)) self.assertTrue("Le paiement a \\xc3\\xa9t\\xc3\\xa9 effectu\\xc3\\xa9\\n" in str(response.content))
response = self.client.get(reverse("core:user_account_detail", kwargs={ response = self.client.get(reverse("core:user_account_detail", kwargs={
"user_id": self.subscriber.id, "user_id": self.subscriber.id,
"year": datetime.now().year, "year": datetime.now().year,
"month": datetime.now().month, "month": datetime.now().month,
})) }))
self.assertTrue("class=\"selected_tab\">Compte (8.30 \\xe2\\x82\\xac)</a>" in str(response.content)) self.assertTrue("class=\"selected_tab\">Compte (8.30 \\xe2\\x82\\xac)</a>" in str(response.content))
self.assertTrue("<td>Eboutic</td>\\n <td><a href=\"/user/3/\">Subscribed User</a></td>\\n" self.assertTrue("<td>Eboutic</td>\\n <td><a href=\"/user/3/\">Subscribed User</a></td>\\n"
" <td>Barbar</td>\\n <td>1</td>\\n <td>1.70 \\xe2\\x82\\xac</td>\\n" " <td>Barbar</td>\\n <td>1</td>\\n <td>1.70 \\xe2\\x82\\xac</td>\\n"
" <td>Compte utilisateur</td>" in str(response.content)) " <td>Compte utilisateur</td>" in str(response.content))
def test_buy_simple_product_with_credit_card(self): def test_buy_simple_product_with_credit_card(self):
self.client.login(username='subscriber', password='plop') self.client.login(username='subscriber', password='plop')
@ -100,11 +100,11 @@ class EbouticTest(TestCase):
"action": "add_product", "action": "add_product",
"product_id": self.barbar.id}) "product_id": self.barbar.id})
self.assertTrue("<input type=\"hidden\" name=\"action\" value=\"add_product\">\\n" self.assertTrue("<input type=\"hidden\" name=\"action\" value=\"add_product\">\\n"
" <button type=\"submit\" name=\"product_id\" value=\"4\"> + </button>\\n" " <button type=\"submit\" name=\"product_id\" value=\"4\"> + </button>\\n"
"</form>\\n Barbar: 1.70 \\xe2\\x82\\xac</li>" in str(response.content)) "</form>\\n Barbar: 1.70 \\xe2\\x82\\xac</li>" in str(response.content))
response = self.client.post(reverse("eboutic:command")) response = self.client.post(reverse("eboutic:command"))
self.assertTrue("<tr>\\n <td>Barbar</td>\\n <td>1</td>\\n" self.assertTrue("<tr>\\n <td>Barbar</td>\\n <td>1</td>\\n"
" <td>1.70 \\xe2\\x82\\xac</td>\\n </tr>" in str(response.content)) " <td>1.70 \\xe2\\x82\\xac</td>\\n </tr>" in str(response.content))
response = self.generate_bank_valid_answer_from_page_content(response.content) response = self.generate_bank_valid_answer_from_page_content(response.content)
@ -112,11 +112,11 @@ class EbouticTest(TestCase):
"user_id": self.subscriber.id, "user_id": self.subscriber.id,
"year": datetime.now().year, "year": datetime.now().year,
"month": datetime.now().month, "month": datetime.now().month,
})) }))
self.assertTrue("class=\"selected_tab\">Compte (0.00 \\xe2\\x82\\xac)</a>" in str(response.content)) self.assertTrue("class=\"selected_tab\">Compte (0.00 \\xe2\\x82\\xac)</a>" in str(response.content))
self.assertTrue("<td>Eboutic</td>\\n <td><a href=\"/user/3/\">Subscribed User</a></td>\\n" self.assertTrue("<td>Eboutic</td>\\n <td><a href=\"/user/3/\">Subscribed User</a></td>\\n"
" <td>Barbar</td>\\n <td>1</td>\\n <td>1.70 \\xe2\\x82\\xac</td>\\n" " <td>Barbar</td>\\n <td>1</td>\\n <td>1.70 \\xe2\\x82\\xac</td>\\n"
" <td>Carte bancaire</td>" in str(response.content)) " <td>Carte bancaire</td>" in str(response.content))
def test_buy_refill_product_with_credit_card(self): def test_buy_refill_product_with_credit_card(self):
self.client.login(username='subscriber', password='plop') self.client.login(username='subscriber', password='plop')
@ -124,11 +124,11 @@ class EbouticTest(TestCase):
"action": "add_product", "action": "add_product",
"product_id": self.refill.id}) "product_id": self.refill.id})
self.assertTrue("<input type=\"hidden\" name=\"action\" value=\"add_product\">\\n" self.assertTrue("<input type=\"hidden\" name=\"action\" value=\"add_product\">\\n"
" <button type=\"submit\" name=\"product_id\" value=\"3\"> + </button>\\n" " <button type=\"submit\" name=\"product_id\" value=\"3\"> + </button>\\n"
"</form>\\n Rechargement 15 \\xe2\\x82\\xac: 15.00 \\xe2\\x82\\xac</li>" in str(response.content)) "</form>\\n Rechargement 15 \\xe2\\x82\\xac: 15.00 \\xe2\\x82\\xac</li>" in str(response.content))
response = self.client.post(reverse("eboutic:command")) response = self.client.post(reverse("eboutic:command"))
self.assertTrue("<tr>\\n <td>Rechargement 15 \\xe2\\x82\\xac</td>\\n <td>1</td>\\n" self.assertTrue("<tr>\\n <td>Rechargement 15 \\xe2\\x82\\xac</td>\\n <td>1</td>\\n"
" <td>15.00 \\xe2\\x82\\xac</td>\\n </tr>" in str(response.content)) " <td>15.00 \\xe2\\x82\\xac</td>\\n </tr>" in str(response.content))
response = self.generate_bank_valid_answer_from_page_content(response.content) response = self.generate_bank_valid_answer_from_page_content(response.content)
@ -136,12 +136,12 @@ class EbouticTest(TestCase):
"user_id": self.subscriber.id, "user_id": self.subscriber.id,
"year": datetime.now().year, "year": datetime.now().year,
"month": datetime.now().month, "month": datetime.now().month,
})) }))
self.assertTrue("class=\"selected_tab\">Compte (15.00 \\xe2\\x82\\xac)</a>" in str(response.content)) self.assertTrue("class=\"selected_tab\">Compte (15.00 \\xe2\\x82\\xac)</a>" in str(response.content))
self.assertTrue("<td>\\n <ul>\\n \\n " self.assertTrue("<td>\\n <ul>\\n \\n "
"<li>1 x Rechargement 15 \\xe2\\x82\\xac - 15.00 \\xe2\\x82\\xac</li>\\n" "<li>1 x Rechargement 15 \\xe2\\x82\\xac - 15.00 \\xe2\\x82\\xac</li>\\n"
" \\n </ul>\\n </td>\\n" " \\n </ul>\\n </td>\\n"
" <td>15.00 \\xe2\\x82\\xac</td>" in str(response.content)) " <td>15.00 \\xe2\\x82\\xac</td>" in str(response.content))
def test_buy_subscribe_product_with_credit_card(self): def test_buy_subscribe_product_with_credit_card(self):
self.client.login(username='old_subscriber', password='plop') self.client.login(username='old_subscriber', password='plop')
@ -151,11 +151,11 @@ class EbouticTest(TestCase):
"action": "add_product", "action": "add_product",
"product_id": self.cotis.id}) "product_id": self.cotis.id})
self.assertTrue("<input type=\"hidden\" name=\"action\" value=\"add_product\">\\n" self.assertTrue("<input type=\"hidden\" name=\"action\" value=\"add_product\">\\n"
" <button type=\"submit\" name=\"product_id\" value=\"1\"> + </button>\\n" " <button type=\"submit\" name=\"product_id\" value=\"1\"> + </button>\\n"
"</form>\\n Cotis 1 semestre: 15.00 \\xe2\\x82\\xac</li>" in str(response.content)) "</form>\\n Cotis 1 semestre: 15.00 \\xe2\\x82\\xac</li>" in str(response.content))
response = self.client.post(reverse("eboutic:command")) response = self.client.post(reverse("eboutic:command"))
self.assertTrue("<tr>\\n <td>Cotis 1 semestre</td>\\n <td>1</td>\\n" self.assertTrue("<tr>\\n <td>Cotis 1 semestre</td>\\n <td>1</td>\\n"
" <td>15.00 \\xe2\\x82\\xac</td>\\n </tr>" in str(response.content)) " <td>15.00 \\xe2\\x82\\xac</td>\\n </tr>" in str(response.content))
response = self.generate_bank_valid_answer_from_page_content(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, "user_id": self.old_subscriber.id,
"year": datetime.now().year, "year": datetime.now().year,
"month": datetime.now().month, "month": datetime.now().month,
})) }))
self.assertTrue("class=\"selected_tab\">Compte (0.00 \\xe2\\x82\\xac)</a>" in str(response.content)) self.assertTrue("class=\"selected_tab\">Compte (0.00 \\xe2\\x82\\xac)</a>" in str(response.content))
self.assertTrue("<td>\\n <ul>\\n \\n " self.assertTrue("<td>\\n <ul>\\n \\n "
"<li>1 x Cotis 1 semestre - 15.00 \\xe2\\x82\\xac</li>\\n" "<li>1 x Cotis 1 semestre - 15.00 \\xe2\\x82\\xac</li>\\n"
" \\n </ul>\\n </td>\\n" " \\n </ul>\\n </td>\\n"
" <td>15.00 \\xe2\\x82\\xac</td>" in str(response.content)) " <td>15.00 \\xe2\\x82\\xac</td>" in str(response.content))
response = self.client.get(reverse("core:user_profile", kwargs={"user_id": self.old_subscriber.id})) response = self.client.get(reverse("core:user_profile", kwargs={"user_id": self.old_subscriber.id}))
self.assertTrue("Cotisant jusqu\\'au" in str(response.content)) self.assertTrue("Cotisant jusqu\\'au" in str(response.content))

View File

@ -22,7 +22,7 @@
# #
# #
from django.conf.urls import url, include from django.conf.urls import url
from eboutic.views import * from eboutic.views import *
@ -33,6 +33,3 @@ urlpatterns = [
url(r'^pay$', EbouticPayWithSith.as_view(), name='pay_with_sith'), url(r'^pay$', EbouticPayWithSith.as_view(), name='pay_with_sith'),
url(r'^et_autoanswer$', EtransactionAutoAnswer.as_view(), name='etransation_autoanswer'), url(r'^et_autoanswer$', EtransactionAutoAnswer.as_view(), name='etransation_autoanswer'),
] ]

View File

@ -24,29 +24,27 @@
from collections import OrderedDict from collections import OrderedDict
from datetime import datetime from datetime import datetime
import pytz
import hmac import hmac
import base64 import base64
from OpenSSL import crypto from OpenSSL import crypto
from django.shortcuts import render
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.views.generic import TemplateView, View from django.views.generic import TemplateView, View
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation
from django.shortcuts import render
from django.db import transaction, DataError from django.db import transaction, DataError
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.conf import settings from django.conf import settings
from counter.models import Customer, Counter, ProductType, Selling 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): class EbouticMain(TemplateView):
template_name = 'eboutic/eboutic_main.jinja' template_name = 'eboutic/eboutic_main.jinja'
def make_basket(self, request): 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 = Basket(user=request.user)
self.basket.save() self.basket.save()
else: else:
@ -60,7 +58,7 @@ class EbouticMain(TemplateView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if not request.user.is_authenticated(): if not request.user.is_authenticated():
return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + 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.object = Counter.objects.filter(type="EBOUTIC").first()
self.make_basket(request) self.make_basket(request)
return super(EbouticMain, self).get(request, *args, **kwargs) return super(EbouticMain, self).get(request, *args, **kwargs)
@ -68,7 +66,7 @@ class EbouticMain(TemplateView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if not request.user.is_authenticated(): if not request.user.is_authenticated():
return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + 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.object = Counter.objects.filter(type="EBOUTIC").first()
self.make_basket(request) self.make_basket(request)
if 'add_product' in request.POST['action']: if 'add_product' in request.POST['action']:
@ -77,7 +75,6 @@ class EbouticMain(TemplateView):
self.del_product(request) self.del_product(request)
return self.render_to_response(self.get_context_data(**kwargs)) return self.render_to_response(self.get_context_data(**kwargs))
def add_product(self, request): def add_product(self, request):
""" Add a product to the basket """ """ Add a product to the basket """
try: try:
@ -108,19 +105,20 @@ class EbouticMain(TemplateView):
kwargs['categories'] = kwargs['categories'].exclude(id=settings.SITH_PRODUCTTYPE_SUBSCRIPTION) kwargs['categories'] = kwargs['categories'].exclude(id=settings.SITH_PRODUCTTYPE_SUBSCRIPTION)
return kwargs return kwargs
class EbouticCommand(TemplateView): class EbouticCommand(TemplateView):
template_name = 'eboutic/eboutic_makecommand.jinja' template_name = 'eboutic/eboutic_makecommand.jinja'
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if not request.user.is_authenticated(): if not request.user.is_authenticated():
return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + 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)) return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs))
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if not request.user.is_authenticated(): if not request.user.is_authenticated():
return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" +
request.path) request.path)
if 'basket_id' not in request.session.keys(): if 'basket_id' not in request.session.keys():
return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs))
self.basket = Basket.objects.filter(id=request.session['basket_id']).first() 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_SITE'] = settings.SITH_EBOUTIC_PBX_SITE
kwargs['et_request']['PBX_RANG'] = settings.SITH_EBOUTIC_PBX_RANG kwargs['et_request']['PBX_RANG'] = settings.SITH_EBOUTIC_PBX_RANG
kwargs['et_request']['PBX_IDENTIFIANT'] = settings.SITH_EBOUTIC_PBX_IDENTIFIANT 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_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_DEVISE'] = 978 # This is Euro. ET support only this value anyway
kwargs['et_request']['PBX_CMD'] = self.basket.id kwargs['et_request']['PBX_CMD'] = self.basket.id
kwargs['et_request']['PBX_PORTEUR'] = self.basket.user.email kwargs['et_request']['PBX_PORTEUR'] = self.basket.user.email
kwargs['et_request']['PBX_RETOUR'] = "Amount:M;BasketID:R;Auto:A;Error:E;Sig:K" 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_TYPECARTE'] = "CB"
kwargs['et_request']['PBX_TIME'] = str(datetime.now().replace(microsecond=0).isoformat('T')) 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, 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'), bytes("&".join(["%s=%s" % (k, v) for k, v in kwargs['et_request'].items()]), 'utf-8'),
"sha512").hexdigest().upper() "sha512").hexdigest().upper()
return kwargs return kwargs
class EbouticPayWithSith(TemplateView): class EbouticPayWithSith(TemplateView):
template_name = 'eboutic/eboutic_payment_result.jinja' template_name = 'eboutic/eboutic_payment_result.jinja'
@ -172,30 +171,31 @@ class EbouticPayWithSith(TemplateView):
for it in b.items.all(): for it in b.items.all():
product = eboutic.products.filter(id=it.product_id).first() product = eboutic.products.filter(id=it.product_id).first()
Selling( Selling(
label=it.product_name, label=it.product_name,
counter=eboutic, counter=eboutic,
club=product.club, club=product.club,
product=product, product=product,
seller=c.user, seller=c.user,
customer=c, customer=c,
unit_price=it.product_unit_price, unit_price=it.product_unit_price,
quantity=it.quantity, quantity=it.quantity,
payment_method="SITH_ACCOUNT", payment_method="SITH_ACCOUNT",
).save() ).save()
b.delete() b.delete()
kwargs['not_enough'] = False kwargs['not_enough'] = False
request.session.pop('basket_id', None) request.session.pop('basket_id', None)
except DataError as e: except DataError as e:
kwargs['not_enough'] = True kwargs['not_enough'] = True
return self.render_to_response(self.get_context_data(**kwargs)) return self.render_to_response(self.get_context_data(**kwargs))
class EtransactionAutoAnswer(View): class EtransactionAutoAnswer(View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if (not 'Amount' in request.GET.keys() or if (not 'Amount' in request.GET.keys() or
not 'BasketID' in request.GET.keys() or not 'BasketID' in request.GET.keys() or
not 'Auto' in request.GET.keys() or not 'Auto' in request.GET.keys() or
not 'Error' 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) return HttpResponse("Bad arguments", status=400)
key = crypto.load_publickey(crypto.FILETYPE_PEM, settings.SITH_EBOUTIC_PUB_KEY) key = crypto.load_publickey(crypto.FILETYPE_PEM, settings.SITH_EBOUTIC_PUB_KEY)
cert = crypto.X509() cert = crypto.X509()
@ -217,12 +217,11 @@ class EtransactionAutoAnswer(View):
i.save() i.save()
for it in b.items.all(): for it in b.items.all():
InvoiceItem(invoice=i, product_id=it.product_id, product_name=it.product_name, type_id=it.type_id, 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() i.validate()
b.delete() b.delete()
except Exception as e: 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() return HttpResponse()
else: else:
return HttpResponse("Payment failed with error: "+request.GET['Error'], status=400) return HttpResponse("Payment failed with error: " + request.GET['Error'], status=400)

View File

@ -23,11 +23,9 @@
# #
from django.db import models from django.db import models
from django.core import validators
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import IntegrityError, transaction
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
@ -35,9 +33,10 @@ from django.utils.functional import cached_property
from datetime import datetime from datetime import datetime
import pytz import pytz
from core.models import User, MetaGroup, Group, SithFile from core.models import User, Group
from club.models import Club from club.models import Club
class Forum(models.Model): class Forum(models.Model):
""" """
The Forum class, made as a tree to allow nice tidy organization 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) is_category = models.BooleanField(_('is a category'), default=False)
parent = models.ForeignKey('Forum', related_name='children', null=True, blank=True) parent = models.ForeignKey('Forum', related_name='children', null=True, blank=True)
owner_club = models.ForeignKey(Club, related_name="owned_forums", verbose_name=_("owner club"), 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, 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, 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) number = models.IntegerField(_("number to choose a specific forum ordering"), default=1)
_last_message = models.ForeignKey('ForumMessage', related_name="forums_where_its_last", _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) _topic_number = models.IntegerField(_("number of topics"), default=0)
class Meta: class Meta:
@ -112,16 +111,18 @@ class Forum(models.Model):
self.view_groups = self.parent.view_groups.all() self.view_groups = self.parent.view_groups.all()
self.save() self.save()
_club_memberships = {} # This cache is particularly efficient: _club_memberships = {} # This cache is particularly efficient:
# divided by 3 the number of requests on the main forum page # divided by 3 the number of requests on the main forum page
# after the first load # after the first load
def is_owned_by(self, user): def is_owned_by(self, user):
if user.is_in_group(settings.SITH_GROUP_FORUM_ADMIN_ID): if user.is_in_group(settings.SITH_GROUP_FORUM_ADMIN_ID):
return True return True
try: m = Forum._club_memberships[self.id][user.id] try:
m = Forum._club_memberships[self.id][user.id]
except: except:
m = self.owner_club.get_membership_for(user) 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: except:
Forum._club_memberships[self.id] = {} Forum._club_memberships[self.id] = {}
Forum._club_memberships[self.id][user.id] = m Forum._club_memberships[self.id][user.id] = m
@ -178,12 +179,13 @@ class Forum(models.Model):
l += c.get_children_list() l += c.get_children_list()
return l return l
class ForumTopic(models.Model): class ForumTopic(models.Model):
forum = models.ForeignKey(Forum, related_name='topics') forum = models.ForeignKey(Forum, related_name='topics')
author = models.ForeignKey(User, related_name='forum_topics') author = models.ForeignKey(User, related_name='forum_topics')
description = models.CharField(_('description'), max_length=256, default="") description = models.CharField(_('description'), max_length=256, default="")
_last_message = models.ForeignKey('ForumMessage', related_name="+", verbose_name=_("the last message"), _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) _title = models.CharField(_('title'), max_length=64, blank=True)
_message_number = models.IntegerField(_("number of messages"), default=0) _message_number = models.IntegerField(_("number of messages"), default=0)
@ -192,7 +194,7 @@ class ForumTopic(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
super(ForumTopic, self).save(*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() self.forum.set_last_message()
def is_owned_by(self, user): def is_owned_by(self, user):
@ -225,6 +227,7 @@ class ForumTopic(models.Model):
def title(self): def title(self):
return self._title return self._title
class ForumMessage(models.Model): class ForumMessage(models.Model):
""" """
"A ForumMessage object represents a message in the forum" -- Cpt. Obvious "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) return "%s (%s) - %s" % (self.id, self.author, self.title)
def save(self, *args, **kwargs): 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) super(ForumMessage, self).save(*args, **kwargs)
if self.is_last_in_topic(): if self.is_last_in_topic():
self.topic._last_message_id = self.id self.topic._last_message_id = self.id
@ -259,15 +262,15 @@ class ForumMessage(models.Model):
def is_last_in_topic(self): def is_last_in_topic(self):
return bool(self.id == self.topic.messages.order_by('date').last().id) 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 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 # 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 return self.topic.forum.is_owned_by(user) or user.id == self.author.id
def can_be_edited_by(self, user): def can_be_edited_by(self, user):
return user.can_edit(self.topic.forum) return user.can_edit(self.topic.forum)
def can_be_viewed_by(self, user): 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): def can_be_moderated_by(self, user):
return self.topic.forum.is_owned_by(user) or user.id == self.author.id 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 return int(self.topic.messages.filter(id__lt=self.id).count() / settings.SITH_FORUM_PAGE_LENGTH) + 1
def mark_as_read(self, user): 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): if not self.is_read(user):
self.readers.add(user) self.readers.add(user)
except: pass except:
pass
def is_read(self, user): def is_read(self, user):
return (self.date < user.forum_infos.last_read_date) or (user in self.readers.all()) 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 meta.action == "DELETE"
return False return False
MESSAGE_META_ACTIONS = [ MESSAGE_META_ACTIONS = [
('EDIT', _("Message edited by")), ('EDIT', _("Message edited by")),
('DELETE', _("Message deleted by")), ('DELETE', _("Message deleted by")),
('UNDELETE', _("Message undeleted by")), ('UNDELETE', _("Message undeleted by")),
] ]
class ForumMessageMeta(models.Model): class ForumMessageMeta(models.Model):
user = models.ForeignKey(User, related_name="forum_message_metas") 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") user = models.OneToOneField(User, related_name="_forum_infos")
last_read_date = models.DateTimeField(_('last read date'), default=datetime(year=settings.SITH_SCHOOL_START_YEAR, 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): def __str__(self):
return str(self.user) return str(self.user)

View File

@ -22,7 +22,7 @@
# #
# #
from django.conf.urls import url, include from django.conf.urls import url
from forum.views import * from forum.views import *

View File

@ -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 import ListView, DetailView, RedirectView
from django.views.generic.edit import UpdateView, CreateView, DeleteView from django.views.generic.edit import UpdateView, CreateView, DeleteView
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.utils.translation import ugettext_lazy as _ 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.utils import timezone
from django.conf import settings from django.conf import settings
from django import forms from django import forms
from django.db import models
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 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.views.forms import MarkdownInput
from core.models import Page
from forum.models import Forum, ForumMessage, ForumTopic, ForumMessageMeta from forum.models import Forum, ForumMessage, ForumTopic, ForumMessageMeta
class ForumMainView(ListView): class ForumMainView(ListView):
queryset = Forum.objects.filter(parent=None).prefetch_related("children___last_message__author", "children___last_message__topic") queryset = Forum.objects.filter(parent=None).prefetch_related("children___last_message__author", "children___last_message__topic")
template_name = "forum/main.jinja" template_name = "forum/main.jinja"
class ForumMarkAllAsRead(RedirectView): class ForumMarkAllAsRead(RedirectView):
permanent = False permanent = False
url = reverse_lazy('forum:last_unread') url = reverse_lazy('forum:last_unread')
@ -56,10 +56,12 @@ class ForumMarkAllAsRead(RedirectView):
fi.last_read_date = timezone.now() fi.last_read_date = timezone.now()
fi.save() fi.save()
for m in request.user.read_messages.filter(date__lt=fi.last_read_date): 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 m.readers.remove(request.user) # Clean up to keep table low in data
except: pass except:
pass
return super(ForumMarkAllAsRead, self).get(request, *args, **kwargs) return super(ForumMarkAllAsRead, self).get(request, *args, **kwargs)
class ForumLastUnread(ListView): class ForumLastUnread(ListView):
model = ForumTopic model = ForumTopic
template_name = "forum/last_unread.jinja" template_name = "forum/last_unread.jinja"
@ -67,12 +69,13 @@ class ForumLastUnread(ListView):
def get_queryset(self): def get_queryset(self):
topic_list = self.model.objects.filter(_last_message__date__gt=self.request.user.forum_infos.last_read_date)\ 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)\ .exclude(_last_message__readers=self.request.user)\
.order_by('-_last_message__date')\ .order_by('-_last_message__date')\
.select_related('_last_message__author', 'author')\ .select_related('_last_message__author', 'author')\
.prefetch_related('forum__edit_groups') .prefetch_related('forum__edit_groups')
return topic_list return topic_list
class ForumForm(forms.ModelForm): class ForumForm(forms.ModelForm):
class Meta: class Meta:
model = Forum model = Forum
@ -80,6 +83,7 @@ class ForumForm(forms.ModelForm):
edit_groups = make_ajax_field(Forum, 'edit_groups', 'groups', help_text="") edit_groups = make_ajax_field(Forum, 'edit_groups', 'groups', help_text="")
view_groups = make_ajax_field(Forum, 'view_groups', 'groups', help_text="") view_groups = make_ajax_field(Forum, 'view_groups', 'groups', help_text="")
class ForumCreateView(CanCreateMixin, CreateView): class ForumCreateView(CanCreateMixin, CreateView):
model = Forum model = Forum
form_class = ForumForm form_class = ForumForm
@ -93,12 +97,15 @@ class ForumCreateView(CanCreateMixin, CreateView):
init['owner_club'] = parent.owner_club init['owner_club'] = parent.owner_club
init['edit_groups'] = parent.edit_groups.all() init['edit_groups'] = parent.edit_groups.all()
init['view_groups'] = parent.view_groups.all() init['view_groups'] = parent.view_groups.all()
except: pass except:
pass
return init return init
class ForumEditForm(ForumForm): class ForumEditForm(ForumForm):
recursive = forms.BooleanField(label=_("Apply rights and club owner recursively"), required=False) recursive = forms.BooleanField(label=_("Apply rights and club owner recursively"), required=False)
class ForumEditView(CanEditPropMixin, UpdateView): class ForumEditView(CanEditPropMixin, UpdateView):
model = Forum model = Forum
pk_url_kwarg = "forum_id" pk_url_kwarg = "forum_id"
@ -112,12 +119,14 @@ class ForumEditView(CanEditPropMixin, UpdateView):
self.object.apply_rights_recursively() self.object.apply_rights_recursively()
return ret return ret
class ForumDeleteView(CanEditPropMixin, DeleteView): class ForumDeleteView(CanEditPropMixin, DeleteView):
model = Forum model = Forum
pk_url_kwarg = "forum_id" pk_url_kwarg = "forum_id"
template_name = "core/delete_confirm.jinja" template_name = "core/delete_confirm.jinja"
success_url = reverse_lazy('forum:main') success_url = reverse_lazy('forum:main')
class ForumDetailView(CanViewMixin, DetailView): class ForumDetailView(CanViewMixin, DetailView):
model = Forum model = Forum
template_name = "forum/forum.jinja" template_name = "forum/forum.jinja"
@ -126,10 +135,10 @@ class ForumDetailView(CanViewMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(ForumDetailView, self).get_context_data(**kwargs) kwargs = super(ForumDetailView, self).get_context_data(**kwargs)
qs = self.object.topics.order_by('-_last_message__date')\ qs = self.object.topics.order_by('-_last_message__date')\
.select_related('_last_message__author', 'author')\ .select_related('_last_message__author', 'author')\
.prefetch_related("forum__edit_groups") .prefetch_related("forum__edit_groups")
paginator = Paginator(qs, paginator = Paginator(qs,
settings.SITH_FORUM_PAGE_LENGTH) settings.SITH_FORUM_PAGE_LENGTH)
page = self.request.GET.get('topic_page') page = self.request.GET.get('topic_page')
try: try:
kwargs["topics"] = paginator.page(page) kwargs["topics"] = paginator.page(page)
@ -139,15 +148,17 @@ class ForumDetailView(CanViewMixin, DetailView):
kwargs["topics"] = paginator.page(paginator.num_pages) kwargs["topics"] = paginator.page(paginator.num_pages)
return kwargs return kwargs
class TopicForm(forms.ModelForm): class TopicForm(forms.ModelForm):
class Meta: class Meta:
model = ForumMessage model = ForumMessage
fields = ['title', 'message'] fields = ['title', 'message']
widgets = { widgets = {
'message': MarkdownInput, 'message': MarkdownInput,
} }
title = forms.CharField(required=True, label=_("Title")) title = forms.CharField(required=True, label=_("Title"))
class ForumTopicCreateView(CanCreateMixin, CreateView): class ForumTopicCreateView(CanCreateMixin, CreateView):
model = ForumMessage model = ForumMessage
form_class = TopicForm form_class = TopicForm
@ -166,12 +177,14 @@ class ForumTopicCreateView(CanCreateMixin, CreateView):
form.instance.author = self.request.user form.instance.author = self.request.user
return super(ForumTopicCreateView, self).form_valid(form) return super(ForumTopicCreateView, self).form_valid(form)
class ForumTopicEditView(CanEditMixin, UpdateView): class ForumTopicEditView(CanEditMixin, UpdateView):
model = ForumTopic model = ForumTopic
fields = ['forum'] fields = ['forum']
pk_url_kwarg = "topic_id" pk_url_kwarg = "topic_id"
template_name = "core/edit.jinja" template_name = "core/edit.jinja"
class ForumTopicDetailView(CanViewMixin, DetailView): class ForumTopicDetailView(CanViewMixin, DetailView):
model = ForumTopic model = ForumTopic
pk_url_kwarg = "topic_id" pk_url_kwarg = "topic_id"
@ -186,9 +199,9 @@ class ForumTopicDetailView(CanViewMixin, DetailView):
kwargs['first_unread_message_id'] = msg.id kwargs['first_unread_message_id'] = msg.id
except: except:
kwargs['first_unread_message_id'] = float("inf") kwargs['first_unread_message_id'] = float("inf")
paginator = Paginator(self.object.messages.select_related('author__avatar_pict')\ paginator = Paginator(self.object.messages.select_related('author__avatar_pict')
.prefetch_related('topic__forum__edit_groups', 'readers').order_by('date'), .prefetch_related('topic__forum__edit_groups', 'readers').order_by('date'),
settings.SITH_FORUM_PAGE_LENGTH) settings.SITH_FORUM_PAGE_LENGTH)
page = self.request.GET.get('page') page = self.request.GET.get('page')
try: try:
kwargs["msgs"] = paginator.page(page) kwargs["msgs"] = paginator.page(page)
@ -198,6 +211,7 @@ class ForumTopicDetailView(CanViewMixin, DetailView):
kwargs["msgs"] = paginator.page(paginator.num_pages) kwargs["msgs"] = paginator.page(paginator.num_pages)
return kwargs return kwargs
class ForumMessageView(SingleObjectMixin, RedirectView): class ForumMessageView(SingleObjectMixin, RedirectView):
model = ForumMessage model = ForumMessage
pk_url_kwarg = "message_id" pk_url_kwarg = "message_id"
@ -207,9 +221,10 @@ class ForumMessageView(SingleObjectMixin, RedirectView):
self.object = self.get_object() self.object = self.get_object()
return self.object.get_url() return self.object.get_url()
class ForumMessageEditView(CanEditMixin, UpdateView): class ForumMessageEditView(CanEditMixin, UpdateView):
model = ForumMessage 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" template_name = "forum/reply.jinja"
pk_url_kwarg = "message_id" pk_url_kwarg = "message_id"
@ -222,6 +237,7 @@ class ForumMessageEditView(CanEditMixin, UpdateView):
kwargs['topic'] = self.object.topic kwargs['topic'] = self.object.topic
return kwargs return kwargs
class ForumMessageDeleteView(SingleObjectMixin, RedirectView): class ForumMessageDeleteView(SingleObjectMixin, RedirectView):
model = ForumMessage model = ForumMessage
pk_url_kwarg = "message_id" pk_url_kwarg = "message_id"
@ -233,6 +249,7 @@ class ForumMessageDeleteView(SingleObjectMixin, RedirectView):
ForumMessageMeta(message=self.object, user=self.request.user, action="DELETE").save() ForumMessageMeta(message=self.object, user=self.request.user, action="DELETE").save()
return self.object.get_absolute_url() return self.object.get_absolute_url()
class ForumMessageUndeleteView(SingleObjectMixin, RedirectView): class ForumMessageUndeleteView(SingleObjectMixin, RedirectView):
model = ForumMessage model = ForumMessage
pk_url_kwarg = "message_id" pk_url_kwarg = "message_id"
@ -244,9 +261,10 @@ class ForumMessageUndeleteView(SingleObjectMixin, RedirectView):
ForumMessageMeta(message=self.object, user=self.request.user, action="UNDELETE").save() ForumMessageMeta(message=self.object, user=self.request.user, action="UNDELETE").save()
return self.object.get_absolute_url() return self.object.get_absolute_url()
class ForumMessageCreateView(CanCreateMixin, CreateView): class ForumMessageCreateView(CanCreateMixin, CreateView):
model = ForumMessage 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" template_name = "forum/reply.jinja"
def dispatch(self, request, *args, **kwargs): 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'] = "> ##### %s\n" % (_("%(author)s said") % {'author': message.author.get_short_name()})
init['message'] += "\n".join([ init['message'] += "\n".join([
"> " + line for line in message.message.split('\n') "> " + line for line in message.message.split('\n')
]) ])
init['message'] += "\n\n" init['message'] += "\n\n"
except Exception as e: except Exception as e:
print(repr(e)) print(repr(e))
@ -277,4 +295,3 @@ class ForumMessageCreateView(CanCreateMixin, CreateView):
kwargs = super(ForumMessageCreateView, self).get_context_data(**kwargs) kwargs = super(ForumMessageCreateView, self).get_context_data(**kwargs)
kwargs['topic'] = self.topic kwargs['topic'] = self.topic
return kwargs return kwargs

View File

@ -27,12 +27,13 @@ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from counter.models import Counter, Product from counter.models import Counter
from core.models import User from core.models import User
from club.models import Club from club.models import Club
# Create your models here. # Create your models here.
class Launderette(models.Model): class Launderette(models.Model):
name = models.CharField(_('name'), max_length=30) name = models.CharField(_('name'), max_length=30)
counter = models.OneToOneField(Counter, verbose_name=_('counter'), related_name='launderette') counter = models.OneToOneField(Counter, verbose_name=_('counter'), related_name='launderette')
@ -78,6 +79,7 @@ class Launderette(models.Model):
def token_list(self): def token_list(self):
return [t.id for t in self.get_token_list()] return [t.id for t in self.get_token_list()]
class Machine(models.Model): class Machine(models.Model):
name = models.CharField(_('name'), max_length=30) name = models.CharField(_('name'), max_length=30)
launderette = models.ForeignKey(Launderette, related_name='machines', verbose_name=_('launderette')) launderette = models.ForeignKey(Launderette, related_name='machines', verbose_name=_('launderette'))
@ -103,6 +105,7 @@ class Machine(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('launderette:launderette_admin', kwargs={"launderette_id": self.launderette.id}) return reverse('launderette:launderette_admin', kwargs={"launderette_id": self.launderette.id})
class Token(models.Model): class Token(models.Model):
name = models.CharField(_('name'), max_length=5) name = models.CharField(_('name'), max_length=5)
launderette = models.ForeignKey(Launderette, related_name='tokens', verbose_name=_('launderette')) launderette = models.ForeignKey(Launderette, related_name='tokens', verbose_name=_('launderette'))
@ -140,6 +143,7 @@ class Token(models.Model):
else: else:
return False return False
class Slot(models.Model): class Slot(models.Model):
start_date = models.DateTimeField(_('start date')) start_date = models.DateTimeField(_('start date'))
type = models.CharField(_('type'), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES) type = models.CharField(_('type'), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES)
@ -156,6 +160,4 @@ class Slot(models.Model):
def __str__(self): def __str__(self):
return "User: %s - Date: %s - Type: %s - Machine: %s - Token: %s" % (self.user, self.start_date, self.get_type_display(), 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)

View File

@ -22,7 +22,7 @@
# #
# #
from django.conf.urls import url, include from django.conf.urls import url
from launderette.views import * from launderette.views import *

View File

@ -26,11 +26,8 @@ from datetime import datetime, timedelta
from collections import OrderedDict from collections import OrderedDict
import pytz import pytz
from django.shortcuts import render from django.views.generic import ListView, DetailView, TemplateView
from django.views.generic import ListView, DetailView, RedirectView, TemplateView
from django.views.generic.edit import UpdateView, CreateView, DeleteView, BaseFormView 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.translation import ugettext as _
from django.utils import dateparse, timezone from django.utils import dateparse, timezone
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
@ -38,7 +35,6 @@ from django.conf import settings
from django.db import transaction, DataError from django.db import transaction, DataError
from django import forms from django import forms
from django.template import defaultfilters from django.template import defaultfilters
from django.utils import formats
from core.models import Page, User from core.models import Page, User
from club.models import Club from club.models import Club
@ -49,6 +45,7 @@ from counter.views import GetUserForm
# For users # For users
class LaunderetteMainView(TemplateView): class LaunderetteMainView(TemplateView):
"""Main presentation view""" """Main presentation view"""
template_name = 'launderette/launderette_main.jinja' template_name = 'launderette/launderette_main.jinja'
@ -59,11 +56,13 @@ class LaunderetteMainView(TemplateView):
kwargs['page'] = Page.objects.filter(name='launderette').first() kwargs['page'] = Page.objects.filter(name='launderette').first()
return kwargs return kwargs
class LaunderetteBookMainView(CanViewMixin, ListView): class LaunderetteBookMainView(CanViewMixin, ListView):
"""Choose which launderette to book""" """Choose which launderette to book"""
model = Launderette model = Launderette
template_name = 'launderette/launderette_book_choose.jinja' template_name = 'launderette/launderette_book_choose.jinja'
class LaunderetteBookView(CanViewMixin, DetailView): class LaunderetteBookView(CanViewMixin, DetailView):
"""Display the launderette schedule""" """Display the launderette schedule"""
model = Launderette 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)): 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, machine=self.machines["WASHING"], type="WASHING").save()
Slot(user=self.subscriber, start_date=self.date + timedelta(hours=1), 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) return super(LaunderetteBookView, self).get(request, *args, **kwargs)
def check_slot(self, type, date=None): 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(): for m in self.object.machines.filter(is_working=True, type=type).all():
slot = Slot.objects.filter(start_date=date, machine=m).first() slot = Slot.objects.filter(start_date=date, machine=m).first()
if slot is None: if slot is None:
@ -121,9 +121,9 @@ class LaunderetteBookView(CanViewMixin, DetailView):
kwargs['planning'] = OrderedDict() kwargs['planning'] = OrderedDict()
kwargs['slot_type'] = self.slot_type kwargs['slot_type'] = self.slot_type
start_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=pytz.UTC) 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] = [] 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 free = False
if self.slot_type == "BOTH" and self.check_slot("WASHING", h) and self.check_slot("DRYING", h + timedelta(hours=1)): if self.slot_type == "BOTH" and self.check_slot("WASHING", h) and self.check_slot("DRYING", h + timedelta(hours=1)):
free = True free = True
@ -137,6 +137,7 @@ class LaunderetteBookView(CanViewMixin, DetailView):
kwargs['planning'][date].append(None) kwargs['planning'][date].append(None)
return kwargs return kwargs
class SlotDeleteView(CanEditPropMixin, DeleteView): class SlotDeleteView(CanEditPropMixin, DeleteView):
"""Delete a slot""" """Delete a slot"""
model = Slot model = Slot
@ -154,6 +155,7 @@ class LaunderetteListView(CanEditPropMixin, ListView):
model = Launderette model = Launderette
template_name = 'launderette/launderette_list.jinja' template_name = 'launderette/launderette_list.jinja'
class LaunderetteEditView(CanEditPropMixin, UpdateView): class LaunderetteEditView(CanEditPropMixin, UpdateView):
"""Edit a launderette""" """Edit a launderette"""
model = Launderette model = Launderette
@ -161,6 +163,7 @@ class LaunderetteEditView(CanEditPropMixin, UpdateView):
fields = ['name'] fields = ['name']
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
class LaunderetteCreateView(CanCreateMixin, CreateView): class LaunderetteCreateView(CanCreateMixin, CreateView):
"""Create a new launderette""" """Create a new launderette"""
model = Launderette model = Launderette
@ -174,11 +177,12 @@ class LaunderetteCreateView(CanCreateMixin, CreateView):
form.instance.counter = c form.instance.counter = c
return super(LaunderetteCreateView, self).form_valid(form) return super(LaunderetteCreateView, self).form_valid(form)
class ManageTokenForm(forms.Form): class ManageTokenForm(forms.Form):
action = forms.ChoiceField(choices=[("BACK", _("Back")), ("ADD", _("Add")), ("DEL", _("Delete"))], initial="BACK", 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", 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")) tokens = forms.CharField(max_length=512, widget=forms.widgets.Textarea, label=_("Tokens, separated by spaces"))
def process(self, launderette): def process(self, launderette):
@ -210,6 +214,7 @@ class ManageTokenForm(forms.Form):
except: except:
self.add_error(None, _("Token %(token_name)s does not exists") % {'token_name': t}) self.add_error(None, _("Token %(token_name)s does not exists") % {'token_name': t})
class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView): class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
"""The admin page of the launderette""" """The admin page of the launderette"""
model = Launderette model = Launderette
@ -253,6 +258,7 @@ class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
def get_success_url(self): def get_success_url(self):
return reverse_lazy('launderette:launderette_admin', args=self.args, kwargs=self.kwargs) return reverse_lazy('launderette:launderette_admin', args=self.args, kwargs=self.kwargs)
class GetLaunderetteUserForm(GetUserForm): class GetLaunderetteUserForm(GetUserForm):
def clean(self): def clean(self):
cleaned_data = super(GetLaunderetteUserForm, self).clean() cleaned_data = super(GetLaunderetteUserForm, self).clean()
@ -261,12 +267,13 @@ class GetLaunderetteUserForm(GetUserForm):
raise forms.ValidationError(_("User has booked no slot")) raise forms.ValidationError(_("User has booked no slot"))
return cleaned_data return cleaned_data
class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView): class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView):
"""The click page of the launderette""" """The click page of the launderette"""
model = Launderette model = Launderette
pk_url_kwarg = "launderette_id" pk_url_kwarg = "launderette_id"
template_name = 'counter/counter_main.jinja' 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): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
@ -301,6 +308,7 @@ class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView):
def get_success_url(self): def get_success_url(self):
return reverse_lazy('launderette:click', args=self.args, kwargs=self.kwargs) return reverse_lazy('launderette:click', args=self.args, kwargs=self.kwargs)
class ClickTokenForm(forms.BaseForm): class ClickTokenForm(forms.BaseForm):
def clean(self): def clean(self):
with transaction.atomic(): with transaction.atomic():
@ -309,11 +317,11 @@ class ClickTokenForm(forms.BaseForm):
counter = Counter.objects.filter(id=self.counter_id).first() counter = Counter.objects.filter(id=self.counter_id).first()
subscriber = customer.user subscriber = customer.user
self.last_basket = { self.last_basket = {
'last_basket': [], 'last_basket': [],
'last_customer': customer.user.get_display_name(), 'last_customer': customer.user.get_display_name(),
} }
total = 0 total = 0
for k,t in self.cleaned_data.items(): for k, t in self.cleaned_data.items():
if t is not None: if t is not None:
slot_id = int(k[5:]) slot_id = int(k[5:])
slot = Slot.objects.filter(id=slot_id).first() 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.borrow_date = datetime.now().replace(tzinfo=pytz.UTC)
t.save() t.save()
price = settings.SITH_LAUNDERETTE_PRICES[t.type] price = settings.SITH_LAUNDERETTE_PRICES[t.type]
s = Selling(label="Jeton "+t.get_type_display()+""+t.name, club=counter.club, product=None, counter=counter, unit_price=price, s = Selling(label="Jeton " + t.get_type_display() + "" + t.name, club=counter.club, product=None, counter=counter, unit_price=price,
quantity=1, seller=operator, customer=customer) quantity=1, seller=operator, customer=customer)
s.save() s.save()
total += price total += price
self.last_basket['last_basket'].append("Jeton "+t.get_type_display()+""+t.name) self.last_basket['last_basket'].append("Jeton " + t.get_type_display() + "" + t.name)
self.last_basket['new_customer_amount'] = str(customer.amount) self.last_basket['new_customer_amount'] = str(customer.amount)
self.last_basket['last_total'] = str(total) self.last_basket['last_total'] = str(total)
return self.cleaned_data return self.cleaned_data
class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView): class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView):
"""The click page of the launderette""" """The click page of the launderette"""
model = Launderette model = Launderette
@ -346,7 +355,7 @@ class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView):
t_name = str(self2.data[field_name]) t_name = str(self2.data[field_name])
if t_name != "": if t_name != "":
t = Token.objects.filter(name=str(self2.data[field_name]), type=slot.type, launderette=self.object, 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: if t is None:
raise forms.ValidationError(_("Token not found")) raise forms.ValidationError(_("Token not found"))
return t 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(): 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)) field_name = "slot-%s" % (str(s.id))
fields[field_name] = forms.CharField(max_length=5, required=False, 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 :/ # 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['subscriber_id'] = self.subscriber.id
kwargs['counter_id'] = self.object.counter.id kwargs['counter_id'] = self.object.counter.id
kwargs['operator_id'] = self.operator.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) return reverse_lazy('launderette:main_click', args=self.args, kwargs=self.kwargs)
class MachineEditView(CanEditPropMixin, UpdateView): class MachineEditView(CanEditPropMixin, UpdateView):
"""Edit a machine""" """Edit a machine"""
model = Machine model = Machine
@ -409,6 +417,7 @@ class MachineEditView(CanEditPropMixin, UpdateView):
fields = ['name', 'launderette', 'type', 'is_working'] fields = ['name', 'launderette', 'type', 'is_working']
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
class MachineDeleteView(CanEditPropMixin, DeleteView): class MachineDeleteView(CanEditPropMixin, DeleteView):
"""Edit a machine""" """Edit a machine"""
model = Machine model = Machine
@ -416,6 +425,7 @@ class MachineDeleteView(CanEditPropMixin, DeleteView):
template_name = 'core/delete_confirm.jinja' template_name = 'core/delete_confirm.jinja'
success_url = reverse_lazy('launderette:launderette_list') success_url = reverse_lazy('launderette:launderette_list')
class MachineCreateView(CanCreateMixin, CreateView): class MachineCreateView(CanCreateMixin, CreateView):
"""Create a new machine""" """Create a new machine"""
model = Machine model = Machine
@ -429,6 +439,3 @@ class MachineCreateView(CanCreateMixin, CreateView):
if obj is not None: if obj is not None:
ret['launderette'] = obj.id ret['launderette'] = obj.id
return ret return ret

View File

@ -30,5 +30,3 @@ from sas.models import *
admin.site.register(Album) admin.site.register(Album)
# admin.site.register(Picture) # admin.site.register(Picture)
admin.site.register(PeoplePictureRelation) admin.site.register(PeoplePictureRelation)

View File

@ -23,11 +23,9 @@
# #
from django.db import models 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.core.urlresolvers import reverse
from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.files.base import ContentFile
from PIL import Image from PIL import Image
from io import BytesIO from io import BytesIO
@ -36,6 +34,7 @@ import os
from core.models import SithFile, User from core.models import SithFile, User
from core.utils import resize_image, exif_auto_rotate from core.utils import resize_image, exif_auto_rotate
class Picture(SithFile): class Picture(SithFile):
class Meta: class Meta:
proxy = True proxy = True
@ -50,12 +49,12 @@ class Picture(SithFile):
def can_be_edited_by(self, user): def can_be_edited_by(self, user):
# file = SithFile.objects.filter(id=self.id).first() # 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): def can_be_viewed_by(self, user):
# file = SithFile.objects.filter(id=self.id).first() # 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 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): def get_download_url(self):
return reverse('sas:download', kwargs={'picture_id': self.id}) return reverse('sas:download', kwargs={'picture_id': self.id})
@ -73,7 +72,8 @@ class Picture(SithFile):
im = Image.open(BytesIO(self.file.read())) im = Image.open(BytesIO(self.file.read()))
try: try:
im = exif_auto_rotate(im) im = exif_auto_rotate(im)
except: pass except:
pass
file = resize_image(im, max(im.size), self.mime_type.split('/')[-1]) file = resize_image(im, max(im.size), self.mime_type.split('/')[-1])
thumb = resize_image(im, 200, 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]) compressed = resize_image(im, 1200, self.mime_type.split('/')[-1])
@ -102,17 +102,18 @@ class Picture(SithFile):
def get_next(self): def get_next(self):
if self.is_moderated: if self.is_moderated:
return self.parent.children.filter(is_moderated=True, asked_for_removal=False, is_folder=False, 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: else:
return Picture.objects.filter(id__gt=self.id, is_moderated=False, is_in_sas=True).order_by('id').first() return Picture.objects.filter(id__gt=self.id, is_moderated=False, is_in_sas=True).order_by('id').first()
def get_previous(self): def get_previous(self):
if self.is_moderated: if self.is_moderated:
return self.parent.children.filter(is_moderated=True, asked_for_removal=False, is_folder=False, 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: else:
return Picture.objects.filter(id__lt=self.id, is_moderated=False, is_in_sas=True).order_by('-id').first() return Picture.objects.filter(id__lt=self.id, is_moderated=False, is_in_sas=True).order_by('-id').first()
class Album(SithFile): class Album(SithFile):
class Meta: class Meta:
proxy = True proxy = True
@ -127,12 +128,12 @@ class Album(SithFile):
def can_be_edited_by(self, user): def can_be_edited_by(self, user):
# file = SithFile.objects.filter(id=self.id).first() # 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): def can_be_viewed_by(self, user):
# file = SithFile.objects.filter(id=self.id).first() # 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 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): def get_absolute_url(self):
return reverse('sas:album', kwargs={'album_id': self.id}) return reverse('sas:album', kwargs={'album_id': self.id})
@ -148,6 +149,7 @@ class Album(SithFile):
self.file.name = self.name + '/thumb.jpg' self.file.name = self.name + '/thumb.jpg'
self.save() self.save()
class PeoplePictureRelation(models.Model): class PeoplePictureRelation(models.Model):
""" """
The PeoplePictureRelation class makes the connection between User and Picture The PeoplePictureRelation class makes the connection between User and Picture

View File

@ -22,7 +22,7 @@
# #
# #
from django.conf.urls import url, include from django.conf.urls import url
from sas.views import * from sas.views import *
@ -40,4 +40,3 @@ urlpatterns = [
# url(r'^album/new$', AlbumCreateView.as_view(), name='album_new'), # url(r'^album/new$', AlbumCreateView.as_view(), name='album_new'),
# url(r'^(?P<club_id>[0-9]+)/$', ClubView.as_view(), name='club_view'), # url(r'^(?P<club_id>[0-9]+)/$', ClubView.as_view(), name='club_view'),
] ]

View File

@ -22,35 +22,31 @@
# #
# #
from django.shortcuts import render, redirect from django.shortcuts import redirect
from django.http import HttpResponseRedirect, HttpResponse from django.http import HttpResponse
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
from django.views.generic import ListView, DetailView, RedirectView, TemplateView from core.views.forms import SelectDate
from django.views.generic.edit import UpdateView, CreateView, DeleteView, ProcessFormView, FormMixin, FormView 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.translation import ugettext_lazy as _
from django.utils import timezone
from django.conf import settings from django.conf import settings
from django.forms.models import modelform_factory
from django import forms from django import forms
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from ajax_select import make_ajax_form, make_ajax_field from ajax_select import make_ajax_field
from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField from ajax_select.fields import AutoCompleteSelectMultipleField
from io import BytesIO from core.views import CanViewMixin, CanEditMixin
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.files import send_file, FileView from core.views.files import send_file, FileView
from core.models import SithFile, User, Notification, RealGroup from core.models import SithFile, User, Notification, RealGroup
from sas.models import Picture, Album, PeoplePictureRelation from sas.models import Picture, Album, PeoplePictureRelation
class SASForm(forms.Form): class SASForm(forms.Form):
album_name = forms.CharField(label=_("Add a new album"), max_length=30, required=False) 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"), images = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}), label=_("Upload images"),
required=False) required=False)
def process(self, parent, owner, files, automodere=False): def process(self, parent, owner, files, automodere=False):
notif = False notif = False
@ -62,10 +58,10 @@ class SASForm(forms.Form):
notif = not automodere notif = not automodere
except Exception as e: except Exception as e:
self.add_error(None, _("Error creating album %(album)s: %(msg)s") % 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: for f in files:
new_file = Picture(parent=parent, name=f.name, file=f, owner=owner, mime_type=f.content_type, size=f._size, 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: if automodere:
new_file.moderator = owner new_file.moderator = owner
try: try:
@ -80,6 +76,7 @@ class SASForm(forms.Form):
if not u.notifications.filter(type="SAS_MODERATION", viewed=False).exists(): if not u.notifications.filter(type="SAS_MODERATION", viewed=False).exists():
Notification(user=u, url=reverse("sas:moderation"), type="SAS_MODERATION").save() Notification(user=u, url=reverse("sas:moderation"), type="SAS_MODERATION").save()
class RelationForm(forms.ModelForm): class RelationForm(forms.ModelForm):
class Meta: class Meta:
model = PeoplePictureRelation model = PeoplePictureRelation
@ -87,6 +84,7 @@ class RelationForm(forms.ModelForm):
widgets = {'picture': forms.HiddenInput} widgets = {'picture': forms.HiddenInput}
users = AutoCompleteSelectMultipleField('users', show_help_text=False, help_text="", label=_("Add user"), required=False) users = AutoCompleteSelectMultipleField('users', show_help_text=False, help_text="", label=_("Add user"), required=False)
class SASMainView(FormView): class SASMainView(FormView):
form_class = SASForm form_class = SASForm
template_name = "sas/main.jinja" 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] kwargs['latest'] = SithFile.objects.filter(is_in_sas=True, is_folder=True, is_moderated=True).order_by('-id')[:5]
return kwargs return kwargs
class PictureView(CanViewMixin, DetailView, FormMixin): class PictureView(CanViewMixin, DetailView, FormMixin):
model = Picture model = Picture
form_class = RelationForm form_class = RelationForm
@ -132,8 +131,9 @@ class PictureView(CanViewMixin, DetailView, FormMixin):
try: try:
user = User.objects.filter(id=int(request.GET['remove_user'])).first() 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): 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() PeoplePictureRelation.objects.filter(user=user, picture=self.object).delete()
except: pass except:
pass
if 'ask_removal' in request.GET.keys(): if 'ask_removal' in request.GET.keys():
self.object.is_moderated = False self.object.is_moderated = False
self.object.asked_for_removal = True self.object.asked_for_removal = True
@ -149,7 +149,7 @@ class PictureView(CanViewMixin, DetailView, FormMixin):
for uid in self.form.cleaned_data['users']: for uid in self.form.cleaned_data['users']:
u = User.objects.filter(id=uid).first() u = User.objects.filter(id=uid).first()
PeoplePictureRelation(user=u, 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(): 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() 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) return super(PictureView, self).form_valid(self.form)
@ -165,15 +165,19 @@ class PictureView(CanViewMixin, DetailView, FormMixin):
def get_success_url(self): def get_success_url(self):
return reverse('sas:picture', kwargs={'picture_id': self.object.id}) return reverse('sas:picture', kwargs={'picture_id': self.object.id})
def send_pict(request, picture_id): def send_pict(request, picture_id):
return send_file(request, picture_id, Picture) return send_file(request, picture_id, Picture)
def send_compressed(request, picture_id): def send_compressed(request, picture_id):
return send_file(request, picture_id, Picture, "compressed") return send_file(request, picture_id, Picture, "compressed")
def send_thumb(request, picture_id): def send_thumb(request, picture_id):
return send_file(request, picture_id, Picture, "thumbnail") return send_file(request, picture_id, Picture, "thumbnail")
class AlbumUploadView(CanViewMixin, DetailView, FormMixin): class AlbumUploadView(CanViewMixin, DetailView, FormMixin):
model = Album model = Album
form_class = SASForm form_class = SASForm
@ -189,7 +193,7 @@ class AlbumUploadView(CanViewMixin, DetailView, FormMixin):
if request.user.is_authenticated() and request.user.is_subscribed: if request.user.is_authenticated() and request.user.is_subscribed:
if self.form.is_valid(): if self.form.is_valid():
self.form.process(parent=parent, owner=request.user, files=files, 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(): if self.form.is_valid():
return HttpResponse(str(self.form.errors), status=200) return HttpResponse(str(self.form.errors), status=200)
return HttpResponse(str(self.form.errors), status=500) return HttpResponse(str(self.form.errors), status=500)
@ -214,14 +218,14 @@ class AlbumView(CanViewMixin, DetailView, FormMixin):
self.form = self.get_form() self.form = self.get_form()
if 'clipboard' not in request.session.keys(): if 'clipboard' not in request.session.keys():
request.session['clipboard'] = [] 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) FileView.handle_clipboard(request, self.object)
parent = SithFile.objects.filter(id=self.object.id).first() parent = SithFile.objects.filter(id=self.object.id).first()
files = request.FILES.getlist('images') files = request.FILES.getlist('images')
if request.user.is_authenticated() and request.user.is_subscribed: if request.user.is_authenticated() and request.user.is_subscribed:
if self.form.is_valid(): if self.form.is_valid():
self.form.process(parent=parent, owner=request.user, files=files, 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(): if self.form.is_valid():
return super(AlbumView, self).form_valid(self.form) return super(AlbumView, self).form_valid(self.form)
else: else:
@ -239,6 +243,7 @@ class AlbumView(CanViewMixin, DetailView, FormMixin):
# Admin views # Admin views
class ModerationView(TemplateView): class ModerationView(TemplateView):
template_name = "sas/moderation.jinja" template_name = "sas/moderation.jinja"
@ -257,23 +262,26 @@ class ModerationView(TemplateView):
a.save() a.save()
elif 'delete' in request.POST.keys(): elif 'delete' in request.POST.keys():
a.delete() a.delete()
except: pass except:
pass
return super(ModerationView, self).get(request, *args, **kwargs) return super(ModerationView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(ModerationView, self).get_context_data(**kwargs) kwargs = super(ModerationView, self).get_context_data(**kwargs)
kwargs['albums_to_moderate'] = Album.objects.filter(is_moderated=False, is_in_sas=True, 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['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')) kwargs['albums'] = Album.objects.filter(id__in=kwargs['pictures'].values('parent').distinct('parent'))
return kwargs return kwargs
class PictureEditForm(forms.ModelForm): class PictureEditForm(forms.ModelForm):
class Meta: class Meta:
model = Picture model = Picture
fields=['name', 'parent'] fields = ['name', 'parent']
parent = make_ajax_field(Picture, 'parent', 'files', help_text="") parent = make_ajax_field(Picture, 'parent', 'files', help_text="")
class AlbumEditForm(forms.ModelForm): class AlbumEditForm(forms.ModelForm):
class Meta: class Meta:
model = Album model = Album
@ -283,16 +291,18 @@ class AlbumEditForm(forms.ModelForm):
edit_groups = make_ajax_field(Album, 'edit_groups', 'groups', help_text="") edit_groups = make_ajax_field(Album, 'edit_groups', 'groups', help_text="")
recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False) recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False)
class PictureEditView(CanEditMixin, UpdateView): class PictureEditView(CanEditMixin, UpdateView):
model=Picture model = Picture
form_class=PictureEditForm form_class = PictureEditForm
template_name='core/edit.jinja' template_name = 'core/edit.jinja'
pk_url_kwarg = "picture_id" pk_url_kwarg = "picture_id"
class AlbumEditView(CanEditMixin, UpdateView): class AlbumEditView(CanEditMixin, UpdateView):
model=Album model = Album
form_class=AlbumEditForm form_class = AlbumEditForm
template_name='core/edit.jinja' template_name = 'core/edit.jinja'
pk_url_kwarg = "album_id" pk_url_kwarg = "album_id"
def form_valid(self, form): def form_valid(self, form):
@ -300,4 +310,3 @@ class AlbumEditView(CanEditMixin, UpdateView):
if form.cleaned_data['recursive']: if form.cleaned_data['recursive']:
self.object.apply_rights_recursively(True) self.object.apply_rights_recursively(True)
return ret return ret

View File

@ -114,14 +114,14 @@ TEMPLATES = [
"app_dirname": "templates", "app_dirname": "templates",
"newstyle_gettext": True, "newstyle_gettext": True,
"context_processors": [ "context_processors": [
"django.contrib.auth.context_processors.auth", "django.contrib.auth.context_processors.auth",
"django.template.context_processors.debug", "django.template.context_processors.debug",
"django.template.context_processors.i18n", "django.template.context_processors.i18n",
"django.template.context_processors.media", "django.template.context_processors.media",
"django.template.context_processors.static", "django.template.context_processors.static",
"django.template.context_processors.tz", "django.template.context_processors.tz",
"django.contrib.messages.context_processors.messages", "django.contrib.messages.context_processors.messages",
], ],
"extensions": [ "extensions": [
"jinja2.ext.do", "jinja2.ext.do",
"jinja2.ext.loopcontrols", "jinja2.ext.loopcontrols",
@ -176,11 +176,11 @@ TEMPLATES = [
] ]
HAYSTACK_CONNECTIONS = { HAYSTACK_CONNECTIONS = {
'default': { 'default': {
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'), 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
}, },
} }
HAYSTACK_SIGNAL_PROCESSOR = 'core.search_indexes.UserOnlySignalProcessor' HAYSTACK_SIGNAL_PROCESSOR = 'core.search_indexes.UserOnlySignalProcessor'
@ -206,9 +206,9 @@ DATABASES = {
LANGUAGE_CODE = 'fr-FR' LANGUAGE_CODE = 'fr-FR'
LANGUAGES = [ LANGUAGES = [
('en', _('English')), ('en', _('English')),
('fr', _('French')), ('fr', _('French')),
] ]
TIME_ZONE = 'Europe/Paris' TIME_ZONE = 'Europe/Paris'
@ -247,55 +247,55 @@ AUTH_ANONYMOUS_MODEL = 'core.models.AnonymousUser'
LOGIN_URL = '/login' LOGIN_URL = '/login'
LOGOUT_URL = '/logout' LOGOUT_URL = '/logout'
LOGIN_REDIRECT_URL = '/' LOGIN_REDIRECT_URL = '/'
DEFAULT_FROM_EMAIL="bibou@git.an" DEFAULT_FROM_EMAIL = "bibou@git.an"
SITH_COM_EMAIL="bibou_com@git.an" SITH_COM_EMAIL = "bibou_com@git.an"
# Email # Email
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
EMAIL_HOST="localhost" EMAIL_HOST = "localhost"
EMAIL_PORT=25 EMAIL_PORT = 25
# Below this line, only Sith-specific variables are defined # Below this line, only Sith-specific variables are defined
IS_OLD_MYSQL_PRESENT = False IS_OLD_MYSQL_PRESENT = False
OLD_MYSQL_INFOS = { OLD_MYSQL_INFOS = {
'host': 'ae-db', 'host': 'ae-db',
'user': "my_user", 'user': "my_user",
'passwd': "password", 'passwd': "password",
'db': "ae2-db", 'db': "ae2-db",
'charset': 'utf8', 'charset': 'utf8',
'use_unicode': True, 'use_unicode': True,
} }
SITH_URL = "my.url.git.an" SITH_URL = "my.url.git.an"
SITH_NAME = "Sith website" SITH_NAME = "Sith website"
# AE configuration # 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 = { SITH_MAIN_CLUB = {
'name': "AE", 'name': "AE",
'unix_name': "ae", 'unix_name': "ae",
'address': "6 Boulevard Anatole France, 90000 Belfort" 'address': "6 Boulevard Anatole France, 90000 Belfort"
} }
# Bar managers # Bar managers
SITH_BAR_MANAGER = { SITH_BAR_MANAGER = {
'name': "BdF", 'name': "BdF",
'unix_name': "bdf", 'unix_name': "bdf",
'address': "6 Boulevard Anatole France, 90000 Belfort" 'address': "6 Boulevard Anatole France, 90000 Belfort"
} }
# Launderette managers # Launderette managers
SITH_LAUNDERETTE_MANAGER = { SITH_LAUNDERETTE_MANAGER = {
'name': "Laverie", 'name': "Laverie",
'unix_name': "laverie", 'unix_name': "laverie",
'address': "6 Boulevard Anatole France, 90000 Belfort" 'address': "6 Boulevard Anatole France, 90000 Belfort"
} }
# Define the date in the year serving as reference for the subscriptions calendar # Define the date in the year serving as reference for the subscriptions calendar
# (month, day) # (month, day)
SITH_START_DATE = (8, 15) # 15th August SITH_START_DATE = (8, 15) # 15th August
# Used to determine the valid promos # Used to determine the valid promos
SITH_SCHOOL_START_YEAR = 1999 SITH_SCHOOL_START_YEAR = 1999
@ -328,74 +328,74 @@ SITH_FORUM_PAGE_LENGTH = 30
# SAS variables # SAS variables
SITH_SAS_ROOT_DIR_ID = 4 SITH_SAS_ROOT_DIR_ID = 4
SITH_BOARD_SUFFIX="-bureau" SITH_BOARD_SUFFIX = "-bureau"
SITH_MEMBER_SUFFIX="-membres" SITH_MEMBER_SUFFIX = "-membres"
SITH_MAIN_BOARD_GROUP=SITH_MAIN_CLUB['unix_name']+SITH_BOARD_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_MAIN_MEMBERS_GROUP = SITH_MAIN_CLUB['unix_name'] + SITH_MEMBER_SUFFIX
SITH_PROFILE_DEPARTMENTS = [ SITH_PROFILE_DEPARTMENTS = [
("TC", _("TC")), ("TC", _("TC")),
("IMSI", _("IMSI")), ("IMSI", _("IMSI")),
("IMAP", _("IMAP")), ("IMAP", _("IMAP")),
("INFO", _("INFO")), ("INFO", _("INFO")),
("GI", _("GI")), ("GI", _("GI")),
("E", _("E")), ("E", _("E")),
("EE", _("EE")), ("EE", _("EE")),
("GESC", _("GESC")), ("GESC", _("GESC")),
("GMC", _("GMC")), ("GMC", _("GMC")),
("MC", _("MC")), ("MC", _("MC")),
("EDIM", _("EDIM")), ("EDIM", _("EDIM")),
("HUMA", _("Humanities")), ("HUMA", _("Humanities")),
("NA", _("N/A")), ("NA", _("N/A")),
] ]
SITH_ACCOUNTING_PAYMENT_METHOD = [ SITH_ACCOUNTING_PAYMENT_METHOD = [
('CHECK', _('Check')), ('CHECK', _('Check')),
('CASH', _('Cash')), ('CASH', _('Cash')),
('TRANSFERT', _('Transfert')), ('TRANSFERT', _('Transfert')),
('CARD', _('Credit card')), ('CARD', _('Credit card')),
] ]
SITH_SUBSCRIPTION_PAYMENT_METHOD = [ SITH_SUBSCRIPTION_PAYMENT_METHOD = [
('CHECK', _('Check')), ('CHECK', _('Check')),
('CARD', _('Credit card')), ('CARD', _('Credit card')),
('CASH', _('Cash')), ('CASH', _('Cash')),
('EBOUTIC', _('Eboutic')), ('EBOUTIC', _('Eboutic')),
('OTHER', _('Other')), ('OTHER', _('Other')),
] ]
SITH_SUBSCRIPTION_LOCATIONS = [ SITH_SUBSCRIPTION_LOCATIONS = [
('BELFORT', _('Belfort')), ('BELFORT', _('Belfort')),
('SEVENANS', _('Sevenans')), ('SEVENANS', _('Sevenans')),
('MONTBELIARD', _('Montbéliard')), ('MONTBELIARD', _('Montbéliard')),
('EBOUTIC', _('Eboutic')), ('EBOUTIC', _('Eboutic')),
] ]
SITH_COUNTER_BARS = [ SITH_COUNTER_BARS = [
(1, "MDE"), (1, "MDE"),
(2, "Foyer"), (2, "Foyer"),
(35, "La Gommette"), (35, "La Gommette"),
] ]
SITH_COUNTER_PAYMENT_METHOD = [ SITH_COUNTER_PAYMENT_METHOD = [
('CHECK', _('Check')), ('CHECK', _('Check')),
('CASH', _('Cash')), ('CASH', _('Cash')),
('CARD', _('Credit card')), ('CARD', _('Credit card')),
] ]
SITH_COUNTER_BANK = [ SITH_COUNTER_BANK = [
('OTHER', 'Autre'), ('OTHER', 'Autre'),
('SOCIETE-GENERALE', 'Société générale'), ('SOCIETE-GENERALE', 'Société générale'),
('BANQUE-POPULAIRE', 'Banque populaire'), ('BANQUE-POPULAIRE', 'Banque populaire'),
('BNP', 'BNP'), ('BNP', 'BNP'),
('CAISSE-EPARGNE', 'Caisse d\'épargne'), ('CAISSE-EPARGNE', 'Caisse d\'épargne'),
('CIC', 'CIC'), ('CIC', 'CIC'),
('CREDIT-AGRICOLE', 'Crédit Agricole'), ('CREDIT-AGRICOLE', 'Crédit Agricole'),
('CREDIT-MUTUEL', 'Credit Mutuel'), ('CREDIT-MUTUEL', 'Credit Mutuel'),
('CREDIT-LYONNAIS', 'Credit Lyonnais'), ('CREDIT-LYONNAIS', 'Credit Lyonnais'),
('LA-POSTE', 'La Poste'), ('LA-POSTE', 'La Poste'),
] ]
# Defines pagination for cash summary # Defines pagination for cash summary
SITH_COUNTER_CASH_SUMMARY_LENGTH = 50 SITH_COUNTER_CASH_SUMMARY_LENGTH = 50
@ -466,7 +466,7 @@ SITH_SUBSCRIPTIONS = {
'price': 15, 'price': 15,
'duration': 2, 'duration': 2,
}, },
# To be completed.... # To be completed....
} }
SITH_CLUB_ROLES = {} SITH_CLUB_ROLES = {}
@ -497,16 +497,16 @@ SITH_CLUB_ROLES = {
# This corresponds to the maximum role a user can freely subscribe to # 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 # 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 # Minutes to timeout the logged barmen
SITH_BARMAN_TIMEOUT=20 SITH_BARMAN_TIMEOUT = 20
# Minutes to delete the last operations # Minutes to delete the last operations
SITH_LAST_OPERATIONS_LIMIT=10 SITH_LAST_OPERATIONS_LIMIT = 10
# Minutes for a counter to be inactive # Minutes for a counter to be inactive
SITH_COUNTER_MINUTE_INACTIVE=10 SITH_COUNTER_MINUTE_INACTIVE = 10
# ET variables # ET variables
SITH_EBOUTIC_ET_URL = "https://preprod-tpeweb.e-transactions.fr/cgi/MYchoix_pagepaiement.cgi" 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 # Launderette variables
SITH_LAUNDERETTE_MACHINE_TYPES = [('WASHING', _('Washing')), ('DRYING', _('Drying'))] SITH_LAUNDERETTE_MACHINE_TYPES = [('WASHING', _('Washing')), ('DRYING', _('Drying'))]
SITH_LAUNDERETTE_PRICES = { SITH_LAUNDERETTE_PRICES = {
'WASHING': 1.0, 'WASHING': 1.0,
'DRYING': 0.75, 'DRYING': 0.75,
} }
SITH_NOTIFICATIONS = [ SITH_NOTIFICATIONS = [
('NEWS_MODERATION', _("A fresh new to be moderated")), ('NEWS_MODERATION', _("A fresh new to be moderated")),
('FILE_MODERATION', _("New files to be moderated")), ('FILE_MODERATION', _("New files to be moderated")),
('SAS_MODERATION', _("New pictures/album to be moderated in the SAS")), ('SAS_MODERATION', _("New pictures/album to be moderated in the SAS")),
('NEW_PICTURES', _("You've been identified on some pictures")), ('NEW_PICTURES', _("You've been identified on some pictures")),
('REFILLING', _("You just refilled of %s")), ('REFILLING', _("You just refilled of %s")),
('SELLING', _("You just bought %s")), ('SELLING', _("You just bought %s")),
('GENERIC', _("You have a notification")), ('GENERIC', _("You have a notification")),
] ]
SITH_QUICK_NOTIF = { SITH_QUICK_NOTIF = {
'qn_success': _("Success!"), 'qn_success': _("Success!"),
'qn_fail': _("Fail!"), 'qn_fail': _("Fail!"),
'qn_weekmail_new_article': _("You successfully posted an article in the Weekmail"), '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_article_edit': _("You successfully edited an article in the Weekmail"),
'qn_weekmail_send_success': _("You successfully sent the Weekmail"), 'qn_weekmail_send_success': _("You successfully sent the Weekmail"),
} }
try: try:
from .settings_custom import * from .settings_custom import *

View File

@ -24,6 +24,7 @@
from debug_toolbar.panels.templates import TemplatesPanel as BaseTemplatesPanel from debug_toolbar.panels.templates import TemplatesPanel as BaseTemplatesPanel
class TemplatesPanel(BaseTemplatesPanel): class TemplatesPanel(BaseTemplatesPanel):
def generate_stats(self, *args): def generate_stats(self, *args):
template = self.templates[0]['template'] template = self.templates[0]['template']

View File

@ -80,4 +80,3 @@ if settings.DEBUG:
urlpatterns += [ urlpatterns += [
url(r'^__debug__/', include(debug_toolbar.urls)), url(r'^__debug__/', include(debug_toolbar.urls)),
] ]

View File

@ -26,6 +26,4 @@ from django.contrib import admin
from subscription.models import Subscription from subscription.models import Subscription
admin.site.register(Subscription) admin.site.register(Subscription)

View File

@ -35,37 +35,38 @@ from core.models import User
from core.utils import get_start_of_semester from core.utils import get_start_of_semester
def validate_type(value): def validate_type(value):
if value not in settings.SITH_SUBSCRIPTIONS.keys(): if value not in settings.SITH_SUBSCRIPTIONS.keys():
raise ValidationError(_('Bad subscription type')) raise ValidationError(_('Bad subscription type'))
def validate_payment(value): def validate_payment(value):
if value not in settings.SITH_SUBSCRIPTION_PAYMENT_METHOD: if value not in settings.SITH_SUBSCRIPTION_PAYMENT_METHOD:
raise ValidationError(_('Bad payment method')) raise ValidationError(_('Bad payment method'))
class Subscription(models.Model): class Subscription(models.Model):
member = models.ForeignKey(User, related_name='subscriptions') member = models.ForeignKey(User, related_name='subscriptions')
subscription_type = models.CharField(_('subscription type'), subscription_type = models.CharField(_('subscription type'),
max_length=255, 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_start = models.DateField(_('subscription start'))
subscription_end = models.DateField(_('subscription end')) subscription_end = models.DateField(_('subscription end'))
payment_method = models.CharField(_('payment method'), payment_method = models.CharField(_('payment method'),
max_length=255, max_length=255,
choices=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD) choices=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD)
location = models.CharField(choices=settings.SITH_SUBSCRIPTION_LOCATIONS, location = models.CharField(choices=settings.SITH_SUBSCRIPTION_LOCATIONS,
max_length=20, verbose_name=_('location')) max_length=20, verbose_name=_('location'))
class Meta: class Meta:
ordering = ['subscription_start',] ordering = ['subscription_start', ]
def clean(self): def clean(self):
try: try:
for s in Subscription.objects.filter(member=self.member).exclude(pk=self.pk).all(): for s in Subscription.objects.filter(member=self.member).exclude(pk=self.pk).all():
if s.is_valid_now(): if s.is_valid_now():
raise ValidationError(_("You can not subscribe many time for the same period")) 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 :'( # calls the model validation :'(
# TODO see SubscriptionForm's clean method # TODO see SubscriptionForm's clean method
raise ValidationError(_("Subscription error")) raise ValidationError(_("Subscription error"))
@ -74,53 +75,53 @@ class Subscription(models.Model):
super(Subscription, self).save() super(Subscription, self).save()
from counter.models import Customer from counter.models import Customer
if not Customer.objects.filter(user=self.member).exists(): if not Customer.objects.filter(user=self.member).exists():
last_id = Customer.objects.count() + 1504 # Number to keep a continuity with the old site 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() Customer(user=self.member, account_id=Customer.generate_account_id(last_id + 1), amount=0).save()
form = PasswordResetForm({'email': self.member.email}) form = PasswordResetForm({'email': self.member.email})
if form.is_valid(): if form.is_valid():
form.save(use_https=True, email_template_name='core/new_user_email.jinja', 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() self.member.make_home()
if settings.IS_OLD_MYSQL_PRESENT: if settings.IS_OLD_MYSQL_PRESENT:
import MySQLdb import MySQLdb
try: # Create subscription on the old site: TODO remove me! try: # Create subscription on the old site: TODO remove me!
LOCATION = { LOCATION = {
"SEVENANS": 5, "SEVENANS": 5,
"BELFORT": 6, "BELFORT": 6,
"MONTBELIARD": 9, "MONTBELIARD": 9,
"EBOUTIC": 5, "EBOUTIC": 5,
} }
TYPE = { TYPE = {
'un-semestre' : 0, 'un-semestre': 0,
'deux-semestres' : 1, 'deux-semestres': 1,
'cursus-tronc-commun' : 2, 'cursus-tronc-commun': 2,
'cursus-branche' : 3, 'cursus-branche': 3,
'membre-honoraire' : 4, 'membre-honoraire': 4,
'assidu' : 5, 'assidu': 5,
'amicale/doceo' : 6, 'amicale/doceo': 6,
'reseau-ut' : 7, 'reseau-ut': 7,
'crous' : 8, 'crous': 8,
'sbarro/esta' : 9, 'sbarro/esta': 9,
'cursus-alternant' : 10, 'cursus-alternant': 10,
} }
PAYMENT = { PAYMENT = {
"CHECK" : 1, "CHECK": 1,
"CARD" : 2, "CARD": 2,
"CASH" : 3, "CASH": 3,
"OTHER" : 4, "OTHER": 4,
"EBOUTIC" : 5, "EBOUTIC": 5,
"OTHER" : 0, "OTHER": 0,
} }
db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS) db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS)
c = db.cursor() c = db.cursor()
c.execute("""INSERT INTO ae_cotisations (id_utilisateur, date_cotis, date_fin_cotis, mode_paiement_cotis, 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, 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], self.subscription_end, PAYMENT[self.payment_method], TYPE[self.subscription_type],
LOCATION[self.location])) LOCATION[self.location]))
db.commit() db.commit()
except Exception as e: 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("FAIL to add subscription to %s to old site" % (self.member), file=f)
print("Reason: %s" % (repr(e)), file=f) print("Reason: %s" % (repr(e)), file=f)
db.rollback() db.rollback()
@ -130,10 +131,9 @@ class Subscription(models.Model):
def __str__(self): def __str__(self):
if hasattr(self, "member") and self.member is not None: 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: else:
return 'No user - '+str(self.pk) return 'No user - ' + str(self.pk)
@staticmethod @staticmethod
def compute_start(d=date.today(), duration=1): def compute_start(d=date.today(), duration=1):
@ -146,7 +146,7 @@ class Subscription(models.Model):
2015-03-17 -> 2015-02-15 2015-03-17 -> 2015-02-15
2015-01-11 -> 2014-08-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 d
return get_start_of_semester(d) return get_start_of_semester(d)
@ -165,12 +165,11 @@ class Subscription(models.Model):
start = Subscription.compute_start(duration=duration) start = Subscription.compute_start(duration=duration)
# This can certainly be simplified, but it works like this # This can certainly be simplified, but it works like this
try: try:
return start.replace(month=(start.month-1+6*duration)%12+1, 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)) year=start.year + int(duration / 2) + (1 if start.month > 6 and duration % 2 == 1 else 0))
except ValueError as e: except ValueError as e:
return start.replace(day=1, month=(start.month+6*duration)%12+1, 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)) year=start.year + int(duration / 2) + (1 if start.month > 6 and duration % 2 == 1 else 0))
def can_be_edited_by(self, user): def can_be_edited_by(self, user):
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user.is_root 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): def is_valid_now(self):
return self.subscription_start <= date.today() and date.today() <= self.subscription_end return self.subscription_start <= date.today() and date.today() <= self.subscription_end
def guy_test(date, duration=4): 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()): 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(): def guy():
guy_test(date(2015, 7, 11)) guy_test(date(2015, 7, 11))
guy_test(date(2015, 8, 11)) guy_test(date(2015, 8, 11))
@ -191,7 +195,7 @@ def guy():
guy_test(date(2015, 2, 11)) guy_test(date(2015, 2, 11))
guy_test(date(2015, 8, 17)) guy_test(date(2015, 8, 17))
guy_test(date(2015, 9, 17)) guy_test(date(2015, 9, 17))
print('='*80) print('=' * 80)
guy_test(date(2015, 7, 11), 1) guy_test(date(2015, 7, 11), 1)
guy_test(date(2015, 8, 11), 2) guy_test(date(2015, 8, 11), 2)
guy_test(date(2015, 2, 17), 3) guy_test(date(2015, 2, 17), 3)
@ -200,7 +204,7 @@ def guy():
guy_test(date(2015, 2, 11), 2) guy_test(date(2015, 2, 11), 2)
guy_test(date(2015, 8, 17), 3) guy_test(date(2015, 8, 17), 3)
guy_test(date(2015, 9, 17), 4) guy_test(date(2015, 9, 17), 4)
print('='*80) print('=' * 80)
bibou_test(1, date(2015, 2, 18)) bibou_test(1, date(2015, 2, 18))
bibou_test(2, date(2015, 2, 18)) bibou_test(2, date(2015, 2, 18))
bibou_test(3, 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(2, date(2015, 9, 18))
bibou_test(3, date(2015, 9, 18)) bibou_test(3, date(2015, 9, 18))
bibou_test(4, date(2015, 9, 18)) bibou_test(4, date(2015, 9, 18))
print('='*80) print('=' * 80)
bibou_test(1, date(2000, 2, 29)) bibou_test(1, date(2000, 2, 29))
bibou_test(2, date(2000, 2, 29)) bibou_test(2, date(2000, 2, 29))
bibou_test(1, date(2000, 5, 31)) bibou_test(1, date(2000, 5, 31))
@ -219,5 +223,6 @@ def guy():
bibou_test(3) bibou_test(3)
bibou_test(4) bibou_test(4)
if __name__ == "__main__": if __name__ == "__main__":
guy() guy()

View File

@ -31,6 +31,3 @@ urlpatterns = [
url(r'^$', NewSubscription.as_view(), name='subscription'), url(r'^$', NewSubscription.as_view(), name='subscription'),
url(r'stats', SubscriptionsStatsView.as_view(), name='stats'), url(r'stats', SubscriptionsStatsView.as_view(), name='stats'),
] ]

View File

@ -26,16 +26,13 @@ from django.views.generic.edit import CreateView, FormView
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.db import IntegrityError
from django import forms from django import forms
from django.forms import Select
from django.conf import settings from django.conf import settings
from ajax_select.fields import AutoCompleteSelectField from ajax_select.fields import AutoCompleteSelectField
import random import random
from subscription.models import Subscription from subscription.models import Subscription
from core.views import CanEditMixin, CanEditPropMixin, CanViewMixin
from core.views.forms import SelectDateTime from core.views.forms import SelectDateTime
from core.models import User 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: 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"))) self.add_error("email", ValidationError(_("A user with that email address already exists")))
else: else:
u = User(last_name = self.cleaned_data.get("last_name"), u = User(last_name=self.cleaned_data.get("last_name"),
first_name = self.cleaned_data.get("first_name"), first_name=self.cleaned_data.get("first_name"),
email = self.cleaned_data.get("email")) email=self.cleaned_data.get("email"))
u.generate_username() u.generate_username()
u.set_password(str(random.randrange(1000000, 10000000))) u.set_password(str(random.randrange(1000000, 10000000)))
u.save() 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")) raise ValidationError(_("You must either choose an existing user or create a new one properly"))
return cleaned_data return cleaned_data
class NewSubscription(CreateView): class NewSubscription(CreateView):
template_name = 'subscription/subscription.jinja' template_name = 'subscription/subscription.jinja'
form_class = SubscriptionForm form_class = SubscriptionForm
@ -119,11 +117,11 @@ class NewSubscription(CreateView):
def form_valid(self, form): def form_valid(self, form):
form.instance.subscription_start = Subscription.compute_start( 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( form.instance.subscription_end = Subscription.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type]['duration'],
start=form.instance.subscription_start start=form.instance.subscription_start
) )
return super(NewSubscription, self).form_valid(form) return super(NewSubscription, self).form_valid(form)

View File

@ -34,14 +34,17 @@ from core.models import User
from core.utils import get_start_of_semester, get_semester from core.utils import get_start_of_semester, get_semester
from club.models import Club from club.models import Club
class TrombiManager(models.Manager): class TrombiManager(models.Manager):
def get_queryset(self): def get_queryset(self):
return super(TrombiManager, self).get_queryset() return super(TrombiManager, self).get_queryset()
class AvailableTrombiManager(models.Manager): class AvailableTrombiManager(models.Manager):
def get_queryset(self): def get_queryset(self):
return super(AvailableTrombiManager, 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): class Trombi(models.Model):
""" """
@ -50,14 +53,14 @@ class Trombi(models.Model):
its Trombi. its Trombi.
""" """
subscription_deadline = models.DateField(_('subscription deadline'), subscription_deadline = models.DateField(_('subscription deadline'),
default=date.today, help_text=_("Before this date, users are " default=date.today, help_text=_("Before this date, users are "
"allowed to subscribe to this Trombi. " "allowed to subscribe to this Trombi. "
"After this date, users subscribed will be allowed to comment on each other.")) "After this date, users subscribed will be allowed to comment on each other."))
comments_deadline = models.DateField(_('comments deadline'), comments_deadline = models.DateField(_('comments deadline'),
default=date.today, help_text=_("After this date, users won't be " default=date.today, help_text=_("After this date, users won't be "
"able to make comments anymore.")) "able to make comments anymore."))
max_chars = models.IntegerField(_('maximum characters'), default=400, 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) show_profiles = models.BooleanField(_("show users profiles to each other"), default=True)
club = models.OneToOneField(Club, related_name='trombi') club = models.OneToOneField(Club, related_name='trombi')
@ -70,7 +73,7 @@ class Trombi(models.Model):
def clean(self): def clean(self):
if self.subscription_deadline > self.comments_deadline: if self.subscription_deadline > self.comments_deadline:
raise ValidationError(_("Closing the subscriptions after the " 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): def get_absolute_url(self):
return reverse('trombi:detail', kwargs={'trombi_id': self.id}) return reverse('trombi:detail', kwargs={'trombi_id': self.id})
@ -81,6 +84,7 @@ class Trombi(models.Model):
def can_be_viewed_by(self, user): def can_be_viewed_by(self, user):
return user.id in [u.user.id for u in self.users.all()] return user.id in [u.user.id for u in self.users.all()]
class TrombiUser(models.Model): class TrombiUser(models.Model):
""" """
This class is only here to avoid cross references between the core, club, 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') 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) 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, 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, 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): def __str__(self):
return str(self.user) return str(self.user)
@ -113,12 +117,13 @@ class TrombiUser(models.Model):
else: else:
end_date = "" end_date = ""
TrombiClubMembership( TrombiClubMembership(
user=self, user=self,
club=str(m.club), club=str(m.club),
role=role[:64], role=role[:64],
start=get_semester(m.start_date), start=get_semester(m.start_date),
end=end_date, end=end_date,
).save() ).save()
class TrombiComment(models.Model): class TrombiComment(models.Model):
""" """
@ -135,6 +140,7 @@ class TrombiComment(models.Model):
return False return False
return user.id == self.author.user.id or user.can_edit(self.author.trombi) return user.id == self.author.user.id or user.can_edit(self.author.trombi)
class TrombiClubMembership(models.Model): class TrombiClubMembership(models.Model):
""" """
This represent a membership to a club This represent a membership to a club
@ -156,4 +162,3 @@ class TrombiClubMembership(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('trombi:profile') return reverse('trombi:profile')

View File

@ -22,7 +22,7 @@
# #
# #
from django.conf.urls import url, include from django.conf.urls import url
from trombi.views import * from trombi.views import *
@ -43,4 +43,3 @@ urlpatterns = [
url(r'^membership/(?P<membership_id>[0-9]+)/edit$', UserTrombiEditMembershipView.as_view(), name='edit_membership'), url(r'^membership/(?P<membership_id>[0-9]+)/edit$', UserTrombiEditMembershipView.as_view(), name='edit_membership'),
url(r'^membership/(?P<membership_id>[0-9]+)/delete$', UserTrombiDeleteMembershipView.as_view(), name='delete_membership'), url(r'^membership/(?P<membership_id>[0-9]+)/delete$', UserTrombiDeleteMembershipView.as_view(), name='delete_membership'),
] ]

View File

@ -23,10 +23,10 @@
# #
from django.http import Http404 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.core.urlresolvers import reverse_lazy, reverse
from django.views.generic import ListView, DetailView, RedirectView, TemplateView from django.views.generic import DetailView, RedirectView, TemplateView
from django.views.generic.edit import UpdateView, CreateView, DeleteView, FormView, SingleObjectMixin from django.views.generic.edit import UpdateView, CreateView, DeleteView
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django import forms from django import forms
from django.conf import settings from django.conf import settings
@ -35,11 +35,12 @@ from django.forms.models import modelform_factory
from datetime import date from datetime import date
from trombi.models import Trombi, TrombiUser, TrombiComment, TrombiClubMembership from trombi.models import Trombi, TrombiUser, TrombiComment, TrombiClubMembership
from core.views.forms import SelectFile, SelectDate from core.views.forms import SelectDate
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, CanCreateMixin, QuickNotifMixin from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, QuickNotifMixin
from core.models import User from core.models import User
from club.models import Club from club.models import Club
class TrombiTabsMixin(TabedViewMixin): class TrombiTabsMixin(TabedViewMixin):
def get_tabs_title(self): def get_tabs_title(self):
return _("Trombi") return _("Trombi")
@ -47,39 +48,42 @@ class TrombiTabsMixin(TabedViewMixin):
def get_list_of_tabs(self): def get_list_of_tabs(self):
tab_list = [] tab_list = []
tab_list.append({ tab_list.append({
'url': reverse('trombi:user_tools'), 'url': reverse('trombi:user_tools'),
'slug': 'tools', 'slug': 'tools',
'name': _("Tools"), 'name': _("Tools"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('trombi:profile'), 'url': reverse('trombi:profile'),
'slug': 'profile', 'slug': 'profile',
'name': _("My profile"), 'name': _("My profile"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('trombi:pictures'), 'url': reverse('trombi:pictures'),
'slug': 'pictures', 'slug': 'pictures',
'name': _("My pictures"), 'name': _("My pictures"),
}) })
try: try:
trombi = self.request.user.trombi_user.trombi trombi = self.request.user.trombi_user.trombi
if self.request.user.is_owner(trombi): if self.request.user.is_owner(trombi):
tab_list.append({ tab_list.append({
'url': reverse('trombi:detail', kwargs={'trombi_id': trombi.id}), 'url': reverse('trombi:detail', kwargs={'trombi_id': trombi.id}),
'slug': 'admin_tools', 'slug': 'admin_tools',
'name': _("Admin tools"), 'name': _("Admin tools"),
}) })
except: pass except:
pass
return tab_list return tab_list
class TrombiForm(forms.ModelForm): class TrombiForm(forms.ModelForm):
class Meta: class Meta:
model = Trombi model = Trombi
fields = ['subscription_deadline', 'comments_deadline', 'max_chars', 'show_profiles'] fields = ['subscription_deadline', 'comments_deadline', 'max_chars', 'show_profiles']
widgets = { widgets = {
'subscription_deadline': SelectDate, 'subscription_deadline': SelectDate,
'comments_deadline': SelectDate, 'comments_deadline': SelectDate,
} }
class TrombiCreateView(CanCreateMixin, CreateView): class TrombiCreateView(CanCreateMixin, CreateView):
""" """
@ -102,6 +106,7 @@ class TrombiCreateView(CanCreateMixin, CreateView):
else: else:
return self.form_invalid(form) return self.form_invalid(form)
class TrombiEditView(CanEditPropMixin, TrombiTabsMixin, UpdateView): class TrombiEditView(CanEditPropMixin, TrombiTabsMixin, UpdateView):
model = Trombi model = Trombi
form_class = TrombiForm form_class = TrombiForm
@ -110,7 +115,8 @@ class TrombiEditView(CanEditPropMixin, TrombiTabsMixin, UpdateView):
current_tab = "admin_tools" current_tab = "admin_tools"
def get_success_url(self): 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): class TrombiDetailView(CanEditMixin, QuickNotifMixin, TrombiTabsMixin, DetailView):
model = Trombi model = Trombi
@ -118,6 +124,7 @@ class TrombiDetailView(CanEditMixin, QuickNotifMixin, TrombiTabsMixin, DetailVie
pk_url_kwarg = 'trombi_id' pk_url_kwarg = 'trombi_id'
current_tab = "admin_tools" current_tab = "admin_tools"
class TrombiDeleteUserView(CanEditPropMixin, TrombiTabsMixin, DeleteView): class TrombiDeleteUserView(CanEditPropMixin, TrombiTabsMixin, DeleteView):
model = TrombiUser model = TrombiUser
pk_url_kwarg = 'user_id' pk_url_kwarg = 'user_id'
@ -127,6 +134,7 @@ class TrombiDeleteUserView(CanEditPropMixin, TrombiTabsMixin, DeleteView):
def get_success_url(self): def get_success_url(self):
return reverse('trombi:detail', kwargs={'trombi_id': self.object.trombi.id}) + "?qn_success" return reverse('trombi:detail', kwargs={'trombi_id': self.object.trombi.id}) + "?qn_success"
class TrombiModerateCommentsView(CanEditPropMixin, QuickNotifMixin, TrombiTabsMixin, DetailView): class TrombiModerateCommentsView(CanEditPropMixin, QuickNotifMixin, TrombiTabsMixin, DetailView):
model = Trombi model = Trombi
template_name = 'trombi/comment_moderation.jinja' template_name = 'trombi/comment_moderation.jinja'
@ -136,13 +144,15 @@ class TrombiModerateCommentsView(CanEditPropMixin, QuickNotifMixin, TrombiTabsMi
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(TrombiModerateCommentsView, self).get_context_data(**kwargs) kwargs = super(TrombiModerateCommentsView, self).get_context_data(**kwargs)
kwargs['comments'] = TrombiComment.objects.filter(is_moderated=False, 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 return kwargs
class TrombiModerateForm(forms.Form): class TrombiModerateForm(forms.Form):
reason = forms.CharField(help_text=_("Explain why you rejected the comment")) reason = forms.CharField(help_text=_("Explain why you rejected the comment"))
action = forms.CharField(initial="delete", widget=forms.widgets.HiddenInput) action = forms.CharField(initial="delete", widget=forms.widgets.HiddenInput)
class TrombiModerateCommentView(DetailView): class TrombiModerateCommentView(DetailView):
model = TrombiComment model = TrombiComment
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
@ -159,42 +169,45 @@ class TrombiModerateCommentView(DetailView):
if request.POST['action'] == "accept": if request.POST['action'] == "accept":
self.object.is_moderated = True self.object.is_moderated = True
self.object.save() 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": elif request.POST['action'] == "reject":
return super(TrombiModerateCommentView, self).get(request, *args, **kwargs) return super(TrombiModerateCommentView, self).get(request, *args, **kwargs)
elif request.POST['action'] == "delete" and "reason" in request.POST.keys(): elif request.POST['action'] == "delete" and "reason" in request.POST.keys():
self.object.author.user.email_user( self.object.author.user.email_user(
subject="[%s] %s" % (settings.SITH_NAME, _("Rejected comment")), subject="[%s] %s" % (settings.SITH_NAME, _("Rejected comment")),
message=_("Your comment to %(target)s on the Trombi \"%(trombi)s\" was rejected for the following " message=_("Your comment to %(target)s on the Trombi \"%(trombi)s\" was rejected for the following "
"reason: %(reason)s\n\n" "reason: %(reason)s\n\n"
"Your comment was:\n\n%(content)s" "Your comment was:\n\n%(content)s"
) % { ) % {
'target': self.object.target.user.get_display_name(), 'target': self.object.target.user.get_display_name(),
'trombi': self.object.author.trombi, 'trombi': self.object.author.trombi,
'reason': request.POST["reason"], 'reason': request.POST["reason"],
'content': self.object.content, 'content': self.object.content,
}, },
) )
self.object.delete() 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 raise Http404
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(TrombiModerateCommentView, self).get_context_data(**kwargs) kwargs = super(TrombiModerateCommentView, self).get_context_data(**kwargs)
kwargs['form'] = TrombiModerateForm() kwargs['form'] = TrombiModerateForm()
return kwargs return kwargs
# User side # User side
class TrombiModelChoiceField(forms.ModelChoiceField): class TrombiModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj): def label_from_instance(self, obj):
return _("%(name)s (deadline: %(date)s)") % {'name': str(obj), 'date': str(obj.subscription_deadline)} return _("%(name)s (deadline: %(date)s)") % {'name': str(obj), 'date': str(obj.subscription_deadline)}
class UserTrombiForm(forms.Form): class UserTrombiForm(forms.Form):
trombi = TrombiModelChoiceField(Trombi.availables.all(), required=False, label=_("Select trombi"), trombi = TrombiModelChoiceField(Trombi.availables.all(), required=False, label=_("Select trombi"),
help_text=_("This allows you to subscribe to a 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, " "Be aware that you can subscribe only once, so don't play with that, "
"or you will expose yourself to the admins' wrath!")) "or you will expose yourself to the admins' wrath!"))
class UserTrombiToolsView(QuickNotifMixin, TrombiTabsMixin, TemplateView): class UserTrombiToolsView(QuickNotifMixin, TrombiTabsMixin, TemplateView):
""" """
@ -207,7 +220,7 @@ class UserTrombiToolsView(QuickNotifMixin, TrombiTabsMixin, TemplateView):
self.form = UserTrombiForm(request.POST) self.form = UserTrombiForm(request.POST)
if self.form.is_valid(): if self.form.is_valid():
trombi_user = TrombiUser(user=request.user, trombi_user = TrombiUser(user=request.user,
trombi=self.form.cleaned_data['trombi']) trombi=self.form.cleaned_data['trombi'])
trombi_user.save() trombi_user.save()
self.quick_notif_list += ['qn_success'] self.quick_notif_list += ['qn_success']
return super(UserTrombiToolsView, self).get(request, *args, **kwargs) return super(UserTrombiToolsView, self).get(request, *args, **kwargs)
@ -222,6 +235,7 @@ class UserTrombiToolsView(QuickNotifMixin, TrombiTabsMixin, TemplateView):
kwargs['date'] = date kwargs['date'] = date
return kwargs return kwargs
class UserTrombiEditPicturesView(TrombiTabsMixin, UpdateView): class UserTrombiEditPicturesView(TrombiTabsMixin, UpdateView):
model = TrombiUser model = TrombiUser
fields = ['profile_pict', 'scrub_pict'] fields = ['profile_pict', 'scrub_pict']
@ -232,18 +246,19 @@ class UserTrombiEditPicturesView(TrombiTabsMixin, UpdateView):
return self.request.user.trombi_user return self.request.user.trombi_user
def get_success_url(self): def get_success_url(self):
return reverse('trombi:user_tools')+"?qn_success" return reverse('trombi:user_tools') + "?qn_success"
class UserTrombiEditProfileView(QuickNotifMixin, TrombiTabsMixin, UpdateView): class UserTrombiEditProfileView(QuickNotifMixin, TrombiTabsMixin, UpdateView):
model = User model = User
form_class = modelform_factory(User, form_class = modelform_factory(User,
fields=['second_email', 'phone', 'department', 'dpt_option', fields=['second_email', 'phone', 'department', 'dpt_option',
'quote', 'parent_address'], 'quote', 'parent_address'],
labels={ labels={
'second_email': _("Personal email (not UTBM)"), 'second_email': _("Personal email (not UTBM)"),
'phone': _("Phone"), 'phone': _("Phone"),
'parent_address': _("Native town"), 'parent_address': _("Native town"),
}) })
template_name = "trombi/edit_profile.jinja" template_name = "trombi/edit_profile.jinja"
current_tab = "profile" current_tab = "profile"
@ -251,7 +266,8 @@ class UserTrombiEditProfileView(QuickNotifMixin, TrombiTabsMixin, UpdateView):
return self.request.user return self.request.user
def get_success_url(self): def get_success_url(self):
return reverse('trombi:user_tools')+"?qn_success" return reverse('trombi:user_tools') + "?qn_success"
class UserTrombiResetClubMembershipsView(RedirectView): class UserTrombiResetClubMembershipsView(RedirectView):
permanent = False permanent = False
@ -262,7 +278,8 @@ class UserTrombiResetClubMembershipsView(RedirectView):
return redirect(self.get_success_url()) return redirect(self.get_success_url())
def get_success_url(self): def get_success_url(self):
return reverse('trombi:profile')+"?qn_success" return reverse('trombi:profile') + "?qn_success"
class UserTrombiDeleteMembershipView(TrombiTabsMixin, CanEditMixin, DeleteView): class UserTrombiDeleteMembershipView(TrombiTabsMixin, CanEditMixin, DeleteView):
model = TrombiClubMembership model = TrombiClubMembership
@ -274,6 +291,7 @@ class UserTrombiDeleteMembershipView(TrombiTabsMixin, CanEditMixin, DeleteView):
def get_success_url(self): def get_success_url(self):
return super(UserTrombiDeleteMembershipView, self).get_success_url() + "?qn_success" return super(UserTrombiDeleteMembershipView, self).get_success_url() + "?qn_success"
class UserTrombiEditMembershipView(CanEditMixin, TrombiTabsMixin, UpdateView): class UserTrombiEditMembershipView(CanEditMixin, TrombiTabsMixin, UpdateView):
model = TrombiClubMembership model = TrombiClubMembership
pk_url_kwarg = "membership_id" pk_url_kwarg = "membership_id"
@ -300,6 +318,7 @@ class UserTrombiProfileView(TrombiTabsMixin, DetailView):
raise Http404() raise Http404()
return super(UserTrombiProfileView, self).get(request, *args, **kwargs) return super(UserTrombiProfileView, self).get(request, *args, **kwargs)
class TrombiCommentFormView(): class TrombiCommentFormView():
""" """
Create/edit a trombi comment Create/edit a trombi comment
@ -312,20 +331,20 @@ class TrombiCommentFormView():
self.trombi = self.request.user.trombi_user.trombi self.trombi = self.request.user.trombi_user.trombi
if date.today() <= self.trombi.subscription_deadline: if date.today() <= self.trombi.subscription_deadline:
raise Http404(_("You can not yet write comment, you must wait for " 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(): if self.trombi.comments_deadline < date.today():
raise Http404(_("You can not write comment anymore, the deadline is " raise Http404(_("You can not write comment anymore, the deadline is "
"already passed.")) "already passed."))
return modelform_factory(self.model, fields=self.fields, return modelform_factory(self.model, fields=self.fields,
widgets={ widgets={
'content': forms.widgets.Textarea(attrs={'maxlength': self.trombi.max_chars}) 'content': forms.widgets.Textarea(attrs={'maxlength': self.trombi.max_chars})
}, },
help_texts={ help_texts={
'content': _("Maximum characters: %(max_length)s") % {'max_length': self.trombi.max_chars} 'content': _("Maximum characters: %(max_length)s") % {'max_length': self.trombi.max_chars}
}) })
def get_success_url(self): 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): def get_context_data(self, **kwargs):
kwargs = super(TrombiCommentFormView, self).get_context_data(**kwargs) kwargs = super(TrombiCommentFormView, self).get_context_data(**kwargs)
@ -335,6 +354,7 @@ class TrombiCommentFormView():
kwargs['target'] = self.object.target kwargs['target'] = self.object.target
return kwargs return kwargs
class TrombiCommentCreateView(TrombiCommentFormView, CreateView): class TrombiCommentCreateView(TrombiCommentFormView, CreateView):
def form_valid(self, form): def form_valid(self, form):
target = get_object_or_404(TrombiUser, id=self.kwargs['user_id']) target = get_object_or_404(TrombiUser, id=self.kwargs['user_id'])
@ -342,11 +362,10 @@ class TrombiCommentCreateView(TrombiCommentFormView, CreateView):
form.instance.target = target form.instance.target = target
return super(TrombiCommentCreateView, self).form_valid(form) return super(TrombiCommentCreateView, self).form_valid(form)
class TrombiCommentEditView(TrombiCommentFormView, CanViewMixin, UpdateView): class TrombiCommentEditView(TrombiCommentFormView, CanViewMixin, UpdateView):
pk_url_kwarg = "comment_id" pk_url_kwarg = "comment_id"
def form_valid(self, form): def form_valid(self, form):
form.instance.is_moderated = False form.instance.is_moderated = False
return super(TrombiCommentEditView, self).form_valid(form) return super(TrombiCommentEditView, self).form_valid(form)