Translate app

This commit is contained in:
Gabriel Augendre 2022-04-25 23:04:49 +02:00
parent d16d9307f4
commit bc3b06bc17
19 changed files with 651 additions and 47 deletions

View file

@ -0,0 +1,61 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-25 23:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/common/templates/403.html:4
msgid "Permission denied"
msgstr ""
#: src/common/templates/403.html:5
msgid "You're not allowed to access this page."
msgstr ""
#: src/common/templates/404.html:4
msgid "Page not found"
msgstr ""
#: src/common/templates/404.html:5
msgid "We tried and tried but couldn't find the page you're looking for."
msgstr ""
#: src/common/templates/500.html:11
msgid "Server error (500)"
msgstr ""
#: src/common/templates/500.html:12
msgid ""
"There's an error on our end. Don't worry though, our engineers are already "
"working to fix it!"
msgstr ""
#: src/common/templates/common/navbar.html:12
msgid "New basket"
msgstr ""
#: src/common/templates/common/navbar.html:17
msgid "Baskets"
msgstr ""
#: src/common/templates/common/navbar.html:20
msgid "Reports"
msgstr ""
#: src/common/templates/common/navbar.html:25
msgid "Admin"
msgstr ""

View file

@ -0,0 +1,62 @@
# Copyright (C) 2022 Gabriel Augendre
# This file is distributed under the same license as the package.
# Gabriel Augendre <gabriel@augendre.info>, 2022.
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-25 23:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: src/common/templates/403.html:4
msgid "Permission denied"
msgstr "Permission refusée"
#: src/common/templates/403.html:5
msgid "You're not allowed to access this page."
msgstr "Vous n'avez pas le droit d'accéder à cette page"
#: src/common/templates/404.html:4
msgid "Page not found"
msgstr "Page non trouvée"
#: src/common/templates/404.html:5
msgid "We tried and tried but couldn't find the page you're looking for."
msgstr ""
"Nous avons cherché partout mais nous n'avons pas trouvé la page que vous "
"cherchiez."
#: src/common/templates/500.html:11
msgid "Server error (500)"
msgstr "Erreur serveur (500)"
#: src/common/templates/500.html:12
msgid ""
"There's an error on our end. Don't worry though, our engineers are already "
"working to fix it!"
msgstr ""
"Il y a une erreur de notre côté. Ne vous inquiétez pas, nos ingénieurs "
"travaillent déjà à sa résolution !"
#: src/common/templates/common/navbar.html:12
msgid "New basket"
msgstr "Nouveau panier"
#: src/common/templates/common/navbar.html:17
msgid "Baskets"
msgstr "Paniers"
#: src/common/templates/common/navbar.html:20
msgid "Reports"
msgstr "Rapports"
#: src/common/templates/common/navbar.html:25
msgid "Admin"
msgstr "Admin"

View file

@ -1,5 +1,6 @@
{% extends "common/base.html" %} {% extends "common/base.html" %}
{% load i18n %}
{% block content %} {% block content %}
<h1>Permission denied</h1> <h1>{% translate "Permission denied" %}</h1>
<p>You're not allowed to access this page.</p> <p>{% translate "You're not allowed to access this page." %}</p>
{% endblock %} {% endblock %}

View file

@ -1,5 +1,6 @@
{% extends "common/base.html" %} {% extends "common/base.html" %}
{% load i18n %}
{% block content %} {% block content %}
<h1>Page not found</h1> <h1>{% translate "Page not found" %}</h1>
<p>We tried and tried but couldn't find the page you're looking for.</p> <p>{% translate "We tried and tried but couldn't find the page you're looking for." %}</p>
{% endblock %} {% endblock %}

View file

@ -1,3 +1,4 @@
{% load i18n %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -7,7 +8,7 @@
<style>html { font-family: sans-serif; }</style> <style>html { font-family: sans-serif; }</style>
</head> </head>
<body> <body>
<h1>Server error (500)</h1> <h1>{% translate "Server error (500)" %}</h1>
<p>There's an error on our end. Don't worry, our engineers are already working on it!</p> <p>{% translate "There's an error on our end. Don't worry though, our engineers are already working to fix it!" %}</p>
</body> </body>
</html> </html>

View file

@ -1,3 +1,4 @@
{% load i18n %}
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="{% url "purchase:list" %}">Checkout</a> <a class="navbar-brand" href="{% url "purchase:list" %}">Checkout</a>
@ -8,20 +9,20 @@
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> <ul class="navbar-nav me-auto mb-2 mb-lg-0">
{% if perms.purchase.add_basket %} {% if perms.purchase.add_basket %}
<li class="nav-item"> <li class="nav-item">
<a href="{% url "purchase:new" %}" class="nav-link">New basket</a> <a href="{% url "purchase:new" %}" class="nav-link">{% translate "New basket" %}</a>
</li> </li>
{% endif %} {% endif %}
{% if perms.purchase.view_basket %} {% if perms.purchase.view_basket %}
<li class="nav-item"> <li class="nav-item">
<a href="{% url "purchase:list" %}" class="nav-link">Baskets</a> <a href="{% url "purchase:list" %}" class="nav-link">{% translate "Baskets" %}</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{% url "purchase:reports" %}" class="nav-link">Reports</a> <a href="{% url "purchase:reports" %}" class="nav-link">{% translate "Reports" %}</a>
</li> </li>
{% endif %} {% endif %}
{% if user.is_staff %} {% if user.is_staff %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url "admin:index" %}">Admin</a> <a class="nav-link" href="{% url "admin:index" %}">{% translate "Admin" %}</a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>

View file

@ -1,5 +1,6 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import register from django.contrib.admin import register
from django.utils.translation import gettext_lazy as _
from purchase.models import Basket, BasketItem, PaymentMethod, Product from purchase.models import Basket, BasketItem, PaymentMethod, Product
from purchase.templatetags.purchase import currency from purchase.templatetags.purchase import currency
@ -14,12 +15,15 @@ class ProductAdmin(admin.ModelAdmin):
def get_queryset(self, request): def get_queryset(self, request):
return super().get_queryset(request).with_sold().with_turnover() return super().get_queryset(request).with_sold().with_turnover()
@admin.display(description=_("unit price"))
def unit_price(self, instance: Product): def unit_price(self, instance: Product):
return currency(instance.unit_price_cents) return currency(instance.unit_price_cents)
@admin.display(description=_("sold"))
def sold(self, instance: Product): def sold(self, instance: Product):
return instance.sold return instance.sold
@admin.display(description=_("turnover"))
def turnover(self, instance: Product): def turnover(self, instance: Product):
return currency(instance.turnover) return currency(instance.turnover)
@ -32,6 +36,7 @@ class PaymentMethodAdmin(admin.ModelAdmin):
def get_queryset(self, request): def get_queryset(self, request):
return super().get_queryset(request).with_turnover() return super().get_queryset(request).with_turnover()
@admin.display(description=_("turnover"))
def turnover(self, instance: Product): def turnover(self, instance: Product):
return currency(instance.turnover) return currency(instance.turnover)
@ -45,6 +50,7 @@ class BasketItemInline(admin.TabularInline):
def get_queryset(self, request): def get_queryset(self, request):
return super().get_queryset(request).priced() return super().get_queryset(request).priced()
@admin.display(description=_("price"))
def price(self, instance) -> str: def price(self, instance) -> str:
return currency(instance.price) return currency(instance.price)
@ -61,5 +67,6 @@ class BasketAdmin(admin.ModelAdmin):
def get_queryset(self, request): def get_queryset(self, request):
return super().get_queryset(request).priced() return super().get_queryset(request).priced()
@admin.display(description=_("price"))
def price(self, instance) -> str: def price(self, instance) -> str:
return currency(instance.price) return currency(instance.price)

View file

@ -1,6 +1,7 @@
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Div, Field, Layout, Submit from crispy_forms.layout import Div, Field, Layout, Submit
from django import forms from django import forms
from django.utils.translation import gettext as _
from purchase.layout import BasketItemField from purchase.layout import BasketItemField
from purchase.models import Basket, Product from purchase.models import Basket, Product
@ -19,7 +20,7 @@ class BasketForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.helper = FormHelper() self.helper = FormHelper()
self.helper.add_input(Submit("submit", "Save")) self.helper.add_input(Submit("submit", _("Save")))
self.helper.layout = Layout() self.helper.layout = Layout()
products = {} products = {}
basket = kwargs.get("instance") basket = kwargs.get("instance")

View file

@ -0,0 +1,210 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-25 23:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/purchase/admin.py:18
msgid "unit price"
msgstr ""
#: src/purchase/admin.py:22
msgid "sold"
msgstr ""
#: src/purchase/admin.py:26 src/purchase/admin.py:39
msgid "turnover"
msgstr ""
#: src/purchase/admin.py:53 src/purchase/admin.py:70
msgid "price"
msgstr ""
#: src/purchase/forms.py:23
msgid "Save"
msgstr ""
#: src/purchase/models.py:10
msgid "created at"
msgstr ""
#: src/purchase/models.py:11
msgid "updated at"
msgstr ""
#: src/purchase/models.py:31 src/purchase/models.py:58
msgid "name"
msgstr ""
#: src/purchase/models.py:36 src/purchase/models.py:128
msgid "payment method"
msgstr ""
#: src/purchase/models.py:37
msgid "payment methods"
msgstr ""
#: src/purchase/models.py:59
msgid "image"
msgstr ""
#: src/purchase/models.py:61
msgid "unit price (cents)"
msgstr ""
#: src/purchase/models.py:61
msgid "unit price in cents"
msgstr ""
#: src/purchase/models.py:64
msgid "display order"
msgstr ""
#: src/purchase/models.py:71 src/purchase/models.py:154
msgid "product"
msgstr ""
#: src/purchase/models.py:72
msgid "products"
msgstr ""
#: src/purchase/models.py:134 src/purchase/models.py:160
msgid "basket"
msgstr ""
#: src/purchase/models.py:135
msgid "baskets"
msgstr ""
#: src/purchase/models.py:138
#, python-format
msgid "Basket #%(id)s"
msgstr ""
#: src/purchase/models.py:162
msgid "quantity"
msgstr ""
#: src/purchase/models.py:167
msgid "basket item"
msgstr ""
#: src/purchase/models.py:168
msgid "basket items"
msgstr ""
#: src/purchase/templates/purchase/basket_confirm_delete.html:7
#, python-format
msgid "Are you sure you want to delete \"%(basket)s\"?"
msgstr ""
#: src/purchase/templates/purchase/basket_form.html:11
msgid "Missing payment method."
msgstr ""
#: src/purchase/templates/purchase/basket_form.html:14
msgid "New basket"
msgstr ""
#: src/purchase/templates/purchase/basket_form.html:18
msgid "New"
msgstr ""
#: src/purchase/templates/purchase/basket_list.html:5
msgid "Baskets"
msgstr ""
#: src/purchase/templates/purchase/basket_list.html:11
#, python-format
msgid "Basket #%(basket_id)s"
msgstr ""
#: src/purchase/templates/purchase/basket_list.html:13
#, python-format
msgid "1 item"
msgid_plural "%(counter)s items"
msgstr[0] ""
msgstr[1] ""
#: src/purchase/templates/purchase/reports.html:6
msgid "Reports"
msgstr ""
#: src/purchase/templates/purchase/reports.html:7
msgid "General"
msgstr ""
#: src/purchase/templates/purchase/reports.html:9
msgid "Total turnover:"
msgstr ""
#: src/purchase/templates/purchase/reports.html:10
msgid "Average basket:"
msgstr ""
#: src/purchase/templates/purchase/reports.html:13
msgid "Products"
msgstr ""
#: src/purchase/templates/purchase/reports.html:16
msgid "Turnover by payment method"
msgstr ""
#: src/purchase/templates/purchase/reports.html:19
msgid "Baskets without payment method"
msgstr ""
#: src/purchase/templates/purchase/snippets/report_no_payment_method.html:6
msgid "Basket ID"
msgstr ""
#: src/purchase/templates/purchase/snippets/report_no_payment_method.html:7
msgid "Price"
msgstr ""
#: src/purchase/templates/purchase/snippets/report_payment_methods.html:6
msgid "Payment method"
msgstr ""
#: src/purchase/templates/purchase/snippets/report_payment_methods.html:7
msgid "# baskets"
msgstr ""
#: src/purchase/templates/purchase/snippets/report_payment_methods.html:8
#: src/purchase/templates/purchase/snippets/report_products.html:8
msgid "Turnover"
msgstr ""
#: src/purchase/templates/purchase/snippets/report_products.html:6
msgid "Product"
msgstr ""
#: src/purchase/templates/purchase/snippets/report_products.html:7
msgid "# sold"
msgstr ""
#: src/purchase/views/basket.py:15
msgid "Successfully created basket."
msgstr ""
#: src/purchase/views/basket.py:30
msgid "Successfully updated basket."
msgstr ""
#: src/purchase/views/basket.py:45
msgid "Basket successfully deleted."
msgstr ""

View file

@ -0,0 +1,223 @@
# Copyright (C) 2022 Gabriel Augendre
# This file is distributed under the same license as the package.
# Gabriel Augendre <gabriel@augendre.info>, 2022.
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-25 23:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: src/purchase/admin.py:18
msgid "unit price"
msgstr "prix unitaire"
#: src/purchase/admin.py:22
msgid "sold"
msgstr "vendu"
#: src/purchase/admin.py:26 src/purchase/admin.py:39
msgid "turnover"
msgstr "chiffre d'affaires"
#: src/purchase/admin.py:53 src/purchase/admin.py:70
msgid "price"
msgstr "prix"
#: src/purchase/forms.py:23
msgid "Save"
msgstr "Enregistrer"
#: src/purchase/models.py:10
msgid "created at"
msgstr "créé à"
#: src/purchase/models.py:11
msgid "updated at"
msgstr "mis à jour à"
#: src/purchase/models.py:31 src/purchase/models.py:58
msgid "name"
msgstr "nom"
#: src/purchase/models.py:36 src/purchase/models.py:128
msgid "payment method"
msgstr "moyen de paiement"
#: src/purchase/models.py:37
msgid "payment methods"
msgstr "moyens de paiement"
#: src/purchase/models.py:59
msgid "image"
msgstr "image"
#: src/purchase/models.py:61
msgid "unit price (cents)"
msgstr "prix unitaire (centimes)"
#: src/purchase/models.py:61
msgid "unit price in cents"
msgstr "prix unitaire en centimes"
#: src/purchase/models.py:64
msgid "display order"
msgstr "ordre d'affichage"
#: src/purchase/models.py:71 src/purchase/models.py:154
msgid "product"
msgstr "produit"
#: src/purchase/models.py:72
msgid "products"
msgstr "produits"
#: src/purchase/models.py:134 src/purchase/models.py:160
msgid "basket"
msgstr "panier"
#: src/purchase/models.py:135
msgid "baskets"
msgstr "paniers"
#: src/purchase/models.py:138
#, python-format
msgid "Basket #%(id)s"
msgstr "Panier n°%(id)s"
#: src/purchase/models.py:162
msgid "quantity"
msgstr "quantité"
#: src/purchase/models.py:167
msgid "basket item"
msgstr "article de panier"
#: src/purchase/models.py:168
msgid "basket items"
msgstr "articles de panier"
#: src/purchase/templates/purchase/basket_confirm_delete.html:7
#, python-format
msgid "Are you sure you want to delete \"%(basket)s\"?"
msgstr "Êtes-vous sûr de vouloir supprimer \"%(basket)s\" ?"
#: src/purchase/templates/purchase/basket_form.html:11
msgid "Missing payment method."
msgstr "Moyen de paiement manquant."
#: src/purchase/templates/purchase/basket_form.html:14
msgid "New basket"
msgstr "Nouveau panier"
#: src/purchase/templates/purchase/basket_form.html:18
msgid "New"
msgstr "Nouveau"
#: src/purchase/templates/purchase/basket_list.html:5
msgid "Baskets"
msgstr "Paniers"
#: src/purchase/templates/purchase/basket_list.html:11
#, python-format
msgid "Basket #%(basket_id)s"
msgstr "Panier n°%(basket_id)s"
#: src/purchase/templates/purchase/basket_list.html:13
#, python-format
msgid "1 item"
msgid_plural "%(counter)s items"
msgstr[0] "1 article"
msgstr[1] "%(counter)s articles"
#: src/purchase/templates/purchase/reports.html:6
msgid "Reports"
msgstr "Rapports"
#: src/purchase/templates/purchase/reports.html:7
msgid "General"
msgstr "Général"
#: src/purchase/templates/purchase/reports.html:9
msgid "Total turnover:"
msgstr "Chiffre d'affaires total :"
#: src/purchase/templates/purchase/reports.html:10
msgid "Average basket:"
msgstr "Panier moyen :"
#: src/purchase/templates/purchase/reports.html:13
msgid "Products"
msgstr "Produits"
#: src/purchase/templates/purchase/reports.html:16
msgid "Turnover by payment method"
msgstr "Chiffre d'affaires par moyen de paiement"
#: src/purchase/templates/purchase/reports.html:19
msgid "Baskets without payment method"
msgstr "Paniers sans moyen de paiement"
#: src/purchase/templates/purchase/snippets/report_no_payment_method.html:6
msgid "Basket ID"
msgstr "Id de panier"
#: src/purchase/templates/purchase/snippets/report_no_payment_method.html:7
msgid "Price"
msgstr "Prix"
#: src/purchase/templates/purchase/snippets/report_payment_methods.html:6
msgid "Payment method"
msgstr "Moyen de paiement"
#: src/purchase/templates/purchase/snippets/report_payment_methods.html:7
msgid "# baskets"
msgstr "Nb. de paniers"
#: src/purchase/templates/purchase/snippets/report_payment_methods.html:8
#: src/purchase/templates/purchase/snippets/report_products.html:8
msgid "Turnover"
msgstr "Chiffre d'affaires"
#: src/purchase/templates/purchase/snippets/report_products.html:6
msgid "Product"
msgstr "Produit"
#: src/purchase/templates/purchase/snippets/report_products.html:7
msgid "# sold"
msgstr "Nb. vendus"
#: src/purchase/views/basket.py:15
msgid "Successfully created basket."
msgstr "Panier correctement créé."
#: src/purchase/views/basket.py:30
msgid "Successfully updated basket."
msgstr "Panier correctement modifié."
#: src/purchase/views/basket.py:45
msgid "Basket successfully deleted."
msgstr "Panier correctement supprimé."
#, python-format
#~ msgid "Total turnover: %(total|currency)s"
#~ msgstr "Chiffre d'affaires total : %(total|currency)s"
#, python-format
#~ msgid "Average basket: %(average_basket|currency)s"
#~ msgstr "Panier moyen : %(average_basket|currency)s"
#, python-brace-format
#~ msgid "Basket #{self.id}"
#~ msgstr "Panier n°{self.id}"
#, python-format
#~ msgid "%(basket.items.count)s items"
#~ msgstr "%(basket.items.count)s articles"

View file

@ -1,12 +1,14 @@
from django.db import models from django.db import models
from django.db.models import Count, F, Sum from django.db.models import Count, F, Sum
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from PIL import Image, ImageOps from PIL import Image, ImageOps
class Model(models.Model): class Model(models.Model):
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("created at"))
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("updated at"))
class Meta: class Meta:
abstract = True abstract = True
@ -26,10 +28,14 @@ class PaymentMethodQuerySet(models.QuerySet):
class PaymentMethod(Model): class PaymentMethod(Model):
name = models.CharField(max_length=50, unique=True) name = models.CharField(max_length=50, unique=True, verbose_name=_("name"))
objects = PaymentMethodQuerySet.as_manager() objects = PaymentMethodQuerySet.as_manager()
class Meta:
verbose_name = _("payment method")
verbose_name_plural = _("payment methods")
def __str__(self): def __str__(self):
return self.name return self.name
@ -49,15 +55,21 @@ class ProductQuerySet(models.QuerySet):
class Product(Model): class Product(Model):
name = models.CharField(max_length=250, unique=True) name = models.CharField(max_length=250, unique=True, verbose_name=_("name"))
image = models.ImageField(null=True, blank=True) image = models.ImageField(null=True, blank=True, verbose_name=_("image"))
unit_price_cents = models.PositiveIntegerField() unit_price_cents = models.PositiveIntegerField(
display_order = models.PositiveIntegerField(default=default_product_display_order) verbose_name=_("unit price (cents)"), help_text=_("unit price in cents")
)
display_order = models.PositiveIntegerField(
default=default_product_display_order, verbose_name=_("display order")
)
objects = ProductQuerySet.as_manager() objects = ProductQuerySet.as_manager()
class Meta: class Meta:
ordering = ["display_order", "name"] ordering = ["display_order", "name"]
verbose_name = _("product")
verbose_name_plural = _("products")
def __str__(self): def __str__(self):
return self.name return self.name
@ -113,12 +125,17 @@ class Basket(Model):
related_name="baskets", related_name="baskets",
null=True, null=True,
blank=True, blank=True,
verbose_name=_("payment method"),
) )
objects = BasketQuerySet.as_manager() objects = BasketQuerySet.as_manager()
class Meta:
verbose_name = _("basket")
verbose_name_plural = _("baskets")
def __str__(self): def __str__(self):
return f"Basket #{self.id}" return gettext("Basket #%(id)s") % {"id": self.id}
def get_absolute_url(self): def get_absolute_url(self):
return reverse("purchase:update", args=(self.pk,)) return reverse("purchase:update", args=(self.pk,))
@ -131,11 +148,21 @@ class BasketItemQuerySet(models.QuerySet):
class BasketItem(Model): class BasketItem(Model):
product = models.ForeignKey( product = models.ForeignKey(
to=Product, on_delete=models.PROTECT, related_name="basket_items" to=Product,
on_delete=models.PROTECT,
related_name="basket_items",
verbose_name=_("product"),
) )
basket = models.ForeignKey( basket = models.ForeignKey(
to=Basket, on_delete=models.CASCADE, related_name="items" to=Basket,
on_delete=models.CASCADE,
related_name="items",
verbose_name=_("basket"),
) )
quantity = models.PositiveIntegerField() quantity = models.PositiveIntegerField(verbose_name=_("quantity"))
objects = BasketItemQuerySet.as_manager() objects = BasketItemQuerySet.as_manager()
class Meta:
verbose_name = _("basket item")
verbose_name_plural = _("basket items")

View file

@ -1,9 +1,10 @@
{% extends "common/base.html" %} {% extends "common/base.html" %}
{% load i18n %}
{% block content %} {% block content %}
<h1>{{ object }}</h1> <h1>{{ object }}</h1>
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
<p>Are you sure you want to delete "{{ object }}"?</p> <p>{% blocktranslate %}Are you sure you want to delete "{{ basket }}"?{% endblocktranslate %}</p>
{{ form }} {{ form }}
<input class="btn btn-danger" type="submit" value="Confirm"> <input class="btn btn-danger" type="submit" value="Confirm">
</form> </form>

View file

@ -1,4 +1,5 @@
{% extends "common/base.html" %} {% extends "common/base.html" %}
{% load i18n %}
{% load crispy_forms_tags purchase %} {% load crispy_forms_tags purchase %}
{% block content %} {% block content %}
{% if object %} {% if object %}
@ -7,13 +8,13 @@
{{ object.created_at }} {{ object.created_at }}
</p> </p>
{% if not object.payment_method %} {% if not object.payment_method %}
<div class="alert alert-danger" role="alert">Missing payment method.</div> <div class="alert alert-danger" role="alert">{% translate "Missing payment method." %}</div>
{% endif %} {% endif %}
{% else %} {% else %}
<h1>New basket</h1> <h1>{% translate "New basket" %}</h1>
{% endif %} {% endif %}
{% crispy form %} {% crispy form %}
{% if object %} {% if object %}
<a href="{% url "purchase:new" %}" class="btn btn-secondary">New</a> <a href="{% url "purchase:new" %}" class="btn btn-secondary">{% translate "New" %}</a>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -1,15 +1,16 @@
{% extends "common/base.html" %} {% extends "common/base.html" %}
{% load i18n %}
{% load purchase %} {% load purchase %}
{% block content %} {% block content %}
<h1>Baskets</h1> <h1>{% translate "Baskets" %}</h1>
<div class="row row-cols-auto g-4"> <div class="row row-cols-auto g-4">
{% for basket in baskets %} {% for basket in baskets %}
<div class="col"> <div class="col">
<div class="card h-100 {% if not basket.payment_method %}bg-warning text-black{% endif %}"> <div class="card h-100 {% if not basket.payment_method %}bg-warning text-black{% endif %}">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Basket #{{ basket.id }}</h5> <h5 class="card-title">{% blocktranslate with basket_id=basket.id %}Basket #{{ basket_id }}{% endblocktranslate %}</h5>
<p class="card-text"> <p class="card-text">
{{ basket.items.count }} items<br> {% blocktranslate count counter=basket.items.count %}1 item{% plural %}{{ counter }} items{% endblocktranslate %}<br>
{{ basket.price|currency }}<br> {{ basket.price|currency }}<br>
{{ basket.payment_method|default:"-" }} {{ basket.payment_method|default:"-" }}
</p> </p>

View file

@ -1,21 +1,22 @@
{% extends "common/base.html" %} {% extends "common/base.html" %}
{% load i18n %}
{% load purchase %} {% load purchase %}
{% block content %} {% block content %}
<h1>Reports</h1> <h1>{% translate "Reports" %}</h1>
<h2>General</h2> <h2>{% translate "General" %}</h2>
<ul> <ul>
<li>Total turnover: {{ total|currency }}</li> <li>{% translate "Total turnover:" %} {{ total|currency }}</li>
<li>Average basket: {{ average_basket|currency }}</li> <li>{% translate "Average basket:" %} {{ average_basket|currency }}</li>
</ul> </ul>
<h2>Products</h2> <h2>{% translate "Products" %}</h2>
{% include "purchase/snippets/report_products.html" %} {% include "purchase/snippets/report_products.html" %}
<h2>Turnover by payment method</h2> <h2>{% translate "Turnover by payment method" %}</h2>
{% include "purchase/snippets/report_payment_methods.html" %} {% include "purchase/snippets/report_payment_methods.html" %}
<h2>Baskets without payment method</h2> <h2>{% translate "Baskets without payment method" %}</h2>
{% include "purchase/snippets/report_no_payment_method.html" %} {% include "purchase/snippets/report_no_payment_method.html" %}
{% endblock %} {% endblock %}

View file

@ -1,9 +1,10 @@
{% load i18n %}
{% load purchase %} {% load purchase %}
<table class="table table-hover table-sm"> <table class="table table-hover table-sm">
<thead><tr> <thead><tr>
<th scope="col">Basket ID</th> <th scope="col">{% translate "Basket ID" %}</th>
<th scope="col">Price</th> <th scope="col">{% translate "Price" %}</th>
</tr></thead> </tr></thead>
<tbody> <tbody>
{% for basket in no_payment_method %} {% for basket in no_payment_method %}

View file

@ -1,10 +1,11 @@
{% load i18n %}
{% load purchase %} {% load purchase %}
<table class="table table-hover table-sm"> <table class="table table-hover table-sm">
<thead><tr> <thead><tr>
<th scope="col">Payment method</th> <th scope="col">{% translate "Payment method" %}</th>
<th scope="col"># baskets</th> <th scope="col">{% translate "# baskets" %}</th>
<th scope="col">Turnover</th> <th scope="col">{% translate "Turnover" %}</th>
</tr></thead> </tr></thead>
<tbody> <tbody>
{% for payment_method in payment_methods %} {% for payment_method in payment_methods %}

View file

@ -1,10 +1,11 @@
{% load i18n %}
{% load purchase %} {% load purchase %}
<table class="table table-hover table-sm"> <table class="table table-hover table-sm">
<thead><tr> <thead><tr>
<th scope="col">Product</th> <th scope="col">{% translate "Product" %}</th>
<th scope="col"># sold</th> <th scope="col">{% translate "# sold" %}</th>
<th scope="col">Turnover</th> <th scope="col">{% translate "Turnover" %}</th>
</tr></thead> </tr></thead>
<tbody> <tbody>
{% for product in products %} {% for product in products %}

View file

@ -1,5 +1,6 @@
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DeleteView, ListView, UpdateView from django.views.generic import CreateView, DeleteView, ListView, UpdateView
from purchase.forms import BasketForm from purchase.forms import BasketForm
@ -11,7 +12,7 @@ class NewBasketView(ProtectedViewsMixin, SuccessMessageMixin, CreateView):
permission_required = ["purchase.add_basket"] permission_required = ["purchase.add_basket"]
model = Basket model = Basket
form_class = BasketForm form_class = BasketForm
success_message = "Successfully created basket." success_message = _("Successfully created basket.")
queryset = Basket.objects.priced() queryset = Basket.objects.priced()
@ -26,7 +27,7 @@ class UpdateBasketView(ProtectedViewsMixin, SuccessMessageMixin, UpdateView):
permission_required = ["purchase.change_basket", "purchase.view_basket"] permission_required = ["purchase.change_basket", "purchase.view_basket"]
model = Basket model = Basket
form_class = BasketForm form_class = BasketForm
success_message = "Successfully updated basket." success_message = _("Successfully updated basket.")
queryset = Basket.objects.priced() queryset = Basket.objects.priced()
@ -41,8 +42,9 @@ class ListBasketsView(ProtectedViewsMixin, ListView):
class DeleteBasketView(ProtectedViewsMixin, SuccessMessageMixin, DeleteView): class DeleteBasketView(ProtectedViewsMixin, SuccessMessageMixin, DeleteView):
permission_required = ["purchase.delete_basket"] permission_required = ["purchase.delete_basket"]
model = Basket model = Basket
success_message = "Basket successfully deleted." success_message = _("Basket successfully deleted.")
queryset = Basket.objects.priced() queryset = Basket.objects.priced()
context_object_name = "basket"
def get_success_url(self): def get_success_url(self):
return reverse("purchase:list") return reverse("purchase:list")