Browse Source

Add categories

pull/4/head
Yoann Piétri 7 years ago
parent
commit
f1241be0c8
  1. 11
      gestion/admin.py
  2. 18
      gestion/forms.py
  3. 46
      gestion/migrations/0007_auto_20190503_1841.py
  4. 28
      gestion/migrations/0008_auto_20190503_1908.py
  5. 39
      gestion/models.py
  6. 37
      gestion/templates/gestion/categories_list.html
  7. 29
      gestion/templates/gestion/category_profile.html
  8. 48
      gestion/templates/gestion/manage.html
  9. 2
      gestion/templates/gestion/product_profile.html
  10. 20
      gestion/templates/gestion/products_index.html
  11. 7
      gestion/urls.py
  12. 94
      gestion/views.py

11
gestion/admin.py

@ -1,7 +1,7 @@
from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from .models import Reload, Refund, Product, Keg, ConsumptionHistory, KegHistory, Consumption, Menu, MenuHistory
from .models import Reload, Refund, Product, Keg, ConsumptionHistory, KegHistory, Consumption, Menu, MenuHistory, Category
class ConsumptionAdmin(SimpleHistoryAdmin):
"""
@ -81,6 +81,12 @@ class RefundAdmin(SimpleHistoryAdmin):
ordering = ('-date', 'amount', 'customer')
search_fields = ('customer',)
class CategoryAdmin(SimpleHistoryAdmin):
"""
The admin class for Category
"""
ordering = ("order",)
admin.site.register(Reload, ReloadAdmin)
admin.site.register(Refund, RefundAdmin)
admin.site.register(Product, ProductAdmin)
@ -89,4 +95,5 @@ admin.site.register(ConsumptionHistory, ConsumptionHistoryAdmin)
admin.site.register(KegHistory, KegHistoryAdmin)
admin.site.register(Consumption, ConsumptionAdmin)
admin.site.register(Menu, MenuAdmin)
admin.site.register(MenuHistory, MenuHistoryAdmin)
admin.site.register(MenuHistory, MenuHistoryAdmin)
admin.site.register(Category, CategoryAdmin)

18
gestion/forms.py

@ -4,7 +4,7 @@ from django.contrib.auth.models import User
from dal import autocomplete
from .models import Reload, Refund, Product, Keg, Menu
from .models import Reload, Refund, Product, Keg, Menu, Category
from preferences.models import PaymentMethod
class ReloadForm(forms.ModelForm):
@ -108,4 +108,18 @@ class GenerateReleveForm(forms.Form):
A form to generate a releve.
"""
begin = forms.DateTimeField(label="Date de début")
end = forms.DateTimeField(label="Date de fin")
end = forms.DateTimeField(label="Date de fin")
class CategoryForm(forms.ModelForm):
"""
A form to create and edit a :class:`~gestion.models.Category`.
"""
class Meta:
model = Category
fields = "__all__"
class SearchCategoryForm(forms.Form):
"""
A form to search a :class:`~gestion.models.Category`.
"""
category = forms.ModelChoiceField(queryset=Category.objects.all(), required=True, label="Catégorie", widget=autocomplete.ModelSelect2(url='gestion:categories-autocomplete', attrs={'data-minimum-input-length':2}))

46
gestion/migrations/0007_auto_20190503_1841.py

@ -0,0 +1,46 @@
# Generated by Django 2.1 on 2019-05-03 16:41
from django.db import migrations, models
import django.db.models.deletion
def create_default_category(apps, schema_editor):
Category = apps.get_model('gestion', 'Category')
new_category = Category(name="Autre")
new_category.save()
class Migration(migrations.Migration):
dependencies = [
('gestion', '0006_auto_20190227_0859'),
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True, verbose_name='Nom')),
],
),
migrations.RemoveField(
model_name='historicalproduct',
name='category',
),
migrations.RemoveField(
model_name='product',
name='category',
),
migrations.RunPython(create_default_category),
migrations.AddField(
model_name='historicalproduct',
name='category',
field=models.ForeignKey(default=1, blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='gestion.Category'),
preserve_default=False,
),
migrations.AddField(
model_name='product',
name='category',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, to='gestion.Category'),
preserve_default=False,
),
]

28
gestion/migrations/0008_auto_20190503_1908.py

@ -0,0 +1,28 @@
# Generated by Django 2.1 on 2019-05-03 17:08
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('gestion', '0007_auto_20190503_1841'),
]
operations = [
migrations.AlterModelOptions(
name='category',
options={'verbose_name': 'Catégorie'},
),
migrations.AddField(
model_name='category',
name='order',
field=models.IntegerField(default=0),
),
migrations.AlterField(
model_name='product',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='gestion.Category', verbose_name='Catégorie'),
),
]

39
gestion/models.py

@ -5,27 +5,32 @@ from django.core.validators import MinValueValidator
from preferences.models import PaymentMethod
from django.core.exceptions import ValidationError
class Category(models.Model):
"""
A product category
"""
class Meta:
verbose_name="Catégorie"
name = models.CharField(max_length=100, verbose_name="Nom", unique=True)
order = models.IntegerField(default=0)
"""
The name of the category
"""
def __str__(self):
return self.name
@property
def active_products(self):
"""
Return active producs of this category
"""
return self.product_set.filter(is_active=True)
class Product(models.Model):
"""
Stores a product.
"""
P_PRESSION = 'PP'
D_PRESSION = 'DP'
G_PRESSION = 'GP'
BOTTLE = 'BT'
SOFT = 'SO'
FOOD = 'FO'
PANINI = 'PA'
TYPEINPUT_CHOICES_CATEGORIE = (
(P_PRESSION, "Pinte Pression"),
(D_PRESSION, "Demi Pression"),
(G_PRESSION, "Galopin pression"),
(BOTTLE, "Bouteille"),
(SOFT, "Soft"),
(FOOD, "En-cas"),
(PANINI, "Ingredients panini"),
)
class Meta:
verbose_name = "Produit"
name = models.CharField(max_length=40, verbose_name="Nom", unique=True)
@ -48,7 +53,7 @@ class Product(models.Model):
"""
The barcode of the product.
"""
category = models.CharField(max_length=2, choices=TYPEINPUT_CHOICES_CATEGORIE, default=FOOD, verbose_name="Catégorie")
category = models.ForeignKey('Category', on_delete=models.PROTECT, verbose_name="Catégorie")
"""
The category of the product
"""

37
gestion/templates/gestion/categories_list.html

@ -0,0 +1,37 @@
{% extends 'base.html' %}
{% block entete %}Gestion des produits{% endblock %}
{% block navbar%}
<ul>
<li><a href="#first">Liste des catégories</a></li>
</ul>
{% endblock %}
{% block content %}
<section id="first" class="main">
<header class="major">
<h2>Liste des catégories</h2>
</header>
{% if perms.gestion.add_category %}
<a class="button" href="{% url 'gestion:addCategory' %}"><i class="fa fa-boxes"></i> Créer une catégorie</a><br><br>
{% endif %}
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Nom</th>
<th>Ordre</th>
<th>Administrer</th>
</tr>
</thead>
<tbody>
{% for category in categories %}
<tr>
<td><a href="{% url 'gestion:categoryProfile' category.pk %}">{{ category }}</a></td>
<td>{% if category.order == 0 %}0 (non affichéé){% else %}{{category.order}}{% endif %}</td>
<td><a href="{% url 'gestion:categoryProfile' category.pk %}" class="button small"><i class="fa fa-eye"></i> Profil</a> {% if perms.gestion.change_category %}<a href="{% url 'gestion:editCategory' category.pk %}" class="button small"><i class="fa fa-pencil-alt"></i> Modifier</a>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

29
gestion/templates/gestion/category_profile.html

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block entete %}Gestion des produits : Profil de {{category}}{% endblock %}
{% block navbar %}
<ul>
<li><a href="#first">Général</a></li>
<li><a href="#second">Liste des produits</a></li>
</ul>
{% endblock %}
{% block content %}
<section id="first" class="main">
<header class="major">
<h2>Général</h2>
</header>
<a class="button small" href="{% url 'gestion:categoriesList' %}">Liste des catégories</a><br><br>
{% if perms.gestion.change_category %}<a href="{% url 'gestion:editCategory' category.pk %}" class="button small">Modifier</a><br>{% endif %}<br>
<strong>Nom</strong> : {{ category.name }}<br>
<strong>Ordre</strong> : {{ category.order }}<br>
</section>
<section id="first" class="main">
<header class="major">
<h2>Liste des produits ({{category.product_set.all.count}} au total dont {{category.active_products.count}} actifs)</h2>
</header>
<ul>
{% for product in category.product_set.all %}
<li><a href="{% url 'gestion:productProfile' product.pk %}">{{product}}</a></li>
{% endfor %}
</ul>
</section>
{% endblock %}

48
gestion/templates/gestion/manage.html

@ -125,7 +125,7 @@
</tr>
{% endif %}
{% endfor %}
{% if not bieresPression|divisibleby:4 %}
{% if not cotisations|divisibleby:4 %}
</tr>
{% endif %}
<tr style="text-align:center; font-weight:bold;"><td colspan="4">Bières pression</td></tr>
@ -141,8 +141,9 @@
{% if not bieresPression|divisibleby:4 %}
</tr>
{% endif %}
<tr style="text-align:center; font-weight:bold;"><td colspan="4">Bières bouteilles</td></tr>
{% for product in bieresBouteille %}
{% for category in categories %}
<tr style="text-align:center; font-weight:bold;"><td colspan="4">{{category}}</td></tr>
{% for product in category.active_products %}
{% if forloop.counter0|divisibleby:4 %}
<tr style="text-align:center">
{% endif %}
@ -151,49 +152,10 @@
</tr>
{% endif %}
{% endfor %}
{% if not bieresBouteille|divisibleby:4 %}
</tr>
{% endif %}
<tr style="text-align:center; font-weight:bold;"><td colspan="4">Paninis</td></tr>
{% for product in panini %}
{% if forloop.counter0|divisibleby:4 %}
<tr style="text-align:center">
{% endif %}
<td><button class="product {% if product.adherentRequired %}special{% endif%}" target="{{product.pk}}">{{product.name}}</button></td>
{% if forloop.counter|divisibleby:4 %}
{% if not category.active_products|divisibleby:4 %}
</tr>
{% endif %}
{% endfor %}
{% if not panini|divisibleby:4 %}
</tr>
{% endif %}
<tr style="text-align:center; font-weight:bold;"><td colspan="4">Boissons sans alcool</td></tr>
{% for product in soft %}
{% if forloop.counter0|divisibleby:4 %}
<tr style="text-align:center">
{% endif %}
<td><button class="product {% if product.adherentRequired %}special{% endif%}" target="{{product.pk}}">{{product.name}}</button></td>
{% if forloop.counter|divisibleby:4 %}
</tr>
{% endif %}
{% endfor %}
{% if not soft|divisibleby:4 %}
</tr>
{% endif %}
<tr style="text-align:center; font-weight:bold;"><td colspan="4">En-cas</td></tr>
{% for product in food %}
{% if forloop.counter0|divisibleby:4 %}
<tr style="text-align:center">
{% endif %}
<td><button class="product {% if product.adherentRequired %}special{% endif%}" target="{{product.pk}}">{{product.name}}</button></td>
{% if forloop.counter|divisibleby:4 %}
</tr>
{% endif %}
{% endfor %}
{% if not autreBouffe|divisibleby:4 %}
</tr>
{% endif %}
{% if menus %}
<tr style="text-align:center; font-weight:bold;"><td colspan="4">Menus</td></tr>
{% for product in menus %}

2
gestion/templates/gestion/product_profile.html

@ -17,7 +17,7 @@
<strong>Stock en soute</strong> : {{ product.stockHold }}<br>
<strong>Stock au bar</strong> : {{ product.stockBar }}<br>
<strong>Code Barre</strong> : {{ product.barcode }}<br>
<strong>Catégorie</strong> : {{ product.category }}<br>
<strong>Catégorie</strong> : <a href="{% url 'gestion:categoryProfile' product.category.pk %}">{{ product.category }}</a><br>
<strong>Actif</strong> : {{ product.is_active | yesno:"Oui, Non"}}<br>
<strong>Dégré</strong> : {{ product.deg }}<br>
<strong>Volume</strong> : {{ product.volume }}cl<br>

20
gestion/templates/gestion/products_index.html

@ -2,6 +2,9 @@
{% block entete %}Gestion des produits{% endblock %}
{% block navbar%}
<ul>
{% if perms.gestion.add_category or perms.gestion.view_category %}
<li><a href="#fifth">Catégories</a></li>
{% endif %}
{% if perms.gestion.add_product or perms.gestion.view_product %}
<li><a href="#first">Produits</a></li>
{% endif %}
@ -17,6 +20,23 @@
</ul>
{% endblock %}
{% block content %}
{% if perms.gestion.add_category or perms.gestion.view_category %}
<section id="fifth" class="main">
<header class="major">
<h2>Catégories</h2>
</header>
Actions possibles :
<ul>
{% if perms.gestion.add_category %}
<li><a href="{% url 'gestion:addCategory' %}">Créer une catégorie</a></li>
{% endif %}
{% if perms.gestion.view_category %}
<li><a href="{% url 'gestion:searchCategory' %}">Rechercher une catégorie</a></li>
<li><a href="{% url 'gestion:categoriesList' %}">Lister toutes les catégories</a></li>
{% endif %}
</ul>
</section>
{% endif %}
{% if perms.gestion.add_product or perms.gestion.view_product %}
<section id="first" class="main">
<header class="major">

7
gestion/urls.py

@ -45,4 +45,11 @@ urlpatterns = [
path('menus-autcomplete', views.MenusAutocomplete.as_view(), name="menus-autocomplete"),
path('cancelReload/<int:pk>', views.cancel_reload, name="cancelReload"),
path('gen_releve', views.gen_releve, name="gen_releve"),
path('categoriesList', views.categoriesList, name="catrgorisList"),
path('addCategory', views.addCategory, name="addCategory"),
path('editCategory/<int:pk>', views.editCategory, name="editCategory"),
path('searchCategory', views.searchCategory, name="searchCategory"),
path('categoryProfile/<int:pk>', views.categoryProfile, name="categoryProfile"),
path('categoriesList', views.categoriesList, name="categoriesList"),
path('categories-autocomplete', views.CategoriesAutocomplete.as_view(), name="categories-autocomplete"),
]

94
gestion/views.py

@ -18,8 +18,8 @@ import simplejson as json
from dal import autocomplete
from decimal import *
from .forms import ReloadForm, RefundForm, ProductForm, KegForm, MenuForm, GestionForm, SearchMenuForm, SearchProductForm, SelectPositiveKegForm, SelectActiveKegForm, PinteForm, GenerateReleveForm
from .models import Product, Menu, Keg, ConsumptionHistory, KegHistory, Consumption, MenuHistory, Pinte, Reload, Refund
from .forms import ReloadForm, RefundForm, ProductForm, KegForm, MenuForm, GestionForm, SearchMenuForm, SearchProductForm, SelectPositiveKegForm, SelectActiveKegForm, PinteForm, GenerateReleveForm, CategoryForm, SearchCategoryForm
from .models import Product, Menu, Keg, ConsumptionHistory, KegHistory, Consumption, MenuHistory, Pinte, Reload, Refund, Category
from preferences.models import PaymentMethod, GeneralPreferences, Cotisation
from users.models import CotisationHistory
@ -30,15 +30,12 @@ def manage(request):
"""
Displays the manage view.
"""
categories = Category.objects.exclude(order=0).order_by('order')
pay_buttons = PaymentMethod.objects.filter(is_active=True)
gestion_form = GestionForm(request.POST or None)
reload_form = ReloadForm(request.POST or None)
refund_form = RefundForm(request.POST or None)
bieresPression = []
bieresBouteille = Product.objects.filter(category=Product.BOTTLE).filter(is_active=True)
panini = Product.objects.filter(category=Product.PANINI).filter(is_active=True)
food = Product.objects.filter(category=Product.FOOD).filter(is_active=True)
soft = Product.objects.filter(category=Product.SOFT).filter(is_active=True)
menus = Menu.objects.filter(is_active=True)
kegs = Keg.objects.filter(is_active=True)
gp, _ = GeneralPreferences.objects.get_or_create(pk=1)
@ -56,11 +53,7 @@ def manage(request):
"reload_form": reload_form,
"refund_form": refund_form,
"bieresPression": bieresPression,
"bieresBouteille": bieresBouteille,
"panini": panini,
"food": food,
"soft": soft,
"menus": menus,
"categories": categories,
"pay_buttons": pay_buttons,
"floating_buttons": floating_buttons,
"cotisations": cotisations
@ -884,3 +877,82 @@ def gen_releve(request):
return render_to_pdf(request, 'gestion/releve.tex', {"consumptions": consumptions, "reloads": reloads, "refunds": refunds, "cotisations": cotisations, "begin": begin, "end": end, "now": now, "value_especes": value_especes, "value_lydia": value_lydia, "value_cheque": value_cheque}, filename="releve.pdf")
else:
return render(request, "form.html", {"form": form, "form_title": "Génération d'un relevé", "form_button": "Générer", "form_button_icon": "file-pdf"})
########## categories ##########
@active_required
@login_required
@permission_required('gestion.add_category')
def addCategory(request):
"""
Displays a :class:`gestion.forms.CategoryForm` to add a category.
"""
form = CategoryForm(request.POST or None)
if(form.is_valid()):
category = form.save()
messages.success(request, "La catégorie a bien été ajoutée")
return redirect(reverse('gestion:categoryProfile', kwargs={'pk':category.pk}))
return render(request, "form.html", {"form": form, "form_title": "Ajout d'une catégorie", "form_button": "Ajouter", "form_button_icon": "plus-square"})
@active_required
@login_required
@permission_required('gestion.change_category')
def editCategory(request, pk):
"""
Displays a :class:`gestion.forms.CategoryForm` to edit a category.
pk
The primary key of the the :class:`gestion.models.Category` to edit.
"""
category = get_object_or_404(Category, pk=pk)
form = CategoryForm(request.POST or None, instance=category)
if(form.is_valid()):
form.save()
messages.success(request, "La catégorie a bien été modifiée")
return redirect(reverse('gestion:categoryProfile', kwargs={'pk': category.pk}))
return render(request, "form.html", {"form": form, "form_title": "Modification d'une catégorie", "form_button": "Modifier", "form_button_icon": "pencil-alt"})
@active_required
@login_required
@permission_required('gestion.view_category')
def categoriesList(request):
"""
Display the list of :class:`categories <gestion.models.Category>`.
"""
categories = Category.objects.all().order_by('order')
return render(request, "gestion/categories_list.html", {"categories": categories})
@active_required
@login_required
@permission_required('gestion.view_category')
def searchCategory(request):
"""
Displays a :class:`gestion.forms.SearchCategory` to search a :class:`gestion.models.Category`.
"""
form = SearchCategoryForm(request.POST or None)
if(form.is_valid()):
return redirect(reverse('gestion:categoryProfile', kwargs={'pk': form.cleaned_data['category'].pk }))
return render(request, "form.html", {"form": form, "form_title":"Rechercher une catégorie", "form_button": "Rechercher", "form_button_icon": "search"})
@active_required
@login_required
@permission_required('gestion.view_category')
def categoryProfile(request, pk):
"""
Displays the profile of a :class:`gestion.models.Category`.
pk
The primary key of the :class:`gestion.models.Category` to display profile.
"""
category = get_object_or_404(Category, pk=pk)
return render(request, "gestion/category_profile.html", {"category": category})
class CategoriesAutocomplete(autocomplete.Select2QuerySetView):
"""
Autocomplete view for active :class:`categories <gestion.models.Category>`.
"""
def get_queryset(self):
qs = Category.objects.all()
if self.q:
qs = qs.filter(name__icontains=self.q)
return qs
Loading…
Cancel
Save