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