Format counter

This commit is contained in:
Pierre Brunet 2017-06-12 09:47:24 +02:00
parent e7de8b2aec
commit d722efc40f
5 changed files with 290 additions and 227 deletions

View File

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

View File

@ -22,13 +22,12 @@
# #
# #
from django.db import models, DataError from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.forms import ValidationError from django.forms import ValidationError
from django.contrib.sites.shortcuts import get_current_site
from django.utils.functional import cached_property from django.utils.functional import cached_property
from datetime import timedelta, date from datetime import timedelta, date
@ -43,6 +42,7 @@ from accounting.models import CurrencyField
from core.models import Group, User, Notification from core.models import Group, User, Notification
from subscription.models import Subscription from subscription.models import Subscription
class Customer(models.Model): class Customer(models.Model):
""" """
This class extends a user to make a customer. It adds some basic customers informations, such as the accound ID, and This class extends a user to make a customer. It adds some basic customers informations, such as the accound ID, and
@ -55,7 +55,7 @@ class Customer(models.Model):
class Meta: class Meta:
verbose_name = _('customer') verbose_name = _('customer')
verbose_name_plural = _('customers') verbose_name_plural = _('customers')
ordering = ['account_id',] ordering = ['account_id', ]
def __str__(self): def __str__(self):
return "%s - %s" % (self.user.username, self.account_id) return "%s - %s" % (self.user.username, self.account_id)
@ -68,9 +68,9 @@ class Customer(models.Model):
def generate_account_id(number): def generate_account_id(number):
number = str(number) number = str(number)
letter = random.choice(string.ascii_lowercase) letter = random.choice(string.ascii_lowercase)
while Customer.objects.filter(account_id=number+letter).exists(): while Customer.objects.filter(account_id=number + letter).exists():
letter = random.choice(string.ascii_lowercase) letter = random.choice(string.ascii_lowercase)
return number+letter return number + letter
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.amount < 0: if self.amount < 0:
@ -118,6 +118,7 @@ class ProductType(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('counter:producttype_list') return reverse('counter:producttype_list')
class Product(models.Model): class Product(models.Model):
""" """
This describes a product, with all its related informations This describes a product, with all its related informations
@ -156,13 +157,14 @@ class Product(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('counter:product_list') return reverse('counter:product_list')
class Counter(models.Model): class Counter(models.Model):
name = models.CharField(_('name'), max_length=30) name = models.CharField(_('name'), max_length=30)
club = models.ForeignKey(Club, related_name="counters", verbose_name=_("club")) club = models.ForeignKey(Club, related_name="counters", verbose_name=_("club"))
products = models.ManyToManyField(Product, related_name="counters", verbose_name=_("products"), blank=True) products = models.ManyToManyField(Product, related_name="counters", verbose_name=_("products"), blank=True)
type = models.CharField(_('counter type'), type = models.CharField(_('counter type'),
max_length=255, max_length=255,
choices=[('BAR',_('Bar')), ('OFFICE',_('Office')), ('EBOUTIC',_('Eboutic'))]) choices=[('BAR', _('Bar')), ('OFFICE', _('Office')), ('EBOUTIC', _('Eboutic'))])
sellers = models.ManyToManyField(User, verbose_name=_('sellers'), related_name='counters', blank=True) sellers = models.ManyToManyField(User, verbose_name=_('sellers'), related_name='counters', blank=True)
edit_groups = models.ManyToManyField(Group, related_name="editable_counters", blank=True) edit_groups = models.ManyToManyField(Group, related_name="editable_counters", blank=True)
view_groups = models.ManyToManyField(Group, related_name="viewable_counters", blank=True) view_groups = models.ManyToManyField(Group, related_name="viewable_counters", blank=True)
@ -173,7 +175,7 @@ class Counter(models.Model):
def __getattribute__(self, name): def __getattribute__(self, name):
if name == "edit_groups": if name == "edit_groups":
return Group.objects.filter(name=self.club.unix_name+settings.SITH_BOARD_SUFFIX).all() return Group.objects.filter(name=self.club.unix_name + settings.SITH_BOARD_SUFFIX).all()
return object.__getattribute__(self, name) return object.__getattribute__(self, name)
def __str__(self): def __str__(self):
@ -265,6 +267,7 @@ class Counter(models.Model):
""" """
return [b.id for b in self.get_barmen_list()] return [b.id for b in self.get_barmen_list()]
class Refilling(models.Model): class Refilling(models.Model):
""" """
Handle the refilling Handle the refilling
@ -309,6 +312,7 @@ class Refilling(models.Model):
).save() ).save()
super(Refilling, self).save(*args, **kwargs) super(Refilling, self).save(*args, **kwargs)
class Selling(models.Model): class Selling(models.Model):
""" """
Handle the sellings Handle the sellings
@ -331,7 +335,7 @@ class Selling(models.Model):
def __str__(self): def __str__(self):
return "Selling: %d x %s (%f) for %s" % (self.quantity, self.label, return "Selling: %d x %s (%f) for %s" % (self.quantity, self.label,
self.quantity*self.unit_price, self.customer.user.get_display_name()) self.quantity * self.unit_price, self.customer.user.get_display_name())
def is_owned_by(self, user): def is_owned_by(self, user):
return user.is_owner(self.counter) and self.payment_method != "CARD" return user.is_owner(self.counter) and self.payment_method != "CARD"
@ -352,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." "You bought an eticket for the event %(event)s.\nYou can download it on this page %(url)s."
) % { ) % {
'event': event, 'event': event,
'url':''.join(( 'url': ''.join((
'<a href="', '<a href="',
self.customer.get_full_url(), self.customer.get_full_url(),
'">', '">',
@ -414,7 +418,8 @@ class Selling(models.Model):
try: try:
if self.product.eticket: if self.product.eticket:
self.send_mail_customer() self.send_mail_customer()
except: pass except:
pass
Notification( Notification(
user=self.customer.user, user=self.customer.user,
url=reverse('core:user_account_detail', url=reverse('core:user_account_detail',
@ -424,6 +429,7 @@ class Selling(models.Model):
).save() ).save()
super(Selling, self).save(*args, **kwargs) super(Selling, self).save(*args, **kwargs)
class Permanency(models.Model): class Permanency(models.Model):
""" """
This class aims at storing a traceability of who was barman where and when This class aims at storing a traceability of who was barman where and when
@ -444,6 +450,7 @@ class Permanency(models.Model):
self.end.strftime("%Y-%m-%d %H:%M:%S") if self.end else "", self.end.strftime("%Y-%m-%d %H:%M:%S") if self.end else "",
) )
class CashRegisterSummary(models.Model): class CashRegisterSummary(models.Model):
user = models.ForeignKey(User, related_name="cash_summaries", verbose_name=_("user")) user = models.ForeignKey(User, related_name="cash_summaries", verbose_name=_("user"))
counter = models.ForeignKey(Counter, related_name="cash_summaries", verbose_name=_("counter")) counter = models.ForeignKey(Counter, related_name="cash_summaries", verbose_name=_("counter"))
@ -515,6 +522,7 @@ class CashRegisterSummary(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('counter:cash_summary_list') return reverse('counter:cash_summary_list')
class CashRegisterSummaryItem(models.Model): class CashRegisterSummaryItem(models.Model):
cash_summary = models.ForeignKey(CashRegisterSummary, related_name="items", verbose_name=_("cash summary")) cash_summary = models.ForeignKey(CashRegisterSummary, related_name="items", verbose_name=_("cash summary"))
value = CurrencyField(_("value")) value = CurrencyField(_("value"))
@ -524,6 +532,7 @@ class CashRegisterSummaryItem(models.Model):
class Meta: class Meta:
verbose_name = _("cash register summary item") verbose_name = _("cash register summary item")
class Eticket(models.Model): class Eticket(models.Model):
""" """
Eticket can be linked to a product an allows PDF generation Eticket can be linked to a product an allows PDF generation
@ -552,6 +561,6 @@ class Eticket(models.Model):
return user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID) return user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID)
def get_hash(self, string): def get_hash(self, string):
import hashlib, hmac import hashlib
import hmac
return hmac.new(bytes(self.secret, 'utf-8'), bytes(string, 'utf-8'), hashlib.sha1).hexdigest() return hmac.new(bytes(self.secret, 'utf-8'), bytes(string, 'utf-8'), hashlib.sha1).hexdigest()

View File

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

View File

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

View File

@ -22,7 +22,7 @@
# #
# #
from django.shortcuts import render, get_object_or_404 from django.shortcuts import get_object_or_404
from django.http import Http404 from django.http import Http404
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.views.generic import ListView, DetailView, RedirectView, TemplateView from django.views.generic import ListView, DetailView, RedirectView, TemplateView
@ -31,7 +31,6 @@ from django.views.generic.edit import UpdateView, CreateView, DeleteView, Proces
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.forms import CheckboxSelectMultiple from django.forms import CheckboxSelectMultiple
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect, HttpResponse from django.http import HttpResponseRedirect, HttpResponse
from django.utils import timezone from django.utils import timezone
from django import forms from django import forms
@ -43,16 +42,17 @@ import re
import pytz import pytz
from datetime import date, timedelta, datetime from datetime import date, timedelta, datetime
from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField
from ajax_select import make_ajax_form, make_ajax_field from ajax_select import make_ajax_field
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, TabedViewMixin from core.views import CanViewMixin, TabedViewMixin
from core.views.forms import SelectUser, LoginForm, SelectDate, SelectDateTime from core.views.forms import LoginForm, SelectDate, SelectDateTime
from core.models import User from core.models import User
from subscription.models import Subscription from subscription.models import Subscription
from counter.models import Counter, Customer, Product, Selling, Refilling, ProductType, \ from counter.models import Counter, Customer, Product, Selling, Refilling, ProductType, \
CashRegisterSummary, CashRegisterSummaryItem, Eticket, Permanency CashRegisterSummary, CashRegisterSummaryItem, Eticket, Permanency
from accounting.models import CurrencyField from accounting.models import CurrencyField
class CounterAdminMixin(View): class CounterAdminMixin(View):
""" """
This view is made to protect counter admin section This view is made to protect counter admin section
@ -72,13 +72,13 @@ class CounterAdminMixin(View):
return True return True
return False return False
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not (request.user.is_root or self._test_group(request.user) if not (request.user.is_root or self._test_group(request.user)
or self._test_club(request.user)): or self._test_club(request.user)):
raise PermissionDenied raise PermissionDenied
return super(CounterAdminMixin, self).dispatch(request, *args, **kwargs) return super(CounterAdminMixin, self).dispatch(request, *args, **kwargs)
class GetUserForm(forms.Form): class GetUserForm(forms.Form):
""" """
The Form class aims at providing a valid user_id field in its cleaned data, in order to pass it to some view, The Form class aims at providing a valid user_id field in its cleaned data, in order to pass it to some view,
@ -107,25 +107,29 @@ class GetUserForm(forms.Form):
cleaned_data['user'] = cus.user cleaned_data['user'] = cus.user
return cleaned_data return cleaned_data
class RefillForm(forms.ModelForm): class RefillForm(forms.ModelForm):
error_css_class = 'error' error_css_class = 'error'
required_css_class = 'required' required_css_class = 'required'
amount = forms.FloatField(min_value=0, widget=forms.NumberInput(attrs={'class':'focus'})) amount = forms.FloatField(min_value=0, widget=forms.NumberInput(attrs={'class': 'focus'}))
class Meta: class Meta:
model = Refilling model = Refilling
fields = ['amount', 'payment_method', 'bank'] fields = ['amount', 'payment_method', 'bank']
class CounterTabsMixin(TabedViewMixin): class CounterTabsMixin(TabedViewMixin):
def get_tabs_title(self): def get_tabs_title(self):
if hasattr(self.object, 'stock_owner') : if hasattr(self.object, 'stock_owner'):
return self.object.stock_owner.counter return self.object.stock_owner.counter
else: else:
return self.object return self.object
def get_list_of_tabs(self): def get_list_of_tabs(self):
tab_list = [] tab_list = []
tab_list.append({ tab_list.append({
'url': reverse_lazy('counter:details', 'url': reverse_lazy('counter:details',
kwargs={'counter_id': self.object.stock_owner.counter.id if hasattr(self.object, 'stock_owner') else self.object.id }), kwargs={'counter_id': self.object.stock_owner.counter.id if hasattr(self.object, 'stock_owner') else self.object.id}),
'slug': 'counter', 'slug': 'counter',
'name': _("Counter"), 'name': _("Counter"),
}) })
@ -149,9 +153,11 @@ class CounterTabsMixin(TabedViewMixin):
'slug': 'take_items_from_stock', 'slug': 'take_items_from_stock',
'name': _("Take items from stock"), 'name': _("Take items from stock"),
}) })
except: pass # The counter just have no stock except:
pass # The counter just have no stock
return tab_list return tab_list
class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, FormMixin): class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, FormMixin):
""" """
The public (barman) view The public (barman) view
@ -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 if self.object.type == "BAR" and not ('counter_token' in self.request.session.keys() and
self.request.session['counter_token'] == self.object.token): # Check the token to avoid the bar to be stolen self.request.session['counter_token'] == self.object.token): # Check the token to avoid the bar to be stolen
return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args,
kwargs={'counter_id': self.object.id})+'?bad_location') kwargs={'counter_id': self.object.id}) + '?bad_location')
return super(CounterMain, self).post(request, *args, **kwargs) return super(CounterMain, self).post(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -210,6 +216,7 @@ class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, F
def get_success_url(self): def get_success_url(self):
return reverse_lazy('counter:click', args=self.args, kwargs=self.kwargs) return reverse_lazy('counter:click', args=self.args, kwargs=self.kwargs)
class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
""" """
The click view The click view
@ -228,7 +235,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
raise Http404 raise Http404
if obj.type == "BAR": if obj.type == "BAR":
if not ('counter_token' in request.session.keys() and if not ('counter_token' in request.session.keys() and
request.session['counter_token'] == obj.token) or len(obj.get_barmen_list())<1: request.session['counter_token'] == obj.token) or len(obj.get_barmen_list()) < 1:
raise PermissionDenied raise PermissionDenied
else: else:
if not request.user.is_authenticated(): if not request.user.is_authenticated():
@ -263,7 +270,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
if self.object.type == "BAR" and not ('counter_token' in self.request.session.keys() and if self.object.type == "BAR" and not ('counter_token' in self.request.session.keys() and
self.request.session['counter_token'] == self.object.token): # Also check the token to avoid the bar to be stolen self.request.session['counter_token'] == self.object.token): # Also check the token to avoid the bar to be stolen
return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args,
kwargs={'counter_id': self.object.id})+'?bad_location') kwargs={'counter_id': self.object.id}) + '?bad_location')
if 'basket' not in request.session.keys(): if 'basket' not in request.session.keys():
request.session['basket'] = {} request.session['basket'] = {}
request.session['basket_total'] = 0 request.session['basket_total'] = 0
@ -312,7 +319,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
def sum_basket(self, request): def sum_basket(self, request):
total = 0 total = 0
for pid,infos in request.session['basket'].items(): for pid, infos in request.session['basket'].items():
total += infos['price'] * infos['qty'] total += infos['price'] * infos['qty']
return total / 100 return total / 100
@ -323,7 +330,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
except: except:
return 0 return 0
def add_product(self, request, q = 1, p=None): def add_product(self, request, q=1, p=None):
""" """
Add a product to the basket Add a product to the basket
q is the quantity passed as integer q is the quantity passed as integer
@ -349,7 +356,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
total_qty_mod_6 = self.get_total_quantity_for_pid(request, pid) % 6 total_qty_mod_6 = self.get_total_quantity_for_pid(request, pid) % 6
bq = int((total_qty_mod_6 + q) / 6) # Integer division bq = int((total_qty_mod_6 + q) / 6) # Integer division
q -= bq q -= bq
if self.customer.amount < (total + round(q*float(price),2)): # Check for enough money if self.customer.amount < (total + round(q * float(price), 2)): # Check for enough money
request.session['not_enough'] = True request.session['not_enough'] = True
return False return False
if product.limit_age >= 18 and not self.customer.user.date_of_birth: if product.limit_age >= 18 and not self.customer.user.date_of_birth:
@ -368,7 +375,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
request.session['basket'][pid]['qty'] += q request.session['basket'][pid]['qty'] += q
request.session['basket'][pid]['bonus_qty'] += bq request.session['basket'][pid]['bonus_qty'] += bq
else: # or create if not else: # or create if not
request.session['basket'][pid] = {'qty': q, 'price': int(price*100), 'bonus_qty': bq} request.session['basket'][pid] = {'qty': q, 'price': int(price * 100), 'bonus_qty': bq}
request.session.modified = True request.session.modified = True
return True return True
@ -414,7 +421,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
""" Finish the click session, and validate the basket """ """ Finish the click session, and validate the basket """
with transaction.atomic(): with transaction.atomic():
request.session['last_basket'] = [] request.session['last_basket'] = []
for pid,infos in request.session['basket'].items(): for pid, infos in request.session['basket'].items():
# This duplicates code for DB optimization (prevent to load many times the same object) # This duplicates code for DB optimization (prevent to load many times the same object)
p = Product.objects.filter(pk=pid).first() p = Product.objects.filter(pk=pid).first()
if self.is_barman_price(): if self.is_barman_price():
@ -423,7 +430,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
uprice = p.selling_price uprice = p.selling_price
if uprice * infos['qty'] > self.customer.amount: if uprice * infos['qty'] > self.customer.amount:
raise DataError(_("You have not enough money to buy all the basket")) raise DataError(_("You have not enough money to buy all the basket"))
request.session['last_basket'].append("%d x %s" % (infos['qty']+infos['bonus_qty'], p.name)) request.session['last_basket'].append("%d x %s" % (infos['qty'] + infos['bonus_qty'], p.name))
s = Selling(label=p.name, product=p, club=p.club, counter=self.object, unit_price=uprice, s = Selling(label=p.name, product=p, club=p.club, counter=self.object, unit_price=uprice,
quantity=infos['qty'], seller=self.operator, customer=self.customer) quantity=infos['qty'], seller=self.operator, customer=self.customer)
s.save() s.save()
@ -470,6 +477,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
kwargs['categories'] = ProductType.objects.all() kwargs['categories'] = ProductType.objects.all()
return kwargs return kwargs
class CounterLogin(RedirectView): class CounterLogin(RedirectView):
""" """
Handle the login of a barman Handle the login of a barman
@ -477,6 +485,7 @@ class CounterLogin(RedirectView):
Logged barmen are stored in the Permanency model Logged barmen are stored in the Permanency model
""" """
permanent = False permanent = False
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
""" """
Register the logged user as barman for this counter Register the logged user as barman for this counter
@ -499,10 +508,12 @@ class CounterLogin(RedirectView):
return super(CounterLogin, self).post(request, *args, **kwargs) return super(CounterLogin, self).post(request, *args, **kwargs)
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
return reverse_lazy('counter:details', args=args, kwargs=kwargs)+"?"+'&'.join(self.errors) return reverse_lazy('counter:details', args=args, kwargs=kwargs) + "?" + '&'.join(self.errors)
class CounterLogout(RedirectView): class CounterLogout(RedirectView):
permanent = False permanent = False
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
""" """
Unregister the user from the barman Unregister the user from the barman
@ -515,7 +526,8 @@ class CounterLogout(RedirectView):
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
return reverse_lazy('counter:details', args=args, kwargs=kwargs) return reverse_lazy('counter:details', args=args, kwargs=kwargs)
## Counter admin views # Counter admin views
class CounterAdminTabsMixin(TabedViewMixin): class CounterAdminTabsMixin(TabedViewMixin):
tabs_title = _("Counter administration") tabs_title = _("Counter administration")
@ -562,6 +574,7 @@ class CounterAdminTabsMixin(TabedViewMixin):
}, },
] ]
class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView): class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView):
""" """
A list view for the admins A list view for the admins
@ -570,6 +583,7 @@ class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView):
template_name = 'counter/counter_list.jinja' template_name = 'counter/counter_list.jinja'
current_tab = "counters" current_tab = "counters"
class CounterEditForm(forms.ModelForm): class CounterEditForm(forms.ModelForm):
class Meta: class Meta:
model = Counter model = Counter
@ -577,6 +591,7 @@ class CounterEditForm(forms.ModelForm):
sellers = make_ajax_field(Counter, 'sellers', 'users', help_text="") sellers = make_ajax_field(Counter, 'sellers', 'users', help_text="")
products = make_ajax_field(Counter, 'products', 'products', help_text="") products = make_ajax_field(Counter, 'products', 'products', help_text="")
class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
""" """
Edit a counter's main informations (for the counter's manager) Edit a counter's main informations (for the counter's manager)
@ -595,6 +610,7 @@ class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
def get_success_url(self): def get_success_url(self):
return reverse_lazy('counter:admin', kwargs={'counter_id': self.object.id}) return reverse_lazy('counter:admin', kwargs={'counter_id': self.object.id})
class CounterEditPropView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): class CounterEditPropView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
""" """
Edit a counter's main informations (for the counter's admin) Edit a counter's main informations (for the counter's admin)
@ -605,16 +621,18 @@ class CounterEditPropView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
current_tab = "counters" current_tab = "counters"
class CounterCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): class CounterCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
""" """
Create a counter (for the admins) Create a counter (for the admins)
""" """
model = Counter model = Counter
form_class = modelform_factory(Counter, fields=['name', 'club', 'type', 'products'], form_class = modelform_factory(Counter, fields=['name', 'club', 'type', 'products'],
widgets={'products':CheckboxSelectMultiple}) widgets={'products': CheckboxSelectMultiple})
template_name = 'core/create.jinja' template_name = 'core/create.jinja'
current_tab = "counters" current_tab = "counters"
class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView): class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView):
""" """
Delete a counter (for the admins) Delete a counter (for the admins)
@ -627,6 +645,7 @@ class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView):
# Product management # Product management
class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
""" """
A list view for the admins A list view for the admins
@ -635,6 +654,7 @@ class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
template_name = 'counter/producttype_list.jinja' template_name = 'counter/producttype_list.jinja'
current_tab = "product_types" current_tab = "product_types"
class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
""" """
A create view for the admins A create view for the admins
@ -644,6 +664,7 @@ class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView
template_name = 'core/create.jinja' template_name = 'core/create.jinja'
current_tab = "products" current_tab = "products"
class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
""" """
An edit view for the admins An edit view for the admins
@ -654,6 +675,7 @@ class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
pk_url_kwarg = "type_id" pk_url_kwarg = "type_id"
current_tab = "products" current_tab = "products"
class ProductArchivedListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): class ProductArchivedListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
""" """
A list view for the admins A list view for the admins
@ -664,6 +686,7 @@ class ProductArchivedListView(CounterAdminTabsMixin, CounterAdminMixin, ListView
ordering = ['name'] ordering = ['name']
current_tab = "archive" current_tab = "archive"
class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
""" """
A list view for the admins A list view for the admins
@ -674,6 +697,7 @@ class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
ordering = ['name'] ordering = ['name']
current_tab = "products" current_tab = "products"
class ProductEditForm(forms.ModelForm): class ProductEditForm(forms.ModelForm):
class Meta: class Meta:
model = Product model = Product
@ -702,6 +726,7 @@ class ProductEditForm(forms.ModelForm):
c.save() c.save()
return ret return ret
class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
""" """
A create view for the admins A create view for the admins
@ -711,6 +736,7 @@ class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
template_name = 'core/create.jinja' template_name = 'core/create.jinja'
current_tab = "products" current_tab = "products"
class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
""" """
An edit view for the admins An edit view for the admins
@ -721,6 +747,7 @@ class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
current_tab = "products" current_tab = "products"
class RefillingDeleteView(DeleteView): class RefillingDeleteView(DeleteView):
""" """
Delete a refilling (for the admins) Delete a refilling (for the admins)
@ -745,6 +772,7 @@ class RefillingDeleteView(DeleteView):
return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs) return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs)
raise PermissionDenied raise PermissionDenied
class SellingDeleteView(DeleteView): class SellingDeleteView(DeleteView):
""" """
Delete a selling (for the admins) Delete a selling (for the admins)
@ -771,6 +799,7 @@ class SellingDeleteView(DeleteView):
# Cash register summaries # Cash register summaries
class CashRegisterSummaryForm(forms.Form): class CashRegisterSummaryForm(forms.Form):
""" """
Provide the cash summary form Provide the cash summary form
@ -839,30 +868,46 @@ class CashRegisterSummaryForm(forms.Form):
summary.save() summary.save()
summary.items.all().delete() summary.items.all().delete()
# Cash # Cash
if cd['ten_cents']: CashRegisterSummaryItem(cash_summary=summary, value=0.1, quantity=cd['ten_cents']).save() if cd['ten_cents']:
if cd['twenty_cents']: CashRegisterSummaryItem(cash_summary=summary, value=0.2, quantity=cd['twenty_cents']).save() CashRegisterSummaryItem(cash_summary=summary, value=0.1, quantity=cd['ten_cents']).save()
if cd['fifty_cents']: CashRegisterSummaryItem(cash_summary=summary, value=0.5, quantity=cd['fifty_cents']).save() if cd['twenty_cents']:
if cd['one_euro']: CashRegisterSummaryItem(cash_summary=summary, value=1, quantity=cd['one_euro']).save() CashRegisterSummaryItem(cash_summary=summary, value=0.2, quantity=cd['twenty_cents']).save()
if cd['two_euros']: CashRegisterSummaryItem(cash_summary=summary, value=2, quantity=cd['two_euros']).save() if cd['fifty_cents']:
if cd['five_euros']: CashRegisterSummaryItem(cash_summary=summary, value=5, quantity=cd['five_euros']).save() CashRegisterSummaryItem(cash_summary=summary, value=0.5, quantity=cd['fifty_cents']).save()
if cd['ten_euros']: CashRegisterSummaryItem(cash_summary=summary, value=10, quantity=cd['ten_euros']).save() if cd['one_euro']:
if cd['twenty_euros']: CashRegisterSummaryItem(cash_summary=summary, value=20, quantity=cd['twenty_euros']).save() CashRegisterSummaryItem(cash_summary=summary, value=1, quantity=cd['one_euro']).save()
if cd['fifty_euros']: CashRegisterSummaryItem(cash_summary=summary, value=50, quantity=cd['fifty_euros']).save() if cd['two_euros']:
if cd['hundred_euros']: CashRegisterSummaryItem(cash_summary=summary, value=100, quantity=cd['hundred_euros']).save() CashRegisterSummaryItem(cash_summary=summary, value=2, quantity=cd['two_euros']).save()
if cd['five_euros']:
CashRegisterSummaryItem(cash_summary=summary, value=5, quantity=cd['five_euros']).save()
if cd['ten_euros']:
CashRegisterSummaryItem(cash_summary=summary, value=10, quantity=cd['ten_euros']).save()
if cd['twenty_euros']:
CashRegisterSummaryItem(cash_summary=summary, value=20, quantity=cd['twenty_euros']).save()
if cd['fifty_euros']:
CashRegisterSummaryItem(cash_summary=summary, value=50, quantity=cd['fifty_euros']).save()
if cd['hundred_euros']:
CashRegisterSummaryItem(cash_summary=summary, value=100, quantity=cd['hundred_euros']).save()
# Checks # Checks
if cd['check_1_quantity']: CashRegisterSummaryItem(cash_summary=summary, value=cd['check_1_value'], if cd['check_1_quantity']:
CashRegisterSummaryItem(cash_summary=summary, value=cd['check_1_value'],
quantity=cd['check_1_quantity'], check=True).save() 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() 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() 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() 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() quantity=cd['check_5_quantity'], check=True).save()
if summary.items.count() < 1: if summary.items.count() < 1:
summary.delete() summary.delete()
class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView): class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView):
""" """
Provide the last operations to allow barmen to delete them Provide the last operations to allow barmen to delete them
@ -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 request.session['counter_token'] and # check if not null for counters that have no token set
Counter.objects.filter(token=request.session['counter_token']).exists()): Counter.objects.filter(token=request.session['counter_token']).exists()):
return super(CounterLastOperationsView, self).dispatch(request, *args, **kwargs) return super(CounterLastOperationsView, self).dispatch(request, *args, **kwargs)
return HttpResponseRedirect(reverse('counter:details', kwargs={'counter_id': self.object.id})+'?bad_location') return HttpResponseRedirect(reverse('counter:details', kwargs={'counter_id': self.object.id}) + '?bad_location')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Add form to the context """ """Add form to the context """
@ -891,6 +936,7 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView):
kwargs['last_sellings'] = self.object.sellings.filter(date__gte=threshold).order_by('-id')[:20] kwargs['last_sellings'] = self.object.sellings.filter(date__gte=threshold).order_by('-id')[:20]
return kwargs return kwargs
class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView):
""" """
Provide the cash summary form Provide the cash summary form
@ -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 request.session['counter_token'] and # check if not null for counters that have no token set
Counter.objects.filter(token=request.session['counter_token']).exists()): Counter.objects.filter(token=request.session['counter_token']).exists()):
return super(CounterCashSummaryView, self).dispatch(request, *args, **kwargs) return super(CounterCashSummaryView, self).dispatch(request, *args, **kwargs)
return HttpResponseRedirect(reverse('counter:details', kwargs={'counter_id': self.object.id})+'?bad_location') return HttpResponseRedirect(reverse('counter:details', kwargs={'counter_id': self.object.id}) + '?bad_location')
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
@ -933,6 +979,7 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView):
kwargs['form'] = self.form kwargs['form'] = self.form
return kwargs return kwargs
class CounterActivityView(DetailView): class CounterActivityView(DetailView):
""" """
Show the bar activity Show the bar activity
@ -941,6 +988,7 @@ class CounterActivityView(DetailView):
pk_url_kwarg = "counter_id" pk_url_kwarg = "counter_id"
template_name = 'counter/activity.jinja' template_name = 'counter/activity.jinja'
class CounterStatView(DetailView, CounterAdminMixin): class CounterStatView(DetailView, CounterAdminMixin):
""" """
Show the bar stats Show the bar stats
@ -957,14 +1005,14 @@ class CounterStatView(DetailView, CounterAdminMixin):
kwargs['User'] = User kwargs['User'] = User
semester_start = Subscription.compute_start(d=date.today(), duration=3) semester_start = Subscription.compute_start(d=date.today(), duration=3)
kwargs['total_sellings'] = Selling.objects.filter(date__gte=semester_start, kwargs['total_sellings'] = Selling.objects.filter(date__gte=semester_start,
counter=self.object).aggregate(total_sellings=Sum(F('quantity')*F('unit_price'), counter=self.object).aggregate(total_sellings=Sum(F('quantity') * F('unit_price'),
output_field=CurrencyField()))['total_sellings'] output_field=CurrencyField()))['total_sellings']
kwargs['top'] = Selling.objects.values('customer__user').annotate( kwargs['top'] = Selling.objects.values('customer__user').annotate(
selling_sum=Sum( selling_sum=Sum(
Case(When(counter=self.object, Case(When(counter=self.object,
date__gte=semester_start, date__gte=semester_start,
unit_price__gt=0, unit_price__gt=0,
then=F('unit_price')*F('quantity')), then=F('unit_price') * F('quantity')),
output_field=CurrencyField() output_field=CurrencyField()
) )
) )
@ -973,7 +1021,7 @@ class CounterStatView(DetailView, CounterAdminMixin):
perm_sum=Sum( perm_sum=Sum(
Case(When(counter=self.object, Case(When(counter=self.object,
end__gt=datetime(year=1999, month=1, day=1), end__gt=datetime(year=1999, month=1, day=1),
then=F('end')-F('start')), then=F('end') - F('start')),
output_field=models.DateTimeField() output_field=models.DateTimeField()
) )
) )
@ -983,14 +1031,14 @@ class CounterStatView(DetailView, CounterAdminMixin):
Case(When(counter=self.object, Case(When(counter=self.object,
start__gt=semester_start, start__gt=semester_start,
end__gt=datetime(year=1999, month=1, day=1), end__gt=datetime(year=1999, month=1, day=1),
then=F('end')-F('start')), then=F('end') - F('start')),
output_field=models.DateTimeField() output_field=models.DateTimeField()
) )
) )
).exclude(perm_sum=None).order_by('-perm_sum').all()[:100] ).exclude(perm_sum=None).order_by('-perm_sum').all()[:100]
kwargs['sith_date']=settings.SITH_START_DATE[0] kwargs['sith_date'] = settings.SITH_START_DATE[0]
kwargs['semester_start']=semester_start kwargs['semester_start'] = semester_start
return kwargs return kwargs
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
@ -1003,6 +1051,7 @@ class CounterStatView(DetailView, CounterAdminMixin):
return super(CanEditMixin, self).dispatch(request, *args, **kwargs) return super(CanEditMixin, self).dispatch(request, *args, **kwargs)
raise PermissionDenied raise PermissionDenied
class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
"""Edit cash summaries""" """Edit cash summaries"""
model = CashRegisterSummary model = CashRegisterSummary
@ -1015,10 +1064,12 @@ class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView)
def get_success_url(self): def get_success_url(self):
return reverse('counter:cash_summary_list') return reverse('counter:cash_summary_list')
class CashSummaryFormBase(forms.Form): class CashSummaryFormBase(forms.Form):
begin_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Begin date"), required=False, widget=SelectDateTime) begin_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Begin date"), required=False, widget=SelectDateTime)
end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), required=False, widget=SelectDateTime) end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), required=False, widget=SelectDateTime)
class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
"""Display a list of cash summaries""" """Display a list of cash summaries"""
model = CashRegisterSummary model = CashRegisterSummary
@ -1056,6 +1107,7 @@ class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
kwargs['refilling_sums'][c.name] = sum([s.amount for s in refillings.all()]) kwargs['refilling_sums'][c.name] = sum([s.amount for s in refillings.all()])
return kwargs return kwargs
class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView): class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView):
template_name = 'counter/invoices_call.jinja' template_name = 'counter/invoices_call.jinja'
current_tab = 'invoices_call' current_tab = 'invoices_call'
@ -1069,24 +1121,25 @@ class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView):
try: try:
start_date = datetime.strptime(self.request.GET['month'], '%Y-%m') start_date = datetime.strptime(self.request.GET['month'], '%Y-%m')
except: except:
start_date = datetime(year=timezone.now().year, month=(timezone.now().month+10)%12+1, day=1) start_date = datetime(year=timezone.now().year, month=(timezone.now().month + 10) % 12 + 1, day=1)
start_date = start_date.replace(tzinfo=pytz.UTC) start_date = start_date.replace(tzinfo=pytz.UTC)
end_date = (start_date + timedelta(days=32)).replace(day=1, hour=0, minute=0, microsecond=0) end_date = (start_date + timedelta(days=32)).replace(day=1, hour=0, minute=0, microsecond=0)
from django.db.models import Sum, Case, When, F, DecimalField from django.db.models import Sum, Case, When, F, DecimalField
kwargs['sum_cb'] = sum([r.amount for r in Refilling.objects.filter(payment_method='CARD', is_validated=True, kwargs['sum_cb'] = sum([r.amount for r in Refilling.objects.filter(payment_method='CARD', is_validated=True,
date__gte=start_date, date__lte=end_date)]) date__gte=start_date, date__lte=end_date)])
kwargs['sum_cb'] += sum([s.quantity*s.unit_price for s in Selling.objects.filter(payment_method='CARD', is_validated=True, kwargs['sum_cb'] += sum([s.quantity * s.unit_price for s in Selling.objects.filter(payment_method='CARD', is_validated=True,
date__gte=start_date, date__lte=end_date)]) date__gte=start_date, date__lte=end_date)])
kwargs['start_date'] = start_date kwargs['start_date'] = start_date
kwargs['sums'] = Selling.objects.values('club__name').annotate(selling_sum=Sum( kwargs['sums'] = Selling.objects.values('club__name').annotate(selling_sum=Sum(
Case(When(date__gte=start_date, Case(When(date__gte=start_date,
date__lt=end_date, date__lt=end_date,
then=F('unit_price')*F('quantity')), then=F('unit_price') * F('quantity')),
output_field=CurrencyField() output_field=CurrencyField()
) )
)).exclude(selling_sum=None).order_by('-selling_sum') )).exclude(selling_sum=None).order_by('-selling_sum')
return kwargs return kwargs
class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
""" """
A list view for the admins A list view for the admins
@ -1096,6 +1149,7 @@ class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
ordering = ['id'] ordering = ['id']
current_tab = "etickets" current_tab = "etickets"
class EticketForm(forms.ModelForm): class EticketForm(forms.ModelForm):
class Meta: class Meta:
model = Eticket model = Eticket
@ -1105,6 +1159,7 @@ class EticketForm(forms.ModelForm):
} }
product = AutoCompleteSelectField('products', show_help_text=False, label=_("Product"), required=True) product = AutoCompleteSelectField('products', show_help_text=False, label=_("Product"), required=True)
class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
""" """
Create an eticket Create an eticket
@ -1114,6 +1169,7 @@ class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
form_class = EticketForm form_class = EticketForm
current_tab = "etickets" current_tab = "etickets"
class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
""" """
Edit an eticket Edit an eticket
@ -1124,6 +1180,7 @@ class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
pk_url_kwarg = "eticket_id" pk_url_kwarg = "eticket_id"
current_tab = "etickets" current_tab = "etickets"
class EticketPDFView(CanViewMixin, DetailView): class EticketPDFView(CanViewMixin, DetailView):
""" """
Display the PDF of an eticket Display the PDF of an eticket
@ -1180,7 +1237,7 @@ class EticketPDFView(CanViewMixin, DetailView):
bounds = qrcode.getBounds() bounds = qrcode.getBounds()
width = bounds[2] - bounds[0] width = bounds[2] - bounds[0]
height = bounds[3] - bounds[1] height = bounds[3] - bounds[1]
d = Drawing(260, 260, transform=[260./width, 0, 0, 260./height, 0, 0]) d = Drawing(260, 260, transform=[260. / width, 0, 0, 260. / height, 0, 0])
d.add(qrcode) d.add(qrcode)
renderPDF.draw(d, p, 10.5 * cm - 130, 6.1 * cm) renderPDF.draw(d, p, 10.5 * cm - 130, 6.1 * cm)
p.drawCentredString(10.5 * cm, 6 * cm, code) p.drawCentredString(10.5 * cm, 6 * cm, code)
@ -1195,4 +1252,3 @@ class EticketPDFView(CanViewMixin, DetailView):
p.showPage() p.showPage()
p.save() p.save()
return response return response