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
@ -56,6 +56,7 @@ class CurrencyField(models.DecimalField):
# 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
@ -352,6 +356,7 @@ class Operation(models.Model):
self.amount, self.date, self.accounting_type, self.done,
)
class AccountingType(models.Model):
"""
Class describing the accounting types.
@ -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):
"""
@ -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,14 +89,15 @@ 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,
@ -112,73 +111,73 @@ class OperationTest(TestCase):
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' : '',
'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,
'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' : '',
'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,
'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' : '',
'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,
'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')
@ -186,19 +185,19 @@ class OperationTest(TestCase):
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,
'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,
'label': '',
'done': False,
})
self.assertFalse(response.status_code == 403)
self.assertTrue(self.journal.operations.filter(amount=23).exists())
@ -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,6 +142,7 @@ class BankAccountCreateView(CanCreateMixin, CreateView):
fields = ['name', 'club', 'iban', 'number']
template_name = 'core/create.jinja'
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,6 +190,7 @@ class ClubAccountCreateView(CanCreateMixin, CreateView):
ret['bank_account'] = obj.id
return ret
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):
@ -219,13 +231,14 @@ class JournalTabsMixin(TabedViewMixin):
})
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)
@ -270,13 +286,14 @@ class JournalDeleteView(CanEditPropMixin, DeleteView):
else:
raise PermissionDenied
# Operation views
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,
@ -350,6 +367,7 @@ class OperationForm(forms.ModelForm):
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),
('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', (-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', (0, 2), (1, 2), 'LEFT'), # line payment_mode
('ALIGN', (-2, -1), (-1, -1), 'LEFT'),
('BOX', (0,0), (-1,-1), 0.25, colors.black),
('BOX', (0, 0), (-1, -1), 0.25, colors.black),
]))
signature = []
signature += [[_("Signature:")]]
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()
@ -532,13 +551,15 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
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 = ""
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,14 +587,15 @@ 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,
@ -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,6 +681,7 @@ 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
model = Label
form_class = modelform_factory(Label, fields=['name', 'club_account'], widgets={
@ -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):
@ -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
@ -195,8 +198,8 @@ 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):
@ -91,6 +85,7 @@ class ClubTabsMixin(TabedViewMixin):
})
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,12 +113,14 @@ 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']
@ -134,9 +133,10 @@ class ClubMemberForm(forms.ModelForm):
"""
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
@ -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
@ -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,
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')),
]
]
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
@ -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")
@ -82,6 +83,7 @@ class ComTabsMixin(TabedViewMixin):
})
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,6 +117,7 @@ class WeekmailDestinationEditView(ComEditView):
# News
class NewsForm(forms.ModelForm):
class Meta:
model = News
@ -155,6 +162,7 @@ class NewsForm(forms.ModelForm):
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,6 +304,7 @@ 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'
@ -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,6 +33,7 @@ admin.site.unregister(AuthGroup)
admin.site.register(RealGroup)
admin.site.register(Page)
@admin.register(SithFile)
class SithFileAdmin(admin.ModelAdmin):
form = make_ajax_form(SithFile, {

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())
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):
@ -99,7 +99,7 @@ class Command(BaseCommand):
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="""
| | | |
@ -154,7 +154,7 @@ Welcome to the wiki page!
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:
@ -174,7 +174,7 @@ Welcome to the wiki page!
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",
@ -183,7 +183,7 @@ Welcome to the wiki page!
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",
@ -192,7 +192,7 @@ Welcome to the wiki page!
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",
@ -201,8 +201,8 @@ Welcome to the wiki page!
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",
@ -211,8 +211,8 @@ Welcome to the wiki page!
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,13 +252,13 @@ 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])
s.subscription_start = s.compute_start()
@ -266,7 +266,7 @@ Welcome to the wiki page!
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])
s.subscription_start = s.compute_start()
@ -274,7 +274,7 @@ Welcome to the wiki page!
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])
s.subscription_start = s.compute_start()
@ -282,7 +282,7 @@ Welcome to the wiki page!
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])
s.subscription_start = s.compute_start()
@ -290,7 +290,7 @@ Welcome to the wiki page!
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])
s.subscription_start = s.compute_start()
@ -298,7 +298,7 @@ Welcome to the wiki page!
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])
s.subscription_start = s.compute_start()
@ -306,7 +306,7 @@ Welcome to the wiki page!
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])
s.subscription_start = s.compute_start(datetime(year=2012, month=9, day=4))
@ -397,14 +397,14 @@ 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),
(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),
@ -428,7 +428,7 @@ 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:
@ -456,7 +456,7 @@ Welcome to the wiki page!
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])
s.subscription_start = s.compute_start()
@ -464,7 +464,7 @@ Welcome to the wiki page!
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])
s.subscription_start = s.compute_start()
@ -483,7 +483,7 @@ 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_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)
@ -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
@ -166,7 +171,8 @@ class SithInlineLexer(InlineLexer):
match = page.search(link)
page = match.group(1) or ""
link = reverse('core:page', kwargs={'page_name': page})
except: pass
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
@ -226,6 +235,7 @@ 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
@ -324,7 +334,7 @@ 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)
print("Reason: %s" % (repr(e)), file=f)
@ -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 \
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')
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
@ -489,6 +499,7 @@ class User(AbstractBaseUser):
infos.save()
return infos
class AnonymousUser(AuthAnonymousUser):
def __init__(self, request):
super(AnonymousUser, self).__init__()
@ -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)
@ -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
@ -792,8 +812,7 @@ class Page(models.Model):
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
@ -854,7 +873,8 @@ 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()
@ -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:
@ -185,6 +185,7 @@ class UserRegistrationTest(TestCase):
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:
@ -260,7 +261,7 @@ class PageHandlingTest(TestCase):
'name': 'guy',
'owner_group': '1',
})
r = self.client.post(reverse('core:page_edit', kwargs={'page_name': 'guy'}), {
self.client.post(reverse('core:page_edit', kwargs={'page_name': 'guy'}), {
'title': 'Bibou',
'content':
'''Guy *bibou*
@ -280,11 +281,12 @@ http://git.an
'<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}),
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}),
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,20 +89,23 @@ 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)
@ -164,12 +171,14 @@ def doku_to_markdown(text):
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
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
@ -205,9 +214,9 @@ def bbcode_to_markdown(text):
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
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
@ -70,6 +69,7 @@ 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"),
@ -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
@ -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"
@ -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,6 +79,7 @@ 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>'\
@ -84,6 +90,7 @@ class MarkdownInput(Textarea):
}
return output
class SelectFile(TextInput):
def render(self, name, value, attrs=None):
if attrs:
@ -98,6 +105,7 @@ class SelectFile(TextInput):
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:
@ -114,6 +122,7 @@ class SelectUser(TextInput):
# 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):
"""
@ -197,7 +206,7 @@ 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()))
@ -223,9 +232,11 @@ class UserProfileForm(forms.ModelForm):
{'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'
@ -84,13 +85,14 @@ class PageRevView(CanViewMixin, DetailView):
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', ],
fields=['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ],
widgets={
'edit_groups':CheckboxSelectMultiple,
'view_groups':CheckboxSelectMultiple,
'edit_groups': CheckboxSelectMultiple,
'view_groups': CheckboxSelectMultiple,
})
template_name = 'core/page_prop.jinja'
@ -115,13 +117,14 @@ 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', ],
fields=['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ],
widgets={
'edit_groups':CheckboxSelectMultiple,
'view_groups':CheckboxSelectMultiple,
'edit_groups': CheckboxSelectMultiple,
'view_groups': CheckboxSelectMultiple,
})
template_name = 'core/page_prop.jinja'
slug_field = '_full_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,10 +59,12 @@ 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:
@ -75,6 +76,7 @@ def search_club(query, as_json=False):
clubs = list(clubs)
return clubs
@login_required
def search_view(request):
result = {
@ -83,6 +85,7 @@ def search_view(request):
}
return render(request, "core/search.jinja", context={'result': result})
@login_required
def search_user_json(request):
result = {
@ -90,6 +93,7 @@ def search_user_json(request):
}
return JsonResponse(result)
@login_required
def search_json(request):
result = {
@ -98,6 +102,7 @@ def search_json(request):
}
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
@ -102,12 +106,14 @@ def password_reset(request):
post_reset_redirect="core:password_reset_done",
)
def password_reset_done(request):
"""
Confirm that the reset email has been sent
"""
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
@ -117,6 +123,7 @@ def password_reset_confirm(request, uidb64=None, token=None):
template_name="core/password_reset_confirm.jinja",
)
def password_reset_complete(request):
"""
Confirm the password has sucessfully been reset
@ -125,12 +132,13 @@ def 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()
@ -196,7 +205,7 @@ class UserTabsMixin(TabedViewMixin):
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_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}),
@ -206,11 +215,13 @@ class UserTabsMixin(TabedViewMixin):
tab_list.append({
'url': reverse('core:user_account', kwargs={'user_id': self.object.id}),
'slug': 'account',
'name': _("Account")+" (%s €)" % self.object.customer.amount,
'name': _("Account") + " (%s €)" % self.object.customer.amount,
})
except: pass
except:
pass
return tab_list
class UserView(UserTabsMixin, CanViewMixin, DetailView):
"""
Display a user's profile
@ -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
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,
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
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]
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
@ -367,6 +384,7 @@ class UserUploadProfilePictView(CanEditMixin, DetailView):
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
@ -493,11 +516,12 @@ class UserAccountBase(UserTabsMixin, DetailView):
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
@ -517,8 +541,8 @@ class UserAccountView(UserAccountBase):
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
@ -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'))])
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):
@ -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
@ -309,6 +312,7 @@ class Refilling(models.Model):
).save()
super(Refilling, self).save(*args, **kwargs)
class Selling(models.Model):
"""
Handle the sellings
@ -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,7 +356,7 @@ 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((
'url': ''.join((
'<a href="',
self.customer.get_full_url(),
'">',
@ -414,7 +418,8 @@ class Selling(models.Model):
try:
if self.product.eticket:
self.send_mail_customer()
except: pass
except:
pass
Notification(
user=self.customer.user,
url=reverse('core:user_account_detail',
@ -424,6 +429,7 @@ class Selling(models.Model):
).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
@ -444,6 +450,7 @@ class Permanency(models.Model):
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"))
counter = models.ForeignKey(Counter, related_name="cash_summaries", verbose_name=_("counter"))
@ -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
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,25 +107,29 @@ 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"),
})
@ -149,9 +153,11 @@ class CounterTabsMixin(TabedViewMixin):
'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
@ -167,7 +173,7 @@ class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, F
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
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):
@ -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():
@ -263,7 +270,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
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
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
@ -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
@ -349,7 +356,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
total_qty_mod_6 = self.get_total_quantity_for_pid(request, pid) % 6
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:
@ -368,7 +375,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
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}
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,7 +430,7 @@ 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)
s.save()
@ -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,7 +526,8 @@ 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")
@ -562,6 +574,7 @@ class CounterAdminTabsMixin(TabedViewMixin):
},
]
class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView):
"""
A list view for the admins
@ -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,6 +697,7 @@ class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
ordering = ['name']
current_tab = "products"
class ProductEditForm(forms.ModelForm):
class Meta:
model = Product
@ -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)
@ -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)
@ -771,6 +799,7 @@ class SellingDeleteView(DeleteView):
# Cash register summaries
class CashRegisterSummaryForm(forms.Form):
"""
Provide the cash summary form
@ -839,30 +868,46 @@ class CashRegisterSummaryForm(forms.Form):
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'],
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'],
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'],
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'],
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'],
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
@ -881,7 +926,7 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView):
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
@ -909,7 +955,7 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView):
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,14 +1005,14 @@ 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'),
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')),
then=F('unit_price') * F('quantity')),
output_field=CurrencyField()
)
)
@ -973,7 +1021,7 @@ class CounterStatView(DetailView, CounterAdminMixin):
perm_sum=Sum(
Case(When(counter=self.object,
end__gt=datetime(year=1999, month=1, day=1),
then=F('end')-F('start')),
then=F('end') - F('start')),
output_field=models.DateTimeField()
)
)
@ -983,14 +1031,14 @@ class CounterStatView(DetailView, CounterAdminMixin):
Case(When(counter=self.object,
start__gt=semester_start,
end__gt=datetime(year=1999, month=1, day=1),
then=F('end')-F('start')),
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):
@ -1003,6 +1051,7 @@ class CounterStatView(DetailView, CounterAdminMixin):
return super(CanEditMixin, self).dispatch(request, *args, **kwargs)
raise PermissionDenied
class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
"""Edit cash summaries"""
model = CashRegisterSummary
@ -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
@ -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,
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')),
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,6 +1149,7 @@ class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
ordering = ['id']
current_tab = "etickets"
class EticketForm(forms.ModelForm):
class Meta:
model = Eticket
@ -1105,6 +1159,7 @@ class EticketForm(forms.ModelForm):
}
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
@ -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,7 +38,7 @@ 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,
@ -47,7 +47,7 @@ class Basket(models.Model):
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
@ -120,6 +121,7 @@ class Invoice(models.Model):
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):
@ -93,7 +94,6 @@ class EbouticTest(TestCase):
" <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')
response = self.client.post(reverse("eboutic:main"), {
@ -171,6 +171,3 @@ class EbouticTest(TestCase):
" <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,23 +24,21 @@
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'
@ -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,6 +105,7 @@ class EbouticMain(TemplateView):
kwargs['categories'] = kwargs['categories'].exclude(id=settings.SITH_PRODUCTTYPE_SUBSCRIPTION)
return kwargs
class EbouticCommand(TemplateView):
template_name = 'eboutic/eboutic_makecommand.jinja'
@ -136,7 +134,7 @@ 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_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
@ -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'),
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'
@ -189,6 +188,7 @@ class EbouticPayWithSith(TemplateView):
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
@ -221,8 +221,7 @@ class EtransactionAutoAnswer(View):
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
@ -118,10 +117,12 @@ class Forum(models.Model):
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,6 +179,7 @@ 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')
@ -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
@ -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")),
]
]
class ForumMessageMeta(models.Model):
user = models.ForeignKey(User, related_name="forum_message_metas")
@ -326,4 +332,3 @@ class ForumUserInfo(models.Model):
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')
@ -57,9 +57,11 @@ class ForumMarkAllAsRead(RedirectView):
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
except:
pass
return super(ForumMarkAllAsRead, self).get(request, *args, **kwargs)
class ForumLastUnread(ListView):
model = ForumTopic
template_name = "forum/last_unread.jinja"
@ -73,6 +75,7 @@ class ForumLastUnread(ListView):
.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"
@ -139,6 +148,7 @@ class ForumDetailView(CanViewMixin, DetailView):
kwargs["topics"] = paginator.page(paginator.num_pages)
return kwargs
class TopicForm(forms.ModelForm):
class Meta:
model = ForumMessage
@ -148,6 +158,7 @@ class TopicForm(forms.ModelForm):
}
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,7 +199,7 @@ 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')\
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')
@ -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):
@ -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)
@ -157,5 +161,3 @@ 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)

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
@ -100,7 +99,8 @@ class LaunderetteBookView(CanViewMixin, DetailView):
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,6 +177,7 @@ 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)
@ -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,6 +267,7 @@ 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
@ -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():
@ -313,7 +321,7 @@ class ClickTokenForm(forms.BaseForm):
'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,
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
@ -356,7 +365,7 @@ class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView):
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")))
# 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])
@ -113,6 +113,7 @@ class Picture(SithFile):
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,31 +22,27 @@
#
#
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"),
@ -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
@ -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
@ -239,6 +243,7 @@ class AlbumView(CanViewMixin, DetailView, FormMixin):
# Admin views
class ModerationView(TemplateView):
template_name = "sas/moderation.jinja"
@ -257,7 +262,8 @@ 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):
@ -268,12 +274,14 @@ class ModerationView(TemplateView):
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

@ -180,7 +180,7 @@ HAYSTACK_CONNECTIONS = {
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
},
}
}
HAYSTACK_SIGNAL_PROCESSOR = 'core.search_indexes.UserOnlySignalProcessor'
@ -208,7 +208,7 @@ LANGUAGE_CODE = 'fr-FR'
LANGUAGES = [
('en', _('English')),
('fr', _('French')),
]
]
TIME_ZONE = 'Europe/Paris'
@ -247,13 +247,13 @@ 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
@ -265,7 +265,7 @@ OLD_MYSQL_INFOS = {
'db': "ae2-db",
'charset': 'utf8',
'use_unicode': True,
}
}
SITH_URL = "my.url.git.an"
@ -277,21 +277,21 @@ SITH_MAIN_CLUB = {
'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"
}
}
# Launderette managers
SITH_LAUNDERETTE_MANAGER = {
'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)
@ -328,11 +328,11 @@ 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")),
@ -348,14 +348,14 @@ SITH_PROFILE_DEPARTMENTS = [
("EDIM", _("EDIM")),
("HUMA", _("Humanities")),
("NA", _("N/A")),
]
]
SITH_ACCOUNTING_PAYMENT_METHOD = [
('CHECK', _('Check')),
('CASH', _('Cash')),
('TRANSFERT', _('Transfert')),
('CARD', _('Credit card')),
]
]
SITH_SUBSCRIPTION_PAYMENT_METHOD = [
('CHECK', _('Check')),
@ -363,26 +363,26 @@ SITH_SUBSCRIPTION_PAYMENT_METHOD = [
('CASH', _('Cash')),
('EBOUTIC', _('Eboutic')),
('OTHER', _('Other')),
]
]
SITH_SUBSCRIPTION_LOCATIONS = [
('BELFORT', _('Belfort')),
('SEVENANS', _('Sevenans')),
('MONTBELIARD', _('Montbéliard')),
('EBOUTIC', _('Eboutic')),
]
]
SITH_COUNTER_BARS = [
(1, "MDE"),
(2, "Foyer"),
(35, "La Gommette"),
]
]
SITH_COUNTER_PAYMENT_METHOD = [
('CHECK', _('Check')),
('CASH', _('Cash')),
('CARD', _('Credit card')),
]
]
SITH_COUNTER_BANK = [
('OTHER', 'Autre'),
@ -395,7 +395,7 @@ SITH_COUNTER_BANK = [
('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"
@ -523,7 +523,7 @@ SITH_LAUNDERETTE_MACHINE_TYPES = [('WASHING', _('Washing')), ('DRYING', _('Dryin
SITH_LAUNDERETTE_PRICES = {
'WASHING': 1.0,
'DRYING': 0.75,
}
}
SITH_NOTIFICATIONS = [
('NEWS_MODERATION', _("A fresh new to be moderated")),
@ -533,7 +533,7 @@ SITH_NOTIFICATIONS = [
('REFILLING', _("You just refilled of %s")),
('SELLING', _("You just bought %s")),
('GENERIC', _("You have a notification")),
]
]
SITH_QUICK_NOTIF = {
'qn_success': _("Success!"),
@ -541,7 +541,7 @@ SITH_QUICK_NOTIF = {
'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,20 +35,21 @@ 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'),
@ -58,7 +59,7 @@ class Subscription(models.Model):
max_length=20, verbose_name=_('location'))
class Meta:
ordering = ['subscription_start',]
ordering = ['subscription_start', ]
def clean(self):
try:
@ -75,7 +76,7 @@ class Subscription(models.Model):
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()
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',
@ -91,25 +92,25 @@ class Subscription(models.Model):
"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)
@ -120,7 +121,7 @@ class Subscription(models.Model):
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):
@ -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

View File

@ -34,15 +34,18 @@ 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())
class Trombi(models.Model):
"""
This is the main class, the Trombi itself.
@ -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,
@ -120,6 +124,7 @@ class TrombiUser(models.Model):
end=end_date,
).save()
class TrombiComment(models.Model):
"""
This represent a comment given by someone to someone else in the same Trombi
@ -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")
@ -69,9 +70,11 @@ class TrombiTabsMixin(TabedViewMixin):
'slug': 'admin_tools',
'name': _("Admin tools"),
})
except: pass
except:
pass
return tab_list
class TrombiForm(forms.ModelForm):
class Meta:
model = Trombi
@ -81,6 +84,7 @@ class TrombiForm(forms.ModelForm):
'comments_deadline': SelectDate,
}
class TrombiCreateView(CanCreateMixin, CreateView):
"""
Create a trombi for a club
@ -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'
@ -139,10 +147,12 @@ class TrombiModerateCommentsView(CanEditPropMixin, QuickNotifMixin, TrombiTabsMi
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,7 +169,7 @@ 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():
@ -176,26 +186,29 @@ class TrombiModerateCommentView(DetailView):
},
)
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!"))
class UserTrombiToolsView(QuickNotifMixin, TrombiTabsMixin, TemplateView):
"""
Display a user's trombi tools
@ -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,7 +246,8 @@ 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
@ -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
@ -325,7 +344,7 @@ class TrombiCommentFormView():
})
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)