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(Label)
admin.site.register(Company)

View File

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

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

View File

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

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

View File

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

View File

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

View File

@ -31,6 +31,7 @@ from club.models import Club
# Create your tests here.
class ClubTest(TestCase):
def setUp(self):
call_command("populate")
@ -41,34 +42,34 @@ class ClubTest(TestCase):
def test_create_add_user_to_club_from_root_ok(self):
self.client.login(username='root', password='plop')
self.client.post(reverse("club:club_members", kwargs={"club_id":self.bdf.id}), {
self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), {
"user": self.skia.id,
"start_date": "12/06/2016",
"role": 3})
response = self.client.get(reverse("club:club_members", kwargs={"club_id":self.bdf.id}))
response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id}))
self.assertTrue(response.status_code == 200)
self.assertTrue("S&#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):
self.client.login(username='root', password='plop')
response = self.client.post(reverse("club:club_members", kwargs={"club_id":self.bdf.id}), {
response = self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), {
"user": self.guy.id,
"start_date": "12/06/2016",
"role": 3})
self.assertTrue(response.status_code == 200)
self.assertTrue('<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))
def test_create_add_user_to_club_from_root_fail_already_in_club(self):
self.client.login(username='root', password='plop')
self.client.post(reverse("club:club_members", kwargs={"club_id":self.bdf.id}), {
self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), {
"user": self.skia.id,
"start_date": "12/06/2016",
"role": 3})
response = self.client.get(reverse("club:club_members", kwargs={"club_id":self.bdf.id}))
response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id}))
self.assertTrue("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,
"start_date": "12/06/2016",
"role": 4})
@ -77,30 +78,29 @@ class ClubTest(TestCase):
def test_create_add_user_to_club_from_skia_ok(self):
self.client.login(username='root', password='plop')
self.client.post(reverse("club:club_members", kwargs={"club_id":self.bdf.id}), {
self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), {
"user": self.skia.id,
"start_date": "12/06/2016",
"role": 10})
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,
"start_date": "12/06/2016",
"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("""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):
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,
"start_date": "12/06/2016",
"role": 3})
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,
"start_date": "12/06/2016",
"role": 10})
self.assertTrue(response.status_code == 200)
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 *
@ -40,4 +40,3 @@ urlpatterns = [
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'),
]

View File

@ -23,27 +23,21 @@
#
from django import forms
from django.shortcuts import render
from django.views.generic import ListView, DetailView, TemplateView
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.core.urlresolvers import reverse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext as _t
from django.conf import settings
from ajax_select.fields import AutoCompleteSelectField
from datetime import timedelta
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 core.models import User
from sith.settings import SITH_MAXIMUM_FREE_ROLE, SITH_MAIN_BOARD_GROUP
from counter.models import Product, Selling, Counter
from sith.settings import SITH_MAXIMUM_FREE_ROLE
from counter.models import Selling, Counter
class ClubTabsMixin(TabedViewMixin):
def get_tabs_title(self):
@ -52,45 +46,46 @@ class ClubTabsMixin(TabedViewMixin):
def get_list_of_tabs(self):
tab_list = []
tab_list.append({
'url': reverse('club:club_view', kwargs={'club_id': self.object.id}),
'slug': 'infos',
'url': reverse('club:club_view', kwargs={'club_id': self.object.id}),
'slug': 'infos',
'name': _("Infos"),
})
})
if self.request.user.can_view(self.object):
tab_list.append({
'url': reverse('club:club_members', kwargs={'club_id': self.object.id}),
'slug': 'members',
'url': reverse('club:club_members', kwargs={'club_id': self.object.id}),
'slug': 'members',
'name': _("Members"),
})
})
tab_list.append({
'url': reverse('club:club_old_members', kwargs={'club_id': self.object.id}),
'slug': 'elderlies',
'url': reverse('club:club_old_members', kwargs={'club_id': self.object.id}),
'slug': 'elderlies',
'name': _("Old members"),
})
})
if self.request.user.can_edit(self.object):
tab_list.append({
'url': reverse('club:tools', kwargs={'club_id': self.object.id}),
'slug': 'tools',
'url': reverse('club:tools', kwargs={'club_id': self.object.id}),
'slug': 'tools',
'name': _("Tools"),
})
})
tab_list.append({
'url': reverse('club:club_edit', kwargs={'club_id': self.object.id}),
'slug': 'edit',
'url': reverse('club:club_edit', kwargs={'club_id': self.object.id}),
'slug': 'edit',
'name': _("Edit"),
})
})
tab_list.append({
'url': reverse('club:club_sellings', kwargs={'club_id': self.object.id}),
'slug': 'sellings',
'url': reverse('club:club_sellings', kwargs={'club_id': self.object.id}),
'slug': 'sellings',
'name': _("Sellings"),
})
})
if self.request.user.is_owner(self.object):
tab_list.append({
'url': reverse('club:club_prop', kwargs={'club_id': self.object.id}),
'slug': 'props',
'url': reverse('club:club_prop', kwargs={'club_id': self.object.id}),
'slug': 'props',
'name': _("Props"),
})
})
return tab_list
class ClubListView(ListView):
"""
List the Clubs
@ -98,6 +93,7 @@ class ClubListView(ListView):
model = Club
template_name = 'club/club_list.jinja'
class ClubView(ClubTabsMixin, DetailView):
"""
Front page of a Club
@ -107,6 +103,7 @@ class ClubView(ClubTabsMixin, DetailView):
template_name = 'club/club_detail.jinja'
current_tab = "infos"
class ClubToolsView(ClubTabsMixin, CanEditMixin, DetailView):
"""
Tools page of a Club
@ -116,27 +113,30 @@ class ClubToolsView(ClubTabsMixin, CanEditMixin, DetailView):
template_name = 'club/club_tools.jinja'
current_tab = "tools"
class ClubMemberForm(forms.ModelForm):
"""
Form handling the members of a club
"""
error_css_class = 'error'
required_css_class = 'required'
class Meta:
model = Membership
fields = ['user', 'role', 'start_date', 'description']
widgets = {
'start_date': SelectDate
}
'start_date': SelectDate
}
user = AutoCompleteSelectField('users', required=True, label=_("Select user"), help_text=None)
def save(self, *args, **kwargs):
"""
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
class ClubMembersView(ClubTabsMixin, CanViewMixin, UpdateView):
"""
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.
"""
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()
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)
if not self.request.user.is_root:
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
(ms is not None and ms.role >= form.cleaned_data['role']) or
request.user.is_board_member or
request.user.is_root):
request.user.is_root):
return self.form_valid(form)
else:
form.add_error(None, _("You do not have the permission to do that"))
@ -182,6 +182,7 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, UpdateView):
else:
return self.form_invalid(form)
class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView):
"""
Old members of a club
@ -191,11 +192,13 @@ class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView):
template_name = 'club/club_old_members.jinja'
current_tab = "elderlies"
class SellingsFormBase(forms.Form):
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)
counter = forms.ModelChoiceField(Counter.objects.order_by('name').all(), label=_("Counter"), required=False)
class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView):
"""
Sellings of a club
@ -207,8 +210,8 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView):
def get_form_class(self):
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)
def get_context_data(self, **kwargs):
@ -235,6 +238,7 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView):
kwargs['form'] = form
return kwargs
class ClubSellingCSVView(ClubSellingView):
"""
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('Total'), kwargs['total']])
writer.writerow([_t('Benefit'), kwargs['benefit']])
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')])
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')])
for o in kwargs['result']:
row = [o.date, o.counter]
if o.seller:
row.append(o.seller.get_display_name())
else: row.append('')
else:
row.append('')
if o.customer:
row.append(o.customer.user.get_display_name())
else: row.append('')
row = row +[o.label, o.quantity, o.quantity * o.unit_price,
o.get_payment_method_display()]
else:
row.append('')
row = row + [o.label, o.quantity, o.quantity * o.unit_price,
o.get_payment_method_display()]
if o.product:
row.append(o.product.selling_price)
row.append(o.product.purchase_price)
row.append(o.product.selling_price - o.product.purchase_price)
else: row = row + ['', '', '']
else:
row = row + ['', '', '']
writer.writerow(row)
return response
class ClubEditView(ClubTabsMixin, CanEditMixin, UpdateView):
"""
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'
current_tab = "edit"
class ClubEditPropView(ClubTabsMixin, CanEditPropMixin, UpdateView):
"""
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'
current_tab = "props"
class ClubCreateView(CanEditPropMixin, CreateView):
"""
Create a club (for the Sith admin)
@ -302,6 +312,7 @@ class ClubCreateView(CanEditPropMixin, CreateView):
fields = ['name', 'unix_name', 'parent']
template_name = 'core/edit.jinja'
class MembershipSetOldView(CanEditMixin, DetailView):
"""
Set a membership as beeing old
@ -319,8 +330,9 @@ class MembershipSetOldView(CanEditMixin, DetailView):
self.object = self.get_object()
return HttpResponseRedirect(reverse('club:club_members', args=self.args, kwargs={'club_id': self.object.club.id}))
class ClubStatView(TemplateView):
template_name="club/stats.jinja"
template_name = "club/stats.jinja"
def get_context_data(self, **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(Weekmail)

View File

@ -25,15 +25,14 @@
from django.shortcuts import render
from django.db import models, transaction
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.contrib.staticfiles.templatetags.staticfiles import static
from django.core.mail import EmailMultiAlternatives
from django.core.exceptions import ValidationError
from core.models import User, Preferences
from club.models import Club
import os
class Sith(models.Model):
"""A one instance class storing all the modifiable infos"""
@ -48,12 +47,14 @@ class Sith(models.Model):
def __str__(self):
return "⛩ Sith ⛩"
NEWS_TYPES = [
('NOTICE', _('Notice')),
('EVENT', _('Event')),
('WEEKLY', _('Weekly')),
('CALL', _('Call')),
]
('NOTICE', _('Notice')),
('EVENT', _('Event')),
('WEEKLY', _('Weekly')),
('CALL', _('Call')),
]
class News(models.Model):
"""The news class"""
@ -81,6 +82,7 @@ class News(models.Model):
def __str__(self):
return "%s: %s" % (self.type, self.title)
class NewsDate(models.Model):
"""
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):
return "%s: %s - %s" % (self.news.title, self.start_date, self.end_date)
class Weekmail(models.Model):
"""
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')]
with transaction.atomic():
email = EmailMultiAlternatives(
subject=self.title,
body=self.render_text(),
from_email=settings.SITH_COM_EMAIL,
to=Sith.objects.first().weekmail_destinations.split(' '),
bcc=dest,
)
subject=self.title,
body=self.render_text(),
from_email=settings.SITH_COM_EMAIL,
to=Sith.objects.first().weekmail_destinations.split(' '),
bcc=dest,
)
email.attach_alternative(self.render_html(), "text/html")
email.send()
self.sent = True
@ -128,12 +131,12 @@ class Weekmail(models.Model):
def render_text(self):
return render(None, "com/weekmail_renderer_text.jinja", context={
'weekmail': self,
}).content.decode('utf-8')
}).content.decode('utf-8')
def render_html(self):
return render(None, "com/weekmail_renderer_html.jinja", context={
'weekmail': self,
}).content.decode('utf-8')
}).content.decode('utf-8')
def get_banner(self):
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):
return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
class WeekmailArticle(models.Model):
weekmail = models.ForeignKey(Weekmail, related_name="articles", verbose_name=_("weekmail"), null=True)
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 core.models import User, RealGroup
from com.models import Sith
class ComTest(TestCase):
def setUp(self):
@ -56,4 +56,3 @@ class ComTest(TestCase):
r = self.client.get(reverse("core:index"))
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))

View File

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

View File

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

View File

@ -23,9 +23,9 @@
#
from django.apps import AppConfig
from django.dispatch import receiver
from django.core.signals import request_started
class SithConfig(AppConfig):
name = 'core'
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_memberships, weak=False, dispatch_uid="clear_cached_memberships")
# 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 accounting.models import ClubAccount, Company
def check_token(request):
return ('counter_token' in request.session.keys() and
request.session['counter_token'] and
Counter.objects.filter(token=request.session['counter_token']).exists())
request.session['counter_token'] and
Counter.objects.filter(token=request.session['counter_token']).exists())
class RightManagedLookupChannel(LookupChannel):
def check_auth(self, request):
if not request.user.was_subscribed and not check_token(request):
raise PermissionDenied
@register('users')
class UsersLookup(RightManagedLookupChannel):
model = User
@ -54,6 +57,7 @@ class UsersLookup(RightManagedLookupChannel):
def format_item_display(self, item):
return item.get_display_name()
@register('groups')
class GroupsLookup(RightManagedLookupChannel):
model = Group
@ -67,6 +71,7 @@ class GroupsLookup(RightManagedLookupChannel):
def format_item_display(self, item):
return item.name
@register('clubs')
class ClubLookup(RightManagedLookupChannel):
model = Club
@ -80,6 +85,7 @@ class ClubLookup(RightManagedLookupChannel):
def format_item_display(self, item):
return item.name
@register('counters')
class CountersLookup(RightManagedLookupChannel):
model = Counter
@ -90,6 +96,7 @@ class CountersLookup(RightManagedLookupChannel):
def format_item_display(self, item):
return item.name
@register('products')
class ProductsLookup(RightManagedLookupChannel):
model = Product
@ -101,6 +108,7 @@ class ProductsLookup(RightManagedLookupChannel):
def format_item_display(self, item):
return "%s (%s)" % (item.name, item.code)
@register('files')
class SithFileLookup(RightManagedLookupChannel):
model = SithFile
@ -108,6 +116,7 @@ class SithFileLookup(RightManagedLookupChannel):
def get_query(self, q, request):
return self.model.objects.filter(name__icontains=q)[:50]
@register('club_accounts')
class ClubAccountLookup(RightManagedLookupChannel):
model = ClubAccount
@ -118,6 +127,7 @@ class ClubAccountLookup(RightManagedLookupChannel):
def format_item_display(self, item):
return item.name
@register('companies')
class CompaniesLookup(RightManagedLookupChannel):
model = Company

View File

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

View File

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

View File

@ -26,7 +26,7 @@ import os
from datetime import date, datetime
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.conf import settings
from django.db import connection
@ -42,7 +42,7 @@ from subscription.models import Subscription
from counter.models import Customer, ProductType, Product, Counter
from com.models import Sith, Weekmail
from election.models import Election, Role, Candidature, ElectionList
from forum.models import Forum, ForumMessage, ForumTopic
from forum.models import Forum, ForumTopic
class Command(BaseCommand):
@ -75,9 +75,9 @@ class Command(BaseCommand):
Group(name="Forum admin").save()
self.reset_index("core", "auth")
root = User(id=0, username='root', last_name="", first_name="Bibou",
email="ae.info@utbm.fr",
date_of_birth="1942-06-12",
is_superuser=True, is_staff=True)
email="ae.info@utbm.fr",
date_of_birth="1942-06-12",
is_superuser=True, is_staff=True)
root.set_password("plop")
root.save()
profiles_root = SithFile(parent=None, name="profiles", is_folder=True, owner=root)
@ -88,18 +88,18 @@ class Command(BaseCommand):
club_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'],
address=settings.SITH_MAIN_CLUB['address'])
address=settings.SITH_MAIN_CLUB['address'])
main_club.save()
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()
launderette_club = Club(id=84, name=settings.SITH_LAUNDERETTE_MANAGER['name'],
unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name'],
address=settings.SITH_LAUNDERETTE_MANAGER['address'])
unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name'],
address=settings.SITH_LAUNDERETTE_MANAGER['address'])
launderette_club.save()
self.reset_index("club")
for b in settings.SITH_COUNTER_BARS:
g = Group(name=b[1]+" admin")
g = Group(name=b[1] + " admin")
g.save()
c = Counter(id=b[0], name=b[1], club=bar_club, type='BAR')
c.save()
@ -120,7 +120,7 @@ class Command(BaseCommand):
p = Page(name='Index')
p.set_lock(root)
p.save()
p.view_groups=[settings.SITH_GROUP_PUBLIC_ID]
p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
p.set_lock(root)
p.save()
PageRev(page=p, title="Wiki index", author=root, content="""
@ -130,7 +130,7 @@ Welcome to the wiki page!
p = Page(name="services")
p.set_lock(root)
p.save()
p.view_groups=[settings.SITH_GROUP_PUBLIC_ID]
p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
p.set_lock(root)
PageRev(page=p, title="Services", author=root, content="""
| | | |
@ -150,18 +150,18 @@ Welcome to the wiki page!
if not options['prod']:
# Adding user Skia
skia = User(username='skia', last_name="Kia", first_name="S'",
email="skia@git.an",
date_of_birth="1942-06-12")
email="skia@git.an",
date_of_birth="1942-06-12")
skia.set_password("plop")
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_profile_path = os.path.join(root_path, 'core/fixtures/images/3.jpg')
with open(skia_profile_path, 'rb') as f:
name = str(skia.id) + "_profile.jpg"
skia_profile = SithFile(parent=profiles_root, name=name,
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))
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))
skia_profile.file.name = name
skia_profile.save()
skia.profile_pict = skia_profile
@ -169,50 +169,50 @@ Welcome to the wiki page!
# Adding user public
public = User(username='public', last_name="Not subscribed", first_name="Public",
email="public@git.an",
date_of_birth="1942-06-12",
is_superuser=False, is_staff=False)
email="public@git.an",
date_of_birth="1942-06-12",
is_superuser=False, is_staff=False)
public.set_password("plop")
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()
# Adding user Subscriber
subscriber = User(username='subscriber', last_name="User", first_name="Subscribed",
email="Subscribed@git.an",
date_of_birth="1942-06-12",
is_superuser=False, is_staff=False)
email="Subscribed@git.an",
date_of_birth="1942-06-12",
is_superuser=False, is_staff=False)
subscriber.set_password("plop")
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()
# Adding user old Subscriber
old_subscriber = User(username='old_subscriber', last_name="Subscriber", first_name="Old",
email="old_subscriber@git.an",
date_of_birth="1942-06-12",
is_superuser=False, is_staff=False)
email="old_subscriber@git.an",
date_of_birth="1942-06-12",
is_superuser=False, is_staff=False)
old_subscriber.set_password("plop")
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()
# Adding user Counter admin
counter = User(username='counter', last_name="Ter", first_name="Coun",
email="counter@git.an",
date_of_birth="1942-06-12",
is_superuser=False, is_staff=False)
email="counter@git.an",
date_of_birth="1942-06-12",
is_superuser=False, is_staff=False)
counter.set_password("plop")
counter.save()
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.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.save()
# Adding user Comptable
comptable = User(username='comptable', last_name="Able", first_name="Compte",
email="compta@git.an",
date_of_birth="1942-06-12",
is_superuser=False, is_staff=False)
email="compta@git.an",
date_of_birth="1942-06-12",
is_superuser=False, is_staff=False)
comptable.set_password("plop")
comptable.save()
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.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.save()
# Adding user 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)
u.set_password("plop")
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()
# Adding user Richard Batsbak
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")
r.set_password("plop")
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()
# Adding syntax help page
p = Page(name='Aide_sur_la_syntaxe')
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()
p.view_groups=[settings.SITH_GROUP_PUBLIC_ID]
p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
p.save(force_lock=True)
p = Page(name='Services')
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)
PageRev(page=p, title="Services", author=skia, content="""
| | | |
@ -252,83 +252,83 @@ Welcome to the wiki page!
# Adding README
p = Page(name='README')
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)
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()
# Subscription
## Root
# Root
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_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
s.save()
## Skia
# Skia
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_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
s.save()
## Counter admin
# Counter admin
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_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
s.save()
## Comptable
# Comptable
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_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
s.save()
## Richard
# Richard
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_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
s.save()
## User
# User
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_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
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],
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_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
s.save()
# Clubs
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",
address="42 de la Boustifaille", parent=main_club)
address="42 de la Boustifaille", parent=main_club)
guyut.save()
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()
troll = Club(name="Troll Penché", unix_name="troll",
address="Terre Du Milieu", parent=main_club)
address="Terre Du Milieu", parent=main_club)
troll.save()
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()
# Counters
@ -341,19 +341,19 @@ Welcome to the wiki page!
r = ProductType(name="Rechargements")
r.save()
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()
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()
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()
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()
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()
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()
@ -375,7 +375,7 @@ Welcome to the wiki page!
refound_counter = Counter(name="Carte AE", club=refound, type='OFFICE')
refound_counter.save()
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()
# Accounting test values:
@ -397,28 +397,28 @@ Welcome to the wiki page!
buying.save()
comptes = AccountingType(code='6', label="Comptes de charge", movement_type='DEBIT')
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()
woenzco = Company(name="Woenzel & co")
woenzco.save()
operation_list = [
(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),
(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),
(300, "Paiement Guy", 'CASH', None, buying, 'USER', skia.id, "", None),
(32.3, "Essence", 'CASH', None, buying, 'OTHER', None, "station", None),
(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),
(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),
]
(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),
(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),
(300, "Paiement Guy", 'CASH', None, buying, 'USER', skia.id, "", None),
(32.3, "Essence", 'CASH', None, buying, 'OTHER', None, "station", None),
(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),
(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),
]
for op in operation_list:
operation = Operation(journal=gj, date=date.today(), amount=op[0],
remark=op[1], mode=op[2], done=True, simpleaccounting_type=op[3],
accounting_type=op[4], target_type=op[5], target_id=op[6],
target_label=op[7], cheque_number=op[8])
remark=op[1], mode=op[2], done=True, simpleaccounting_type=op[3],
accounting_type=op[4], target_type=op[5], target_id=op[6],
target_label=op[7], cheque_number=op[8])
operation.clean()
operation.save()
@ -428,14 +428,14 @@ Welcome to the wiki page!
date_of_birth="1942-06-12")
sli.set_password("plop")
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_profile_path = os.path.join(root_path, 'core/fixtures/images/5.jpg')
with open(sli_profile_path, 'rb') as f:
name = str(sli.id) + "_profile.jpg"
sli_profile = SithFile(parent=profiles_root, name=name,
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))
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))
sli_profile.file.name = name
sli_profile.save()
sli.profile_pict = sli_profile
@ -450,27 +450,27 @@ Welcome to the wiki page!
with open(krophil_profile_path, 'rb') as f:
name = str(krophil.id) + "_profile.jpg"
krophil_profile = SithFile(parent=profiles_root, name=name,
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))
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))
krophil_profile.file.name = name
krophil_profile.save()
krophil.profile_pict = krophil_profile
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],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
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],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start)
s.save()
# Add barman to counter
@ -483,8 +483,8 @@ Welcome to the wiki page!
subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_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',
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_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')
el.save()
el.view_groups.add(public_group)
el.edit_groups.add(ae_board_group)
@ -519,4 +519,3 @@ Welcome to the wiki page!
various.save()
Forum(name="Promos", description="Réservé aux Promos", parent=various).save()
ForumTopic(forum=hall)

View File

@ -23,9 +23,8 @@
#
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.conf import settings
class Command(BaseCommand):
@ -37,7 +36,7 @@ class Command(BaseCommand):
def handle(self, *args, **options):
root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
try:
os.mkdir(os.path.join(root_path)+'/data')
os.mkdir(os.path.join(root_path) + '/data')
print("Data dir created")
except Exception as e:
repr(e)

View File

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

View File

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

View File

@ -44,14 +44,17 @@ from datetime import datetime, timedelta, date
import unicodedata
class RealGroupManager(AuthGroupManager):
def get_queryset(self):
return super(RealGroupManager, self).get_queryset().filter(is_meta=False)
class MetaGroupManager(AuthGroupManager):
def get_queryset(self):
return super(MetaGroupManager, self).get_queryset().filter(is_meta=True)
class Group(AuthGroup):
is_meta = models.BooleanField(
_('meta group status'),
@ -69,8 +72,10 @@ class Group(AuthGroup):
"""
return reverse('core:group_list')
class MetaGroup(Group):
objects = MetaGroupManager()
class Meta:
proxy = True
@ -78,20 +83,24 @@ class MetaGroup(Group):
super(MetaGroup, self).__init__(*args, **kwargs)
self.is_meta = True
class RealGroup(Group):
objects = RealGroupManager()
class Meta:
proxy = True
def validate_promo(value):
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:
raise ValidationError(
_('%(value)s is not a valid promo (between 0 and %(end)s)'),
params={'value': value, 'end': delta},
)
class User(AbstractBaseUser):
"""
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)
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,
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,
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,
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")
tshirt_size = models.CharField(_("tshirt size"), max_length=5, choices=[
("-", _("-")),
@ -165,7 +174,7 @@ class User(AbstractBaseUser):
("XL", _("XL")),
("XXL", _("XXL")),
("XXXL", _("XXXL")),
], default="-")
], default="-")
role = models.CharField(_("role"), max_length=15, choices=[
("STUDENT", _("Student")),
("ADMINISTRATIVE", _("Administrative agent")),
@ -174,9 +183,9 @@ class User(AbstractBaseUser):
("DOCTOR", _("Doctor")),
("FORMER STUDENT", _("Former student")),
("SERVICE", _("Service")),
], blank=True, default="")
], blank=True, default="")
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="")
semester = models.CharField(_("semester"), max_length=5, blank=True, default="")
quote = models.CharField(_("quote"), max_length=256, blank=True, default="")
@ -226,11 +235,12 @@ class User(AbstractBaseUser):
_club_memberships = {}
_group_names = {}
_group_ids = {}
def is_in_group(self, group_name):
"""If the user is in the group passed in argument (as string or by id)"""
group_id = 0
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():
g = User._group_ids[group_name]
else:
@ -253,7 +263,7 @@ class User(AbstractBaseUser):
return self.is_subscribed
if group_id == settings.SITH_GROUP_OLD_SUBSCRIBERS_ID:
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
if group_name[-len(settings.SITH_BOARD_SUFFIX):] == settings.SITH_BOARD_SUFFIX:
name = group_name[:-len(settings.SITH_BOARD_SUFFIX)]
@ -315,7 +325,7 @@ class User(AbstractBaseUser):
else:
create = True
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
try:
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"))
db.commit()
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,
self.email), file=f)
self.email), file=f)
print("Reason: %s" % (repr(e)), file=f)
db.rollback()
@ -401,13 +411,13 @@ class User(AbstractBaseUser):
Returns the generated username
"""
def remove_accents(data):
return ''.join(x for x in unicodedata.normalize('NFKD', data) if \
unicodedata.category(x)[0] == 'L').lower()
user_name = remove_accents(self.first_name[0]+self.last_name).encode('ascii', 'ignore').decode('utf-8')
return ''.join(x for x in unicodedata.normalize('NFKD', data) if
unicodedata.category(x)[0] == 'L').lower()
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()]
if user_name in un_set:
i = 1
while user_name+str(i) in un_set:
while user_name + str(i) in un_set:
i += 1
user_name += str(i)
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"),
_("Profile"),
escape(self.get_display_name()),
)
)
@cached_property
def subscribed(self):
@ -489,6 +499,7 @@ class User(AbstractBaseUser):
infos.save()
return infos
class AnonymousUser(AuthAnonymousUser):
def __init__(self, request):
super(AnonymousUser, self).__init__()
@ -530,7 +541,7 @@ class AnonymousUser(AuthAnonymousUser):
The anonymous user is only the public group
"""
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()
if g:
group_name = g.name
@ -557,6 +568,7 @@ class AnonymousUser(AuthAnonymousUser):
def get_display_name(self):
return _("Visitor")
class Preferences(models.Model):
user = models.OneToOneField(User, related_name="preferences")
receive_weekmail = models.BooleanField(
@ -576,15 +588,19 @@ class Preferences(models.Model):
def get_absolute_url(self):
return self.user.get_absolute_url()
def get_directory(instance, filename):
return '.{0}/{1}'.format(instance.get_parent_path(), filename)
def get_compressed_directory(instance, filename):
return '.{0}/compressed/{1}'.format(instance.get_parent_path(), filename)
def get_thumbnail_directory(instance, filename):
return '.{0}/thumbnail/{1}'.format(instance.get_parent_path(), filename)
class SithFile(models.Model):
name = models.CharField(_('file name'), max_length=256, blank=False)
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)
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)
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:
verbose_name = _("file")
@ -763,18 +779,22 @@ class SithFile(models.Model):
def __str__(self):
return self.get_parent_path() + "/" + self.name
class LockError(Exception):
"""There was a lock error on the object"""
pass
class AlreadyLocked(LockError):
"""The object is already locked"""
pass
class NotLocked(LockError):
"""The object is not locked"""
pass
class Page(models.Model):
"""
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!
"""
name = models.CharField(_('page unix name'), max_length=30,
validators=[
validators.RegexValidator(
r'^[A-z.+-]+$',
_('Enter a valid page name. This value may contain only '
'unaccented letters, numbers ' 'and ./+/-/_ characters.')
),
],
blank=False)
validators=[
validators.RegexValidator(
r'^[A-z.+-]+$',
_('Enter a valid page name. This value may contain only '
'unaccented letters, numbers ' 'and ./+/-/_ characters.')
), ],
blank=False)
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
# 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
"""
locked = kwargs.pop('force_lock', False)
if not locked: locked = self.is_locked()
if not locked:
locked = self.is_locked()
if not locked:
raise NotLocked("The page is not locked and thus can not be saved")
self.full_clean()
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
# recursive method
# 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')
class Meta:
ordering = ['date',]
ordering = ['date', ]
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
self.page.unset_lock()
class Notification(models.Model):
user = models.ForeignKey(User, related_name='notifications')
url = models.CharField(_("url"), max_length=255)
@ -1015,4 +1036,3 @@ class Notification(models.Model):
if self.param:
return self.get_type_display() % self.param
return self.get_type_display()

View File

@ -27,30 +27,32 @@ from django import template
from django.template.defaultfilters import stringfilter
from django.utils.safestring import mark_safe
from core.scss.processor import ScssProcessor
from django.utils.html import escape
from core.markdown import markdown as md
register = template.Library()
@register.filter(is_safe=False)
@stringfilter
def markdown(text):
return mark_safe("<div class=\"markdown\">%s</div>" % md(text))
@register.filter()
@stringfilter
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.
"""
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
for py, php in python2PHP.items():
php_format_string = php_format_string.replace(py, php)
return php_format_string
@register.simple_tag()
def scss(path):
"""

View File

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

View File

@ -27,9 +27,10 @@ import re
# Image utils
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
from django.conf import settings
@ -49,16 +50,17 @@ def get_start_of_semester(d=date.today()):
today = d
year = today.year
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:
start, start2 = start2, start
if today < start:
return start2.replace(year=year-1)
return start2.replace(year=year - 1)
elif today < start2:
return start
else:
return start2
def get_semester(d=date.today()):
start = get_start_of_semester(d)
if start.month <= 6:
@ -66,6 +68,7 @@ def get_semester(d=date.today()):
else:
return "A" + str(start.year)[-2:]
def scale_dimension(width, height, long_edge):
if width > height:
ratio = long_edge * 1. / width
@ -73,6 +76,7 @@ def scale_dimension(width, height, long_edge):
ratio = long_edge * 1. / height
return int(width * ratio), int(height * ratio)
def resize_image(im, edge, format):
(w, h) = im.size
(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)
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 :
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)
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:
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
def doku_to_markdown(text):
"""This is a quite correct doku translator"""
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'<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'([^:]|^)\/\/(.*?)\/\/', 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'<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'^======(.*?)======', 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)
@ -121,93 +128,95 @@ def doku_to_markdown(text):
text = re.sub(r'dfile://', r'file://', text)
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 += "\n[^%s]: %s\n" % (i, fn)
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'[\1](\1)', text) # Links 2
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)', text) # Video (transform to classic links, since we can't integrate them)
text = re.sub(r'\[\[(.*?)\|(.*?)\]\]', r'[\2](\1)', text) # Links
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'![\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'###(\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 = []
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)
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('^', '|')
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
elif enter or quit: # Quote part
for quote in enter: # Enter quotes (support multiple at a time)
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
for quote in enter: # Enter quotes (support multiple at a time)
quote_level += 1
try:
new_text.append("> " * quote_level + "##### " + quote.group(2))
except:
new_text.append("> " * quote_level)
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
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), '')
quote_level -= 1
final_newline = True
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
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
else:
new_text.append(line)
return "\n".join(new_text)
def bbcode_to_markdown(text):
"""This is a very basic BBcode translator"""
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'\[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'\[strike\](.*?)\[\/strike\]', r'~~\1~~', text, flags=re.DOTALL) # Strike 2
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'\[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'\[strike\](.*?)\[\/strike\]', r'~~\1~~', text, flags=re.DOTALL) # Strike 2
text = re.sub(r'article://', r'page://', 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'\1', text) # Links 2
text = re.sub(r'\[img\](.*)\[\/img\]', r'![\1](\1 "\1")', text) # Images
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'\[img\](.*)\[\/img\]', r'![\1](\1 "\1")', text) # Images
new_text = []
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)
quit = re.finditer(r'\[/quote\]', line)
if enter or quit: # Quote part
for quote in enter: # Enter quotes (support multiple at a time)
if enter or quit: # Quote part
for quote in enter: # Enter quotes (support multiple at a time)
quote_level += 1
try:
new_text.append("> " * quote_level + "##### " + quote.group(2))
except:
new_text.append("> " * quote_level)
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
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), '')
quote_level -= 1
final_newline = True
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
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
else:
new_text.append(line)
return "\n".join(new_text)

View File

@ -23,29 +23,28 @@
#
# 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.edit import UpdateView, CreateView, FormMixin, DeleteView
from django.views.generic.edit import UpdateView, FormMixin, DeleteView
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 import CheckboxSelectMultiple
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.http import HttpResponse
from django.core.servers.basehttp import FileWrapper
from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
from django.core.exceptions import PermissionDenied
from django import forms
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.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
def send_file(request, file_id, file_class=SithFile, file_attr="file"):
"""
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)
if not (can_view(f, request.user) or
('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())
):
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')
return response
class AddFilesForm(forms.Form):
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"),
required=False)
required=False)
def process(self, parent, owner, files):
notif = False
@ -85,10 +85,10 @@ class AddFilesForm(forms.Form):
notif = True
except Exception as e:
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:
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:
new_file.clean()
new_file.save()
@ -100,6 +100,7 @@ class AddFilesForm(forms.Form):
if not u.notifications.filter(type="FILE_MODERATION", viewed=False).exists():
Notification(user=u, url=reverse("core:file_moderation"), type="FILE_MODERATION").save()
class FileListView(ListView):
template_name = 'core/file_list.jinja'
context_object_name = "file_list"
@ -114,6 +115,7 @@ class FileListView(ListView):
kwargs['popup'] = 'popup'
return kwargs
class FileEditView(CanEditMixin, UpdateView):
model = SithFile
pk_url_kwarg = "file_id"
@ -138,6 +140,7 @@ class FileEditView(CanEditMixin, UpdateView):
kwargs['popup'] = 'popup'
return kwargs
class FileEditPropForm(forms.ModelForm):
class Meta:
model = SithFile
@ -147,6 +150,7 @@ class FileEditPropForm(forms.ModelForm):
view_groups = make_ajax_field(SithFile, 'view_groups', 'groups', help_text="", label=_("view group"))
recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False)
class FileEditPropView(CanEditPropMixin, UpdateView):
model = SithFile
pk_url_kwarg = "file_id"
@ -175,6 +179,7 @@ class FileEditPropView(CanEditPropMixin, UpdateView):
kwargs['popup'] = 'popup'
return kwargs
class FileView(CanViewMixin, DetailView, FormMixin):
"""This class handle the upload of new files into a folder"""
model = SithFile
@ -217,7 +222,7 @@ class FileView(CanViewMixin, DetailView, FormMixin):
request.session['clipboard'] = []
if request.user.can_edit(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')
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)
@ -237,6 +242,7 @@ class FileView(CanViewMixin, DetailView, FormMixin):
kwargs['clipboard'] = SithFile.objects.filter(id__in=self.request.session['clipboard'])
return kwargs
class FileDeleteView(CanEditPropMixin, DeleteView):
model = SithFile
pk_url_kwarg = "file_id"
@ -244,7 +250,7 @@ class FileDeleteView(CanEditPropMixin, DeleteView):
context_object_name = "file"
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():
return self.request.GET['next']
if self.object.parent is None:
@ -258,6 +264,7 @@ class FileDeleteView(CanEditPropMixin, DeleteView):
kwargs['popup'] = 'popup'
return kwargs
class FileModerationView(TemplateView):
template_name = "core/file_moderation.jinja"
@ -266,6 +273,7 @@ class FileModerationView(TemplateView):
kwargs['files'] = SithFile.objects.filter(is_moderated=False)[:100]
return kwargs
class FileModerateView(CanEditPropMixin, SingleObjectMixin):
model = SithFile
pk_url_kwarg = "file_id"
@ -278,4 +286,3 @@ class FileModerateView(CanEditPropMixin, SingleObjectMixin):
if 'next' in self.request.GET.keys():
return redirect(self.request.GET['next'])
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.conf import settings
from django.db import transaction
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.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget
from ajax_select.fields import AutoCompleteSelectField
import logging
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
@ -50,6 +52,7 @@ class SelectSingle(Select):
attrs = {'class': "select_single"}
return super(SelectSingle, self).render(name, value, attrs)
class SelectMultiple(Select):
def render(self, name, value, attrs=None):
if attrs:
@ -58,6 +61,7 @@ class SelectMultiple(Select):
attrs = {'class': "select_multiple"}
return super(SelectMultiple, self).render(name, value, attrs)
class SelectDateTime(DateTimeInput):
def render(self, name, value, attrs=None):
if attrs:
@ -66,6 +70,7 @@ class SelectDateTime(DateTimeInput):
attrs = {'class': "select_datetime"}
return super(SelectDateTime, self).render(name, value, attrs)
class SelectDate(DateInput):
def render(self, name, value, attrs=None):
if attrs:
@ -74,16 +79,18 @@ class SelectDate(DateInput):
attrs = {'class': "select_date"}
return super(SelectDate, self).render(name, value, attrs)
class MarkdownInput(Textarea):
def render(self, name, value, attrs=None):
output = '<p><a href="%(syntax_url)s">%(help_text)s</a></p>'\
'<div class="markdown_editor">%(content)s</div>' % {
'syntax_url': Page.get_page_by_full_name(settings.SITH_CORE_PAGE_SYNTAX).get_absolute_url(),
'help_text': _("Help on the syntax"),
'content': super(MarkdownInput, self).render(name, value, attrs),
}
'syntax_url': Page.get_page_by_full_name(settings.SITH_CORE_PAGE_SYNTAX).get_absolute_url(),
'help_text': _("Help on the syntax"),
'content': super(MarkdownInput, self).render(name, value, attrs),
}
return output
class SelectFile(TextInput):
def render(self, name, value, attrs=None):
if attrs:
@ -91,13 +98,14 @@ class SelectFile(TextInput):
else:
attrs = {'class': "select_file"}
output = '%(content)s<div name="%(name)s" class="choose_file_widget" title="%(title)s"></div>' % {
'content': super(SelectFile, self).render(name, value, attrs),
'title': _("Choose file"),
'name': name,
}
'content': super(SelectFile, self).render(name, value, attrs),
'title': _("Choose file"),
'name': name,
}
output += '<span name="' + name + '" class="choose_file_button">' + ugettext("Choose file") + '</span>'
return output
class SelectUser(TextInput):
def render(self, name, value, attrs=None):
if attrs:
@ -105,15 +113,16 @@ class SelectUser(TextInput):
else:
attrs = {'class': "select_user"}
output = '%(content)s<div name="%(name)s" class="choose_user_widget" title="%(title)s"></div>' % {
'content': super(SelectUser, self).render(name, value, attrs),
'title': _("Choose user"),
'name': name,
}
'content': super(SelectUser, self).render(name, value, attrs),
'title': _("Choose user"),
'name': name,
}
output += '<span name="' + name + '" class="choose_user_button">' + ugettext("Choose user") + '</span>'
return output
# Forms
class LoginForm(AuthenticationForm):
def __init__(self, *arg, **kwargs):
if 'data' in kwargs.keys():
@ -128,14 +137,17 @@ class LoginForm(AuthenticationForm):
else:
user = User.objects.filter(username=data['username']).first()
data['username'] = user.username
except: pass
except:
pass
kwargs['data'] = data
super(LoginForm, self).__init__(*arg, **kwargs)
self.fields['username'].label = _("Username, email, or account number")
class RegisteringForm(UserCreationForm):
error_css_class = 'error'
required_css_class = 'required'
class Meta:
model = User
fields = ('first_name', 'last_name', 'email')
@ -148,9 +160,6 @@ class RegisteringForm(UserCreationForm):
user.save()
return user
from core.utils import resize_image
from io import BytesIO
from PIL import Image
class UserProfileForm(forms.ModelForm):
"""
@ -161,22 +170,22 @@ class UserProfileForm(forms.ModelForm):
class Meta:
model = User
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',
'tshirt_size', 'role', 'department', 'dpt_option', 'semester', 'quote', 'school', 'promo',
'forum_signature', 'is_subscriber_viewable']
'scrub_pict', 'sex', 'second_email', 'address', 'parent_address', 'phone', 'parent_phone',
'tshirt_size', 'role', 'department', 'dpt_option', 'semester', 'quote', 'school', 'promo',
'forum_signature', 'is_subscriber_viewable']
widgets = {
'date_of_birth': SelectDate,
'profile_pict': forms.ClearableFileInput,
'avatar_pict': forms.ClearableFileInput,
'scrub_pict': forms.ClearableFileInput,
'phone': PhoneNumberInternationalFallbackWidget,
'parent_phone': PhoneNumberInternationalFallbackWidget,
}
'date_of_birth': SelectDate,
'profile_pict': forms.ClearableFileInput,
'avatar_pict': forms.ClearableFileInput,
'scrub_pict': forms.ClearableFileInput,
'phone': PhoneNumberInternationalFallbackWidget,
'parent_phone': PhoneNumberInternationalFallbackWidget,
}
labels = {
'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"),
'scrub_pict': _("Scrub: let other know how your scrub looks like!"),
}
'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"),
'scrub_pict': _("Scrub: let other know how your scrub looks like!"),
}
def __init__(self, *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['scrub_pict'] = scrub
parent = SithFile.objects.filter(parent=None, name="profiles").first()
for field,f in files:
for field, f in files:
with transaction.atomic():
try:
im = Image.open(BytesIO(f.read()))
new_file = SithFile(parent=parent, name=self.generate_name(field, f),
file=resize_image(im, 400, f.content_type.split('/')[-1]),
owner=self.instance, is_folder=False, mime_type=f.content_type, size=f._size,
moderator=self.instance, is_moderated=True)
file=resize_image(im, 400, f.content_type.split('/')[-1]),
owner=self.instance, is_folder=False, mime_type=f.content_type, size=f._size,
moderator=self.instance, is_moderated=True)
new_file.file.name = new_file.name
old = SithFile.objects.filter(parent=parent, name=new_file.name).first()
if old:
@ -216,16 +225,18 @@ class UserProfileForm(forms.ModelForm):
except ValidationError as e:
self._errors.pop(field, None)
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:
self._errors.pop(field, None)
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()
class UserPropForm(forms.ModelForm):
error_css_class = 'error'
required_css_class = 'required'
class Meta:
model = User
fields = ['groups']
@ -236,13 +247,16 @@ class UserPropForm(forms.ModelForm):
'groups': CheckboxSelectMultiple,
}
class UserGodfathersForm(forms.Form):
type = forms.ChoiceField(choices=[('godfather', _("Godfather")), ('godchild', _("Godchild"))], label=_("Add"))
user = AutoCompleteSelectField('users', required=True, label=_("Select user"), help_text=None)
class PagePropForm(forms.ModelForm):
error_css_class = 'error'
required_css_class = 'required'
class Meta:
model = Page
fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ]
@ -255,4 +269,3 @@ class PagePropForm(forms.ModelForm):
super(PagePropForm, self).__init__(*arg, **kwargs)
self.fields['edit_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.views import CanEditMixin
class GroupListView(CanEditMixin, ListView):
"""
Displays the group list
@ -36,17 +37,20 @@ class GroupListView(CanEditMixin, ListView):
model = RealGroup
template_name = "core/group_list.jinja"
class GroupEditView(CanEditMixin, UpdateView):
model = RealGroup
pk_url_kwarg = "group_id"
template_name = "core/group_edit.jinja"
fields = ['name', 'description']
class GroupCreateView(CanEditMixin, CreateView):
model = RealGroup
template_name = "core/group_edit.jinja"
fields = ['name', 'description']
class GroupDeleteView(CanEditMixin, DeleteView):
model = RealGroup
pk_url_kwarg = "group_id"

View File

@ -23,23 +23,22 @@
#
# 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.views.generic import ListView, DetailView
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 import CheckboxSelectMultiple, modelform_factory
from django.forms import CheckboxSelectMultiple
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
class PageListView(CanViewMixin, ListView):
model = Page
template_name = 'core/page_list.jinja'
class PageView(CanViewMixin, DetailView):
model = Page
template_name = 'core/page_detail.jinja'
@ -54,6 +53,7 @@ class PageView(CanViewMixin, DetailView):
context['new_page'] = self.kwargs['page_name']
return context
class PageHistView(CanViewMixin, DetailView):
model = Page
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'])
return self.page
class PageRevView(CanViewMixin, DetailView):
model = Page
template_name = 'core/page_detail.jinja'
@ -78,20 +79,21 @@ class PageRevView(CanViewMixin, DetailView):
rev = self.page.revisions.get(id=self.kwargs['rev'])
context['rev'] = rev
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
else:
context['new_page'] = self.kwargs['page_name']
return context
class PageCreateView(CanCreateMixin, CreateView):
model = Page
form_class = modelform_factory(Page,
fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ],
widgets={
'edit_groups':CheckboxSelectMultiple,
'view_groups':CheckboxSelectMultiple,
})
fields=['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ],
widgets={
'edit_groups': CheckboxSelectMultiple,
'view_groups': CheckboxSelectMultiple,
})
template_name = 'core/page_prop.jinja'
def get_initial(self):
@ -115,14 +117,15 @@ class PageCreateView(CanCreateMixin, CreateView):
ret = super(PageCreateView, self).form_valid(form)
return ret
class PagePropView(CanEditPropMixin, UpdateView):
model = Page
form_class = modelform_factory(Page,
fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ],
widgets={
'edit_groups':CheckboxSelectMultiple,
'view_groups':CheckboxSelectMultiple,
})
fields=['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ],
widgets={
'edit_groups': CheckboxSelectMultiple,
'view_groups': CheckboxSelectMultiple,
})
template_name = 'core/page_prop.jinja'
slug_field = '_full_name'
slug_url_kwarg = 'page_name'
@ -130,7 +133,7 @@ class PagePropView(CanEditPropMixin, UpdateView):
def get_object(self):
o = super(PagePropView, self).get_object()
# Create the page if it does not exists
#if p == None:
# if p == None:
# parent_name = '/'.join(page_name.split('/')[:-1])
# name = page_name.split('/')[-1]
# if parent_name == "":
@ -145,9 +148,10 @@ class PagePropView(CanEditPropMixin, UpdateView):
raise e
return self.page
class PageEditView(CanEditMixin, UpdateView):
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'
def get_object(self):

View File

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

View File

@ -24,30 +24,29 @@
# This file contains all the views that concern the user model
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.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.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 import CheckboxSelectMultiple
from django.template.response import TemplateResponse
from django.conf import settings
from django.views.generic.dates import YearMixin, MonthMixin
from django.utils import timezone
from datetime import timedelta, datetime, date
from datetime import timedelta, date
import logging
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 club.models import Club
from subscription.models import Subscription
from trombi.views import UserTrombiForm
def login(request):
"""
The login view
@ -56,24 +55,28 @@ def login(request):
"""
return views.login(request, template_name="core/login.jinja", authentication_form=LoginForm)
def logout(request):
"""
The logout view
"""
return views.logout_then_login(request)
def password_change(request):
"""
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"))
def password_change_done(request):
"""
Allows a user to change its password
"""
return views.password_change_done(request, template_name="core/password_change_done.jinja")
def password_root_change(request, user_id):
"""
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)
return TemplateResponse(request, "core/password_change.jinja", {'form': form, 'target': user})
def password_reset(request):
"""
Allows someone to enter an email adresse for resetting password
@ -100,7 +104,8 @@ def password_reset(request):
template_name="core/password_reset.jinja",
email_template_name="core/password_reset_email.jinja",
post_reset_redirect="core:password_reset_done",
)
)
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")
def password_reset_confirm(request, uidb64=None, token=None):
"""
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,
post_reset_redirect="core:password_reset_complete",
template_name="core/password_reset_confirm.jinja",
)
)
def password_reset_complete(request):
"""
@ -123,14 +130,15 @@ def password_reset_complete(request):
"""
return views.password_reset_complete(request,
template_name="core/password_reset_complete.jinja",
)
)
def register(request):
context = {}
if request.method == 'POST':
form = RegisteringForm(request.POST)
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()
context['user_registered'] = u
context['tests'] = 'TEST_REGISTER_USER_FORM_OK'
@ -143,6 +151,7 @@ def register(request):
context['form'] = form.as_p()
return render(request, "core/register.jinja", context)
class UserTabsMixin(TabedViewMixin):
def get_tabs_title(self):
return self.object.get_display_name()
@ -150,67 +159,69 @@ class UserTabsMixin(TabedViewMixin):
def get_list_of_tabs(self):
tab_list = []
tab_list.append({
'url': reverse('core:user_profile', kwargs={'user_id': self.object.id}),
'slug': 'infos',
'url': reverse('core:user_profile', kwargs={'user_id': self.object.id}),
'slug': 'infos',
'name': _("Infos"),
})
})
tab_list.append({
'url': reverse('core:user_godfathers', kwargs={'user_id': self.object.id}),
'slug': 'godfathers',
'url': reverse('core:user_godfathers', kwargs={'user_id': self.object.id}),
'slug': 'godfathers',
'name': _("Godfathers"),
})
})
tab_list.append({
'url': reverse('core:user_pictures', kwargs={'user_id': self.object.id}),
'slug': 'pictures',
'url': reverse('core:user_pictures', kwargs={'user_id': self.object.id}),
'slug': 'pictures',
'name': _("Pictures"),
})
})
if self.request.user == self.object:
tab_list.append({
'url': reverse('core:user_tools'),
'slug': 'tools',
'url': reverse('core:user_tools'),
'slug': 'tools',
'name': _("Tools"),
})
})
if self.request.user.can_edit(self.object):
tab_list.append({
'url': reverse('core:user_edit', kwargs={'user_id': self.object.id}),
'slug': 'edit',
'url': reverse('core:user_edit', kwargs={'user_id': self.object.id}),
'slug': 'edit',
'name': _("Edit"),
})
})
tab_list.append({
'url': reverse('core:user_prefs', kwargs={'user_id': self.object.id}),
'slug': 'prefs',
'url': reverse('core:user_prefs', kwargs={'user_id': self.object.id}),
'slug': 'prefs',
'name': _("Preferences"),
})
})
if self.request.user.can_view(self.object):
tab_list.append({
'url': reverse('core:user_clubs', kwargs={'user_id': self.object.id}),
'slug': 'clubs',
'url': reverse('core:user_clubs', kwargs={'user_id': self.object.id}),
'slug': 'clubs',
'name': _("Clubs"),
})
})
if self.request.user.is_owner(self.object):
tab_list.append({
'url': reverse('core:user_groups', kwargs={'user_id': self.object.id}),
'slug': 'groups',
'url': reverse('core:user_groups', kwargs={'user_id': self.object.id}),
'slug': 'groups',
'name': _("Groups"),
})
})
try:
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_BAR_MANAGER['unix_name']+settings.SITH_BOARD_SUFFIX)
or self.request.user.is_root)):
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_root)):
tab_list.append({
'url': reverse('core:user_stats', kwargs={'user_id': self.object.id}),
'slug': 'stats',
'url': reverse('core:user_stats', kwargs={'user_id': self.object.id}),
'slug': 'stats',
'name': _("Stats"),
})
})
tab_list.append({
'url': reverse('core:user_account', kwargs={'user_id': self.object.id}),
'slug': 'account',
'name': _("Account")+" (%s €)" % self.object.customer.amount,
})
except: pass
'url': reverse('core:user_account', kwargs={'user_id': self.object.id}),
'slug': 'account',
'name': _("Account") + " (%s €)" % self.object.customer.amount,
})
except:
pass
return tab_list
class UserView(UserTabsMixin, CanViewMixin, DetailView):
"""
Display a user's profile
@ -225,8 +236,8 @@ class UserView(UserTabsMixin, CanViewMixin, DetailView):
def DeleteUserGodfathers(request, user_id, godfather_id, is_father):
user = User.objects.get(id=user_id)
if ((user == request.user) or
request.user.is_root or
request.user.is_board_member):
request.user.is_root or
request.user.is_board_member):
ud = get_object_or_404(User, id=godfather_id)
if is_father == "True":
user.godfathers.remove(ud)
@ -236,6 +247,7 @@ def DeleteUserGodfathers(request, user_id, godfather_id, is_father):
raise PermissionDenied
return redirect('core:user_godfathers', user_id=user_id)
class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
"""
Display a user's pictures
@ -246,6 +258,7 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
template_name = "core/user_pictures.jinja"
current_tab = 'pictures'
class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView):
"""
Display a user's godfathers
@ -277,6 +290,7 @@ class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView):
kwargs['form'] = UserGodfathersForm()
return kwargs
class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
"""
Display a user's stats
@ -295,7 +309,7 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
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_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):
raise PermissionDenied
@ -303,26 +317,27 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
def get_context_data(self, **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
foyer = Counter.objects.filter(name="Foyer").first()
mde = Counter.objects.filter(name="MDE").first()
gommette = Counter.objects.filter(name="La Gommette").first()
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_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_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
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,
date__gte=semester_start)])
kwargs['total_gommette_buyings'] = sum([b.unit_price*b.quantity for b in
self.object.customer.buyings.filter(counter=gommette, date__gte=semester_start)])
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_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_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
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,
date__gte=semester_start)])
kwargs['total_gommette_buyings'] = sum([b.unit_price * b.quantity for b in
self.object.customer.buyings.filter(counter=gommette, date__gte=semester_start)])
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
class UserMiniView(CanViewMixin, DetailView):
"""
Display a user's profile
@ -332,6 +347,7 @@ class UserMiniView(CanViewMixin, DetailView):
context_object_name = "profile"
template_name = "core/user_mini.jinja"
class UserListView(ListView, CanEditPropMixin):
"""
Displays the user list
@ -339,6 +355,7 @@ class UserListView(ListView, CanEditPropMixin):
model = User
template_name = "core/user_list.jinja"
class UserUploadProfilePictView(CanEditMixin, DetailView):
"""
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"))
f = request.FILES['new_profile_pict']
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()))
new_file = SithFile(parent=parent, name=name,
file=resize_image(im, 400, f.content_type.split('/')[-1]),
owner=self.object, is_folder=False, mime_type=f.content_type, size=f._size)
file=resize_image(im, 400, f.content_type.split('/')[-1]),
owner=self.object, is_folder=False, mime_type=f.content_type, size=f._size)
new_file.file.name = name
new_file.save()
self.object.profile_pict = new_file
self.object.save()
return redirect("core:user_edit", user_id=self.object.id)
class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView):
"""
Edit a user's profile
@ -412,6 +430,7 @@ class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView):
kwargs['form'] = self.form
return kwargs
class UserClubView(UserTabsMixin, CanViewMixin, DetailView):
"""
Display the user's club(s)
@ -422,6 +441,7 @@ class UserClubView(UserTabsMixin, CanViewMixin, DetailView):
template_name = "core/user_clubs.jinja"
current_tab = "clubs"
class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView):
"""
Edit a user's preferences
@ -453,6 +473,7 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView):
kwargs['trombi_form'] = UserTrombiForm()
return kwargs
class UserUpdateGroupView(UserTabsMixin, CanEditPropMixin, UpdateView):
"""
Edit a user's groups
@ -461,10 +482,11 @@ class UserUpdateGroupView(UserTabsMixin, CanEditPropMixin, UpdateView):
pk_url_kwarg = "user_id"
template_name = "core/user_group.jinja"
form_class = modelform_factory(User, fields=['groups'],
widgets={'groups':CheckboxSelectMultiple})
widgets={'groups': CheckboxSelectMultiple})
context_object_name = "profile"
current_tab = "groups"
class UserToolsView(QuickNotifMixin, UserTabsMixin, TemplateView):
"""
Displays the logged user's tools
@ -481,6 +503,7 @@ class UserToolsView(QuickNotifMixin, UserTabsMixin, TemplateView):
kwargs['object'] = self.request.user
return kwargs
class UserAccountBase(UserTabsMixin, DetailView):
"""
Base class for UserAccount
@ -489,15 +512,16 @@ class UserAccountBase(UserTabsMixin, DetailView):
pk_url_kwarg = "user_id"
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)
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_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):
return res
raise PermissionDenied
class UserAccountView(UserAccountBase):
"""
Display a user's account
@ -511,14 +535,14 @@ class UserAccountView(UserAccountBase):
stats.append([])
i = 0
for month in obj.filter(date__year=year.year).datetimes(
'date', 'month', order='DESC'):
'date', 'month', order='DESC'):
q = obj.filter(
date__year=month.year,
date__month=month.month
)
stats[i].append({
'sum':sum([calc(p) for p in q]),
'date':month
'sum': sum([calc(p) for p in q]),
'date': month
})
i += 1
return stats
@ -551,6 +575,7 @@ class UserAccountView(UserAccountBase):
print(repr(e))
return kwargs
class UserAccountDetailView(UserAccountBase, YearMixin, MonthMixin):
"""
Display a user's account for month
@ -568,4 +593,3 @@ class UserAccountDetailView(UserAccountBase, YearMixin, MonthMixin):
pass
kwargs['tab'] = "account"
return kwargs

View File

@ -36,4 +36,3 @@ admin.site.register(Selling)
admin.site.register(Permanency)
admin.site.register(CashRegisterSummary)
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 import timezone
from django.conf import settings
from django.core.urlresolvers import reverse
from django.forms import ValidationError
from django.contrib.sites.shortcuts import get_current_site
from django.utils.functional import cached_property
from datetime import timedelta, date
@ -43,6 +42,7 @@ from accounting.models import CurrencyField
from core.models import Group, User, Notification
from subscription.models import Subscription
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
@ -55,7 +55,7 @@ class Customer(models.Model):
class Meta:
verbose_name = _('customer')
verbose_name_plural = _('customers')
ordering = ['account_id',]
ordering = ['account_id', ]
def __str__(self):
return "%s - %s" % (self.user.username, self.account_id)
@ -68,9 +68,9 @@ class Customer(models.Model):
def generate_account_id(number):
number = str(number)
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)
return number+letter
return number + letter
def save(self, *args, **kwargs):
if self.amount < 0:
@ -118,6 +118,7 @@ class ProductType(models.Model):
def get_absolute_url(self):
return reverse('counter:producttype_list')
class Product(models.Model):
"""
This describes a product, with all its related informations
@ -125,7 +126,7 @@ class Product(models.Model):
name = models.CharField(_('name'), max_length=64)
description = models.TextField(_('description'), 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)
purchase_price = CurrencyField(_('purchase price'))
selling_price = CurrencyField(_('selling price'))
@ -135,7 +136,7 @@ class Product(models.Model):
limit_age = models.IntegerField(_('limit age'), default=0)
tray = models.BooleanField(_('tray price'), default=False)
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)
archived = models.BooleanField(_("archived"), default=False)
@ -156,13 +157,14 @@ class Product(models.Model):
def get_absolute_url(self):
return reverse('counter:product_list')
class Counter(models.Model):
name = models.CharField(_('name'), max_length=30)
club = models.ForeignKey(Club, related_name="counters", verbose_name=_("club"))
products = models.ManyToManyField(Product, related_name="counters", verbose_name=_("products"), blank=True)
type = models.CharField(_('counter type'),
max_length=255,
choices=[('BAR',_('Bar')), ('OFFICE',_('Office')), ('EBOUTIC',_('Eboutic'))])
max_length=255,
choices=[('BAR', _('Bar')), ('OFFICE', _('Office')), ('EBOUTIC', _('Eboutic'))])
sellers = models.ManyToManyField(User, verbose_name=_('sellers'), related_name='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)
@ -173,7 +175,7 @@ class Counter(models.Model):
def __getattribute__(self, name):
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)
def __str__(self):
@ -248,7 +250,7 @@ class Counter(models.Model):
Update the barman activity to prevent timeout
"""
for p in Permanency.objects.filter(counter=self, end=None).all():
p.save() # Update activity
p.save() # Update activity
def is_open(self):
return len(self.barmen_list) > 0
@ -265,6 +267,7 @@ class Counter(models.Model):
"""
return [b.id for b in self.get_barmen_list()]
class Refilling(models.Model):
"""
Handle the refilling
@ -275,9 +278,9 @@ class Refilling(models.Model):
customer = models.ForeignKey(Customer, related_name="refillings", blank=False)
date = models.DateTimeField(_('date'))
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,
choices=settings.SITH_COUNTER_BANK, default='OTHER')
choices=settings.SITH_COUNTER_BANK, default='OTHER')
is_validated = models.BooleanField(_('is validated'), default=False)
class Meta:
@ -303,12 +306,13 @@ class Refilling(models.Model):
self.customer.save()
self.is_validated = True
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}),
param=str(self.amount),
type="REFILLING",
).save()
kwargs={'user_id': self.customer.user.id, 'year': self.date.year, 'month': self.date.month}),
param=str(self.amount),
type="REFILLING",
).save()
super(Refilling, self).save(*args, **kwargs)
class Selling(models.Model):
"""
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)
date = models.DateTimeField(_('date'))
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)
class Meta:
@ -331,7 +335,7 @@ class Selling(models.Model):
def __str__(self):
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):
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."
) % {
'event': event,
'url':''.join((
'<a href="',
self.customer.get_full_url(),
'">',
self.customer.get_full_url(),
'</a>'
))
'url': ''.join((
'<a href="',
self.customer.get_full_url(),
'">',
self.customer.get_full_url(),
'</a>'
))
}
message_txt = _(
"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 self.product and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER:
sub = Subscription(
member=u,
subscription_type='un-semestre',
payment_method="EBOUTIC",
location="EBOUTIC",
)
member=u,
subscription_type='un-semestre',
payment_method="EBOUTIC",
location="EBOUTIC",
)
sub.subscription_start = Subscription.compute_start()
sub.subscription_start = Subscription.compute_start(
duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'])
sub.subscription_end = Subscription.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'],
start=sub.subscription_start)
duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'],
start=sub.subscription_start)
sub.save()
elif self.product and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS:
u = User.objects.filter(id=self.customer.user.id).first()
sub = Subscription(
member=u,
subscription_type='deux-semestres',
payment_method="EBOUTIC",
location="EBOUTIC",
)
member=u,
subscription_type='deux-semestres',
payment_method="EBOUTIC",
location="EBOUTIC",
)
sub.subscription_start = Subscription.compute_start()
sub.subscription_start = Subscription.compute_start(
duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'])
sub.subscription_end = Subscription.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'],
start=sub.subscription_start)
duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'],
start=sub.subscription_start)
sub.save()
try:
if self.product.eticket:
self.send_mail_customer()
except: pass
except:
pass
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}),
param="%d x %s" % (self.quantity, self.label),
type="SELLING",
).save()
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}),
param="%d x %s" % (self.quantity, self.label),
type="SELLING",
).save()
super(Selling, self).save(*args, **kwargs)
class Permanency(models.Model):
"""
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):
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.activity.strftime("%Y-%m-%d %H:%M:%S"),
self.end.strftime("%Y-%m-%d %H:%M:%S") if self.end else "",
)
self.start.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 "",
)
class CashRegisterSummary(models.Model):
user = models.ForeignKey(User, related_name="cash_summaries", verbose_name=_("user"))
@ -515,6 +522,7 @@ class CashRegisterSummary(models.Model):
def get_absolute_url(self):
return reverse('counter:cash_summary_list')
class CashRegisterSummaryItem(models.Model):
cash_summary = models.ForeignKey(CashRegisterSummary, related_name="items", verbose_name=_("cash summary"))
value = CurrencyField(_("value"))
@ -524,6 +532,7 @@ class CashRegisterSummaryItem(models.Model):
class Meta:
verbose_name = _("cash register summary item")
class Eticket(models.Model):
"""
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)
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()

View File

@ -24,7 +24,6 @@
import re
from pprint import pprint
from django.test import TestCase
from django.core.urlresolvers import reverse
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 *

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.core.exceptions import PermissionDenied
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 import CheckboxSelectMultiple
from django.core.urlresolvers import reverse_lazy, reverse
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect, HttpResponse
from django.utils import timezone
from django import forms
@ -43,16 +42,17 @@ import re
import pytz
from datetime import date, timedelta, datetime
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.forms import SelectUser, LoginForm, SelectDate, SelectDateTime
from core.views import CanViewMixin, TabedViewMixin
from core.views.forms import LoginForm, SelectDate, SelectDateTime
from core.models import User
from subscription.models import Subscription
from counter.models import Counter, Customer, Product, Selling, Refilling, ProductType, \
CashRegisterSummary, CashRegisterSummaryItem, Eticket, Permanency
CashRegisterSummary, CashRegisterSummaryItem, Eticket, Permanency
from accounting.models import CurrencyField
class CounterAdminMixin(View):
"""
This view is made to protect counter admin section
@ -72,13 +72,13 @@ class CounterAdminMixin(View):
return True
return False
def dispatch(self, request, *args, **kwargs):
if not (request.user.is_root or self._test_group(request.user)
or self._test_club(request.user)):
raise PermissionDenied
return super(CounterAdminMixin, self).dispatch(request, *args, **kwargs)
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,
@ -107,51 +107,57 @@ class GetUserForm(forms.Form):
cleaned_data['user'] = cus.user
return cleaned_data
class RefillForm(forms.ModelForm):
error_css_class = 'error'
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:
model = Refilling
fields = ['amount', 'payment_method', 'bank']
class CounterTabsMixin(TabedViewMixin):
def get_tabs_title(self):
if hasattr(self.object, 'stock_owner') :
if hasattr(self.object, 'stock_owner'):
return self.object.stock_owner.counter
else:
return self.object
def get_list_of_tabs(self):
tab_list = []
tab_list.append({
'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',
'name': _("Counter"),
})
})
if self.object.stock_owner.counter.type if hasattr(self.object, 'stock_owner') else self.object.type == "BAR":
tab_list.append({
'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',
'name': _("Cash summary"),
})
})
tab_list.append({
'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',
'name': _("Last operations"),
})
})
try:
tab_list.append({
'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',
'name': _("Take items from stock"),
})
except: pass # The counter just have no stock
})
except:
pass # The counter just have no stock
return tab_list
class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, FormMixin):
"""
The public (barman) view
@ -159,15 +165,15 @@ class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, F
model = Counter
template_name = 'counter/counter_main.jinja'
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"
def post(self, request, *args, **kwargs):
self.object = self.get_object()
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,
kwargs={'counter_id': self.object.id})+'?bad_location')
kwargs={'counter_id': self.object.id}) + '?bad_location')
return super(CounterMain, self).post(request, *args, **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['login_form'] = LoginForm()
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:
kwargs['login_form'].add_error(None, _("Bad credentials"))
if "sellers" in self.request.GET:
kwargs['login_form'].add_error(None, _("User is not barman"))
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:
kwargs['form'].add_error(None, _("Bad location, someone is already logged in somewhere else"))
if self.object.type == 'BAR':
@ -210,6 +216,7 @@ class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, F
def get_success_url(self):
return reverse_lazy('counter:click', args=self.args, kwargs=self.kwargs)
class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
"""
The click view
@ -228,7 +235,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
raise Http404
if obj.type == "BAR":
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
else:
if not request.user.is_authenticated():
@ -237,10 +244,10 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
def get(self, request, *args, **kwargs):
"""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_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['not_allowed'] = False
request.session['no_age'] = False
@ -248,8 +255,8 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
ret = super(CounterClick, self).get(request, *args, **kwargs)
if ((self.object.type != "BAR" and not request.user.is_authenticated()) or
(self.object.type == "BAR" and
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
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
return ret
def post(self, request, *args, **kwargs):
@ -258,16 +265,16 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
self.refill_form = None
if ((self.object.type != "BAR" and not request.user.is_authenticated()) or
(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)
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,
kwargs={'counter_id': self.object.id})+'?bad_location')
kwargs={'counter_id': self.object.id}) + '?bad_location')
if 'basket' not in request.session.keys():
request.session['basket'] = {}
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['not_allowed'] = False
request.session['no_age'] = False
@ -312,7 +319,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
def sum_basket(self, request):
total = 0
for pid,infos in request.session['basket'].items():
for pid, infos in request.session['basket'].items():
total += infos['price'] * infos['qty']
return total / 100
@ -323,7 +330,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
except:
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
q is the quantity passed as integer
@ -344,12 +351,12 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
if not can_buy:
request.session['not_allowed'] = True
return False
bq = 0 # Bonus quantity, for trays
if product.tray: # Handle the tray to adjust the quantity q to add and the bonus quantity bq
bq = 0 # Bonus quantity, for trays
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
bq = int((total_qty_mod_6 + q) / 6) # Integer division
bq = int((total_qty_mod_6 + q) / 6) # Integer division
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
return False
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:
request.session['not_allowed'] = True
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
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]['bonus_qty'] += bq
else: # or create if not
request.session['basket'][pid] = {'qty': q, 'price': int(price*100), 'bonus_qty': bq}
else: # or create if not
request.session['basket'][pid] = {'qty': q, 'price': int(price * 100), 'bonus_qty': bq}
request.session.modified = True
return True
@ -414,7 +421,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
""" Finish the click session, and validate the basket """
with transaction.atomic():
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)
p = Product.objects.filter(pk=pid).first()
if self.is_barman_price():
@ -423,13 +430,13 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
uprice = p.selling_price
if uprice * infos['qty'] > self.customer.amount:
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,
quantity=infos['qty'], seller=self.operator, customer=self.customer)
quantity=infos['qty'], seller=self.operator, customer=self.customer)
s.save()
if infos['bonus_qty']:
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()
request.session['last_customer'] = self.customer.user.get_display_name()
request.session['last_total'] = "%0.2f" % self.sum_basket(request)
@ -437,8 +444,8 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
del request.session['basket']
request.session.modified = True
kwargs = {
'counter_id': self.object.id,
}
'counter_id': self.object.id,
}
return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, kwargs=kwargs))
def cancel(self, request):
@ -470,6 +477,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
kwargs['categories'] = ProductType.objects.all()
return kwargs
class CounterLogin(RedirectView):
"""
Handle the login of a barman
@ -477,6 +485,7 @@ class CounterLogin(RedirectView):
Logged barmen are stored in the Permanency model
"""
permanent = False
def post(self, request, *args, **kwargs):
"""
Register the logged user as barman for this counter
@ -499,10 +508,12 @@ class CounterLogin(RedirectView):
return super(CounterLogin, self).post(request, *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):
permanent = False
def post(self, request, *args, **kwargs):
"""
Unregister the user from the barman
@ -515,52 +526,54 @@ class CounterLogout(RedirectView):
def get_redirect_url(self, *args, **kwargs):
return reverse_lazy('counter:details', args=args, kwargs=kwargs)
## Counter admin views
# Counter admin views
class CounterAdminTabsMixin(TabedViewMixin):
tabs_title = _("Counter administration")
list_of_tabs = [
{
'url': reverse_lazy('stock:list'),
'slug': 'stocks',
'name': _("Stocks"),
},
{
'url': reverse_lazy('counter:admin_list'),
'slug': 'counters',
'name': _("Counters"),
},
{
'url': reverse_lazy('counter:product_list'),
'slug': 'products',
'name': _("Products"),
},
{
'url': reverse_lazy('counter:product_list_archived'),
'slug': 'archive',
'name': _("Archived products"),
},
{
'url': reverse_lazy('counter:producttype_list'),
'slug': 'product_types',
'name': _("Product types"),
},
{
'url': reverse_lazy('counter:cash_summary_list'),
'slug': 'cash_summary',
'name': _("Cash register summaries"),
},
{
'url': reverse_lazy('counter:invoices_call'),
'slug': 'invoices_call',
'name': _("Invoices call"),
},
{
'url': reverse_lazy('counter:eticket_list'),
'slug': 'etickets',
'name': _("Etickets"),
},
]
{
'url': reverse_lazy('stock:list'),
'slug': 'stocks',
'name': _("Stocks"),
},
{
'url': reverse_lazy('counter:admin_list'),
'slug': 'counters',
'name': _("Counters"),
},
{
'url': reverse_lazy('counter:product_list'),
'slug': 'products',
'name': _("Products"),
},
{
'url': reverse_lazy('counter:product_list_archived'),
'slug': 'archive',
'name': _("Archived products"),
},
{
'url': reverse_lazy('counter:producttype_list'),
'slug': 'product_types',
'name': _("Product types"),
},
{
'url': reverse_lazy('counter:cash_summary_list'),
'slug': 'cash_summary',
'name': _("Cash register summaries"),
},
{
'url': reverse_lazy('counter:invoices_call'),
'slug': 'invoices_call',
'name': _("Invoices call"),
},
{
'url': reverse_lazy('counter:eticket_list'),
'slug': 'etickets',
'name': _("Etickets"),
},
]
class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView):
"""
@ -570,6 +583,7 @@ class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView):
template_name = 'counter/counter_list.jinja'
current_tab = "counters"
class CounterEditForm(forms.ModelForm):
class Meta:
model = Counter
@ -577,6 +591,7 @@ class CounterEditForm(forms.ModelForm):
sellers = make_ajax_field(Counter, 'sellers', 'users', help_text="")
products = make_ajax_field(Counter, 'products', 'products', help_text="")
class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
"""
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):
return reverse_lazy('counter:admin', kwargs={'counter_id': self.object.id})
class CounterEditPropView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
"""
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'
current_tab = "counters"
class CounterCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
"""
Create a counter (for the admins)
"""
model = Counter
form_class = modelform_factory(Counter, fields=['name', 'club', 'type', 'products'],
widgets={'products':CheckboxSelectMultiple})
widgets={'products': CheckboxSelectMultiple})
template_name = 'core/create.jinja'
current_tab = "counters"
class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView):
"""
Delete a counter (for the admins)
@ -627,6 +645,7 @@ class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView):
# Product management
class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
"""
A list view for the admins
@ -635,6 +654,7 @@ class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
template_name = 'counter/producttype_list.jinja'
current_tab = "product_types"
class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
"""
A create view for the admins
@ -644,6 +664,7 @@ class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView
template_name = 'core/create.jinja'
current_tab = "products"
class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
"""
An edit view for the admins
@ -654,6 +675,7 @@ class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
pk_url_kwarg = "type_id"
current_tab = "products"
class ProductArchivedListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
"""
A list view for the admins
@ -664,6 +686,7 @@ class ProductArchivedListView(CounterAdminTabsMixin, CounterAdminMixin, ListView
ordering = ['name']
current_tab = "archive"
class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
"""
A list view for the admins
@ -674,11 +697,12 @@ class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
ordering = ['name']
current_tab = "products"
class ProductEditForm(forms.ModelForm):
class Meta:
model = Product
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)
buying_groups = AutoCompleteSelectMultipleField('groups', show_help_text=False, help_text="", label=_("Buying groups"), required=False)
club = AutoCompleteSelectField('clubs', show_help_text=False)
@ -702,6 +726,7 @@ class ProductEditForm(forms.ModelForm):
c.save()
return ret
class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
"""
A create view for the admins
@ -711,6 +736,7 @@ class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
template_name = 'core/create.jinja'
current_tab = "products"
class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
"""
An edit view for the admins
@ -721,6 +747,7 @@ class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
template_name = 'core/edit.jinja'
current_tab = "products"
class RefillingDeleteView(DeleteView):
"""
Delete a refilling (for the admins)
@ -736,7 +763,7 @@ class RefillingDeleteView(DeleteView):
self.object = self.get_object()
if (timezone.now() - self.object.date <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) 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()):
self.success_url = reverse('counter:details', kwargs={'counter_id': self.object.counter.id})
return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs)
@ -745,6 +772,7 @@ class RefillingDeleteView(DeleteView):
return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs)
raise PermissionDenied
class SellingDeleteView(DeleteView):
"""
Delete a selling (for the admins)
@ -760,7 +788,7 @@ class SellingDeleteView(DeleteView):
self.object = self.get_object()
if (timezone.now() - self.object.date <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) 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()):
self.success_url = reverse('counter:details', kwargs={'counter_id': self.object.counter.id})
return super(SellingDeleteView, self).dispatch(request, *args, **kwargs)
@ -771,6 +799,7 @@ class SellingDeleteView(DeleteView):
# Cash register summaries
class CashRegisterSummaryForm(forms.Form):
"""
Provide the cash summary form
@ -831,38 +860,54 @@ class CashRegisterSummaryForm(forms.Form):
def save(self, counter=None):
cd = self.cleaned_data
summary = self.instance or CashRegisterSummary(
counter=counter,
user=counter.get_random_barman(),
)
counter=counter,
user=counter.get_random_barman(),
)
summary.comment = cd['comment']
summary.emptied = cd['emptied']
summary.save()
summary.items.all().delete()
# Cash
if cd['ten_cents']: CashRegisterSummaryItem(cash_summary=summary, value=0.1, quantity=cd['ten_cents']).save()
if cd['twenty_cents']: CashRegisterSummaryItem(cash_summary=summary, value=0.2, quantity=cd['twenty_cents']).save()
if cd['fifty_cents']: CashRegisterSummaryItem(cash_summary=summary, value=0.5, quantity=cd['fifty_cents']).save()
if cd['one_euro']: CashRegisterSummaryItem(cash_summary=summary, value=1, quantity=cd['one_euro']).save()
if cd['two_euros']: 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()
if cd['ten_cents']:
CashRegisterSummaryItem(cash_summary=summary, value=0.1, quantity=cd['ten_cents']).save()
if cd['twenty_cents']:
CashRegisterSummaryItem(cash_summary=summary, value=0.2, quantity=cd['twenty_cents']).save()
if cd['fifty_cents']:
CashRegisterSummaryItem(cash_summary=summary, value=0.5, quantity=cd['fifty_cents']).save()
if cd['one_euro']:
CashRegisterSummaryItem(cash_summary=summary, value=1, quantity=cd['one_euro']).save()
if cd['two_euros']:
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
if cd['check_1_quantity']: CashRegisterSummaryItem(cash_summary=summary, value=cd['check_1_value'],
quantity=cd['check_1_quantity'], check=True).save()
if cd['check_2_quantity']: CashRegisterSummaryItem(cash_summary=summary, value=cd['check_2_value'],
quantity=cd['check_2_quantity'], check=True).save()
if cd['check_3_quantity']: CashRegisterSummaryItem(cash_summary=summary, value=cd['check_3_value'],
quantity=cd['check_3_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 cd['check_1_quantity']:
CashRegisterSummaryItem(cash_summary=summary, value=cd['check_1_value'],
quantity=cd['check_1_quantity'], check=True).save()
if cd['check_2_quantity']:
CashRegisterSummaryItem(cash_summary=summary, value=cd['check_2_value'],
quantity=cd['check_2_quantity'], check=True).save()
if cd['check_3_quantity']:
CashRegisterSummaryItem(cash_summary=summary, value=cd['check_3_value'],
quantity=cd['check_3_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:
summary.delete()
class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView):
"""
Provide the last operations to allow barmen to delete them
@ -878,10 +923,10 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView):
"""
self.object = self.get_object()
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
Counter.objects.filter(token=request.session['counter_token']).exists()):
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()):
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):
"""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]
return kwargs
class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView):
"""
Provide the cash summary form
@ -906,10 +952,10 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView):
"""
self.object = self.get_object()
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
Counter.objects.filter(token=request.session['counter_token']).exists()):
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()):
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):
self.object = self.get_object()
@ -933,6 +979,7 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView):
kwargs['form'] = self.form
return kwargs
class CounterActivityView(DetailView):
"""
Show the bar activity
@ -941,6 +988,7 @@ class CounterActivityView(DetailView):
pk_url_kwarg = "counter_id"
template_name = 'counter/activity.jinja'
class CounterStatView(DetailView, CounterAdminMixin):
"""
Show the bar stats
@ -957,40 +1005,40 @@ class CounterStatView(DetailView, CounterAdminMixin):
kwargs['User'] = User
semester_start = Subscription.compute_start(d=date.today(), duration=3)
kwargs['total_sellings'] = Selling.objects.filter(date__gte=semester_start,
counter=self.object).aggregate(total_sellings=Sum(F('quantity')*F('unit_price'),
output_field=CurrencyField()))['total_sellings']
counter=self.object).aggregate(total_sellings=Sum(F('quantity') * F('unit_price'),
output_field=CurrencyField()))['total_sellings']
kwargs['top'] = Selling.objects.values('customer__user').annotate(
selling_sum=Sum(
Case(When(counter=self.object,
date__gte=semester_start,
unit_price__gt=0,
then=F('unit_price')*F('quantity')),
output_field=CurrencyField()
)
)
).exclude(selling_sum=None).order_by('-selling_sum').all()[:100]
selling_sum=Sum(
Case(When(counter=self.object,
date__gte=semester_start,
unit_price__gt=0,
then=F('unit_price') * F('quantity')),
output_field=CurrencyField()
)
)
).exclude(selling_sum=None).order_by('-selling_sum').all()[:100]
kwargs['top_barman'] = Permanency.objects.values('user').annotate(
perm_sum=Sum(
Case(When(counter=self.object,
end__gt=datetime(year=1999, month=1, day=1),
then=F('end')-F('start')),
output_field=models.DateTimeField()
)
)
).exclude(perm_sum=None).order_by('-perm_sum').all()[:100]
perm_sum=Sum(
Case(When(counter=self.object,
end__gt=datetime(year=1999, month=1, day=1),
then=F('end') - F('start')),
output_field=models.DateTimeField()
)
)
).exclude(perm_sum=None).order_by('-perm_sum').all()[:100]
kwargs['top_barman_semester'] = Permanency.objects.values('user').annotate(
perm_sum=Sum(
Case(When(counter=self.object,
start__gt=semester_start,
end__gt=datetime(year=1999, month=1, day=1),
then=F('end')-F('start')),
output_field=models.DateTimeField()
)
)
).exclude(perm_sum=None).order_by('-perm_sum').all()[:100]
perm_sum=Sum(
Case(When(counter=self.object,
start__gt=semester_start,
end__gt=datetime(year=1999, month=1, day=1),
then=F('end') - F('start')),
output_field=models.DateTimeField()
)
)
).exclude(perm_sum=None).order_by('-perm_sum').all()[:100]
kwargs['sith_date']=settings.SITH_START_DATE[0]
kwargs['semester_start']=semester_start
kwargs['sith_date'] = settings.SITH_START_DATE[0]
kwargs['semester_start'] = semester_start
return kwargs
def dispatch(self, request, *args, **kwargs):
@ -999,11 +1047,12 @@ class CounterStatView(DetailView, CounterAdminMixin):
except:
if (request.user.is_root
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)
raise PermissionDenied
class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
"""Edit cash summaries"""
model = CashRegisterSummary
template_name = 'counter/cash_register_summary.jinja'
@ -1015,10 +1064,12 @@ class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView)
def get_success_url(self):
return reverse('counter:cash_summary_list')
class CashSummaryFormBase(forms.Form):
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)
class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
"""Display a list of cash summaries"""
model = CashRegisterSummary
@ -1047,7 +1098,7 @@ class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
refillings = refillings.filter(date__gt=last_summary.date)
cashredistersummaries = cashredistersummaries.filter(date__gt=last_summary.date)
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))
if form.is_valid() and 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()])
return kwargs
class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView):
template_name = 'counter/invoices_call.jinja'
current_tab = 'invoices_call'
@ -1069,24 +1121,25 @@ class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView):
try:
start_date = datetime.strptime(self.request.GET['month'], '%Y-%m')
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)
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
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)])
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['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)])
kwargs['start_date'] = start_date
kwargs['sums'] = Selling.objects.values('club__name').annotate(selling_sum=Sum(
Case(When(date__gte=start_date,
date__lt=end_date,
then=F('unit_price')*F('quantity')),
output_field=CurrencyField()
)
)).exclude(selling_sum=None).order_by('-selling_sum')
date__lt=end_date,
then=F('unit_price') * F('quantity')),
output_field=CurrencyField()
)
)).exclude(selling_sum=None).order_by('-selling_sum')
return kwargs
class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
"""
A list view for the admins
@ -1096,15 +1149,17 @@ class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
ordering = ['id']
current_tab = "etickets"
class EticketForm(forms.ModelForm):
class Meta:
model = Eticket
fields = ['product', 'banner', 'event_title', 'event_date']
widgets = {
'event_date': SelectDate,
}
'event_date': SelectDate,
}
product = AutoCompleteSelectField('products', show_help_text=False, label=_("Product"), required=True)
class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
"""
Create an eticket
@ -1114,6 +1169,7 @@ class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
form_class = EticketForm
current_tab = "etickets"
class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
"""
Edit an eticket
@ -1124,6 +1180,7 @@ class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
pk_url_kwarg = "eticket_id"
current_tab = "etickets"
class EticketPDFView(CanViewMixin, DetailView):
"""
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)
if eticket.event_date:
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.drawCentredString(10.5 * cm, 15 * cm, "%s : %d %s" % (user.get_display_name(), self.object.quantity, str(_("people(s)"))))
p.setFont("Courier-Bold", 14)
@ -1180,7 +1237,7 @@ class EticketPDFView(CanViewMixin, DetailView):
bounds = qrcode.getBounds()
width = bounds[2] - bounds[0]
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)
renderPDF.draw(d, p, 10.5 * cm - 130, 6.1 * cm)
p.drawCentredString(10.5 * cm, 6 * cm, code)
@ -1195,4 +1252,3 @@ class EticketPDFView(CanViewMixin, DetailView):
p.showPage()
p.save()
return response

View File

@ -27,9 +27,9 @@ from django.utils.translation import ugettext_lazy as _
from django.conf import settings
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 subscription.models import Subscription
class Basket(models.Model):
"""
@ -38,16 +38,16 @@ class Basket(models.Model):
user = models.ForeignKey(User, related_name='baskets', verbose_name=_('user'), blank=False)
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()
if item is None:
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:
item.quantity += q
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()
if item is not None:
item.quantity -= q
@ -64,6 +64,7 @@ class Basket(models.Model):
def __str__(self):
return "%s's basket (%d items)" % (self.user, self.items.all().count())
class Invoice(models.Model):
"""
Invoices are generated once the payment has been validated
@ -92,34 +93,35 @@ class Invoice(models.Model):
for i in self.items.all():
if i.type_id == settings.SITH_COUNTER_PRODUCTTYPE_REFILLING:
new = Refilling(
counter=eboutic,
customer=self.user.customer,
operator=self.user,
amount=i.product_unit_price * i.quantity,
payment_method="CARD",
bank="OTHER",
date=self.date,
)
counter=eboutic,
customer=self.user.customer,
operator=self.user,
amount=i.product_unit_price * i.quantity,
payment_method="CARD",
bank="OTHER",
date=self.date,
)
new.save()
else:
product = Product.objects.filter(id=i.product_id).first()
new = Selling(
label=i.product_name,
counter=eboutic,
club=product.club,
product=product,
seller=self.user,
customer=self.user.customer,
unit_price=i.product_unit_price,
quantity=i.quantity,
payment_method="CARD",
is_validated=True,
date=self.date,
)
label=i.product_name,
counter=eboutic,
club=product.club,
product=product,
seller=self.user,
customer=self.user.customer,
unit_price=i.product_unit_price,
quantity=i.quantity,
payment_method="CARD",
is_validated=True,
date=self.date,
)
new.save()
self.validated = True
self.save()
class AbstractBaseItem(models.Model):
product_id = models.IntegerField(_('product id'))
product_name = models.CharField(_('product name'), max_length=255)
@ -133,8 +135,10 @@ class AbstractBaseItem(models.Model):
def __str__(self):
return "Item: %s (%s) x%d" % (self.product_name, self.product_unit_price, self.quantity)
class BasketItem(AbstractBaseItem):
basket = models.ForeignKey(Basket, related_name='items', verbose_name=_('basket'))
class InvoiceItem(AbstractBaseItem):
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 core.models import User
from counter.models import Customer, ProductType, Product, Counter, Refilling
from counter.models import Product, Counter, Refilling
class EbouticTest(TestCase):
def setUp(self):
@ -74,25 +75,24 @@ class EbouticTest(TestCase):
"action": "add_product",
"product_id": self.barbar.id})
self.assertTrue("<input type=\"hidden\" name=\"action\" value=\"add_product\">\\n"
" <button type=\"submit\" name=\"product_id\" value=\"4\"> + </button>\\n"
"</form>\\n Barbar: 1.70 \\xe2\\x82\\xac</li>" in str(response.content))
" <button type=\"submit\" name=\"product_id\" value=\"4\"> + </button>\\n"
"</form>\\n Barbar: 1.70 \\xe2\\x82\\xac</li>" in str(response.content))
response = self.client.post(reverse("eboutic:command"))
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"), {
"action": "pay_with_sith_account"
})
})
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={
"user_id": self.subscriber.id,
"year": datetime.now().year,
"month": datetime.now().month,
}))
}))
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"
" <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>Barbar</td>\\n <td>1</td>\\n <td>1.70 \\xe2\\x82\\xac</td>\\n"
" <td>Compte utilisateur</td>" in str(response.content))
def test_buy_simple_product_with_credit_card(self):
self.client.login(username='subscriber', password='plop')
@ -100,11 +100,11 @@ class EbouticTest(TestCase):
"action": "add_product",
"product_id": self.barbar.id})
self.assertTrue("<input type=\"hidden\" name=\"action\" value=\"add_product\">\\n"
" <button type=\"submit\" name=\"product_id\" value=\"4\"> + </button>\\n"
"</form>\\n Barbar: 1.70 \\xe2\\x82\\xac</li>" in str(response.content))
" <button type=\"submit\" name=\"product_id\" value=\"4\"> + </button>\\n"
"</form>\\n Barbar: 1.70 \\xe2\\x82\\xac</li>" in str(response.content))
response = self.client.post(reverse("eboutic:command"))
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)
@ -112,11 +112,11 @@ class EbouticTest(TestCase):
"user_id": self.subscriber.id,
"year": datetime.now().year,
"month": datetime.now().month,
}))
}))
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"
" <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>Barbar</td>\\n <td>1</td>\\n <td>1.70 \\xe2\\x82\\xac</td>\\n"
" <td>Carte bancaire</td>" in str(response.content))
def test_buy_refill_product_with_credit_card(self):
self.client.login(username='subscriber', password='plop')
@ -124,11 +124,11 @@ class EbouticTest(TestCase):
"action": "add_product",
"product_id": self.refill.id})
self.assertTrue("<input type=\"hidden\" name=\"action\" value=\"add_product\">\\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))
" <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))
response = self.client.post(reverse("eboutic:command"))
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)
@ -136,12 +136,12 @@ class EbouticTest(TestCase):
"user_id": self.subscriber.id,
"year": datetime.now().year,
"month": datetime.now().month,
}))
}))
self.assertTrue("class=\"selected_tab\">Compte (15.00 \\xe2\\x82\\xac)</a>" in str(response.content))
self.assertTrue("<td>\\n <ul>\\n \\n "
"<li>1 x Rechargement 15 \\xe2\\x82\\xac - 15.00 \\xe2\\x82\\xac</li>\\n"
" \\n </ul>\\n </td>\\n"
" <td>15.00 \\xe2\\x82\\xac</td>" in str(response.content))
"<li>1 x Rechargement 15 \\xe2\\x82\\xac - 15.00 \\xe2\\x82\\xac</li>\\n"
" \\n </ul>\\n </td>\\n"
" <td>15.00 \\xe2\\x82\\xac</td>" in str(response.content))
def test_buy_subscribe_product_with_credit_card(self):
self.client.login(username='old_subscriber', password='plop')
@ -151,11 +151,11 @@ class EbouticTest(TestCase):
"action": "add_product",
"product_id": self.cotis.id})
self.assertTrue("<input type=\"hidden\" name=\"action\" value=\"add_product\">\\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))
" <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))
response = self.client.post(reverse("eboutic:command"))
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)
@ -163,14 +163,11 @@ class EbouticTest(TestCase):
"user_id": self.old_subscriber.id,
"year": datetime.now().year,
"month": datetime.now().month,
}))
}))
self.assertTrue("class=\"selected_tab\">Compte (0.00 \\xe2\\x82\\xac)</a>" in str(response.content))
self.assertTrue("<td>\\n <ul>\\n \\n "
"<li>1 x Cotis 1 semestre - 15.00 \\xe2\\x82\\xac</li>\\n"
" \\n </ul>\\n </td>\\n"
" <td>15.00 \\xe2\\x82\\xac</td>" in str(response.content))
"<li>1 x Cotis 1 semestre - 15.00 \\xe2\\x82\\xac</li>\\n"
" \\n </ul>\\n </td>\\n"
" <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}))
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 *
@ -33,6 +33,3 @@ urlpatterns = [
url(r'^pay$', EbouticPayWithSith.as_view(), name='pay_with_sith'),
url(r'^et_autoanswer$', EtransactionAutoAnswer.as_view(), name='etransation_autoanswer'),
]

View File

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

View File

@ -23,11 +23,9 @@
#
from django.db import models
from django.core import validators
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from django.db import IntegrityError, transaction
from django.core.urlresolvers import reverse
from django.utils import timezone
from django.utils.functional import cached_property
@ -35,9 +33,10 @@ from django.utils.functional import cached_property
from datetime import datetime
import pytz
from core.models import User, MetaGroup, Group, SithFile
from core.models import User, Group
from club.models import Club
class Forum(models.Model):
"""
The Forum class, made as a tree to allow nice tidy organization
@ -52,14 +51,14 @@ class Forum(models.Model):
is_category = models.BooleanField(_('is a category'), default=False)
parent = models.ForeignKey('Forum', related_name='children', null=True, blank=True)
owner_club = models.ForeignKey(Club, related_name="owned_forums", verbose_name=_("owner club"),
default=settings.SITH_MAIN_CLUB_ID)
default=settings.SITH_MAIN_CLUB_ID)
edit_groups = models.ManyToManyField(Group, related_name="editable_forums", blank=True,
default=[settings.SITH_GROUP_OLD_SUBSCRIBERS_ID])
default=[settings.SITH_GROUP_OLD_SUBSCRIBERS_ID])
view_groups = models.ManyToManyField(Group, related_name="viewable_forums", blank=True,
default=[settings.SITH_GROUP_PUBLIC_ID])
default=[settings.SITH_GROUP_PUBLIC_ID])
number = models.IntegerField(_("number to choose a specific forum ordering"), default=1)
_last_message = models.ForeignKey('ForumMessage', related_name="forums_where_its_last",
verbose_name=_("the last message"), null=True, on_delete=models.SET_NULL)
verbose_name=_("the last message"), null=True, on_delete=models.SET_NULL)
_topic_number = models.IntegerField(_("number of topics"), default=0)
class Meta:
@ -112,16 +111,18 @@ class Forum(models.Model):
self.view_groups = self.parent.view_groups.all()
self.save()
_club_memberships = {} # This cache is particularly efficient:
# divided by 3 the number of requests on the main forum page
# after the first load
_club_memberships = {} # This cache is particularly efficient:
# divided by 3 the number of requests on the main forum page
# after the first load
def is_owned_by(self, user):
if user.is_in_group(settings.SITH_GROUP_FORUM_ADMIN_ID):
return True
try: m = Forum._club_memberships[self.id][user.id]
try:
m = Forum._club_memberships[self.id][user.id]
except:
m = self.owner_club.get_membership_for(user)
try: Forum._club_memberships[self.id][user.id] = m
try:
Forum._club_memberships[self.id][user.id] = m
except:
Forum._club_memberships[self.id] = {}
Forum._club_memberships[self.id][user.id] = m
@ -178,12 +179,13 @@ class Forum(models.Model):
l += c.get_children_list()
return l
class ForumTopic(models.Model):
forum = models.ForeignKey(Forum, related_name='topics')
author = models.ForeignKey(User, related_name='forum_topics')
description = models.CharField(_('description'), max_length=256, default="")
_last_message = models.ForeignKey('ForumMessage', related_name="+", verbose_name=_("the last message"),
null=True, on_delete=models.SET_NULL)
null=True, on_delete=models.SET_NULL)
_title = models.CharField(_('title'), max_length=64, blank=True)
_message_number = models.IntegerField(_("number of messages"), default=0)
@ -192,7 +194,7 @@ class ForumTopic(models.Model):
def save(self, *args, **kwargs):
super(ForumTopic, self).save(*args, **kwargs)
self.forum.set_topic_number() # Recompute the cached value
self.forum.set_topic_number() # Recompute the cached value
self.forum.set_last_message()
def is_owned_by(self, user):
@ -225,6 +227,7 @@ class ForumTopic(models.Model):
def title(self):
return self._title
class ForumMessage(models.Model):
"""
"A ForumMessage object represents a message in the forum" -- Cpt. Obvious
@ -244,7 +247,7 @@ class ForumMessage(models.Model):
return "%s (%s) - %s" % (self.id, self.author, self.title)
def save(self, *args, **kwargs):
self._deleted = self.is_deleted() # Recompute the cached value
self._deleted = self.is_deleted() # Recompute the cached value
super(ForumMessage, self).save(*args, **kwargs)
if self.is_last_in_topic():
self.topic._last_message_id = self.id
@ -259,15 +262,15 @@ class ForumMessage(models.Model):
def is_last_in_topic(self):
return bool(self.id == self.topic.messages.order_by('date').last().id)
def is_owned_by(self, user): # Anyone can create a topic: it's better to
# check the rights at the forum level, since it's more controlled
def is_owned_by(self, user): # Anyone can create a topic: it's better to
# check the rights at the forum level, since it's more controlled
return self.topic.forum.is_owned_by(user) or user.id == self.author.id
def can_be_edited_by(self, user):
return user.can_edit(self.topic.forum)
def can_be_viewed_by(self, user):
return not self._deleted # No need to check the real rights since it's already done by the Topic view
return not self._deleted # No need to check the real rights since it's already done by the Topic view
def can_be_moderated_by(self, user):
return self.topic.forum.is_owned_by(user) or user.id == self.author.id
@ -282,10 +285,11 @@ class ForumMessage(models.Model):
return int(self.topic.messages.filter(id__lt=self.id).count() / settings.SITH_FORUM_PAGE_LENGTH) + 1
def mark_as_read(self, user):
try: # Need the try/except because of AnonymousUser
try: # Need the try/except because of AnonymousUser
if not self.is_read(user):
self.readers.add(user)
except: pass
except:
pass
def is_read(self, user):
return (self.date < user.forum_infos.last_read_date) or (user in self.readers.all())
@ -296,11 +300,13 @@ class ForumMessage(models.Model):
return meta.action == "DELETE"
return False
MESSAGE_META_ACTIONS = [
('EDIT', _("Message edited by")),
('DELETE', _("Message deleted by")),
('UNDELETE', _("Message undeleted by")),
]
('EDIT', _("Message edited by")),
('DELETE', _("Message deleted by")),
('UNDELETE', _("Message undeleted by")),
]
class ForumMessageMeta(models.Model):
user = models.ForeignKey(User, related_name="forum_message_metas")
@ -322,8 +328,7 @@ class ForumUserInfo(models.Model):
"""
user = models.OneToOneField(User, related_name="_forum_infos")
last_read_date = models.DateTimeField(_('last read date'), default=datetime(year=settings.SITH_SCHOOL_START_YEAR,
month=1, day=1, tzinfo=pytz.UTC))
month=1, day=1, tzinfo=pytz.UTC))
def __str__(self):
return str(self.user)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,6 +31,3 @@ urlpatterns = [
url(r'^$', NewSubscription.as_view(), name='subscription'),
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.core.exceptions import PermissionDenied, ValidationError
from django.core.urlresolvers import reverse_lazy
from django.db import IntegrityError
from django import forms
from django.forms import Select
from django.conf import settings
from ajax_select.fields import AutoCompleteSelectField
import random
from subscription.models import Subscription
from core.views import CanEditMixin, CanEditPropMixin, CanViewMixin
from core.views.forms import SelectDateTime
from core.models import User
@ -85,9 +82,9 @@ class SubscriptionForm(forms.ModelForm):
if User.objects.filter(email=cleaned_data.get("email")).first() is not None:
self.add_error("email", ValidationError(_("A user with that email address already exists")))
else:
u = User(last_name = self.cleaned_data.get("last_name"),
first_name = self.cleaned_data.get("first_name"),
email = self.cleaned_data.get("email"))
u = User(last_name=self.cleaned_data.get("last_name"),
first_name=self.cleaned_data.get("first_name"),
email=self.cleaned_data.get("email"))
u.generate_username()
u.set_password(str(random.randrange(1000000, 10000000)))
u.save()
@ -102,6 +99,7 @@ class SubscriptionForm(forms.ModelForm):
raise ValidationError(_("You must either choose an existing user or create a new one properly"))
return cleaned_data
class NewSubscription(CreateView):
template_name = 'subscription/subscription.jinja'
form_class = SubscriptionForm
@ -119,11 +117,11 @@ class NewSubscription(CreateView):
def form_valid(self, form):
form.instance.subscription_start = Subscription.compute_start(
duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type]['duration'])
duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type]['duration'])
form.instance.subscription_end = Subscription.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type]['duration'],
start=form.instance.subscription_start
)
duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type]['duration'],
start=form.instance.subscription_start
)
return super(NewSubscription, self).form_valid(form)

View File

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

View File

@ -22,7 +22,7 @@
#
#
from django.conf.urls import url, include
from django.conf.urls import url
from trombi.views import *
@ -43,4 +43,3 @@ urlpatterns = [
url(r'^membership/(?P<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'),
]

View File

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