diff --git a/.gitignore b/.gitignore index 31d08d6..db54a1f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ __pycache__ .python_version *.egg-info +*.mo media/ ./static/ diff --git a/Readme.md b/Readme.md index a06358c..9add83e 100644 --- a/Readme.md +++ b/Readme.md @@ -28,9 +28,16 @@ following might sound familiar: - ForgeJo: `git clone https://git.dunkelstern.de/dunkelstern/inventory.git` 2. Change to checkout: `cd inventory` 3. Install virtualenv and dependencies: `poetry install --no-root` -4. Migrate the Database: `poetry run python manage.py migrate` -5. Create an admin user: `poetry run python manage.py createsuperuser` -6. Run the server +4. If you want to use the system in another language than the default english set it + up in the `inventory_project/settings.py`: + ```python + LANGUAGE_CODE = 'en-us' # or something like 'de-de' + ``` + see the settings file for defined languages. +5. If you changed the language rebuild the translation files: `poetry run python manage.py compilemessages` +6. Migrate the Database: `poetry run python manage.py migrate` +7. Create an admin user: `poetry run python manage.py createsuperuser` +8. Run the server - Development server (not for deployment!): `poetry run python manage.py runserver` - Deployment via `gunicorn` on port 8000: `poetry run gunicorn inventory_project.wsgi -b 0.0.0.0:8000` @@ -51,6 +58,16 @@ go to `http://localhost:8000` to enter the inventory management system directly [the service file](inventory.service) in the root of this repository for an example. +### Changelog + +#### 1.1 + +- Part count can be configured to be available in settings +- Currency can be configured in settings +- Complete system is translateable (English and German are provided) + +![Settings](docs/settings.jpeg) + ### Screenshots #### Login diff --git a/docs/settings.jpeg b/docs/settings.jpeg new file mode 100644 index 0000000..fa1ebf4 Binary files /dev/null and b/docs/settings.jpeg differ diff --git a/inventory/locale/de/LC_MESSAGES/django.po b/inventory/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000..2cc50fb --- /dev/null +++ b/inventory/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,561 @@ +# Translation for Inventory management system +# Copyright (C) 2025 Johannes Schriewer +# This file is distributed under the same license as the Inventory package. +# Johannes Schriewer , 2025 +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: 1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-01-07 23:07+0100\n" +"PO-Revision-Date: 2025-01-07 23:00+0100\n" +"Last-Translator: Johannes Schriewer \n" +"Language: German\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" + +#: .\inventory\models\area.py:8 .\inventory\models\box.py:10 +#: .\inventory\models\distributor.py:6 .\inventory\models\documentation.py:6 +#: .\inventory\models\form_factor.py:6 .\inventory\models\item.py:8 +#: .\inventory\models\layout.py:6 .\inventory\models\manufacturer.py:6 +#: .\inventory\models\tag.py:6 .\inventory\models\workshop.py:8 +#: .\inventory\templates\inventory\distributor_detail.html:26 +#: .\inventory\templates\inventory\item_detail.html:27 +#: .\inventory\templates\inventory\item_list.html:7 +#: .\inventory\templates\inventory\manufacturer_detail.html:26 +msgid "Name" +msgstr "Name" + +#: .\inventory\models\area.py:9 .\inventory\models\box.py:11 +#: .\inventory\models\distributor.py:7 .\inventory\models\form_factor.py:7 +#: .\inventory\models\item.py:9 .\inventory\models\layout.py:7 +#: .\inventory\models\manufacturer.py:7 .\inventory\models\tag.py:7 +#: .\inventory\models\workshop.py:9 +#: .\inventory\templates\inventory\distributor_detail.html:30 +#: .\inventory\templates\inventory\item_detail.html:31 +#: .\inventory\templates\inventory\item_list.html:9 +#: .\inventory\templates\inventory\manufacturer_detail.html:30 +msgid "Description" +msgstr "Beschreibung" + +#: .\inventory\models\area.py:11 +msgid "Show sub area" +msgstr "Unterbereiche anzeigen" + +#: .\inventory\models\area.py:13 +msgid "Allow sub-areas to be defined in this area" +msgstr "Erlaube Unterbereiche in diesem Bereich" + +#: .\inventory\models\area.py:15 .\inventory\models\box.py:14 +#: .\inventory\models\distributor.py:16 .\inventory\models\documentation.py:7 +#: .\inventory\models\form_factor.py:13 .\inventory\models\item.py:76 +#: .\inventory\models\layout.py:8 .\inventory\models\manufacturer.py:13 +#: .\inventory\models\tag.py:8 .\inventory\models\workshop.py:17 +#: .\inventory\templates\inventory\distributor_detail.html:89 +#: .\inventory\templates\inventory\item_detail.html:128 +#: .\inventory\templates\inventory\manufacturer_detail.html:57 +msgid "Created at" +msgstr "Erstellt am" + +#: .\inventory\models\area.py:16 .\inventory\models\box.py:15 +#: .\inventory\models\distributor.py:17 .\inventory\models\documentation.py:8 +#: .\inventory\models\form_factor.py:14 .\inventory\models\item.py:77 +#: .\inventory\models\layout.py:9 .\inventory\models\manufacturer.py:14 +#: .\inventory\models\tag.py:9 .\inventory\models\workshop.py:18 +#: .\inventory\templates\inventory\distributor_detail.html:91 +#: .\inventory\templates\inventory\item_detail.html:130 +#: .\inventory\templates\inventory\manufacturer_detail.html:59 +msgid "Changed at" +msgstr "Geändert am" + +#: .\inventory\models\area.py:24 +msgid "Area" +msgstr "Bereich" + +#: .\inventory\models\area.py:25 +#: .\inventory\templates\inventory\area_detail.html:25 +#: .\inventory\templates\inventory\workshop_detail.html:23 +msgid "Areas" +msgstr "Bereiche" + +#: .\inventory\models\box.py:12 .\inventory\models\distributor.py:14 +#: .\inventory\models\form_factor.py:11 .\inventory\models\item.py:58 +#: .\inventory\models\manufacturer.py:11 .\inventory\models\tag.py:17 +#: .\inventory\models\workshop.py:15 .\inventory\templates\base.html:58 +#: .\inventory\templates\inventory\distributor_detail.html:77 +#: .\inventory\templates\inventory\item_detail.html:54 +#: .\inventory\templates\inventory\manufacturer_detail.html:45 +#: .\inventory\templates\inventory\tag_list.html:9 +msgid "Tags" +msgstr "Tags" + +#: .\inventory\models\box.py:35 +msgid "Box" +msgstr "Kisten" + +#: .\inventory\models\box.py:36 +#: .\inventory\templates\inventory\box-generic.html:7 +#: .\inventory\templates\inventory\tag_detail.html:43 +msgid "Boxes" +msgstr "Kisten" + +#: .\inventory\models\container.py:9 .\inventory\models\layout.py:18 +msgid "Layout" +msgstr "Layout" + +#: .\inventory\models\container.py:35 .\inventory\models\container.py:45 +#: .\inventory\models\container.py:51 +#: .\inventory\templates\inventory\item_list.html:10 +msgid "Container" +msgstr "Behälter" + +#: .\inventory\models\container.py:46 +#: .\inventory\templates\inventory\area_detail.html:51 +#: .\inventory\templates\inventory\workshop_detail.html:49 +msgid "Containers" +msgstr "Behälter" + +#: .\inventory\models\container.py:56 +msgid "Index of compartment in layout" +msgstr "Index des Abteils im Layout" + +#: .\inventory\models\distributor.py:9 .\inventory\models\manufacturer.py:9 +#: .\inventory\templates\inventory\distributor_detail.html:40 +#: .\inventory\templates\inventory\manufacturer_detail.html:40 +msgid "Web link" +msgstr "Web link" + +#: .\inventory\models\distributor.py:10 +#: .\inventory\templates\inventory\item_detail.html:121 +msgid "Search link" +msgstr "Such-link" + +#: .\inventory\models\distributor.py:10 +msgid "Use {} for search placeholder" +msgstr "Benutze {} oder {0} als Platzhalter" + +#: .\inventory\models\distributor.py:11 +#: .\inventory\templates\inventory\distributor_detail.html:65 +msgid "Phone" +msgstr "Telefon" + +#: .\inventory\models\distributor.py:12 +#: .\inventory\templates\inventory\distributor_detail.html:71 +msgid "E-Mail" +msgstr "E-Mail" + +#: .\inventory\models\distributor.py:13 .\inventory\models\form_factor.py:9 +#: .\inventory\models\manufacturer.py:10 +#: .\inventory\templates\inventory\distributor_detail.html:35 +#: .\inventory\templates\inventory\distributor_list.html:15 +#: .\inventory\templates\inventory\manufacturer_detail.html:35 +#: .\inventory\templates\inventory\manufacturer_list.html:15 +msgid "Icon" +msgstr "Symbol" + +#: .\inventory\models\distributor.py:24 .\inventory\models\item.py:31 +#: .\inventory\templates\inventory\distributor_detail.html:6 +#: .\inventory\templates\inventory\distributor_detail.html:10 +#: .\inventory\templates\inventory\distributor_list.html:16 +#: .\inventory\templates\inventory\item_detail.html:96 +#: .\inventory\templates\inventory\item_list.html:15 +msgid "Distributor" +msgstr "Händler" + +#: .\inventory\models\distributor.py:25 .\inventory\templates\base.html:55 +#: .\inventory\templates\inventory\distributor_list.html:5 +#: .\inventory\templates\inventory\distributor_list.html:8 +#: .\inventory\templates\inventory\tag_detail.html:63 +msgid "Distributors" +msgstr "Händler" + +#: .\inventory\models\documentation.py:17 +#: .\inventory\models\documentation.py:18 .\inventory\models\item.py:52 +#: .\inventory\templates\inventory\item_detail.html:135 +msgid "Documentation" +msgstr "Dokumentation" + +#: .\inventory\models\form_factor.py:10 +#: .\inventory\templates\inventory\cell.html:16 +#: .\inventory\templates\inventory\item_list.html:25 +#: .\inventory\templates\inventory\search_result_item.html:6 +msgid "Datasheet" +msgstr "Datenblatt" + +#: .\inventory\models\form_factor.py:23 .\inventory\models\item.py:17 +#: .\inventory\templates\inventory\item_detail.html:68 +msgid "Form factor" +msgstr "Formfaktor" + +#: .\inventory\models\form_factor.py:24 +#: .\inventory\templates\inventory\tag_detail.html:121 +msgid "Form factors" +msgstr "Formfaktoren" + +#: .\inventory\models\item.py:11 +msgid "Size" +msgstr "Größe" + +#: .\inventory\models\item.py:13 +msgid "Number of sub-compartments this item takes up" +msgstr "Anzahl der Unterteilungen die dieses Teil benötigt" + +#: .\inventory\models\item.py:24 .\inventory\models\manufacturer.py:21 +#: .\inventory\templates\inventory\item_detail.html:85 +#: .\inventory\templates\inventory\item_list.html:12 +#: .\inventory\templates\inventory\manufacturer_detail.html:6 +#: .\inventory\templates\inventory\manufacturer_detail.html:10 +#: .\inventory\templates\inventory\manufacturer_list.html:16 +msgid "Manufacturer" +msgstr "Hersteller" + +#: .\inventory\models\item.py:37 +msgid "Distributor item no." +msgstr "Händler Bestellnummer" + +#: .\inventory\models\item.py:43 +#: .\inventory\templates\inventory\item_detail.html:107 +msgid "Price" +msgstr "Preis" + +#: .\inventory\models\item.py:49 +msgid "Last ordered on" +msgstr "Zuletzt bestellt am" + +#: .\inventory\models\item.py:63 +msgid "Count" +msgstr "Anzahl" + +#: .\inventory\models\item.py:66 +msgid "Number of parts available" +msgstr "Anzahl der verfügbaren Teile" + +#: .\inventory\models\item.py:69 +#: .\inventory\templates\inventory\item_detail.html:49 +msgid "Low watermark" +msgstr "Bestellgrenze" + +#: .\inventory\models\item.py:72 +msgid "Low watermark on which to alert ordering more" +msgstr "Untere Grenze ab der das Teil nachbestellt werden soll" + +#: .\inventory\models\item.py:75 +msgid "Custom metadata, used by templates" +msgstr "Metadaten die in der Vorlage verwendet werden können" + +#: .\inventory\models\item.py:98 +msgid "Item" +msgstr "Teil" + +#: .\inventory\models\item.py:99 +#: .\inventory\templates\inventory\box-generic.html:16 +#: .\inventory\templates\inventory\manufacturer_detail.html:64 +#: .\inventory\templates\inventory\tag_detail.html:113 +msgid "Items" +msgstr "Teile" + +#: .\inventory\models\layout.py:10 +msgid "Data" +msgstr "Konfiguration" + +#: .\inventory\models\layout.py:11 +msgid "Template name" +msgstr "Vorlage" + +#: .\inventory\models\layout.py:19 +msgid "Layouts" +msgstr "Layouts" + +#: .\inventory\models\manufacturer.py:22 .\inventory\templates\base.html:52 +#: .\inventory\templates\inventory\manufacturer_list.html:5 +#: .\inventory\templates\inventory\manufacturer_list.html:8 +#: .\inventory\templates\inventory\tag_detail.html:88 +msgid "Manufacturers" +msgstr "Hersteller" + +#: .\inventory\models\settings.py:13 +msgid "Default container to display when calling the index page" +msgstr "Standardbehälter der beim Aufruf der Index-Seite gezeigt werden soll" + +#: .\inventory\models\settings.py:17 +msgid "Show item count in overview and warn on low watermarks" +msgstr "Aktiviere die Mengenangaben und Nachbestellgrenzen" + +#: .\inventory\models\settings.py:19 +msgid "Currency" +msgstr "Währung" + +#: .\inventory\models\settings.py:19 +msgid "Currency name" +msgstr "Name der Währung" + +#: .\inventory\models\settings.py:20 +msgid "Currency symbol" +msgstr "Währungssymbol" + +#: .\inventory\models\settings.py:22 +msgid "Currency symbol at end" +msgstr "Währungssymbol am Ende" + +#: .\inventory\models\settings.py:24 +msgid "Currency symbol after amount" +msgstr "Zeige das Währungssymbol nach der Zahl an" + +#: .\inventory\models\settings.py:28 .\inventory\models\settings.py:31 +#: .\inventory\models\settings.py:32 +msgid "Settings" +msgstr "Einstellungen" + +#: .\inventory\models\tag.py:16 +#: .\inventory\templates\inventory\tag_detail.html:5 +msgid "Tag" +msgstr "Tag" + +#: .\inventory\models\workshop.py:11 +msgid "Show boxes" +msgstr "Behälter anzeigen" + +#: .\inventory\models\workshop.py:13 +msgid "Allow boxes to be defined directly in this workshop" +msgstr "Erlaube Behälter in dieser Werkstatt" + +#: .\inventory\models\workshop.py:26 .\inventory\templates\base.html:61 +#: .\inventory\templates\inventory\workshop_detail.html:5 +msgid "Workshop" +msgstr "Werkstatt" + +#: .\inventory\models\workshop.py:27 +#: .\inventory\templates\inventory\tag_detail.html:23 +#: .\inventory\templates\inventory\workshop_list.html:5 +#: .\inventory\templates\inventory\workshop_list.html:8 +msgid "Workshops" +msgstr "Werkstätten" + +#: .\inventory\templates\base.html:43 .\inventory\templates\base.html:46 +#: .\inventory\templates\base.html:47 +#: .\inventory\templates\inventory\distributor_detail.html:45 +#: .\inventory\templates\inventory\distributor_detail.html:58 +#: .\inventory\templates\inventory\search.html:5 +#: .\inventory\templates\inventory\search.html:8 +#: .\inventory\templates\inventory\search.html:13 +#: .\inventory\templates\inventory\search.html:14 +#: .\inventory\templates\inventory\search_result.html:6 +#: .\inventory\templates\inventory\search_result.html:9 +#: .\inventory\templates\inventory\search_result.html:14 +#: .\inventory\templates\inventory\search_result.html:15 +#: .\inventory\templates\inventory\tag_list.html:43 +#: .\inventory\templates\inventory\tag_list.html:44 +msgid "Search" +msgstr "Suchen" + +#: .\inventory\templates\base.html:68 +msgid "Logout" +msgstr "Ausloggen" + +#: .\inventory\templates\inventory\area_detail.html:16 +#: .\inventory\templates\inventory\area_detail.html:36 +#: .\inventory\templates\inventory\area_detail.html:62 +#: .\inventory\templates\inventory\box-detail.html:20 +#: .\inventory\templates\inventory\cell.html:19 +#: .\inventory\templates\inventory\distributor_detail.html:17 +#: .\inventory\templates\inventory\distributor_list.html:33 +#: .\inventory\templates\inventory\item_detail.html:18 +#: .\inventory\templates\inventory\item_list.html:28 +#: .\inventory\templates\inventory\manufacturer_detail.html:17 +#: .\inventory\templates\inventory\manufacturer_list.html:33 +#: .\inventory\templates\inventory\tag_detail.html:16 +#: .\inventory\templates\inventory\tag_detail.html:33 +#: .\inventory\templates\inventory\tag_detail.html:53 +#: .\inventory\templates\inventory\tag_detail.html:78 +#: .\inventory\templates\inventory\tag_detail.html:103 +#: .\inventory\templates\inventory\workshop_detail.html:16 +#: .\inventory\templates\inventory\workshop_detail.html:34 +#: .\inventory\templates\inventory\workshop_detail.html:59 +#: .\inventory\templates\inventory\workshop_list.html:21 +msgid "Edit" +msgstr "Bearbeiten" + +#: .\inventory\templates\inventory\area_detail.html:41 +#: .\inventory\templates\inventory\workshop_detail.html:39 +msgid "No areas defined" +msgstr "Keine Bereiche definiert" + +#: .\inventory\templates\inventory\area_detail.html:47 +#: .\inventory\templates\inventory\workshop_detail.html:45 +msgid "Create new area..." +msgstr "Neuen Bereich anlegen..." + +#: .\inventory\templates\inventory\area_detail.html:67 +#: .\inventory\templates\inventory\workshop_detail.html:64 +msgid "No containers defined" +msgstr "Keine Behälter definiert..." + +#: .\inventory\templates\inventory\area_detail.html:73 +#: .\inventory\templates\inventory\workshop_detail.html:70 +msgid "Create new container..." +msgstr "Neuen Behälter anlegen..." + +#: .\inventory\templates\inventory\box-detail.html:12 +#: .\inventory\templates\inventory\distributor_detail.html:9 +#: .\inventory\templates\inventory\item_detail.html:10 +#: .\inventory\templates\inventory\manufacturer_detail.html:9 +#: .\inventory\templates\inventory\tag_detail.html:8 +#: .\inventory\templates\inventory\workshop_detail.html:8 +msgid "Back" +msgstr "Zurück" + +#: .\inventory\templates\inventory\cell.html:21 +msgid "Details" +msgstr "Details" + +#: .\inventory\templates\inventory\cell.html:26 +#: .\inventory\templates\inventory\item_detail.html:42 +msgid "Low stock" +msgstr "Wenig Vorrat" + +#: .\inventory\templates\inventory\cell.html:43 +msgid "New item..." +msgstr "Neues Teil..." + +#: .\inventory\templates\inventory\distributor_detail.html:57 +msgid "Search query" +msgstr "Suchanfrage" + +#: .\inventory\templates\inventory\distributor_detail.html:83 +#: .\inventory\templates\inventory\item_detail.html:60 +#: .\inventory\templates\inventory\manufacturer_detail.html:51 +#: .\inventory\templates\inventory\tag_list.html:50 +msgid "No tags" +msgstr "Keine Tags" + +#: .\inventory\templates\inventory\distributor_list.html:8 +#: .\inventory\templates\inventory\tag_list.html:9 +#: .\inventory\templates\inventory\workshop_list.html:8 +#: .\inventory\templates\registration\login.html:5 +msgid "Inventory management" +msgstr "Inventarverwaltung" + +#: .\inventory\templates\inventory\distributor_list.html:43 +msgid "Create new distributor..." +msgstr "Neuen Händler anlegen..." + +#: .\inventory\templates\inventory\item_detail.html:36 +#: .\inventory\templates\inventory\item_detail.html:45 +msgid "Amount" +msgstr "Anzahl" + +#: .\inventory\templates\inventory\item_detail.html:46 +msgid "Update" +msgstr "Speichern" + +#: .\inventory\templates\inventory\item_detail.html:108 +msgid "Sum" +msgstr "Summe" + +#: .\inventory\templates\inventory\item_detail.html:114 +msgid "Last ordered" +msgstr "Zuletzt bestellt" + +#: .\inventory\templates\inventory\manufacturer_list.html:8 +msgid "Inventory Management" +msgstr "Inventarverwaltung" + +#: .\inventory\templates\inventory\manufacturer_list.html:42 +msgid "Create new manufacturer..." +msgstr "Neuen Hersteller anlegen..." + +#: .\inventory\templates\inventory\pagination.html:6 +#: .\inventory\templates\inventory\search_result.html:33 +msgid "First page" +msgstr "Erste Seite" + +#: .\inventory\templates\inventory\pagination.html:7 +#: .\inventory\templates\inventory\search_result.html:34 +msgid "Previous page" +msgstr "Vorherige Seite" + +#: .\inventory\templates\inventory\pagination.html:13 +#: .\inventory\templates\inventory\search_result.html:38 +msgid "Next page" +msgstr "Nächste Seite" + +#: .\inventory\templates\inventory\pagination.html:14 +#: .\inventory\templates\inventory\search_result.html:39 +msgid "Last page" +msgstr "Letzte Seite" + +#: .\inventory\templates\inventory\search_result.html:18 +msgid "Search result for" +msgstr "Suchergebnisse für" + +#: .\inventory\templates\inventory\search_result.html:27 +msgid "Nothing found" +msgstr "Nichts gefunden" + +#: .\inventory\templates\inventory\search_result_item.html:11 +msgid "Contained in" +msgstr "Im Behälter" + +#: .\inventory\templates\inventory\set_index.html:10 +msgid "Currently set as index page" +msgstr "Momentan als Index-Seite definiert" + +#: .\inventory\templates\inventory\set_index.html:12 +msgid "Promote to index page" +msgstr "Als Index-Seite definieren" + +#: .\inventory\templates\inventory\tag_list.html:55 +msgid "Create new tag..." +msgstr "Neues Tag anlegen..." + +#: .\inventory\templates\inventory\workshop_list.html:30 +msgid "Create new workshop..." +msgstr "Neue Werkstatt anlegen..." + +#: .\inventory\templates\registration\login.html:5 +msgid "Login" +msgstr "Einloggen" + +#: .\inventory\templates\registration\login.html:11 +msgid "Your username and password didn't match. Please try again." +msgstr "" +"Benutzername und Passwort passen nicht zusammen, bitte versuche es noch " +"einmal." + +#: .\inventory\templates\registration\login.html:17 +msgid "" +"\n" +" Your account doesn't have access to this page. To proceed,\n" +" please login with an account that has access.\n" +" " +msgstr "" +"\n" +" Dein Zugang hat keinen Zugriff auf diese Seite.\n" +" Zum Fortfahren bitte mit einem Zugang einloggen der die\n" +" entsprechenden Rechte besitzt!\n" +" " + +#: .\inventory\templates\registration\login.html:24 +msgid "" +"\n" +" Please login to see this page.\n" +" " +msgstr "" +"\n" +" Bitte einloggen um auf diese Seite Zugriff zu bekommen.\n" +" " + +#: .\inventory\templates\registration\login.html:49 +msgid "Lost password?" +msgstr "Passwort vergessen?" + +#: .\inventory_project\settings.py:121 +msgid "German" +msgstr "Deutsch" + +#: .\inventory_project\settings.py:122 +msgid "English" +msgstr "Englisch" diff --git a/inventory/models/area.py b/inventory/models/area.py index 275127d..0a5834f 100644 --- a/inventory/models/area.py +++ b/inventory/models/area.py @@ -1,14 +1,19 @@ +from django.utils.translation import gettext_lazy as _ from django.urls import reverse from django.db import models from .container import Container, CanBeContained class Area(CanBeContained, Container): - name = models.CharField(max_length=255, unique=True) - description = models.CharField(max_length=4096) - show_sub_area = models.BooleanField(default=True, help_text="Allow sub-areas to be defined in this area") - created_at = models.DateTimeField(auto_now_add=True) - changed_at = models.DateTimeField(auto_now=True) + name = models.CharField(_("Name"), max_length=255, unique=True) + description = models.CharField(_("Description"), max_length=4096) + show_sub_area = models.BooleanField( + _("Show sub area"), + default=True, + help_text=_("Allow sub-areas to be defined in this area") + ) + created_at = models.DateTimeField(_("Created at"), auto_now_add=True) + changed_at = models.DateTimeField(_("Changed at"), auto_now=True) @property def url(self): @@ -16,3 +21,5 @@ class Area(CanBeContained, Container): class Meta: ordering = ("name", ) + verbose_name = _("Area") + verbose_name_plural = _("Areas") diff --git a/inventory/models/box.py b/inventory/models/box.py index eca2fdc..f54e738 100644 --- a/inventory/models/box.py +++ b/inventory/models/box.py @@ -1,3 +1,4 @@ +from django.utils.translation import gettext_lazy as _ from django.urls import reverse from django.utils.text import slugify from django.template.loader import get_template, TemplateDoesNotExist @@ -6,15 +7,12 @@ from .container import CanBeContained, Container class Box(CanBeContained, Container): - name = models.CharField(max_length=255, unique=True) - description = models.CharField(max_length=4096) - tags = models.ManyToManyField('inventory.Tag', blank=True) + name = models.CharField(_("Name"), max_length=255, unique=True) + description = models.CharField(_("Description"), max_length=4096) + tags = models.ManyToManyField('inventory.Tag', verbose_name=_("Tags"), blank=True) - created_at = models.DateTimeField(auto_now_add=True) - changed_at = models.DateTimeField(auto_now=True) - - class Meta: - verbose_name_plural = 'Boxes' + created_at = models.DateTimeField(_("Created at"), auto_now_add=True) + changed_at = models.DateTimeField(_("Changed at"), auto_now=True) @property def template_name(self): @@ -34,3 +32,5 @@ class Box(CanBeContained, Container): class Meta: ordering = ("name", ) + verbose_name = _("Box") + verbose_name_plural = _("Boxes") diff --git a/inventory/models/container.py b/inventory/models/container.py index bfb81fb..b68508a 100644 --- a/inventory/models/container.py +++ b/inventory/models/container.py @@ -1,9 +1,16 @@ +from django.utils.translation import gettext_lazy as _ from django.db import models from django.apps import apps class Container(models.Model): - layout = models.ForeignKey('inventory.Layout', on_delete=models.PROTECT, null=True, blank=True) + layout = models.ForeignKey( + 'inventory.Layout', + verbose_name=_("Layout"), + on_delete=models.PROTECT, + null=True, + blank=True + ) @property def subclass(self): @@ -25,7 +32,7 @@ class Container(models.Model): _, obj = self.subclass if obj is not None: return obj.name - return 'Container' + return _("Container") @property def url(self): @@ -34,15 +41,19 @@ class Container(models.Model): return obj.url return None + class Meta: + verbose_name = _("Container") + verbose_name_plural = _("Containers") class CanBeContained(models.Model): container = models.ForeignKey( 'inventory.Container', + verbose_name=_("Container"), related_name="%(class)s_related", null=True, on_delete=models.CASCADE ) - index = models.PositiveIntegerField('Index of compartment in layout') + index = models.PositiveIntegerField(_("Index of compartment in layout")) class Meta: abstract = True diff --git a/inventory/models/distributor.py b/inventory/models/distributor.py index 554d15c..52ee73c 100644 --- a/inventory/models/distributor.py +++ b/inventory/models/distributor.py @@ -1,22 +1,25 @@ +from django.utils.translation import gettext_lazy as _ from django.db import models class Distributor(models.Model): - name = models.CharField(max_length=255, unique=True) - description = models.CharField(max_length=4096) + name = models.CharField(_("Name"), max_length=255, unique=True) + description = models.CharField(_("Description"), max_length=4096) - web_link = models.URLField(null=True, blank=True) - search_link = models.URLField(help_text='Use {} for search placeholder', null=True, blank=True) - phone = models.CharField(max_length=128, null=True, blank=True) - email = models.EmailField(null=True, blank=True, default=None) - icon = models.ImageField(null=True, blank=True) - tags = models.ManyToManyField('inventory.Tag', blank=True) + web_link = models.URLField(_("Web link"), null=True, blank=True) + search_link = models.URLField(_("Search link"), help_text=_("Use {} for search placeholder"), null=True, blank=True) + phone = models.CharField(_("Phone"), max_length=128, null=True, blank=True) + email = models.EmailField(_("E-Mail"), null=True, blank=True, default=None) + icon = models.ImageField(_("Icon"), null=True, blank=True) + tags = models.ManyToManyField('inventory.Tag', verbose_name=_("Tags"), blank=True) - created_at = models.DateTimeField(auto_now_add=True) - changed_at = models.DateTimeField(auto_now=True) + created_at = models.DateTimeField(_("Created at"), auto_now_add=True) + changed_at = models.DateTimeField(_("Changed at"), auto_now=True) def __str__(self): return self.name class Meta: - ordering = ("name", ) \ No newline at end of file + ordering = ("name", ) + verbose_name = _("Distributor") + verbose_name_plural = _("Distributors") diff --git a/inventory/models/documentation.py b/inventory/models/documentation.py index d044092..e66e932 100644 --- a/inventory/models/documentation.py +++ b/inventory/models/documentation.py @@ -1,10 +1,11 @@ +from django.utils.translation import gettext_lazy as _ from django.db import models class Documentation(models.Model): - name = models.CharField(max_length=255, unique=True) - created_at = models.DateTimeField(auto_now_add=True) - changed_at = models.DateTimeField(auto_now=True) + name = models.CharField(_("Name"), max_length=255, unique=True) + created_at = models.DateTimeField(_("Created at"), auto_now_add=True) + changed_at = models.DateTimeField(_("Changed at"), auto_now=True) file = models.FileField() @@ -13,3 +14,5 @@ class Documentation(models.Model): class Meta: ordering = ("name", ) + verbose_name = _("Documentation") + verbose_name_plural = _("Documentation") diff --git a/inventory/models/form_factor.py b/inventory/models/form_factor.py index 767ada2..d68815c 100644 --- a/inventory/models/form_factor.py +++ b/inventory/models/form_factor.py @@ -1,16 +1,17 @@ +from django.utils.translation import gettext_lazy as _ from django.db import models class FormFactor(models.Model): - name = models.CharField(max_length=255, unique=True, db_collation="numeric") - description = models.CharField(max_length=4096, db_collation="numeric") + name = models.CharField(_("Name"), max_length=255, unique=True, db_collation="numeric") + description = models.CharField(_("Description"), max_length=4096, db_collation="numeric") - icon = models.ImageField(null=True, blank=True) - datasheet = models.FileField(null=True, blank=True) - tags = models.ManyToManyField('inventory.Tag', blank=True) + icon = models.ImageField(_("Icon"), null=True, blank=True) + datasheet = models.FileField(_("Datasheet"), null=True, blank=True) + tags = models.ManyToManyField('inventory.Tag', verbose_name=_("Tags"), blank=True) - created_at = models.DateTimeField(auto_now_add=True) - changed_at = models.DateTimeField(auto_now=True) + created_at = models.DateTimeField(_("Created at"), auto_now_add=True) + changed_at = models.DateTimeField(_("Changed at"), auto_now=True) def __str__(self): items = [self.name] @@ -19,3 +20,5 @@ class FormFactor(models.Model): class Meta: ordering = ("name", ) + verbose_name = _("Form factor") + verbose_name_plural = _("Form factors") diff --git a/inventory/models/item.py b/inventory/models/item.py index 676223f..474583a 100644 --- a/inventory/models/item.py +++ b/inventory/models/item.py @@ -1,32 +1,80 @@ +from django.utils.translation import gettext_lazy as _ from django.db import models from .container import CanBeContained class Item(CanBeContained): - name = models.TextField(max_length=255, db_collation="numeric") - description = models.CharField(max_length=4096, db_collation="numeric") - size = models.PositiveIntegerField(default=1, help_text="Number of sub-compartments this item takes up") - form_factor = models.ForeignKey('inventory.FormFactor', null=True, blank=True, on_delete=models.PROTECT) - manufacturer = models.ForeignKey('inventory.Manufacturer', null=True, blank=True, on_delete=models.PROTECT) - distributor = models.ForeignKey('inventory.Distributor', null=True, blank=True, on_delete=models.PROTECT) - distributor_item_no = models.CharField(max_length=255, null=True, blank=True) - price = models.DecimalField(decimal_places=3, max_digits=7, null=True, blank=True) - last_ordered_on = models.DateField(null=True, blank=True) - documentation = models.ManyToManyField('inventory.Documentation', related_name='items', blank=True) - tags = models.ManyToManyField('inventory.Tag', blank=True) + name = models.TextField(_("Name"), max_length=255, db_collation="numeric") + description = models.CharField(_("Description"), max_length=4096, db_collation="numeric") + size = models.PositiveIntegerField( + _("Size"), + default=1, + help_text=_("Number of sub-compartments this item takes up") + ) + form_factor = models.ForeignKey( + 'inventory.FormFactor', + verbose_name=_("Form factor"), + null=True, + blank=True, + on_delete=models.PROTECT + ) + manufacturer = models.ForeignKey( + 'inventory.Manufacturer', + verbose_name=_("Manufacturer"), + null=True, + blank=True, + on_delete=models.PROTECT + ) + distributor = models.ForeignKey( + 'inventory.Distributor', + verbose_name=_("Distributor"), + null=True, + blank=True, + on_delete=models.PROTECT + ) + distributor_item_no = models.CharField( + _("Distributor item no."), + max_length=255, + null=True, + blank=True + ) + price = models.DecimalField( + _("Price"), + decimal_places=3, + max_digits=7, + null=True, + blank=True + ) + last_ordered_on = models.DateField(_("Last ordered on"), null=True, blank=True) + documentation = models.ManyToManyField( + 'inventory.Documentation', + verbose_name=_("Documentation"), + related_name='items', + blank=True + ) + tags = models.ManyToManyField( + 'inventory.Tag', + verbose_name=_("Tags"), + blank=True + ) count = models.PositiveIntegerField( + _("Count"), default=1, null=False, - help_text="Number of parts available" + help_text=_("Number of parts available") ) low_count = models.PositiveIntegerField( + _("Low watermark"), default=0, null=False, - help_text="Low watermark on which to alert ordering more" + help_text=_("Low watermark on which to alert ordering more") ) + metadata = models.JSONField(_("Custom metadata, used by templates"), blank=True, null=True) + created_at = models.DateTimeField(_("Created at"), auto_now_add=True) + changed_at = models.DateTimeField(_("Changed at"), auto_now=True) def __str__(self): items = [self.name, self.description] @@ -47,3 +95,5 @@ class Item(CanBeContained): class Meta: ordering = ("name", ) + verbose_name = _("Item") + verbose_name_plural = _("Items") diff --git a/inventory/models/layout.py b/inventory/models/layout.py index 1358194..645ec5f 100644 --- a/inventory/models/layout.py +++ b/inventory/models/layout.py @@ -1,16 +1,19 @@ +from django.utils.translation import gettext_lazy as _ from django.db import models class Layout(models.Model): - name = models.CharField(max_length=255, unique=True) - description = models.CharField(max_length=4096) - created_at = models.DateTimeField(auto_now_add=True) - changed_at = models.DateTimeField(auto_now=True) - data = models.JSONField() - template_name = models.CharField(max_length=255, null=True, blank=True) + name = models.CharField(_("Name"), max_length=255, unique=True) + description = models.CharField(_("Description"), max_length=4096) + created_at = models.DateTimeField(_("Created at"), auto_now_add=True) + changed_at = models.DateTimeField(_("Changed at"), auto_now=True) + data = models.JSONField(_("Data")) + template_name = models.CharField(_("Template name"), max_length=255, null=True, blank=True) def __str__(self): return self.name class Meta: ordering = ("name", ) + verbose_name = _("Layout") + verbose_name_plural = _("Layouts") diff --git a/inventory/models/manufacturer.py b/inventory/models/manufacturer.py index 9a94be5..57e4ead 100644 --- a/inventory/models/manufacturer.py +++ b/inventory/models/manufacturer.py @@ -1,19 +1,22 @@ +from django.utils.translation import gettext_lazy as _ from django.db import models class Manufacturer(models.Model): - name = models.CharField(max_length=255, unique=True) - description = models.CharField(max_length=4096, blank=True) + name = models.CharField(_("Name"), max_length=255, unique=True) + description = models.CharField(_("Description"), max_length=4096, blank=True) - web_link = models.URLField(null=True, blank=True) - icon = models.ImageField(null=True, blank=True) - tags = models.ManyToManyField('inventory.Tag', blank=True) + web_link = models.URLField(_("Web link"), null=True, blank=True) + icon = models.ImageField(_("Icon"), null=True, blank=True) + tags = models.ManyToManyField('inventory.Tag', verbose_name=_("Tags"), blank=True) - created_at = models.DateTimeField(auto_now_add=True) - changed_at = models.DateTimeField(auto_now=True) + created_at = models.DateTimeField(_("Created at"), auto_now_add=True) + changed_at = models.DateTimeField(_("Changed at"), auto_now=True) def __str__(self): return self.name class Meta: ordering = ("name", ) + verbose_name = _("Manufacturer") + verbose_name_plural = _("Manufacturers") diff --git a/inventory/models/settings.py b/inventory/models/settings.py index 71b066b..2953a9a 100644 --- a/inventory/models/settings.py +++ b/inventory/models/settings.py @@ -1,3 +1,5 @@ +from django.utils.translation import gettext_lazy as _ +from django.utils.translation import gettext from django.db import models @@ -8,20 +10,23 @@ class Settings(models.Model): default=None, null=True, blank=True, + help_text=_("Default container to display when calling the index page") ) track_amount = models.BooleanField( default=False, - help_text="Show item count in overview and warn on low watermarks" + help_text=_("Show item count in overview and warn on low watermarks") ) - currency = models.CharField(max_length=30, help_text="Currency name", default="Euro") - currency_symbol = models.CharField(max_length=20, default="€") + currency = models.CharField(_("Currency"), max_length=30, help_text=_("Currency name"), default="Euro") + currency_symbol = models.CharField(_("Currency symbol"), max_length=20, default="€") currency_symbol_position = models.BooleanField( + _("Currency symbol at end"), default=True, - help_text="Currency symbol after amount" + help_text=_("Currency symbol after amount") ) def __str__(self): - return 'Settings' + return gettext("Settings") class Meta: - verbose_name_plural = 'Settings' + verbose_name = _("Settings") + verbose_name_plural = _("Settings") diff --git a/inventory/models/tag.py b/inventory/models/tag.py index 895db0d..ac5a0e2 100644 --- a/inventory/models/tag.py +++ b/inventory/models/tag.py @@ -1,14 +1,17 @@ +from django.utils.translation import gettext_lazy as _ from django.db import models class Tag(models.Model): - name = models.CharField(max_length=255, unique=True, db_collation="numeric") - description = models.CharField(max_length=4096, db_collation="numeric") - created_at = models.DateTimeField(auto_now_add=True) - changed_at = models.DateTimeField(auto_now=True) + name = models.CharField(_("Name"), max_length=255, unique=True, db_collation="numeric") + description = models.CharField(_("Description"), max_length=4096, db_collation="numeric") + created_at = models.DateTimeField(_("Created at"), auto_now_add=True) + changed_at = models.DateTimeField(_("Changed at"), auto_now=True) def __str__(self): return self.name class Meta: ordering = ['name', 'pk'] + verbose_name = _("Tag") + verbose_name_plural = _("Tags") diff --git a/inventory/models/workshop.py b/inventory/models/workshop.py index d25cd4f..c06902f 100644 --- a/inventory/models/workshop.py +++ b/inventory/models/workshop.py @@ -1,16 +1,21 @@ +from django.utils.translation import gettext_lazy as _ from django.urls import reverse from django.db import models from .container import Container class Workshop(Container): - name = models.CharField(max_length=255, unique=True) - description = models.CharField(max_length=4096) - show_boxes = models.BooleanField(default=True, help_text="Allow boxes to be defined directly in this workshop") - tags = models.ManyToManyField('inventory.Tag', blank=True) + name = models.CharField(_("Name"), max_length=255, unique=True) + description = models.CharField(_("Description"), max_length=4096) + show_boxes = models.BooleanField( + _("Show boxes"), + default=True, + help_text=_("Allow boxes to be defined directly in this workshop") + ) + tags = models.ManyToManyField('inventory.Tag', verbose_name=_("Tags"), blank=True) - created_at = models.DateTimeField(auto_now_add=True) - changed_at = models.DateTimeField(auto_now=True) + created_at = models.DateTimeField(_("Created at"), auto_now_add=True) + changed_at = models.DateTimeField(_("Changed at"), auto_now=True) @property def url(self): @@ -18,3 +23,5 @@ class Workshop(Container): class Meta: ordering = ("name", ) + verbose_name = _("Workshop") + verbose_name_plural = _("Workshops") diff --git a/inventory/templates/base.html b/inventory/templates/base.html index 908708a..8e9e976 100644 --- a/inventory/templates/base.html +++ b/inventory/templates/base.html @@ -1,6 +1,8 @@ {% load static %} +{% load i18n %} +{% get_current_language as LANGUAGE_CODE %} - + @@ -38,32 +40,32 @@ } } - +
- - + +
  • - +
  • - +
  • - +
  • - +
  • {% if user.is_authenticated %}
    {% csrf_token %} - +
    {% endif %} diff --git a/inventory/templates/inventory/area_detail.html b/inventory/templates/inventory/area_detail.html index 02e6d0b..748f2ae 100644 --- a/inventory/templates/inventory/area_detail.html +++ b/inventory/templates/inventory/area_detail.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load static %} +{% load i18n %} {% block title %}{{ area.name }}{% endblock %} @@ -12,7 +13,7 @@ {% block header_icons %} {% if user.is_staff %}
  • - +
  • {% include 'inventory/set_index.html' with item=area is_index=is_index %} {% endif %} @@ -21,7 +22,7 @@ {% block content %} {% if area.show_sub_area %} -

    Areas

    +

    {% translate 'Areas' %}

    @@ -32,22 +33,22 @@ {% empty %} - + {% endfor %}
    {% if user.is_staff %} - + {% endif %}
    No areas defined
    {% translate 'No areas defined' %}
    {% if user.is_staff %} -

    Create new area...

    +

    {% translate 'Create new area...' %}

    {% endif %} {% endif %} -

    Containers

    +

    {% translate 'Containers' %}

    @@ -58,17 +59,17 @@ {% empty %} - + {% endfor %}
    {% if user.is_staff %} - + {% endif %}
    No containers defined
    {% translate 'No containers defined' %}
    {% if user.is_staff %} -

    Create new container...

    +

    {% translate 'Create new container...' %}

    {% endif %} {% endblock %} \ No newline at end of file diff --git a/inventory/templates/inventory/box-detail.html b/inventory/templates/inventory/box-detail.html index 67976c8..0efd05b 100644 --- a/inventory/templates/inventory/box-detail.html +++ b/inventory/templates/inventory/box-detail.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load static %} +{% load i18n %} {% block head %} @@ -8,7 +9,7 @@ {% block title %}{{ object.name }}{% endblock %} {% block header_bar %} - + {{ object.name }} {{ object.description}} {% endblock %} @@ -16,7 +17,7 @@ {% block header_icons %} {% if user.is_staff %}
  • - +
  • {% include 'inventory/set_index.html' with item=object is_index=is_index %} {% endif %} diff --git a/inventory/templates/inventory/box-generic.html b/inventory/templates/inventory/box-generic.html index 995ea02..86e8ef2 100644 --- a/inventory/templates/inventory/box-generic.html +++ b/inventory/templates/inventory/box-generic.html @@ -1,9 +1,10 @@ {% extends "inventory/box-detail.html" %} +{% load i18n %} {% block content %} {% if box.box_related.exists %} -

    Boxes

    +

    {% translate 'Boxes' %}