From 21c05cc779f2183775290ee5e9ab16ff291b3e38 Mon Sep 17 00:00:00 2001 From: guillaume-renaud Date: Wed, 28 Dec 2016 19:25:43 +0100 Subject: [PATCH] Addition of the ShoppingList view to know the item to buy --- counter/templates/counter/counter_list.jinja | 4 +- counter/views.py | 5 + stock/migrations/0014_auto_20161228_1717.py | 19 +++ stock/models.py | 17 ++- .../templates/stock/shopping_list_items.jinja | 34 +++++ stock/templates/stock/stock_item_list.jinja | 2 +- stock/templates/stock/stock_list.jinja | 1 + stock/templates/stock/stock_main.jinja | 11 -- .../templates/stock/stock_shopping_list.jinja | 56 +++++++- stock/urls.py | 13 +- stock/views.py | 136 +++++++++++++++++- 11 files changed, 280 insertions(+), 18 deletions(-) create mode 100644 stock/migrations/0014_auto_20161228_1717.py create mode 100644 stock/templates/stock/shopping_list_items.jinja delete mode 100644 stock/templates/stock/stock_main.jinja diff --git a/counter/templates/counter/counter_list.jinja b/counter/templates/counter/counter_list.jinja index b3d30ded..bc6144b2 100644 --- a/counter/templates/counter/counter_list.jinja +++ b/counter/templates/counter/counter_list.jinja @@ -32,12 +32,12 @@ {% trans %}Edit{% endtrans %} - {% trans %}Stats{% endtrans %} - {% endif %} - {%if c.stock %} + {% if c.stock %} Stock - + {% trans %}Shopping lists{% endtrans %} - {% else %} {% trans %}Create new stock{% endtrans%} - {% endif %} - {% endif %} {% if user.is_owner(c) %} {% trans %}Props{% endtrans %} {% endif %} diff --git a/counter/views.py b/counter/views.py index 00edd36d..4678fd44 100644 --- a/counter/views.py +++ b/counter/views.py @@ -88,6 +88,11 @@ class CounterTabsMixin(TabedViewMixin): 'slug': 'last_ops', 'name': _("Last operations"), }) + tab_list.append({ + 'url': reverse_lazy('stock:items_list', kwargs={'stock_id': self.object.stock.id}), + 'slug': 'stock_items_list', + 'name': _("Stock items list"), + }) return tab_list class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, FormMixin): diff --git a/stock/migrations/0014_auto_20161228_1717.py b/stock/migrations/0014_auto_20161228_1717.py new file mode 100644 index 00000000..f6b7bfb4 --- /dev/null +++ b/stock/migrations/0014_auto_20161228_1717.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0013_auto_20161228_1006'), + ] + + operations = [ + migrations.AlterField( + model_name='shoppinglist', + name='stock_owner', + field=models.ForeignKey(related_name='shopping_lists', null=True, to='stock.Stock'), + ), + ] diff --git a/stock/models.py b/stock/models.py index e016493a..46f30b34 100644 --- a/stock/models.py +++ b/stock/models.py @@ -28,9 +28,24 @@ class StockItem(models.Model): on_delete=models.SET_NULL) stock_owner = models.ForeignKey(Stock, related_name="items") + def __str__(self): + return "%s" % (self.name) + + def get_absolute_url(self): + return reverse('stock:items_list', kwargs={'stock_id':self.stock_owner.id}) + +class ShoppingList(models.Model): + """ + The ShoppingList class, used to make an history of the shopping lists + """ + date = models.DateTimeField(_('date')) + name = models.CharField(_('name'), max_length=64) + todo = models.BooleanField(_('todo')) + items_to_buy = models.ManyToManyField(StockItem, verbose_name=_('items to buy'), related_name="shopping_lists") + stock_owner = models.ForeignKey(Stock, null=True, related_name="shopping_lists") def __str__(self): return "%s (%s)" % (self.name, self.effective_quantity) def get_absolute_url(self): - return reverse('stock:items_list', kwargs={'stock_id':self.stock_owner.id}) \ No newline at end of file + return reverse('stock:shoppinglist_list') diff --git a/stock/templates/stock/shopping_list_items.jinja b/stock/templates/stock/shopping_list_items.jinja new file mode 100644 index 00000000..822db850 --- /dev/null +++ b/stock/templates/stock/shopping_list_items.jinja @@ -0,0 +1,34 @@ +{% extends "core/base.jinja" %} + +{% block title %} +{% trans %}{{ shoppinglist }}'s items{% endtrans %} +{% endblock %} + +{% block content %} +{% if current_tab == "stocks" %} + {% trans %}Back{% endtrans %} +{% endif %} + +

{{ shoppinglist.name }}

+{% for t in ProductType.objects.order_by('name') %} + {% if shoppinglist.items_to_buy.filter(type=t) %} +

{{ t }}

+ + + + + + + + + {% for i in shoppinglist.items_to_buy.filter(type=t).order_by('name') %} + + + + + {% endfor %} + +
{% trans %}Name{% endtrans %}{% trans %}Number{% endtrans %}
{{ i.name }}{{ i.tobuy_quantity }}
+ {% endif %} +{% endfor %} +{% endblock %} \ No newline at end of file diff --git a/stock/templates/stock/stock_item_list.jinja b/stock/templates/stock/stock_item_list.jinja index ef1a3a4d..a5370389 100644 --- a/stock/templates/stock/stock_item_list.jinja +++ b/stock/templates/stock/stock_item_list.jinja @@ -7,7 +7,7 @@ {% block content %} {% if current_tab == "stocks" %}

{% trans %}New item{% endtrans %}

-
{% trans %}Shopping list{% endtrans %}
+
{% trans %}Shopping list{% endtrans %}
{% endif %} {% if stock %}

{{ stock }}

diff --git a/stock/templates/stock/stock_list.jinja b/stock/templates/stock/stock_list.jinja index 829ad6c5..6b425128 100644 --- a/stock/templates/stock/stock_list.jinja +++ b/stock/templates/stock/stock_list.jinja @@ -13,6 +13,7 @@ {% if user.can_edit(s) %} {{s}} - Edit + - {% trans %}Shopping lists{% endtrans %} {% endif %} {% endfor %} diff --git a/stock/templates/stock/stock_main.jinja b/stock/templates/stock/stock_main.jinja deleted file mode 100644 index 9c585863..00000000 --- a/stock/templates/stock/stock_main.jinja +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "core/base.jinja" %} - -{% block title %} -{{stock}} -{% endblock %} - -{% block content %} -

{{stock}}

-{% trans %}New Item{% endtrans %} -{% endblock %} - diff --git a/stock/templates/stock/stock_shopping_list.jinja b/stock/templates/stock/stock_shopping_list.jinja index 96b48085..febed4bc 100644 --- a/stock/templates/stock/stock_shopping_list.jinja +++ b/stock/templates/stock/stock_shopping_list.jinja @@ -5,6 +5,60 @@ Shopping list for {{ stock }} {% endblock %} {% block content %} -

Shopping list for {{ stock }}

+{% if current_tab == "stocks" %} + {% trans %}Do shopping{% endtrans %} +{% endif %} +

{% trans s=stock %}Shopping lists history for {{ s }}{% endtrans %}

+

To do

+ + + + + + + + + + {% for s in shoppinglist_list.filter(todo=True).filter(stock_owner=stock).order_by('-date') %} + + + + + + + + {% endfor %} + +
{% trans %}Date{% endtrans %}{% trans %}Name{% endtrans %}{% trans %}Number of items{% endtrans %}
{{ s.date|localtime|date("Y-m-d H:i") }}{{ s.name }}{{ s.items_to_buy.count() }} + {% trans %}Mark as done{% endtrans %} + + {% trans %}Delete{% endtrans %} +
+ +

Done

+ + + + + + + + + + {% for s in shoppinglist_list.filter(todo=False).filter(stock_owner=stock).order_by('-date') %} + + + + + + + + {% endfor %} + +
{% trans %}Date{% endtrans %}{% trans %}Name{% endtrans %}{% trans %}Number of items{% endtrans %}
{{ s.date|localtime|date("Y-m-d H:i") }}{{ s.name }}{{ s.items_to_buy.count() }} + {% trans %}Mark as todo{% endtrans %} + + {% trans %}Delete{% endtrans %} +
{% endblock %} \ No newline at end of file diff --git a/stock/urls.py b/stock/urls.py index d7512b24..987c7a3b 100644 --- a/stock/urls.py +++ b/stock/urls.py @@ -12,5 +12,16 @@ urlpatterns = [ url(r'^(?P[0-9]+)$', StockItemList.as_view(), name='items_list'), url(r'^(?P[0-9]+)/stockItem/newItem$', StockItemCreateView.as_view(), name='new_item'), url(r'^stockItem/(?P[0-9]+)/edit$', StockItemEditView.as_view(), name='edit_item'), - url(r'^(?P[0-9]+)/shopping$', StockShoppingListView.as_view(), name='shopping_list'), + +# ShoppingList urls + url(r'^(?P[0-9]+)/shoppingList/list$', StockShoppingListView.as_view(), name='shoppinglist_list'), + url(r'^(?P[0-9]+)/shoppingList/create$', StockItemQuantityBaseFormView.as_view(), name='shoppinglist_create'), + url(r'^(?P[0-9]+)/shoppingList/(?P[0-9]+)/items$', StockShoppingListItemListView.as_view(), + name='shoppinglist_items'), + url(r'^(?P[0-9]+)/shoppingList/(?P[0-9]+)/delete$', StockShoppingListDeleteView.as_view(), + name='shoppinglist_delete'), + url(r'^(?P[0-9]+)/shoppingList/(?P[0-9]+)/setDone$', StockShopppingListSetDone.as_view(), + name='shoppinglist_set_done'), + url(r'^(?P[0-9]+)/shoppingList/(?P[0-9]+)/setTodo$', StockShopppingListSetTodo.as_view(), + name='shoppinglist_set_todo'), ] diff --git a/stock/views.py b/stock/views.py index 614d9752..aff866cc 100644 --- a/stock/views.py +++ b/stock/views.py @@ -3,6 +3,7 @@ from django.views.generic import ListView, DetailView, RedirectView, TemplateVie from django.views.generic.edit import UpdateView, CreateView, DeleteView, ProcessFormView, FormMixin from django.utils.translation import ugettext_lazy as _ from django import forms +from django.http import HttpResponseRedirect, HttpResponse from django.forms.models import modelform_factory from django.core.urlresolvers import reverse_lazy, reverse @@ -159,7 +160,140 @@ class StockShoppingListView(CounterAdminTabsMixin, CanViewMixin, ListView): ret['stock'] = Stock.objects.filter(id=self.kwargs['stock_id']).first(); return ret -class StockItemOutEditView(CounterAdminTabsMixin, CanViewMixin, UpdateView): + +class StockItemQuantityForm(forms.BaseForm): + def clean(self): + with transaction.atomic(): + self.stock = Stock.objects.filter(id=self.stock_id).first() + shopping_list = ShoppingList(name="Courses "+self.stock.counter.name, date=timezone.now(), todo=True) + shopping_list.save() + shopping_list.stock_owner = self.stock + shopping_list.save() + for k,t in self.cleaned_data.items(): + if t is not None: + item_id = int(k[5:]) + item = StockItem.objects.filter(id=item_id).first() + item.tobuy_quantity = t + item.shopping_lists.add(shopping_list) + item.save() + + return self.cleaned_data + +class StockItemQuantityBaseFormView(CounterAdminTabsMixin, CanEditMixin, DetailView, BaseFormView): """ docstring for StockItemOutList """ + model = StockItem + template_name = "stock/shopping_list_quantity.jinja" + pk_url_kwarg = "stock_id" + current_tab = "stocks" + + def get_form_class(self): + fields = OrderedDict() + kwargs = {} + for t in ProductType.objects.order_by('name').all(): + for i in self.stock.items.filter(type=t).order_by('name').all(): + if i.effective_quantity <= i.minimal_quantity: + field_name = "item-%s" % (str(i.id)) + fields[field_name] = forms.CharField(max_length=30, required=True, label=str(i), + help_text=str(i.effective_quantity)+" left") + kwargs[field_name] = i.effective_quantity + kwargs['stock_id'] = self.stock.id + kwargs['base_fields'] = fields + return type('StockItemQuantityForm', (StockItemQuantityForm,), kwargs) + + def get(self, request, *args, **kwargs): + """ + Simple get view + """ + self.stock = Stock.objects.filter(id=self.kwargs['stock_id']).first() + return super(StockItemQuantityBaseFormView, self).get(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + """ + Handle the many possibilities of the post request + """ + self.object = self.get_object() + self.stock = Stock.objects.filter(id=self.kwargs['stock_id']).first() + return super(StockItemQuantityBaseFormView, self).post(request, *args, **kwargs) + + def form_valid(self, form): + """ + We handle here the redirection, passing the user id of the asked customer + """ + return super(StockItemQuantityBaseFormView, self).form_valid(form) + + def get_context_data(self, **kwargs): + """ + We handle here the login form for the barman + """ + kwargs = super(StockItemQuantityBaseFormView, self).get_context_data(**kwargs) + if 'form' not in kwargs.keys(): + kwargs['form'] = self.get_form() + kwargs['stock'] = self.stock + return kwargs + + def get_success_url(self): + return reverse_lazy('stock:shoppinglist_list', args=self.args, kwargs=self.kwargs) + + +class StockShoppingListItemListView(CounterAdminTabsMixin, CanViewMixin, ListView): + """docstring for StockShoppingListItemListView""" + model = ShoppingList + template_name = "stock/shopping_list_items.jinja" + pk_url_kwarg = "shoppinglist_id" + current_tab = "stocks" + + def get_context_data(self): + ret = super(StockShoppingListItemListView, self).get_context_data() + if 'shoppinglist_id' in self.kwargs.keys(): + ret['shoppinglist'] = ShoppingList.objects.filter(id=self.kwargs['shoppinglist_id']).first(); + return ret + +class StockShoppingListDeleteView(CounterAdminTabsMixin, CanEditMixin, DeleteView): + """ + Delete a ShoppingList (for the resonsible account) + """ + model = ShoppingList + pk_url_kwarg = "shoppinglist_id" + template_name = 'core/delete_confirm.jinja' + current_tab = "stocks" + + def get_success_url(self): + return reverse_lazy('stock:shoppinglist_list', kwargs={'stock_id':self.object.stock_owner.id}) + + +class StockShopppingListSetDone(CanEditMixin, DetailView): + """ + Set a ShoppingList as done + """ + model = ShoppingList + pk_url_kwarg = "shoppinglist_id" + + def get(self, request, *args, **kwargs): + self.object = self.get_object() + self.object.todo = False + self.object.save() + return HttpResponseRedirect(reverse('stock:shoppinglist_list', args=self.args, kwargs={'stock_id':self.object.stock_owner.id})) + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + return HttpResponseRedirect(reverse('stock:shoppinglist_list', args=self.args, kwargs={'stock_id':self.object.stock_owner.id})) + + +class StockShopppingListSetTodo(CanEditMixin, DetailView): + """ + Set a ShoppingList as done + """ + model = ShoppingList + pk_url_kwarg = "shoppinglist_id" + + def get(self, request, *args, **kwargs): + self.object = self.get_object() + self.object.todo = True + self.object.save() + return HttpResponseRedirect(reverse('stock:shoppinglist_list', args=self.args, kwargs={'stock_id':self.object.stock_owner.id})) + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + return HttpResponseRedirect(reverse('stock:shoppinglist_list', args=self.args, kwargs={'stock_id':self.object.stock_owner.id}))