3
0
Fork 0
mirror of https://github.com/nanoy42/coope synced 2024-12-23 23:43:47 +00:00

Add categories

This commit is contained in:
Yoann Pétri 2019-05-03 21:09:32 +02:00
parent 4849cac128
commit f1241be0c8
12 changed files with 303 additions and 76 deletions

View file

@ -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)
@ -90,3 +96,4 @@ admin.site.register(KegHistory, KegHistoryAdmin)
admin.site.register(Consumption, ConsumptionAdmin)
admin.site.register(Menu, MenuAdmin)
admin.site.register(MenuHistory, MenuHistoryAdmin)
admin.site.register(Category, CategoryAdmin)

View file

@ -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):
@ -109,3 +109,17 @@ class GenerateReleveForm(forms.Form):
"""
begin = forms.DateTimeField(label="Date de début")
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}))

View file

@ -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,
),
]

View file

@ -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'),
),
]

View file

@ -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
"""

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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>

View file

@ -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">

View file

@ -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"),
]

View file

@ -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