diff --git a/gestion/admin.py b/gestion/admin.py
index f2deb46..aa5508c 100644
--- a/gestion/admin.py
+++ b/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)
\ No newline at end of file
+admin.site.register(MenuHistory, MenuHistoryAdmin)
+admin.site.register(Category, CategoryAdmin)
\ No newline at end of file
diff --git a/gestion/forms.py b/gestion/forms.py
index 9e734a0..535a238 100644
--- a/gestion/forms.py
+++ b/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")
\ No newline at end of file
+ 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}))
\ No newline at end of file
diff --git a/gestion/migrations/0007_auto_20190503_1841.py b/gestion/migrations/0007_auto_20190503_1841.py
new file mode 100644
index 0000000..fb1b9bb
--- /dev/null
+++ b/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,
+ ),
+ ]
diff --git a/gestion/migrations/0008_auto_20190503_1908.py b/gestion/migrations/0008_auto_20190503_1908.py
new file mode 100644
index 0000000..193a342
--- /dev/null
+++ b/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'),
+ ),
+ ]
diff --git a/gestion/models.py b/gestion/models.py
index 116a85e..d00a2f1 100644
--- a/gestion/models.py
+++ b/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
"""
diff --git a/gestion/templates/gestion/categories_list.html b/gestion/templates/gestion/categories_list.html
new file mode 100644
index 0000000..10bd2bd
--- /dev/null
+++ b/gestion/templates/gestion/categories_list.html
@@ -0,0 +1,37 @@
+{% extends 'base.html' %}
+{% block entete %}Gestion des produits{% endblock %}
+{% block navbar%}
+
+{% endblock %}
+{% block content %}
+
+
+ {% if perms.gestion.add_category %}
+ Créer une catégorie
+ {% endif %}
+
+
+
+
+ Nom |
+ Ordre |
+ Administrer |
+
+
+
+ {% for category in categories %}
+
+ {{ category }} |
+ {% if category.order == 0 %}0 (non affichéé){% else %}{{category.order}}{% endif %} |
+ Profil {% if perms.gestion.change_category %} Modifier{% endif %} |
+
+ {% endfor %}
+
+
+
+
+{% endblock %}
diff --git a/gestion/templates/gestion/category_profile.html b/gestion/templates/gestion/category_profile.html
new file mode 100644
index 0000000..b64217e
--- /dev/null
+++ b/gestion/templates/gestion/category_profile.html
@@ -0,0 +1,29 @@
+{% extends "base.html" %}
+{% block entete %}Gestion des produits : Profil de {{category}}{% endblock %}
+{% block navbar %}
+
+{% endblock %}
+{% block content %}
+
+
+ Liste des catégories
+ {% if perms.gestion.change_category %}Modifier
{% endif %}
+ Nom : {{ category.name }}
+ Ordre : {{ category.order }}
+
+
+
+ Liste des produits ({{category.product_set.all.count}} au total dont {{category.active_products.count}} actifs)
+
+
+ {% for product in category.product_set.all %}
+ - {{product}}
+ {% endfor %}
+
+
+{% endblock %}
diff --git a/gestion/templates/gestion/manage.html b/gestion/templates/gestion/manage.html
index 3b4c3f0..8f0c22b 100644
--- a/gestion/templates/gestion/manage.html
+++ b/gestion/templates/gestion/manage.html
@@ -125,7 +125,7 @@
{% endif %}
{% endfor %}
- {% if not bieresPression|divisibleby:4 %}
+ {% if not cotisations|divisibleby:4 %}
{% endif %}
Bières pression |
@@ -141,8 +141,9 @@
{% if not bieresPression|divisibleby:4 %}
{% endif %}
- Bières bouteilles |
- {% for product in bieresBouteille %}
+ {% for category in categories %}
+ {{category}} |
+ {% for product in category.active_products %}
{% if forloop.counter0|divisibleby:4 %}
{% endif %}
@@ -151,49 +152,10 @@
{% endif %}
{% endfor %}
- {% if not bieresBouteille|divisibleby:4 %}
-
- {% endif %}
- Paninis |
- {% for product in panini %}
- {% if forloop.counter0|divisibleby:4 %}
-
- {% endif %}
- |
- {% if forloop.counter|divisibleby:4 %}
+ {% if not category.active_products|divisibleby:4 %}
{% endif %}
{% endfor %}
- {% if not panini|divisibleby:4 %}
-
- {% endif %}
- Boissons sans alcool |
- {% for product in soft %}
- {% if forloop.counter0|divisibleby:4 %}
-
- {% endif %}
- |
- {% if forloop.counter|divisibleby:4 %}
-
- {% endif %}
- {% endfor %}
- {% if not soft|divisibleby:4 %}
-
- {% endif %}
-
- En-cas |
- {% for product in food %}
- {% if forloop.counter0|divisibleby:4 %}
-
- {% endif %}
- |
- {% if forloop.counter|divisibleby:4 %}
-
- {% endif %}
- {% endfor %}
- {% if not autreBouffe|divisibleby:4 %}
-
- {% endif %}
{% if menus %}
Menus |
{% for product in menus %}
diff --git a/gestion/templates/gestion/product_profile.html b/gestion/templates/gestion/product_profile.html
index 7035f2f..6c52477 100644
--- a/gestion/templates/gestion/product_profile.html
+++ b/gestion/templates/gestion/product_profile.html
@@ -17,7 +17,7 @@
Stock en soute : {{ product.stockHold }}
Stock au bar : {{ product.stockBar }}
Code Barre : {{ product.barcode }}
- Catégorie : {{ product.category }}
+ Catégorie : {{ product.category }}
Actif : {{ product.is_active | yesno:"Oui, Non"}}
Dégré : {{ product.deg }}
Volume : {{ product.volume }}cl
diff --git a/gestion/templates/gestion/products_index.html b/gestion/templates/gestion/products_index.html
index 764c143..545bd6e 100644
--- a/gestion/templates/gestion/products_index.html
+++ b/gestion/templates/gestion/products_index.html
@@ -2,6 +2,9 @@
{% block entete %}Gestion des produits{% endblock %}
{% block navbar%}
+ {% if perms.gestion.add_category or perms.gestion.view_category %}
+ - Catégories
+ {% endif %}
{% if perms.gestion.add_product or perms.gestion.view_product %}
- Produits
{% endif %}
@@ -17,6 +20,23 @@
{% endblock %}
{% block content %}
+{% if perms.gestion.add_category or perms.gestion.view_category %}
+
+
+ Actions possibles :
+
+
+{% endif %}
{% if perms.gestion.add_product or perms.gestion.view_product %}
diff --git a/gestion/urls.py b/gestion/urls.py
index 538a7d4..0b68c60 100644
--- a/gestion/urls.py
+++ b/gestion/urls.py
@@ -45,4 +45,11 @@ urlpatterns = [
path('menus-autcomplete', views.MenusAutocomplete.as_view(), name="menus-autocomplete"),
path('cancelReload/', 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/', views.editCategory, name="editCategory"),
+ path('searchCategory', views.searchCategory, name="searchCategory"),
+ path('categoryProfile/', views.categoryProfile, name="categoryProfile"),
+ path('categoriesList', views.categoriesList, name="categoriesList"),
+ path('categories-autocomplete', views.CategoriesAutocomplete.as_view(), name="categories-autocomplete"),
]
\ No newline at end of file
diff --git a/gestion/views.py b/gestion/views.py
index 3008704..07a459f 100644
--- a/gestion/views.py
+++ b/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 `.
+ """
+ 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 `.
+ """
+ def get_queryset(self):
+ qs = Category.objects.all()
+ if self.q:
+ qs = qs.filter(name__icontains=self.q)
+ return qs
\ No newline at end of file