views.py 14.6 KB
Newer Older
Skia's avatar
Skia committed
1
from django.shortcuts import render
Skia's avatar
Skia committed
2
from django.views.generic import ListView, DetailView, RedirectView
Skia's avatar
Skia committed
3
from django.views.generic.edit import UpdateView, CreateView, DeleteView, ProcessFormView, FormMixin
Skia's avatar
Skia committed
4
5
6
from django.forms.models import modelform_factory
from django.forms import CheckboxSelectMultiple
from django.core.urlresolvers import reverse_lazy
Skia's avatar
Skia committed
7
from django.contrib.auth.forms import AuthenticationForm
8
from django.http import HttpResponseRedirect
Skia's avatar
Skia committed
9
from django.utils import timezone
Skia's avatar
Skia committed
10
from django import forms
Skia's avatar
Skia committed
11
from django.utils.translation import ugettext_lazy as _
12
from django.conf import settings
Skia's avatar
Skia committed
13

Skia's avatar
Skia committed
14
import re
Skia's avatar
Skia committed
15
16

from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin
Skia's avatar
Skia committed
17
from subscription.models import Subscriber
Skia's avatar
Skia committed
18
from counter.models import Counter, Customer, Product, Selling, Refilling
Skia's avatar
Skia committed
19

Skia's avatar
Skia committed
20
class GetUserForm(forms.Form):
21
22
23
24
25
26
27
    """
    The Form class aims at providing a valid user_id field in its cleaned data, in order to pass it to some view,
    reverse function, or any other use.

    The Form implements a nice JS widget allowing the user to type a customer account id, or search the database with
    some nickname, first name, or last name (TODO)
    """
Skia's avatar
Skia committed
28
    code = forms.CharField(label="Code", max_length=10, required=False)
29
30
    id = forms.IntegerField(label="ID", required=False)
# TODO: add a nice JS widget to search for users
Skia's avatar
Skia committed
31

Skia's avatar
Skia committed
32
33
34
35
    def as_p(self):
        self.fields['code'].widget.attrs['autofocus'] = True
        return super(GetUserForm, self).as_p()

36
37
38
39
40
41
42
43
44
45
46
47
    def clean(self):
        cleaned_data = super(GetUserForm, self).clean()
        user = None
        if cleaned_data['code'] != "":
            user = Customer.objects.filter(account_id=cleaned_data['code']).first()
        elif cleaned_data['id'] is not None:
            user = Customer.objects.filter(user=cleaned_data['id']).first()
        if user is None:
            raise forms.ValidationError("User not found")
        cleaned_data['user_id'] = user.user.id
        return cleaned_data

48
49
50
51
52
53
54
class RefillForm(forms.ModelForm):
    error_css_class = 'error'
    required_css_class = 'required'
    class Meta:
        model = Refilling
        fields = ['amount', 'payment_method', 'bank']

55
class CounterMain(DetailView, ProcessFormView, FormMixin):
Skia's avatar
Skia committed
56
57
58
    """
    The public (barman) view
    """
Skia's avatar
Skia committed
59
    model = Counter
Skia's avatar
Skia committed
60
    template_name = 'counter/counter_main.jinja'
Skia's avatar
Skia committed
61
    pk_url_kwarg = "counter_id"
62
    form_class = GetUserForm # Form to enter a client code and get the corresponding user id
Skia's avatar
Skia committed
63

Skia's avatar
Skia committed
64
    def get_context_data(self, **kwargs):
Skia's avatar
Skia committed
65
        """
66
        We handle here the login form for the barman
Skia's avatar
Skia committed
67
        """
68
69
        if self.request.method == 'POST':
            self.object = self.get_object()
Skia's avatar
Skia committed
70
        kwargs = super(CounterMain, self).get_context_data(**kwargs)
71
# TODO: make some checks on the counter type, in order not to make the AuthenticationForm if there is no need to
Skia's avatar
Skia committed
72
        kwargs['login_form'] = AuthenticationForm()
Skia's avatar
Skia committed
73
        kwargs['login_form'].fields['username'].widget.attrs['autofocus'] = True
Skia's avatar
Skia committed
74
        kwargs['form'] = self.get_form()
Skia's avatar
Skia committed
75
76
77
78
        if self.object.type == 'BAR':
            kwargs['barmen'] = Counter.get_barmen_list(self.object.id)
        elif self.request.user.is_authenticated():
            kwargs['barmen'] = [self.request.user]
79
80
81
82
83
        if 'last_basket' in self.request.session.keys():
            kwargs['last_basket'] = self.request.session.pop('last_basket')
            kwargs['last_customer'] = self.request.session.pop('last_customer')
            kwargs['last_total'] = self.request.session.pop('last_total')
            kwargs['new_customer_amount'] = self.request.session.pop('new_customer_amount')
Skia's avatar
Skia committed
84
85
        return kwargs

86
87
88
89
90
91
92
93
94
95
    def form_valid(self, form):
        """
        We handle here the redirection, passing the user id of the asked customer
        """
        self.kwargs['user_id'] = form.cleaned_data['user_id']
        return super(CounterMain, self).form_valid(form)

    def get_success_url(self):
        return reverse_lazy('counter:click', args=self.args, kwargs=self.kwargs)

96
class CounterClick(DetailView):
Skia's avatar
Skia committed
97
98
    """
    The click view
99
100
    This is a detail view not to have to worry about loading the counter
    Everything is made by hand in the post method
Skia's avatar
Skia committed
101
    """
102
    model = Counter
Skia's avatar
Skia committed
103
104
    template_name = 'counter/counter_click.jinja'
    pk_url_kwarg = "counter_id"
105
106

    def get(self, request, *args, **kwargs):
107
        """Simple get view"""
108
        self.customer = Customer.objects.filter(user__id=self.kwargs['user_id']).first()
109
110
111
112
        if 'basket' not in request.session.keys(): # Init the basket session entry
            request.session['basket'] = {}
            request.session['basket_total'] = 0
        request.session['not_enough'] = False
113
        self.refill_form = None
114
        ret = super(CounterClick, self).get(request, *args, **kwargs)
Skia's avatar
Skia committed
115
116
117
118
        if ((self.object.type != "BAR" and not request.user.is_authenticated()) or
                (self.object.type == "BAR" and
                len(Counter.get_barmen_list(self.object.id)) < 1)): # Check that at least one barman is logged in
            ret = self.cancel(request) # Otherwise, go to main view
119
        return ret
Skia's avatar
Skia committed
120
121

    def post(self, request, *args, **kwargs):
122
        """ Handle the many possibilities of the post request """
123
124
        self.object = self.get_object()
        self.customer = Customer.objects.filter(user__id=self.kwargs['user_id']).first()
125
        self.refill_form = None
126
127
        if len(Counter.get_barmen_list(self.object.id)) < 1: # Check that at least one barman is logged in
            return self.cancel(request)
128
129
        if 'basket' not in request.session.keys():
            request.session['basket'] = {}
130
131
            request.session['basket_total'] = 0
        request.session['not_enough'] = False
132
133
134
135
136

        if 'add_product' in request.POST['action']:
            self.add_product(request)
        elif 'del_product' in request.POST['action']:
            self.del_product(request)
Skia's avatar
Skia committed
137
138
        elif 'refill' in request.POST['action']:
            self.refill(request)
Skia's avatar
Skia committed
139
140
        elif 'code' in request.POST['action']:
            return self.parse_code(request)
141
142
143
144
145
146
147
        elif 'cancel' in request.POST['action']:
            return self.cancel(request)
        elif 'finish' in request.POST['action']:
            return self.finish(request)
        context = self.get_context_data(object=self.object)
        return self.render_to_response(context)

Skia's avatar
Skia committed
148
    def is_barman_price(self):
149
        if self.customer.user.id in [s.id for s in Counter.get_barmen_list(self.object.id)]:
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
            return True
        else:
            return False

    def get_price(self, pid):
        p = Product.objects.filter(pk=pid).first()
        if self.is_barman_price():
            price = p.special_selling_price
        else:
            price = p.selling_price
        return price

    def sum_basket(self, request):
        total = 0
        for pid,infos in request.session['basket'].items():
            total += infos['price'] * infos['qty']
166
        return total / 100
167

Skia's avatar
Skia committed
168
    def add_product(self, request, q = 1, p=None):
169
        """ Add a product to the basket """
Skia's avatar
Skia committed
170
171
        pid = p or request.POST['product_id']
        pid = str(pid)
172
        price = self.get_price(pid)
173
        total = self.sum_basket(request)
Skia's avatar
Skia committed
174
        if self.customer.amount < (total + q*float(price)):
175
            request.session['not_enough'] = True
Skia's avatar
Skia committed
176
            return False
177
        if pid in request.session['basket']:
178
            request.session['basket'][pid]['qty'] += q
179
        else:
180
            request.session['basket'][pid] = {'qty': q, 'price': int(price*100)}
Skia's avatar
Skia committed
181
        request.session['not_enough'] = False # Reset not_enough to save the session
182
        request.session.modified = True
Skia's avatar
Skia committed
183
        return True
184
185
186

    def del_product(self, request):
        """ Delete a product from the basket """
187
188
        pid = str(request.POST['product_id'])
        if pid in request.session['basket']:
189
190
            request.session['basket'][pid]['qty'] -= 1
            if request.session['basket'][pid]['qty'] <= 0:
191
                del request.session['basket'][pid]
192
        else:
193
            request.session['basket'][pid] = 0
194
195
        request.session.modified = True

Skia's avatar
Skia committed
196
197
198
    def parse_code(self, request):
        """Parse the string entered by the barman"""
        string = str(request.POST['code']).upper()
Skia's avatar
Skia committed
199
        if string == _("END"):
Skia's avatar
Skia committed
200
            return self.finish(request)
Skia's avatar
Skia committed
201
        elif string == _("CAN"):
Skia's avatar
Skia committed
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
            return self.cancel(request)
        regex = re.compile(r"^((?P<nb>[0-9]+)X)?(?P<code>[A-Z0-9]+)$")
        m = regex.match(string)
        if m is not None:
            nb = m.group('nb')
            code = m.group('code')
            if nb is None:
                nb = 1
            else:
                nb = int(nb)
            p = Product.objects.filter(code=code).first()
            if p is not None:
                while nb > 0 and not self.add_product(request, nb, p.id):
                    nb -= 1
        context = self.get_context_data(object=self.object)
        return self.render_to_response(context)

219
220
    def finish(self, request):
        """ Finish the click session, and validate the basket """
221
        if self.is_barman_price():
222
223
224
225
            seller = self.customer.user
        else:
            seller = Counter.get_random_barman(self.object.id)
        request.session['last_basket'] = []
226
        for pid,infos in request.session['basket'].items():
227
            # This duplicates code for DB optimization (prevent to load many times the same object)
Skia's avatar
Skia committed
228
            p = Product.objects.filter(pk=pid).first()
229
            if self.is_barman_price():
230
231
232
                uprice = p.special_selling_price
            else:
                uprice = p.selling_price
233
            request.session['last_basket'].append("%d x %s" % (infos['qty'], p.name))
234
            s = Selling(product=p, counter=self.object, unit_price=uprice,
235
                   quantity=infos['qty'], seller=seller, customer=self.customer)
Skia's avatar
Skia committed
236
            s.save()
237
        request.session['last_customer'] = self.customer.user.get_display_name()
238
        request.session['last_total'] = "%0.2f" % self.sum_basket(request)
239
        request.session['new_customer_amount'] = str(self.customer.amount)
240
        del request.session['basket']
241
        request.session.modified = True
242
243
244
        kwargs = {
                'counter_id': self.object.id,
                }
245
246
247
248
249
        return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, kwargs=kwargs))

    def cancel(self, request):
        """ Cancel the click session """
        kwargs = {'counter_id': self.object.id}
250
        request.session.pop('basket', None)
251
        return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, kwargs=kwargs))
252

Skia's avatar
Skia committed
253
254
255
256
257
258
    def refill(self, request):
        """Refill the customer's account"""
        if self.is_barman_price():
            operator = self.customer.user
        else:
            operator = Counter.get_random_barman(self.object.id)
259
260
261
262
263
264
265
266
        form = RefillForm(request.POST)
        if form.is_valid():
            form.instance.counter = self.object
            form.instance.operator = operator
            form.instance.customer = self.customer
            form.instance.save()
        else:
            self.refill_form = form
Skia's avatar
Skia committed
267

268
    def get_context_data(self, **kwargs):
269
        """ Add customer to the context """
270
271
        kwargs = super(CounterClick, self).get_context_data(**kwargs)
        kwargs['customer'] = self.customer
272
        kwargs['basket_total'] = self.sum_basket(self.request)
273
        kwargs['refill_form'] = self.refill_form or RefillForm()
274
275
        return kwargs

Skia's avatar
Skia committed
276
class CounterLogin(RedirectView):
Skia's avatar
Skia committed
277
278
279
280
281
    """
    Handle the login of a barman

    Logged barmen are stored in the class-wide variable 'barmen_session', in the Counter model
    """
Skia's avatar
Skia committed
282
    permanent = False
Skia's avatar
Skia committed
283
    def post(self, request, *args, **kwargs):
Skia's avatar
Skia committed
284
285
286
        """
        Register the logged user as barman for this counter
        """
Skia's avatar
Skia committed
287
288
        self.counter_id = kwargs['counter_id']
        form = AuthenticationForm(request, data=request.POST)
Skia's avatar
Skia committed
289
        if form.is_valid():
Skia's avatar
Skia committed
290
            user = Subscriber.objects.filter(username=form.cleaned_data['username']).first()
Skia's avatar
Skia committed
291
292
            if user.is_subscribed():
                Counter.add_barman(self.counter_id, user.id)
Skia's avatar
Skia committed
293
294
295
296
297
298
299
300
301
302
        else:
            print("Error logging the barman") # TODO handle that nicely
        return super(CounterLogin, self).post(request, *args, **kwargs)

    def get_redirect_url(self, *args, **kwargs):
        return reverse_lazy('counter:details', args=args, kwargs=kwargs)

class CounterLogout(RedirectView):
    permanent = False
    def post(self, request, *args, **kwargs):
Skia's avatar
Skia committed
303
304
305
        """
        Unregister the user from the barman
        """
Skia's avatar
Skia committed
306
        self.counter_id = kwargs['counter_id']
Skia's avatar
Skia committed
307
        Counter.del_barman(self.counter_id, request.POST['user_id'])
Skia's avatar
Skia committed
308
309
310
311
        return super(CounterLogout, self).post(request, *args, **kwargs)

    def get_redirect_url(self, *args, **kwargs):
        return reverse_lazy('counter:details', args=args, kwargs=kwargs)
Skia's avatar
Skia committed
312

313
314
## Counter admin views

Skia's avatar
Skia committed
315
316
317
318
319
320
321
class CounterListView(CanViewMixin, ListView):
    """
    A list view for the admins
    """
    model = Counter
    template_name = 'counter/counter_list.jinja'

Skia's avatar
Skia committed
322
323
class CounterEditView(CanEditMixin, UpdateView):
    """
Skia's avatar
Skia committed
324
    Edit a counter's main informations (for the counter's admin)
Skia's avatar
Skia committed
325
326
327
328
329
330
331
332
333
    """
    model = Counter
    form_class = modelform_factory(Counter, fields=['name', 'club', 'type', 'products'],
            widgets={'products':CheckboxSelectMultiple})
    pk_url_kwarg = "counter_id"
    template_name = 'counter/counter_edit.jinja'

class CounterCreateView(CanEditMixin, CreateView):
    """
Skia's avatar
Skia committed
334
    Create a counter (for the admins)
Skia's avatar
Skia committed
335
336
337
338
339
340
341
342
    """
    model = Counter
    form_class = modelform_factory(Counter, fields=['name', 'club', 'type', 'products'],
            widgets={'products':CheckboxSelectMultiple})
    template_name = 'counter/counter_edit.jinja'

class CounterDeleteView(CanEditMixin, DeleteView):
    """
Skia's avatar
Skia committed
343
    Delete a counter (for the admins)
Skia's avatar
Skia committed
344
345
346
347
348
    """
    model = Counter
    pk_url_kwarg = "counter_id"
    template_name = 'core/delete_confirm.jinja'
    success_url = reverse_lazy('counter:admin_list')
Skia's avatar
Skia committed
349

350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# User accounting infos

class UserAccountView(DetailView):
    """
    Display a user's account
    """
    model = Customer
    pk_url_kwarg = "user_id"
    template_name = "counter/user_account.jinja"

    def dispatch(self, request, *arg, **kwargs):
        res = super(UserAccountView, self).dispatch(request, *arg, **kwargs)
        if (self.object.user == request.user
            or request.user.is_in_group(settings.SITH_GROUPS['accounting-admin']['name'])
            or request.user.is_in_group(settings.SITH_GROUPS['root']['name'])):
            return res
        raise PermissionDenied

    def get_context_data(self, **kwargs):
        kwargs = super(UserAccountView, self).get_context_data(**kwargs)
        kwargs['profile'] = self.object.user
        # TODO: add list of month where account has activity
        return kwargs

Skia's avatar
Skia committed
374