Features: 1) 2.8.9 update

Fixes: 1) wtf go read diff;

Extra: ???
This commit is contained in:
Egor Pavlovich Gorbunov 2025-06-18 15:05:58 +03:00
parent 6ce7b7a6f9
commit 328ccaa615
373 changed files with 13931 additions and 20027 deletions

View file

@ -81,4 +81,5 @@ media/
.env .env
# Host's scripts # Host's scripts
scripts scripts/Windows
scripts/Unix

1
.gitignore vendored
View file

@ -60,7 +60,6 @@ htmlcov/
.tox/ .tox/
.nox/ .nox/
.scrapy .scrapy
.coverage.*
.cover .cover
.pybuilder/ .pybuilder/

View file

@ -0,0 +1,49 @@
# syntax=docker/dockerfile:1
FROM python:3.12-bookworm
LABEL authors="fureunoir"
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
LANG=C.UTF-8 \
DEBIAN_FRONTEND=noninteractive \
PATH="/root/.local/bin:$PATH"
WORKDIR /app
RUN set -eux; \
sed -i 's|https://deb.debian.org/debian|https://ftp.uk.debian.org/debian|g' /etc/apt/sources.list.d/debian.sources; \
apt-get update; \
apt-get install -y --no-install-recommends wget gnupg; \
wget -qO - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -; \
echo "deb http://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" \
> /etc/apt/sources.list.d/pgdg.list; \
apt-get update; \
apt-get install -y --no-install-recommends --fix-missing \
build-essential \
libpq-dev \
gettext \
libgettextpo-dev \
graphviz-dev \
libgts-dev \
libpq5 \
graphviz \
binutils \
libproj-dev \
postgresql-client-17 \
gdal-bin; \
rm -rf /var/lib/apt/lists/*; \
pip install --upgrade pip; \
curl -sSL https://install.python-poetry.org | python3
COPY pyproject.toml pyproject.toml
COPY poetry.lock poetry.lock
RUN poetry config virtualenvs.create false
RUN poetry install --extras="graph worker openai testing" --no-interaction --no-ansi
COPY ./scripts/Docker/app-entrypoint.sh /usr/local/bin/app-entrypoint.sh
RUN chmod +x /usr/local/bin/app-entrypoint.sh
COPY . .
ENTRYPOINT ["app-entrypoint.sh"]

View file

@ -39,6 +39,11 @@ COPY pyproject.toml pyproject.toml
COPY poetry.lock poetry.lock COPY poetry.lock poetry.lock
RUN poetry config virtualenvs.create false RUN poetry config virtualenvs.create false
RUN poetry install -E graph -E worker -E AI --no-interaction --no-ansi RUN poetry install --extras="worker openai" --no-interaction --no-ansi
COPY . . COPY ./scripts/Docker/beat-entrypoint.sh /usr/local/bin/beat-entrypoint.sh
RUN chmod +x /usr/local/bin/beat-entrypoint.sh
COPY . .
ENTRYPOINT ["beat-entrypoint.sh"]

View file

@ -0,0 +1,49 @@
# syntax=docker/dockerfile:1
FROM python:3.12-bookworm
LABEL authors="fureunoir"
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
LANG=C.UTF-8 \
DEBIAN_FRONTEND=noninteractive \
PATH="/root/.local/bin:$PATH"
WORKDIR /app
RUN set -eux; \
sed -i 's|https://deb.debian.org/debian|https://ftp.uk.debian.org/debian|g' /etc/apt/sources.list.d/debian.sources; \
apt-get update; \
apt-get install -y --no-install-recommends wget gnupg; \
wget -qO - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -; \
echo "deb http://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" \
> /etc/apt/sources.list.d/pgdg.list; \
apt-get update; \
apt-get install -y --no-install-recommends --fix-missing \
build-essential \
libpq-dev \
gettext \
libgettextpo-dev \
graphviz-dev \
libgts-dev \
libpq5 \
graphviz \
binutils \
libproj-dev \
postgresql-client-17 \
gdal-bin; \
rm -rf /var/lib/apt/lists/*; \
pip install --upgrade pip; \
curl -sSL https://install.python-poetry.org | python3
COPY pyproject.toml pyproject.toml
COPY poetry.lock poetry.lock
RUN poetry config virtualenvs.create false
RUN poetry install --extras="worker openai" --no-interaction --no-ansi
COPY ./scripts/Docker/worker-entrypoint.sh /usr/local/bin/worker-entrypoint.sh
RUN chmod +x /usr/local/bin/worker-entrypoint.sh
COPY . .
ENTRYPOINT ["worker-entrypoint.sh"]

View file

@ -3,7 +3,8 @@
![LOGO](core/docs/images/evibes-big.png) ![LOGO](core/docs/images/evibes-big.png)
eVibes is an eCommerce backend service built with Django. Its designed for flexibility, making it ideal for various use eVibes is an eCommerce backend service built with Django. Its designed for flexibility, making it ideal for various use
cases and learning Django skills. The project is easy to customize, allowing for straightforward editing and extension. cases and learning Django skills. The project is straightforward to customize, allowing for straightforward editing and
extension.
## Table of Contents ## Table of Contents
@ -20,7 +21,7 @@ cases and learning Django skills. The project is easy to customize, allowing for
## Features ## Features
- **Modular Architecture**: Easily extend and customize the backend to fit your needs. - **Modular Architecture**: Extend and customize the backend to fit your needs.
- **Dockerized Deployment**: Quick setup and deployment using Docker and Docker Compose. - **Dockerized Deployment**: Quick setup and deployment using Docker and Docker Compose.
- **Asynchronous Task Processing**: Integrated Celery workers and beat scheduler for background tasks. - **Asynchronous Task Processing**: Integrated Celery workers and beat scheduler for background tasks.
- **GraphQL and REST APIs**: Supports both GraphQL and RESTful API endpoints. - **GraphQL and REST APIs**: Supports both GraphQL and RESTful API endpoints.
@ -32,7 +33,7 @@ cases and learning Django skills. The project is easy to customize, allowing for
### Prerequisites ### Prerequisites
- Docker and Docker Compose installed on your machine - that's it! - Docker and Docker Compose are installed on your machine.
### Installation ### Installation
@ -49,7 +50,7 @@ cases and learning Django skills. The project is easy to customize, allowing for
git checkout storefront-<option: astro, nuxt, remix, svelte, solid, analog > git checkout storefront-<option: astro, nuxt, remix, svelte, solid, analog >
``` ```
3. Generate your .env file. Check and confirm the contents afterwards. 3. Generate your .env file. Check and confirm the contents afterward.
- Windows - Windows
```powershell ```powershell
@ -85,29 +86,29 @@ cases and learning Django skills. The project is easy to customize, allowing for
6. Bring to production. 6. Bring to production.
Include `nginx` file to your nginx configuration, you really want to install and Include `nginx` file to your nginx configuration, you really want to install and
run [Certbot](https://certbot.eff.org/) afterwards! run [Certbot](https://certbot.eff.org/) afterward!
## Configuration ## Configuration
### Dockerfile ### Dockerfile
Don't forget to change the Remember to change the
`RUN sed -i 's|https://deb.debian.org/debian|https://ftp.<locale>.debian.org/debian|g' /etc/apt/sources.list.d/debian.sources` `RUN sed -i 's|https://deb.debian.org/debian|https://ftp.<locale>.debian.org/debian|g' /etc/apt/sources.list.d/debian.sources`
before running installment scripts before running installment scripts
### nginx ### nginx
Please comment-out SSL-related lines, then apply needed configurations, run `certbot --cert-only --nginx`, Please comment-out SSL-related lines, then apply necessary configurations, run `certbot --cert-only --nginx`,
decomment previously commented lines and enjoy eVibes over HTTPS! decomment previously commented lines and enjoy eVibes over HTTPS!
### .env ### .env
After .env file generation, you may want to edit some of it's values such as macroservices' API keys, database password, After .env file generation, you may want to edit some of its values, such as macroservices' API keys, database password,
redis password, etc. redis password, etc.
## Usage ## Usage
- Add needed subdomains to DNS-settings of your domain, those are: - Add necessary subdomains to DNS-settings of your domain, those are:
1. @.your-domain.com 1. @.your-domain.com
2. www.your-domain.com 2. www.your-domain.com

View file

@ -35,9 +35,10 @@ class PostAdmin(admin.ModelAdmin):
def preview_html(self, obj): def preview_html(self, obj):
html = obj.content.html or "<em>{}</em>".format(_("(no content yet)")) html = obj.content.html or "<em>{}</em>".format(_("(no content yet)"))
# noinspection DjangoSafeString
return mark_safe(html) return mark_safe(html)
preview_html.short_description = _("rendered HTML") preview_html.short_description = _("rendered HTML") # type: ignore
@admin.register(PostTag) @admin.register(PostTag)

View file

@ -1,13 +1,13 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: ar-AR\n" "Language: ar-ar\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(لا يوجد محتوى بعد)" msgstr "(لا يوجد محتوى بعد)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "تم تقديمه بتنسيق HTML" msgstr "تم تقديمه بتنسيق HTML"
@ -25,47 +25,52 @@ msgstr "تم تقديمه بتنسيق HTML"
msgid "blog" msgid "blog"
msgstr "المدونة" msgstr "المدونة"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "عنوان المنشور" msgstr "عنوان المنشور"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "العنوان" msgstr "العنوان"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "المنشور" msgstr "المنشور"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "المنشورات" msgstr "المنشورات"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr ""
"ملفات تخفيض السعر غير مدعومة Yer - استخدم محتوى تخفيض السعر بدلاً من ذلك!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "يجب توفير ملف ترميز أو محتوى ترميز مخفض - متنافيان" msgstr "يجب توفير ملف ترميز أو محتوى ترميز مخفض - متنافيان"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "معرّف العلامة الداخلي لعلامة المنشور" msgstr "معرّف العلامة الداخلي لعلامة المنشور"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "اسم العلامة" msgstr "اسم العلامة"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "اسم سهل الاستخدام لعلامة المنشور" msgstr "اسم سهل الاستخدام لعلامة المنشور"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "اسم عرض العلامة" msgstr "اسم عرض العلامة"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "علامة المشاركة" msgstr "علامة المشاركة"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "علامات المشاركة" msgstr "علامات المشاركة"

View file

@ -1,13 +1,13 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: cs-CZ\n" "Language: cs-cz\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(zatím bez obsahu)" msgstr "(zatím bez obsahu)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "Vykreslené HTML" msgstr "Vykreslené HTML"
@ -25,49 +25,54 @@ msgstr "Vykreslené HTML"
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "Název příspěvku" msgstr "Název příspěvku"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "Název" msgstr "Název"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "Příspěvek" msgstr "Příspěvek"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "Příspěvky" msgstr "Příspěvky"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr ""
"Soubory Markdown nejsou podporovány - místo toho použijte obsah Markdown!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"musí být poskytnut soubor markdown nebo obsah markdown - vzájemně se " "musí být poskytnut soubor markdown nebo obsah markdown - vzájemně se "
"vylučují." "vylučují."
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "interní identifikátor tagu pro tag příspěvku" msgstr "interní identifikátor tagu pro tag příspěvku"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "Název štítku" msgstr "Název štítku"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Uživatelsky přívětivý název pro značku příspěvku" msgstr "Uživatelsky přívětivý název pro značku příspěvku"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "Zobrazení názvu štítku" msgstr "Zobrazení názvu štítku"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "Označení příspěvku" msgstr "Označení příspěvku"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "Štítky příspěvků" msgstr "Štítky příspěvků"

View file

@ -1,13 +1,13 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: da-DK\n" "Language: da-dk\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(intet indhold endnu)" msgstr "(intet indhold endnu)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "Rendered HTML" msgstr "Rendered HTML"
@ -25,48 +25,52 @@ msgstr "Rendered HTML"
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "Indlæggets titel" msgstr "Indlæggets titel"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "Titel" msgstr "Titel"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "Indlæg" msgstr "Indlæg"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "Indlæg" msgstr "Indlæg"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr "Markdown-filer understøttes ikke - brug markdown-indhold i stedet!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"en markdown-fil eller markdown-indhold skal leveres - gensidigt udelukkende" "en markdown-fil eller markdown-indhold skal leveres - gensidigt udelukkende"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "intern tag-identifikator for indlægs-tagget" msgstr "intern tag-identifikator for indlægs-tagget"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "Tag-navn" msgstr "Tag-navn"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Brugervenligt navn til posttagget" msgstr "Brugervenligt navn til posttagget"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "Navn på tag-visning" msgstr "Navn på tag-visning"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "Tag til indlæg" msgstr "Tag til indlæg"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "Tags til indlæg" msgstr "Tags til indlæg"

View file

@ -1,13 +1,13 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: de-DE\n" "Language: de-de\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(noch kein Inhalt)" msgstr "(noch kein Inhalt)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "Gerendertes HTML" msgstr "Gerendertes HTML"
@ -25,49 +25,55 @@ msgstr "Gerendertes HTML"
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "Titel des Beitrags" msgstr "Titel des Beitrags"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "Titel" msgstr "Titel"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "Beitrag" msgstr "Beitrag"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "Beiträge" msgstr "Beiträge"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr ""
"Markdown-Dateien werden nicht unterstützt - verwenden Sie stattdessen "
"Markdown-Inhalte!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"eine Markdown-Datei oder ein Markdown-Inhalt muss bereitgestellt werden - " "eine Markdown-Datei oder ein Markdown-Inhalt muss bereitgestellt werden - "
"beide schließen sich gegenseitig aus" "beide schließen sich gegenseitig aus"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "interner Tag-Bezeichner für den Post-Tag" msgstr "interner Tag-Bezeichner für den Post-Tag"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "Tag name" msgstr "Tag name"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Benutzerfreundlicher Name für das Post-Tag" msgstr "Benutzerfreundlicher Name für das Post-Tag"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "Tag-Anzeigename" msgstr "Tag-Anzeigename"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "Tag eintragen" msgstr "Tag eintragen"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "Tags eintragen" msgstr "Tags eintragen"

View file

@ -5,9 +5,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -21,7 +21,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(no content yet)" msgstr "(no content yet)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "Rendered HTML" msgstr "Rendered HTML"
@ -29,48 +29,52 @@ msgstr "Rendered HTML"
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "Post's title" msgstr "Post's title"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "Title" msgstr "Title"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "Post" msgstr "Post"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "Posts" msgstr "Posts"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr "Markdown files are not supported yer - use markdown content instead!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "internal tag identifier for the post tag" msgstr "internal tag identifier for the post tag"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "Tag name" msgstr "Tag name"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "User-friendly name for the post tag" msgstr "User-friendly name for the post tag"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "Tag display name" msgstr "Tag display name"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "Post tag" msgstr "Post tag"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "Post tags" msgstr "Post tags"

View file

@ -1,13 +1,13 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: en-US\n" "Language: en-us\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(no content yet)" msgstr "(no content yet)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "Rendered HTML" msgstr "Rendered HTML"
@ -25,48 +25,52 @@ msgstr "Rendered HTML"
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "Post's title" msgstr "Post's title"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "Title" msgstr "Title"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "Post" msgstr "Post"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "Posts" msgstr "Posts"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr "Markdown files are not supported yer - use markdown content instead!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "internal tag identifier for the post tag" msgstr "internal tag identifier for the post tag"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "Tag name" msgstr "Tag name"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "User-friendly name for the post tag" msgstr "User-friendly name for the post tag"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "Tag display name" msgstr "Tag display name"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "Post tag" msgstr "Post tag"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "Post tags" msgstr "Post tags"

View file

@ -1,13 +1,13 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: es-ES\n" "Language: es-es\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(sin contenido aún)" msgstr "(sin contenido aún)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "HTML renderizado" msgstr "HTML renderizado"
@ -25,49 +25,54 @@ msgstr "HTML renderizado"
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "Título del mensaje" msgstr "Título del mensaje"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "Título" msgstr "Título"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "Publicar en" msgstr "Publicar en"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "Puestos" msgstr "Puestos"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr ""
"No se admiten archivos Markdown - ¡utiliza contenido Markdown en su lugar!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"se debe proporcionar un archivo markdown o contenido markdown - mutuamente " "se debe proporcionar un archivo markdown o contenido markdown - mutuamente "
"excluyentes" "excluyentes"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "identificador interno de la etiqueta post" msgstr "identificador interno de la etiqueta post"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "Nombre de la etiqueta" msgstr "Nombre de la etiqueta"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Nombre fácil de usar para la etiqueta de la entrada" msgstr "Nombre fácil de usar para la etiqueta de la entrada"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "Nombre de la etiqueta" msgstr "Nombre de la etiqueta"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "Etiqueta postal" msgstr "Etiqueta postal"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "Etiquetas" msgstr "Etiquetas"

View file

@ -1,13 +1,13 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: fr-FR\n" "Language: fr-fr\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(pas encore de contenu)" msgstr "(pas encore de contenu)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "HTML rendu" msgstr "HTML rendu"
@ -25,49 +25,55 @@ msgstr "HTML rendu"
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "Titre du message" msgstr "Titre du message"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "Titre" msgstr "Titre"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "Poste" msgstr "Poste"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "Postes" msgstr "Postes"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr ""
"Les fichiers Markdown ne sont pas pris en charge - utilisez plutôt du "
"contenu Markdown !"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"un fichier markdown ou un contenu markdown doit être fourni - ils s'excluent " "un fichier markdown ou un contenu markdown doit être fourni - ils s'excluent"
"mutuellement" " mutuellement"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "identifiant interne de la balise post" msgstr "identifiant interne de la balise post"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "Nom du jour" msgstr "Nom du jour"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Nom convivial pour la balise post" msgstr "Nom convivial pour la balise post"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "Nom d'affichage de l'étiquette" msgstr "Nom d'affichage de l'étiquette"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "Tag de poste" msgstr "Tag de poste"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "Tags de la poste" msgstr "Tags de la poste"

View file

@ -2,12 +2,12 @@
# Copyright (C) 2025 EGOR <FUREUNOIR> GORBUNOV # Copyright (C) 2025 EGOR <FUREUNOIR> GORBUNOV
# This file is distributed under the same license as the EVIBES package. # This file is distributed under the same license as the EVIBES package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025. # EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -20,7 +20,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "" msgstr ""
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "" msgstr ""
@ -28,47 +28,51 @@ msgstr ""
msgid "blog" msgid "blog"
msgstr "" msgstr ""
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "" msgstr ""
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "" msgstr ""
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "" msgstr ""
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "" msgstr ""
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr ""
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "" msgstr ""
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "" msgstr ""
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "" msgstr ""
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "" msgstr ""
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "" msgstr ""
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "" msgstr ""

View file

@ -1,13 +1,13 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: it-IT\n" "Language: it-it\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(ancora senza contenuti)" msgstr "(ancora senza contenuti)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "HTML renderizzato" msgstr "HTML renderizzato"
@ -25,49 +25,53 @@ msgstr "HTML renderizzato"
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "Titolo del post" msgstr "Titolo del post"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "Titolo" msgstr "Titolo"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "Posta" msgstr "Posta"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "Messaggi" msgstr "Messaggi"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr "I file Markdown non sono supportati: usa invece i contenuti Markdown!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"deve essere fornito un file markdown o un contenuto markdown - si escludono " "deve essere fornito un file markdown o un contenuto markdown - si escludono "
"a vicenda" "a vicenda"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "identificatore interno del tag post" msgstr "identificatore interno del tag post"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "Nome del tag" msgstr "Nome del tag"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Nome intuitivo per il tag del post" msgstr "Nome intuitivo per il tag del post"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "Nome del tag" msgstr "Nome del tag"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "Post tag" msgstr "Post tag"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "Tag dei post" msgstr "Tag dei post"

View file

@ -1,13 +1,13 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: ja-JP\n" "Language: ja-jp\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(内容はまだありません)" msgstr "(内容はまだありません)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "レンダリングされたHTML" msgstr "レンダリングされたHTML"
@ -25,49 +25,51 @@ msgstr "レンダリングされたHTML"
msgid "blog" msgid "blog"
msgstr "ブログ" msgstr "ブログ"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "投稿タイトル" msgstr "投稿タイトル"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "タイトル" msgstr "タイトル"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "ポスト" msgstr "ポスト"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "投稿" msgstr "投稿"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr "マークダウン・ファイルはサポートされていません - 代わりにマークダウン・コンテンツを使用してください!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr "マークダウン・ファイルまたはマークダウン・コンテンツを提供しなければならない。"
"マークダウン・ファイルまたはマークダウン・コンテンツを提供しなければならな"
"い。"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "投稿タグの内部タグ識別子" msgstr "投稿タグの内部タグ識別子"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "タグ名" msgstr "タグ名"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "投稿タグのユーザーフレンドリーな名前" msgstr "投稿タグのユーザーフレンドリーな名前"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "タグ表示名" msgstr "タグ表示名"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "投稿タグ" msgstr "投稿タグ"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "投稿タグ" msgstr "投稿タグ"

View file

@ -2,12 +2,12 @@
# Copyright (C) 2025 EGOR <FUREUNOIR> GORBUNOV # Copyright (C) 2025 EGOR <FUREUNOIR> GORBUNOV
# This file is distributed under the same license as the EVIBES package. # This file is distributed under the same license as the EVIBES package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025. # EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -20,7 +20,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "" msgstr ""
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "" msgstr ""
@ -28,47 +28,51 @@ msgstr ""
msgid "blog" msgid "blog"
msgstr "" msgstr ""
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "" msgstr ""
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "" msgstr ""
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "" msgstr ""
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "" msgstr ""
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr ""
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "" msgstr ""
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "" msgstr ""
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "" msgstr ""
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "" msgstr ""
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "" msgstr ""
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "" msgstr ""

View file

@ -1,13 +1,13 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: nl-NL\n" "Language: nl-nl\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(nog geen inhoud)" msgstr "(nog geen inhoud)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "HTML weergeven" msgstr "HTML weergeven"
@ -25,49 +25,55 @@ msgstr "HTML weergeven"
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "Titel van de post" msgstr "Titel van de post"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "Titel" msgstr "Titel"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "Plaats" msgstr "Plaats"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "Berichten" msgstr "Berichten"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr ""
"Markdown-bestanden worden niet ondersteund - gebruik in plaats daarvan "
"markdown-inhoud!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"er moet een markdown-bestand of markdown-inhoud worden geleverd - wederzijds " "er moet een markdown-bestand of markdown-inhoud worden geleverd - wederzijds"
"exclusief" " exclusief"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "interne tagidentifier voor de posttag" msgstr "interne tagidentifier voor de posttag"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "Tag naam" msgstr "Tag naam"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Gebruiksvriendelijke naam voor de posttag" msgstr "Gebruiksvriendelijke naam voor de posttag"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "Tag weergavenaam" msgstr "Tag weergavenaam"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "Post tag" msgstr "Post tag"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "Post tags" msgstr "Post tags"

View file

@ -1,13 +1,13 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: pl-PL\n" "Language: pl-pl\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(brak treści)" msgstr "(brak treści)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "Renderowany HTML" msgstr "Renderowany HTML"
@ -25,49 +25,54 @@ msgstr "Renderowany HTML"
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "Tytuł postu" msgstr "Tytuł postu"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "Tytuł" msgstr "Tytuł"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "Post" msgstr "Post"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "Posty" msgstr "Posty"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr ""
"Pliki Markdown nie są obsługiwane - zamiast tego użyj zawartości Markdown!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"należy dostarczyć plik markdown lub zawartość markdown - wzajemnie się " "należy dostarczyć plik markdown lub zawartość markdown - wzajemnie się "
"wykluczające" "wykluczające"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "wewnętrzny identyfikator tagu posta" msgstr "wewnętrzny identyfikator tagu posta"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "Nazwa tagu" msgstr "Nazwa tagu"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Przyjazna dla użytkownika nazwa tagu posta" msgstr "Przyjazna dla użytkownika nazwa tagu posta"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "Wyświetlana nazwa znacznika" msgstr "Wyświetlana nazwa znacznika"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "Tag posta" msgstr "Tag posta"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "Tagi postów" msgstr "Tagi postów"

View file

@ -1,13 +1,13 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: pt-BR\n" "Language: pt-br\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(ainda não há conteúdo)" msgstr "(ainda não há conteúdo)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "HTML renderizado" msgstr "HTML renderizado"
@ -25,48 +25,54 @@ msgstr "HTML renderizado"
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "Título da postagem" msgstr "Título da postagem"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "Título" msgstr "Título"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "Postar" msgstr "Postar"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "Publicações" msgstr "Publicações"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr ""
"Os arquivos markdown não são suportados - use conteúdo markdown em vez "
"disso!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"um arquivo ou conteúdo de markdown deve ser fornecido - mutuamente exclusivo" "um arquivo ou conteúdo de markdown deve ser fornecido - mutuamente exclusivo"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "identificador de tag interno para a tag de postagem" msgstr "identificador de tag interno para a tag de postagem"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "Nome da etiqueta" msgstr "Nome da etiqueta"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Nome de fácil utilização para a tag de postagem" msgstr "Nome de fácil utilização para a tag de postagem"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "Nome de exibição da tag" msgstr "Nome de exibição da tag"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "Etiqueta de postagem" msgstr "Etiqueta de postagem"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "Tags de postagem" msgstr "Tags de postagem"

View file

@ -1,13 +1,13 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: ro-RO\n" "Language: ro-ro\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(fără conținut încă)" msgstr "(fără conținut încă)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "HTML redat" msgstr "HTML redat"
@ -25,49 +25,55 @@ msgstr "HTML redat"
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "Titlul postului" msgstr "Titlul postului"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "Titlul" msgstr "Titlul"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "Post" msgstr "Post"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "Mesaje" msgstr "Mesaje"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr ""
"Fișierele Markdown nu sunt acceptate - utilizați în schimb conținut "
"Markdown!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"trebuie furnizat un fișier markdown sau conținut markdown - se exclud " "trebuie furnizat un fișier markdown sau conținut markdown - se exclud "
"reciproc" "reciproc"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "identificator intern de etichetă pentru eticheta postului" msgstr "identificator intern de etichetă pentru eticheta postului"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "Nume etichetă" msgstr "Nume etichetă"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Nume ușor de utilizat pentru eticheta postului" msgstr "Nume ușor de utilizat pentru eticheta postului"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "Nume afișare etichetă" msgstr "Nume afișare etichetă"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "Etichetă post" msgstr "Etichetă post"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "Etichete poștale" msgstr "Etichete poștale"

View file

@ -1,13 +1,13 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: ru-RU\n" "Language: ru-ru\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(пока без содержания)" msgstr "(пока без содержания)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "Рендеринг HTML" msgstr "Рендеринг HTML"
@ -25,49 +25,55 @@ msgstr "Рендеринг HTML"
msgid "blog" msgid "blog"
msgstr "Блог" msgstr "Блог"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "Заголовок сообщения" msgstr "Заголовок сообщения"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "Название" msgstr "Название"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "Пост" msgstr "Пост"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "Посты" msgstr "Посты"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr ""
"Файлы в формате Markdown не поддерживаются - используйте вместо них "
"содержимое в формате Markdown!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"необходимо предоставить файл разметки или содержимое разметки - " "необходимо предоставить файл разметки или содержимое разметки - "
"взаимоисключающие варианты" "взаимоисключающие варианты"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "внутренний идентификатор тега для тега post" msgstr "внутренний идентификатор тега для тега post"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "Название тега" msgstr "Название тега"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Удобное для пользователя название тега поста" msgstr "Удобное для пользователя название тега поста"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "Отображаемое имя тега" msgstr "Отображаемое имя тега"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "Тэг поста" msgstr "Тэг поста"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "Тэги постов" msgstr "Тэги постов"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n" "Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n" "POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)" msgid "(no content yet)"
msgstr "(暂无内容)" msgstr "(暂无内容)"
#: blog/admin.py:40 #: blog/admin.py:41
msgid "rendered HTML" msgid "rendered HTML"
msgstr "渲染的 HTML" msgstr "渲染的 HTML"
@ -25,47 +25,51 @@ msgstr "渲染的 HTML"
msgid "blog" msgid "blog"
msgstr "博客" msgstr "博客"
#: blog/models.py:15 #: blog/models.py:17
msgid "post title" msgid "post title"
msgstr "帖子标题" msgstr "帖子标题"
#: blog/models.py:15 #: blog/models.py:17
msgid "title" msgid "title"
msgstr "标题" msgstr "标题"
#: blog/models.py:62 #: blog/models.py:64
msgid "post" msgid "post"
msgstr "职位" msgstr "职位"
#: blog/models.py:63 #: blog/models.py:65
msgid "posts" msgid "posts"
msgstr "职位" msgstr "职位"
#: blog/models.py:67 #: blog/models.py:69
msgid "markdown files are not supported yet - use markdown content instead"
msgstr "不支持 Markdown 文件,请使用 Markdown 内容!"
#: blog/models.py:71
msgid "" msgid ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
msgstr "必须提供标记符文件或标记符内容 - 相互排斥" msgstr "必须提供标记符文件或标记符内容 - 相互排斥"
#: blog/models.py:78 #: blog/models.py:82
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "职位标签的内部标签标识符" msgstr "职位标签的内部标签标识符"
#: blog/models.py:79 #: blog/models.py:83
msgid "tag name" msgid "tag name"
msgstr "标签名称" msgstr "标签名称"
#: blog/models.py:83 #: blog/models.py:87
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "方便用户使用的帖子标签名称" msgstr "方便用户使用的帖子标签名称"
#: blog/models.py:84 #: blog/models.py:88
msgid "tag display name" msgid "tag display name"
msgstr "标签显示名称" msgstr "标签显示名称"
#: blog/models.py:92 #: blog/models.py:96
msgid "post tag" msgid "post tag"
msgstr "职位标签" msgstr "职位标签"
#: blog/models.py:93 #: blog/models.py:97
msgid "post tags" msgid "post tags"
msgstr "帖子标签" msgstr "帖子标签"

View file

@ -10,11 +10,13 @@ from core.abstract import NiceModel
class Post(NiceModel): class Post(NiceModel):
is_publicly_visible = True is_publicly_visible = True
author = ForeignKey(to="vibes_auth.User", on_delete=CASCADE, blank=False, null=False, related_name="posts") author: ForeignKey = ForeignKey(
title = CharField( to="vibes_auth.User", on_delete=CASCADE, blank=False, null=False, related_name="posts"
)
title: CharField = CharField(
unique=True, max_length=128, blank=False, null=False, help_text=_("post title"), verbose_name=_("title") unique=True, max_length=128, blank=False, null=False, help_text=_("post title"), verbose_name=_("title")
) )
content = MarkdownField( content: MarkdownField = MarkdownField(
"content", "content",
extensions=[ extensions=[
TocExtension(toc_depth=3), TocExtension(toc_depth=3),
@ -51,9 +53,9 @@ class Post(NiceModel):
blank=True, blank=True,
null=True, null=True,
) )
file = FileField(upload_to="posts/", blank=True, null=True) file: FileField = FileField(upload_to="posts/", blank=True, null=True)
slug = AutoSlugField(populate_from="title", allow_unicode=True, unique=True, editable=False) slug: AutoSlugField = AutoSlugField(populate_from="title", allow_unicode=True, unique=True, editable=False)
tags = ManyToManyField(to="blog.PostTag", blank=True, related_name="posts") tags: ManyToManyField = ManyToManyField(to="blog.PostTag", blank=True, related_name="posts")
def __str__(self): def __str__(self):
return f"{self.title} | {self.author.first_name} {self.author.last_name}" return f"{self.title} | {self.author.first_name} {self.author.last_name}"
@ -63,6 +65,8 @@ class Post(NiceModel):
verbose_name_plural = _("posts") verbose_name_plural = _("posts")
def save(self, **kwargs): def save(self, **kwargs):
if self.file:
raise ValueError(_("markdown files are not supported yet - use markdown content instead"))
if not any([self.file, self.content]) or all([self.file, self.content]): if not any([self.file, self.content]) or all([self.file, self.content]):
raise ValueError(_("a markdown file or markdown content must be provided - mutually exclusive")) raise ValueError(_("a markdown file or markdown content must be provided - mutually exclusive"))
super().save(**kwargs) super().save(**kwargs)
@ -71,14 +75,14 @@ class Post(NiceModel):
class PostTag(NiceModel): class PostTag(NiceModel):
is_publicly_visible = True is_publicly_visible = True
tag_name = CharField( tag_name: CharField = CharField(
blank=False, blank=False,
null=False, null=False,
max_length=255, max_length=255,
help_text=_("internal tag identifier for the post tag"), help_text=_("internal tag identifier for the post tag"),
verbose_name=_("tag name"), verbose_name=_("tag name"),
) )
name = CharField( name: CharField = CharField(
max_length=255, max_length=255,
help_text=_("user-friendly name for the post tag"), help_text=_("user-friendly name for the post tag"),
verbose_name=_("tag display name"), verbose_name=_("tag display name"),

View file

@ -31,4 +31,5 @@ class MarkdownEditorWidget(forms.Textarea):
}}); }});
</script> </script>
""" """
# noinspection DjangoSafeString
return mark_safe(textarea_html + init_js) return mark_safe(textarea_html + init_js)

View file

@ -7,14 +7,14 @@ from django_extensions.db.fields import CreationDateTimeField, ModificationDateT
class NiceModel(Model): class NiceModel(Model):
id = None id = None
uuid = UUIDField( uuid: UUIDField = UUIDField(
verbose_name=_("unique id"), verbose_name=_("unique id"),
help_text=_("unique id is used to surely identify any database object"), help_text=_("unique id is used to surely identify any database object"),
primary_key=True, primary_key=True,
default=uuid.uuid4, default=uuid.uuid4,
editable=False, editable=False,
) )
is_active = BooleanField( is_active: BooleanField = BooleanField(
default=True, default=True,
verbose_name=_("is active"), verbose_name=_("is active"),
help_text=_("if set to false, this object can't be seen by users without needed permission"), help_text=_("if set to false, this object can't be seen by users without needed permission"),

View file

@ -134,8 +134,8 @@ class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, TabbedTranslationAdmin)
def indented_title(self, instance): def indented_title(self, instance):
return instance.name return instance.name
indented_title.short_description = _("name") indented_title.short_description = _("name") # type: ignore
indented_title.admin_order_field = "name" indented_title.admin_order_field = "name" # type: ignore
@admin.register(Brand) @admin.register(Brand)
@ -191,12 +191,12 @@ class ProductAdmin(BasicModelAdmin, TabbedTranslationAdmin):
def price(self, obj): def price(self, obj):
return obj.price return obj.price
price.short_description = _("price") price.short_description = _("price") # type: ignore
def rating(self, obj): def rating(self, obj):
return obj.rating return obj.rating
rating.short_description = _("rating") rating.short_description = _("rating") # type: ignore
fieldsets = ( fieldsets = (
( (
@ -278,7 +278,7 @@ class OrderAdmin(BasicModelAdmin):
def is_business(self, obj): def is_business(self, obj):
return obj.is_business return obj.is_business
is_business.short_description = _("is business") is_business.short_description = _("is business") # type: ignore
def get_queryset(self, request): def get_queryset(self, request):
qs = super().get_queryset(request) qs = super().get_queryset(request)
@ -431,6 +431,6 @@ class ConstanceConfig:
admin.site.unregister([Config]) # type: ignore admin.site.unregister([Config]) # type: ignore
admin.site.register([ConstanceConfig], ConstanceAdmin) # type: ignore admin.site.register([ConstanceConfig], ConstanceAdmin) # type: ignore
admin.site.site_title = f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]}" admin.site.site_title = f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]}" # type: ignore
admin.site.site_header = "eVibes" admin.site.site_header = "eVibes"
admin.site.index_title = f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]}" admin.site.index_title = f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]}" # type: ignore

View file

@ -67,7 +67,7 @@ def process_query(query: str = ""):
response = search.execute() response = search.execute()
# Collect results, guard against None values # Collect results, guard against None values
results = {"products": [], "categories": [], "brands": [], "posts": []} results: dict = {"products": [], "categories": [], "brands": [], "posts": []}
for hit in response.hits: for hit in response.hits:
obj_uuid = getattr(hit, "uuid", None) or hit.meta.id obj_uuid = getattr(hit, "uuid", None) or hit.meta.id
obj_name = getattr(hit, "name", None) or getattr(hit, "title", None) or "N/A" obj_name = getattr(hit, "name", None) or getattr(hit, "title", None) or "N/A"

View file

@ -335,20 +335,16 @@ class CategoryFilter(FilterSet):
fields = ["uuid", "name", "parent_uuid", "slug", "tags", "level", "order_by", "whole"] fields = ["uuid", "name", "parent_uuid", "slug", "tags", "level", "order_by", "whole"]
def filter_whole_categories(self, queryset, _name, value): def filter_whole_categories(self, queryset, _name, value):
has_own_products = Exists( has_own_products = Exists(Product.objects.filter(category=OuterRef("pk")))
Product.objects.filter(category=OuterRef('pk'))
)
has_desc_products = Exists( has_desc_products = Exists(
Product.objects.filter( Product.objects.filter(
is_active=True, is_active=True,
category__tree_id=OuterRef('tree_id'), category__tree_id=OuterRef("tree_id"),
category__lft__gt=OuterRef('lft'), category__lft__gt=OuterRef("lft"),
category__rght__lt=OuterRef('rght'), category__rght__lt=OuterRef("rght"),
) )
) )
annotated = queryset.annotate( annotated = queryset.annotate(has_products=has_own_products | has_desc_products)
has_products=has_own_products | has_desc_products
)
if value: if value:
return annotated.filter(has_products=True).distinct() return annotated.filter(has_products=True).distinct()
return annotated.filter(has_products=False).distinct() return annotated.filter(has_products=False).distinct()

View file

@ -619,6 +619,7 @@ class ContactUs(BaseMutation):
return ContactUs(received=False, error=str(e)) return ContactUs(received=False, error=str(e))
# noinspection PyArgumentList
class Search(BaseMutation): class Search(BaseMutation):
class Arguments: class Arguments:
query = String(required=True) query = String(required=True)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -50,7 +50,7 @@ class Command(BaseCommand):
time.sleep(1) time.sleep(1)
self.stdout.write(self.style.SUCCESS("Redis available!")) self.stdout.write(self.style.SUCCESS("Redis available!"))
# Create and start threads for database and Redis # Create and start threads for the database and Redis
db_thread = threading.Thread(target=wait_for_db) db_thread = threading.Thread(target=wait_for_db)
redis_thread = threading.Thread(target=wait_for_redis) redis_thread = threading.Thread(target=wait_for_redis)

View file

@ -9,14 +9,14 @@ from django.core.management.base import BaseCommand, CommandError
# Patterns to identify placeholders # Patterns to identify placeholders
PLACEHOLDER_REGEXES = [ PLACEHOLDER_REGEXES = [
re.compile(r"\{[^}]+\}"), # {name}, {type(instance)!s}, etc. re.compile(r"\{[^}]+"), # {name}, {type(instance)!s}, etc.
re.compile(r"%\([^)]+\)[sd]"), # %(verbose_name)s, %(count)d re.compile(r"%\([^)]+\)[sd]"), # %(verbose_name)s, %(count)d
] ]
def extract_placeholders(text: str) -> set[str]: def extract_placeholders(text: str) -> set[str]:
""" """
Extract all placeholders from given text. Extract all placeholders from the given text.
""" """
phs: list[str] = [] phs: list[str] = []
for rx in PLACEHOLDER_REGEXES: for rx in PLACEHOLDER_REGEXES:
@ -29,33 +29,33 @@ def load_po_sanitized(path: str) -> polib.POFile:
Load a .po file via polib, sanitizing on parse errors. Load a .po file via polib, sanitizing on parse errors.
Raises CommandError if still unparsable. Raises CommandError if still unparsable.
""" """
try: with contextlib.suppress(Exception):
return polib.pofile(path) return polib.pofile(path)
except Exception:
# read raw text # read raw text
try: try:
with open(path, encoding="utf-8") as f: with open(path, encoding="utf-8") as f:
text = f.read() text = f.read()
except OSError as e: except OSError as e:
raise CommandError(f"{path}: cannot read file ({e})") raise CommandError(f"{path}: cannot read file ({e})")
# fix fuzzy flags and empty header entries # fix fuzzy flags and empty header entries
text = re.sub(r"^#,(?!\s)", "#, ", text, flags=re.MULTILINE) text = re.sub(r"^#,(?!\s)", "#, ", text, flags=re.MULTILINE)
parts = text.split("\n\n", 1) parts = text.split("\n\n", 1)
header = parts[0] header = parts[0]
rest = parts[1] if len(parts) > 1 else "" rest = parts[1] if len(parts) > 1 else ""
rest = re.sub(r"^msgid \"\"\s*\nmsgstr \"\"\s*\n?", "", rest, flags=re.MULTILINE) rest = re.sub(r"^msgid \"\"\s*\nmsgstr \"\"\s*\n?", "", rest, flags=re.MULTILINE)
sanitized = header + "\n\n" + rest sanitized = header + "\n\n" + rest
tmp = NamedTemporaryFile(mode="w+", delete=False, suffix=".po", encoding="utf-8") # noqa: SIM115 tmp = NamedTemporaryFile(mode="w+", delete=False, suffix=".po", encoding="utf-8") # noqa: SIM115
try: try:
tmp.write(sanitized) tmp.write(sanitized)
tmp.flush() tmp.flush()
tmp.close() tmp.close()
return polib.pofile(tmp.name) return polib.pofile(tmp.name)
except Exception as e: except Exception as e:
raise CommandError(f"{path}: syntax error after sanitization ({e})") raise CommandError(f"{path}: syntax error after sanitization ({e})")
finally: finally:
with contextlib.suppress(OSError): with contextlib.suppress(OSError):
os.unlink(tmp.name) os.unlink(tmp.name)
class Command(BaseCommand): class Command(BaseCommand):

View file

@ -145,6 +145,9 @@ class Command(BaseCommand):
self.stdout.write(f"{app_conf.label}: loading English PO…") self.stdout.write(f"{app_conf.label}: loading English PO…")
en_po = load_po_sanitized(en_path) en_po = load_po_sanitized(en_path)
if not en_po:
raise CommandError(f"Failed to load en_GB PO for {app_conf.label}")
missing = [e for e in en_po if e.msgid and not e.msgstr and not e.obsolete] missing = [e for e in en_po if e.msgid and not e.msgstr and not e.obsolete]
if missing: if missing:
self.stdout.write(self.style.NOTICE(f"⚠️ {len(missing)} missing in en_GB")) self.stdout.write(self.style.NOTICE(f"⚠️ {len(missing)} missing in en_GB"))
@ -176,17 +179,17 @@ class Command(BaseCommand):
new_po.metadata = en_po.metadata.copy() new_po.metadata = en_po.metadata.copy()
new_po.metadata["Language"] = target_lang new_po.metadata["Language"] = target_lang
for e in entries: for entry in entries:
prev = old_tgt.find(e.msgid) if old_tgt else None prev = old_tgt.find(entry.msgid) if old_tgt else None
new_po.append( new_po.append(
polib.POEntry( polib.POEntry(
msgid=e.msgid, msgid=entry.msgid,
msgstr=prev.msgstr if prev and prev.msgstr else "", msgstr=prev.msgstr if prev and prev.msgstr else "",
msgctxt=e.msgctxt, msgctxt=entry.msgctxt,
comment=e.comment, comment=entry.comment,
tcomment=e.tcomment, tcomment=entry.tcomment,
occurrences=e.occurrences, occurrences=entry.occurrences,
flags=e.flags, flags=entry.flags,
) )
) )

View file

@ -9,11 +9,11 @@ logger = logging.getLogger("django.request")
class AddressManager(models.Manager): class AddressManager(models.Manager):
def create(self, raw_data: str, **kwargs): def create(self, raw_data: str, **kwargs): # type: ignore
if not raw_data: if not raw_data:
raise ValueError("'raw_data' (address string) must be provided.") raise ValueError("'raw_data' (address string) must be provided.")
params = { params: dict[str, str | int] = {
"format": "json", "format": "json",
"addressdetails": 1, "addressdetails": 1,
"q": raw_data, "q": raw_data,

View file

@ -56,7 +56,7 @@ logger = logging.getLogger(__name__)
class AttributeGroup(ExportModelOperationsMixin("attribute_group"), NiceModel): class AttributeGroup(ExportModelOperationsMixin("attribute_group"), NiceModel):
is_publicly_visible = True is_publicly_visible = True
parent = ForeignKey( parent: ForeignKey = ForeignKey(
"self", "self",
on_delete=CASCADE, on_delete=CASCADE,
null=True, null=True,
@ -65,7 +65,7 @@ class AttributeGroup(ExportModelOperationsMixin("attribute_group"), NiceModel):
help_text=_("parent of this group"), help_text=_("parent of this group"),
verbose_name=_("parent attribute group"), verbose_name=_("parent attribute group"),
) )
name = CharField( name: CharField = CharField(
max_length=255, max_length=255,
verbose_name=_("attribute group's name"), verbose_name=_("attribute group's name"),
help_text=_("attribute group's name"), help_text=_("attribute group's name"),
@ -83,21 +83,21 @@ class AttributeGroup(ExportModelOperationsMixin("attribute_group"), NiceModel):
class Attribute(ExportModelOperationsMixin("attribute"), NiceModel): class Attribute(ExportModelOperationsMixin("attribute"), NiceModel):
is_publicly_visible = True is_publicly_visible = True
categories = ManyToManyField( categories: ManyToManyField = ManyToManyField(
"core.Category", "core.Category",
related_name="attributes", related_name="attributes",
help_text=_("category of this attribute"), help_text=_("category of this attribute"),
verbose_name=_("categories"), verbose_name=_("categories"),
) )
group = ForeignKey( group: ForeignKey = ForeignKey(
"core.AttributeGroup", "core.AttributeGroup",
on_delete=CASCADE, on_delete=CASCADE,
related_name="attributes", related_name="attributes",
help_text=_("group of this attribute"), help_text=_("group of this attribute"),
verbose_name=_("attribute group"), verbose_name=_("attribute group"),
) )
value_type = CharField( value_type: CharField = CharField(
max_length=50, max_length=50,
choices=[ choices=[
("string", _("string")), ("string", _("string")),
@ -111,7 +111,7 @@ class Attribute(ExportModelOperationsMixin("attribute"), NiceModel):
verbose_name=_("value type"), verbose_name=_("value type"),
) )
name = CharField( name: CharField = CharField(
max_length=255, max_length=255,
help_text=_("name of this attribute"), help_text=_("name of this attribute"),
verbose_name=_("attribute's name"), verbose_name=_("attribute's name"),
@ -129,14 +129,14 @@ class Attribute(ExportModelOperationsMixin("attribute"), NiceModel):
class AttributeValue(ExportModelOperationsMixin("attribute_value"), NiceModel): class AttributeValue(ExportModelOperationsMixin("attribute_value"), NiceModel):
is_publicly_visible = True is_publicly_visible = True
attribute = ForeignKey( attribute: ForeignKey = ForeignKey(
"core.Attribute", "core.Attribute",
on_delete=CASCADE, on_delete=CASCADE,
related_name="values", related_name="values",
help_text=_("attribute of this value"), help_text=_("attribute of this value"),
verbose_name=_("attribute"), verbose_name=_("attribute"),
) )
product = ForeignKey( product: ForeignKey = ForeignKey(
"core.Product", "core.Product",
on_delete=CASCADE, on_delete=CASCADE,
blank=False, blank=False,
@ -145,7 +145,7 @@ class AttributeValue(ExportModelOperationsMixin("attribute_value"), NiceModel):
verbose_name=_("associated product"), verbose_name=_("associated product"),
related_name="attributes", related_name="attributes",
) )
value = TextField( value: TextField = TextField(
verbose_name=_("attribute value"), verbose_name=_("attribute value"),
help_text=_("the specific value for this attribute"), help_text=_("the specific value for this attribute"),
) )
@ -169,7 +169,7 @@ class Category(ExportModelOperationsMixin("category"), NiceModel, MPTTModel):
validators=[validate_category_image_dimensions], validators=[validate_category_image_dimensions],
verbose_name=_("category image"), verbose_name=_("category image"),
) )
markup_percent = IntegerField( markup_percent: IntegerField = IntegerField(
default=0, default=0,
validators=[MinValueValidator(0), MaxValueValidator(100)], validators=[MinValueValidator(0), MaxValueValidator(100)],
help_text=_("define a markup percentage for products in this category"), help_text=_("define a markup percentage for products in this category"),
@ -185,28 +185,28 @@ class Category(ExportModelOperationsMixin("category"), NiceModel, MPTTModel):
verbose_name=_("parent category"), verbose_name=_("parent category"),
) )
name = CharField( name: CharField = CharField(
max_length=255, max_length=255,
verbose_name=_("category name"), verbose_name=_("category name"),
help_text=_("provide a name for this category"), help_text=_("provide a name for this category"),
unique=True, unique=True,
) )
description = TextField( # noqa: DJ001 description: TextField = TextField(
blank=True, blank=True,
null=True, null=True,
help_text=_("add a detailed description for this category"), help_text=_("add a detailed description for this category"),
verbose_name=_("category description"), verbose_name=_("category description"),
) )
slug = AutoSlugField( slug: AutoSlugField = AutoSlugField(
populate_from=("uuid", "name"), populate_from=("uuid", "name"),
allow_unicode=True, allow_unicode=True,
unique=True, unique=True,
editable=False, editable=False,
null=True, null=True,
) )
tags = ManyToManyField( tags: ManyToManyField = ManyToManyField(
"core.CategoryTag", "core.CategoryTag",
blank=True, blank=True,
help_text=_("tags that help describe or group this category"), help_text=_("tags that help describe or group this category"),
@ -230,7 +230,7 @@ class Category(ExportModelOperationsMixin("category"), NiceModel, MPTTModel):
class Brand(ExportModelOperationsMixin("brand"), NiceModel): class Brand(ExportModelOperationsMixin("brand"), NiceModel):
is_publicly_visible = True is_publicly_visible = True
name = CharField( name: CharField = CharField(
max_length=255, max_length=255,
help_text=_("name of this brand"), help_text=_("name of this brand"),
verbose_name=_("brand name"), verbose_name=_("brand name"),
@ -252,13 +252,13 @@ class Brand(ExportModelOperationsMixin("brand"), NiceModel):
validators=[validate_category_image_dimensions], validators=[validate_category_image_dimensions],
verbose_name=_("brand big image"), verbose_name=_("brand big image"),
) )
description = TextField( # noqa: DJ001 description: TextField = TextField(
blank=True, blank=True,
null=True, null=True,
help_text=_("add a detailed description of the brand"), help_text=_("add a detailed description of the brand"),
verbose_name=_("brand description"), verbose_name=_("brand description"),
) )
categories = ManyToManyField( categories: ManyToManyField = ManyToManyField(
"core.Category", "core.Category",
blank=True, blank=True,
help_text=_("optional categories that this brand is associated with"), help_text=_("optional categories that this brand is associated with"),
@ -276,14 +276,14 @@ class Brand(ExportModelOperationsMixin("brand"), NiceModel):
class Product(ExportModelOperationsMixin("product"), NiceModel): class Product(ExportModelOperationsMixin("product"), NiceModel):
is_publicly_visible = True is_publicly_visible = True
category = ForeignKey( category: ForeignKey = ForeignKey(
"core.Category", "core.Category",
on_delete=CASCADE, on_delete=CASCADE,
help_text=_("category this product belongs to"), help_text=_("category this product belongs to"),
verbose_name=_("category"), verbose_name=_("category"),
related_name="products", related_name="products",
) )
brand = ForeignKey( brand: ForeignKey = ForeignKey(
"core.Brand", "core.Brand",
on_delete=CASCADE, on_delete=CASCADE,
blank=True, blank=True,
@ -291,31 +291,31 @@ class Product(ExportModelOperationsMixin("product"), NiceModel):
help_text=_("optionally associate this product with a brand"), help_text=_("optionally associate this product with a brand"),
verbose_name=_("brand"), verbose_name=_("brand"),
) )
tags = ManyToManyField( tags: ManyToManyField = ManyToManyField(
"core.ProductTag", "core.ProductTag",
blank=True, blank=True,
help_text=_("tags that help describe or group this product"), help_text=_("tags that help describe or group this product"),
verbose_name=_("product tags"), verbose_name=_("product tags"),
) )
is_digital = BooleanField( is_digital: BooleanField = BooleanField(
default=False, default=False,
help_text=_("indicates whether this product is digitally delivered"), help_text=_("indicates whether this product is digitally delivered"),
verbose_name=_("is product digital"), verbose_name=_("is product digital"),
blank=False, blank=False,
null=False, null=False,
) )
name = CharField( name: CharField = CharField(
max_length=255, max_length=255,
help_text=_("provide a clear identifying name for the product"), help_text=_("provide a clear identifying name for the product"),
verbose_name=_("product name"), verbose_name=_("product name"),
) )
description = TextField( # noqa: DJ001 description: TextField = TextField(
blank=True, blank=True,
null=True, null=True,
help_text=_("add a detailed description of the product"), help_text=_("add a detailed description of the product"),
verbose_name=_("product description"), verbose_name=_("product description"),
) )
partnumber = CharField( # noqa: DJ001 partnumber: CharField = CharField(
unique=True, unique=True,
default=None, default=None,
blank=False, blank=False,
@ -388,13 +388,13 @@ class Vendor(ExportModelOperationsMixin("vendor"), NiceModel):
help_text=_("stores credentials and endpoints required for vendor communication"), help_text=_("stores credentials and endpoints required for vendor communication"),
verbose_name=_("authentication info"), verbose_name=_("authentication info"),
) )
markup_percent = IntegerField( markup_percent: IntegerField = IntegerField(
default=0, default=0,
validators=[MinValueValidator(0), MaxValueValidator(100)], validators=[MinValueValidator(0), MaxValueValidator(100)],
help_text=_("define the markup for products retrieved from this vendor"), help_text=_("define the markup for products retrieved from this vendor"),
verbose_name=_("vendor markup percentage"), verbose_name=_("vendor markup percentage"),
) )
name = CharField( name: CharField = CharField(
max_length=255, max_length=255,
help_text=_("name of this vendor"), help_text=_("name of this vendor"),
verbose_name=_("vendor name"), verbose_name=_("vendor name"),
@ -417,13 +417,13 @@ class Vendor(ExportModelOperationsMixin("vendor"), NiceModel):
class Feedback(ExportModelOperationsMixin("feedback"), NiceModel): class Feedback(ExportModelOperationsMixin("feedback"), NiceModel):
is_publicly_visible = True is_publicly_visible = True
comment = TextField( # noqa: DJ001 comment: TextField = TextField(
blank=True, blank=True,
null=True, null=True,
help_text=_("user-provided comments about their experience with the product"), help_text=_("user-provided comments about their experience with the product"),
verbose_name=_("feedback comments"), verbose_name=_("feedback comments"),
) )
order_product = OneToOneField( order_product: OneToOneField = OneToOneField(
"core.OrderProduct", "core.OrderProduct",
on_delete=CASCADE, on_delete=CASCADE,
blank=False, blank=False,
@ -431,7 +431,7 @@ class Feedback(ExportModelOperationsMixin("feedback"), NiceModel):
help_text=_("references the specific product in an order that this feedback is about"), help_text=_("references the specific product in an order that this feedback is about"),
verbose_name=_("related order product"), verbose_name=_("related order product"),
) )
rating = FloatField( rating: FloatField = FloatField(
blank=True, blank=True,
null=True, null=True,
help_text=_("user-assigned rating for the product"), help_text=_("user-assigned rating for the product"),
@ -450,7 +450,7 @@ class Feedback(ExportModelOperationsMixin("feedback"), NiceModel):
class Order(ExportModelOperationsMixin("order"), NiceModel): class Order(ExportModelOperationsMixin("order"), NiceModel):
is_publicly_visible = False is_publicly_visible = False
billing_address = ForeignKey( billing_address: ForeignKey = ForeignKey(
"core.Address", "core.Address",
on_delete=CASCADE, on_delete=CASCADE,
blank=True, blank=True,
@ -459,7 +459,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
help_text=_("the billing address used for this order"), help_text=_("the billing address used for this order"),
verbose_name=_("billing address"), verbose_name=_("billing address"),
) )
promo_code = ForeignKey( promo_code: ForeignKey = ForeignKey(
"core.PromoCode", "core.PromoCode",
on_delete=PROTECT, on_delete=PROTECT,
blank=True, blank=True,
@ -467,7 +467,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
help_text=_("optional promo code applied to this order"), help_text=_("optional promo code applied to this order"),
verbose_name=_("applied promo code"), verbose_name=_("applied promo code"),
) )
shipping_address = ForeignKey( shipping_address: ForeignKey = ForeignKey(
"core.Address", "core.Address",
on_delete=CASCADE, on_delete=CASCADE,
blank=True, blank=True,
@ -476,7 +476,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
help_text=_("the shipping address used for this order"), help_text=_("the shipping address used for this order"),
verbose_name=_("shipping address"), verbose_name=_("shipping address"),
) )
status = CharField( status: CharField = CharField(
default="PENDING", default="PENDING",
max_length=64, max_length=64,
choices=ORDER_STATUS_CHOICES, choices=ORDER_STATUS_CHOICES,
@ -495,7 +495,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
help_text=_("json representation of order attributes for this order"), help_text=_("json representation of order attributes for this order"),
verbose_name=_("attributes"), verbose_name=_("attributes"),
) )
user = ForeignKey( user: ForeignKey = ForeignKey(
"vibes_auth.User", "vibes_auth.User",
on_delete=CASCADE, on_delete=CASCADE,
help_text=_("the user who placed the order"), help_text=_("the user who placed the order"),
@ -504,14 +504,14 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
blank=True, blank=True,
null=True, null=True,
) )
buy_time = DateTimeField( buy_time: DateTimeField = DateTimeField(
help_text=_("the timestamp when the order was finalized"), help_text=_("the timestamp when the order was finalized"),
verbose_name=_("buy time"), verbose_name=_("buy time"),
default=None, default=None,
null=True, null=True,
blank=True, blank=True,
) )
human_readable_id = CharField( human_readable_id: CharField = CharField(
max_length=8, max_length=8,
help_text=_("a human-readable identifier for the order"), help_text=_("a human-readable identifier for the order"),
verbose_name=_("human readable id"), verbose_name=_("human readable id"),
@ -524,7 +524,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
verbose_name_plural = _("orders") verbose_name_plural = _("orders")
def __str__(self) -> str: def __str__(self) -> str:
return f"#{self.pk} for {self.user.email if self.user else 'unregistered user'}" return f"#{self.pk} for {self.user.email if self.user else 'unregistered user'}" # type: ignore
@property @property
def is_business(self) -> bool: def is_business(self) -> bool:
@ -576,7 +576,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
promotions = Promotion.objects.filter(is_active=True, products__in=[product]).order_by("discount_percent") promotions = Promotion.objects.filter(is_active=True, products__in=[product]).order_by("discount_percent")
if promotions.exists(): if promotions.exists():
buy_price -= round(product.price * (promotions.first().discount_percent / 100), 2) buy_price -= round(product.price * (promotions.first().discount_percent / 100), 2) # type: ignore
order_product, is_created = OrderProduct.objects.get_or_create( order_product, is_created = OrderProduct.objects.get_or_create(
product=product, product=product,
@ -597,7 +597,12 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
name = "Product" name = "Product"
raise Http404(_(f"{name} does not exist: {product_uuid}")) raise Http404(_(f"{name} does not exist: {product_uuid}"))
def remove_product(self, product_uuid: str | None = None, attributes: dict = dict, zero_quantity: bool = False): def remove_product(
self, product_uuid: str | None = None, attributes: dict | None = None, zero_quantity: bool = False
):
if attributes is None:
attributes = {}
if self.status not in ["PENDING", "MOMENTAL"]: if self.status not in ["PENDING", "MOMENTAL"]:
raise ValueError(_("you cannot remove products from an order that is not a pending one")) raise ValueError(_("you cannot remove products from an order that is not a pending one"))
try: try:
@ -818,31 +823,31 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel):
is_publicly_visible = False is_publicly_visible = False
buy_price = FloatField( buy_price: FloatField = FloatField(
blank=True, blank=True,
null=True, null=True,
help_text=_("the price paid by the customer for this product at purchase time"), help_text=_("the price paid by the customer for this product at purchase time"),
verbose_name=_("purchase price at order time"), verbose_name=_("purchase price at order time"),
) )
comments = TextField( # noqa: DJ001 comments: TextField = TextField(
blank=True, blank=True,
null=True, null=True,
help_text=_("internal comments for admins about this ordered product"), help_text=_("internal comments for admins about this ordered product"),
verbose_name=_("internal comments"), verbose_name=_("internal comments"),
) )
notifications = JSONField( notifications: JSONField = JSONField(
blank=True, blank=True,
null=True, null=True,
help_text=_("json structure of notifications to display to users"), help_text=_("json structure of notifications to display to users"),
verbose_name=_("user notifications"), verbose_name=_("user notifications"),
) )
attributes = JSONField( attributes: JSONField = JSONField(
blank=True, blank=True,
null=True, null=True,
help_text=_("json representation of this item's attributes"), help_text=_("json representation of this item's attributes"),
verbose_name=_("ordered product attributes"), verbose_name=_("ordered product attributes"),
) )
order = ForeignKey( order: ForeignKey = ForeignKey(
"core.Order", "core.Order",
on_delete=CASCADE, on_delete=CASCADE,
help_text=_("reference to the parent order that contains this product"), help_text=_("reference to the parent order that contains this product"),
@ -850,7 +855,7 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel):
related_name="order_products", related_name="order_products",
null=True, null=True,
) )
product = ForeignKey( product: ForeignKey = ForeignKey(
"core.Product", "core.Product",
on_delete=PROTECT, on_delete=PROTECT,
blank=True, blank=True,
@ -858,14 +863,14 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel):
help_text=_("the specific product associated with this order line"), help_text=_("the specific product associated with this order line"),
verbose_name=_("associated product"), verbose_name=_("associated product"),
) )
quantity = PositiveIntegerField( quantity: PositiveIntegerField = PositiveIntegerField(
blank=False, blank=False,
null=False, null=False,
default=1, default=1,
help_text=_("quantity of this specific product in the order"), help_text=_("quantity of this specific product in the order"),
verbose_name=_("product quantity"), verbose_name=_("product quantity"),
) )
status = CharField( status: CharField = CharField(
max_length=128, max_length=128,
blank=False, blank=False,
null=False, null=False,
@ -921,7 +926,7 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel):
return self.download.url return self.download.url
return "" return ""
def do_feedback(self, rating: int = 10, comment: str = "", action: str = "add"): def do_feedback(self, rating: int = 10, comment: str = "", action: str = "add") -> None:
if action not in ["add", "remove"]: if action not in ["add", "remove"]:
raise ValueError(_(f"wrong action specified for feedback: {action}")) raise ValueError(_(f"wrong action specified for feedback: {action}"))
if action == "remove" and self.feedback: if action == "remove" and self.feedback:
@ -938,14 +943,14 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel):
class ProductTag(ExportModelOperationsMixin("product_tag"), NiceModel): class ProductTag(ExportModelOperationsMixin("product_tag"), NiceModel):
is_publicly_visible = True is_publicly_visible = True
tag_name = CharField( tag_name: CharField = CharField(
blank=False, blank=False,
null=False, null=False,
max_length=255, max_length=255,
help_text=_("internal tag identifier for the product tag"), help_text=_("internal tag identifier for the product tag"),
verbose_name=_("tag name"), verbose_name=_("tag name"),
) )
name = CharField( name: CharField = CharField(
max_length=255, max_length=255,
help_text=_("user-friendly name for the product tag"), help_text=_("user-friendly name for the product tag"),
verbose_name=_("tag display name"), verbose_name=_("tag display name"),
@ -963,14 +968,14 @@ class ProductTag(ExportModelOperationsMixin("product_tag"), NiceModel):
class CategoryTag(ExportModelOperationsMixin("category_tag"), NiceModel): class CategoryTag(ExportModelOperationsMixin("category_tag"), NiceModel):
is_publicly_visible = True is_publicly_visible = True
tag_name = CharField( tag_name: CharField = CharField(
blank=False, blank=False,
null=False, null=False,
max_length=255, max_length=255,
help_text=_("internal tag identifier for the product tag"), help_text=_("internal tag identifier for the product tag"),
verbose_name=_("tag name"), verbose_name=_("tag name"),
) )
name = CharField( name: CharField = CharField(
max_length=255, max_length=255,
help_text=_("user-friendly name for the product tag"), help_text=_("user-friendly name for the product tag"),
verbose_name=_("tag display name"), verbose_name=_("tag display name"),
@ -988,7 +993,7 @@ class CategoryTag(ExportModelOperationsMixin("category_tag"), NiceModel):
class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel): class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel):
is_publicly_visible = True is_publicly_visible = True
alt = CharField( alt: CharField = CharField(
max_length=255, max_length=255,
help_text=_("provide alternative text for the image for accessibility"), help_text=_("provide alternative text for the image for accessibility"),
verbose_name=_("image alt text"), verbose_name=_("image alt text"),
@ -998,13 +1003,13 @@ class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel):
verbose_name=_("product image"), verbose_name=_("product image"),
upload_to=get_product_uuid_as_path, upload_to=get_product_uuid_as_path,
) )
priority = IntegerField( priority: IntegerField = IntegerField(
default=1, default=1,
validators=[MinValueValidator(1)], validators=[MinValueValidator(1)],
help_text=_("determines the order in which images are displayed"), help_text=_("determines the order in which images are displayed"),
verbose_name=_("display priority"), verbose_name=_("display priority"),
) )
product = ForeignKey( product: ForeignKey = ForeignKey(
"core.Product", "core.Product",
on_delete=CASCADE, on_delete=CASCADE,
help_text=_("the product that this image represents"), help_text=_("the product that this image represents"),
@ -1027,7 +1032,7 @@ class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel):
class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel): class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel):
is_publicly_visible = False is_publicly_visible = False
code = CharField( code: CharField = CharField(
max_length=20, max_length=20,
unique=True, unique=True,
default=get_random_code, default=get_random_code,
@ -1042,7 +1047,7 @@ class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel):
help_text=_("fixed discount amount applied if percent is not used"), help_text=_("fixed discount amount applied if percent is not used"),
verbose_name=_("fixed discount amount"), verbose_name=_("fixed discount amount"),
) )
discount_percent = IntegerField( discount_percent: IntegerField = IntegerField(
validators=[MinValueValidator(1), MaxValueValidator(100)], validators=[MinValueValidator(1), MaxValueValidator(100)],
blank=True, blank=True,
null=True, null=True,
@ -1067,7 +1072,7 @@ class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel):
help_text=_("timestamp when the promocode was used, blank if not used yet"), help_text=_("timestamp when the promocode was used, blank if not used yet"),
verbose_name=_("usage timestamp"), verbose_name=_("usage timestamp"),
) )
user = ForeignKey( user: ForeignKey = ForeignKey(
"vibes_auth.User", "vibes_auth.User",
on_delete=CASCADE, on_delete=CASCADE,
help_text=_("user assigned to this promocode if applicable"), help_text=_("user assigned to this promocode if applicable"),
@ -1125,12 +1130,12 @@ class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel):
class Promotion(ExportModelOperationsMixin("promotion"), NiceModel): class Promotion(ExportModelOperationsMixin("promotion"), NiceModel):
is_publicly_visible = True is_publicly_visible = True
discount_percent = IntegerField( discount_percent: IntegerField = IntegerField(
validators=[MinValueValidator(1), MaxValueValidator(100)], validators=[MinValueValidator(1), MaxValueValidator(100)],
help_text=_("percentage discount for the selected products"), help_text=_("percentage discount for the selected products"),
verbose_name=_("discount percentage"), verbose_name=_("discount percentage"),
) )
name = CharField( name: CharField = CharField(
max_length=256, max_length=256,
unique=True, unique=True,
help_text=_("provide a unique name for this promotion"), help_text=_("provide a unique name for this promotion"),
@ -1142,7 +1147,7 @@ class Promotion(ExportModelOperationsMixin("promotion"), NiceModel):
help_text=_("add a detailed description of the product"), help_text=_("add a detailed description of the product"),
verbose_name=_("promotion description"), verbose_name=_("promotion description"),
) )
products = ManyToManyField( products: ManyToManyField = ManyToManyField(
"core.Product", "core.Product",
blank=True, blank=True,
help_text=_("select which products are included in this promotion"), help_text=_("select which products are included in this promotion"),
@ -1162,7 +1167,7 @@ class Promotion(ExportModelOperationsMixin("promotion"), NiceModel):
class Stock(ExportModelOperationsMixin("stock"), NiceModel): class Stock(ExportModelOperationsMixin("stock"), NiceModel):
is_publicly_visible = False is_publicly_visible = False
vendor = ForeignKey( vendor: ForeignKey = ForeignKey(
"core.Vendor", "core.Vendor",
on_delete=CASCADE, on_delete=CASCADE,
help_text=_("the vendor supplying this product stock"), help_text=_("the vendor supplying this product stock"),
@ -1173,7 +1178,7 @@ class Stock(ExportModelOperationsMixin("stock"), NiceModel):
help_text=_("final price to the customer after markups"), help_text=_("final price to the customer after markups"),
verbose_name=_("selling price"), verbose_name=_("selling price"),
) )
product = ForeignKey( product: ForeignKey = ForeignKey(
"core.Product", "core.Product",
on_delete=CASCADE, on_delete=CASCADE,
help_text=_("the product associated with this stock entry"), help_text=_("the product associated with this stock entry"),
@ -1187,12 +1192,12 @@ class Stock(ExportModelOperationsMixin("stock"), NiceModel):
help_text=_("the price paid to the vendor for this product"), help_text=_("the price paid to the vendor for this product"),
verbose_name=_("vendor purchase price"), verbose_name=_("vendor purchase price"),
) )
quantity = IntegerField( quantity: IntegerField = IntegerField(
default=0, default=0,
help_text=_("available quantity of the product in stock"), help_text=_("available quantity of the product in stock"),
verbose_name=_("quantity in stock"), verbose_name=_("quantity in stock"),
) )
sku = CharField( sku: CharField = CharField(
max_length=255, max_length=255,
help_text=_("vendor-assigned SKU for identifying the product"), help_text=_("vendor-assigned SKU for identifying the product"),
verbose_name=_("vendor sku"), verbose_name=_("vendor sku"),
@ -1217,13 +1222,13 @@ class Stock(ExportModelOperationsMixin("stock"), NiceModel):
class Wishlist(ExportModelOperationsMixin("wishlist"), NiceModel): class Wishlist(ExportModelOperationsMixin("wishlist"), NiceModel):
is_publicly_visible = False is_publicly_visible = False
products = ManyToManyField( products: ManyToManyField = ManyToManyField(
"core.Product", "core.Product",
blank=True, blank=True,
help_text=_("products that the user has marked as wanted"), help_text=_("products that the user has marked as wanted"),
verbose_name=_("wishlisted products"), verbose_name=_("wishlisted products"),
) )
user = OneToOneField( user: OneToOneField = OneToOneField(
"vibes_auth.User", "vibes_auth.User",
on_delete=CASCADE, on_delete=CASCADE,
blank=True, blank=True,
@ -1278,8 +1283,8 @@ class Wishlist(ExportModelOperationsMixin("wishlist"), NiceModel):
class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceModel): class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceModel):
is_publicly_visible = False is_publicly_visible = False
order_product = OneToOneField(to=OrderProduct, on_delete=CASCADE, related_name="download") order_product: OneToOneField = OneToOneField(to=OrderProduct, on_delete=CASCADE, related_name="download")
num_downloads = IntegerField(default=0) num_downloads: IntegerField = IntegerField(default=0)
class Meta: class Meta:
verbose_name = _("download") verbose_name = _("download")
@ -1293,13 +1298,15 @@ class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceMo
if self.order_product.status != "FINISHED": if self.order_product.status != "FINISHED":
raise ValueError(_("you can not download a digital asset for a non-finished order")) raise ValueError(_("you can not download a digital asset for a non-finished order"))
return f"https://api.{config.BASE_URL}/download/{urlsafe_base64_encode(force_bytes(self.order_product.uuid))}" return (
f"https://api.{config.BASE_DOMAIN}/download/{urlsafe_base64_encode(force_bytes(self.order_product.uuid))}"
)
class Documentary(ExportModelOperationsMixin("attribute_group"), NiceModel): class Documentary(ExportModelOperationsMixin("attribute_group"), NiceModel):
is_publicly_visible = True is_publicly_visible = True
product = ForeignKey(to=Product, on_delete=CASCADE, related_name="documentaries") product: ForeignKey = ForeignKey(to=Product, on_delete=CASCADE, related_name="documentaries")
document = FileField(upload_to=get_product_uuid_as_path) document = FileField(upload_to=get_product_uuid_as_path)
class Meta: class Meta:
@ -1326,22 +1333,26 @@ class Address(ExportModelOperationsMixin("address"), NiceModel):
help_text=_("address line for the customer"), help_text=_("address line for the customer"),
verbose_name=_("address line"), verbose_name=_("address line"),
) )
street = CharField(_("street"), max_length=255, null=True) # noqa: DJ001 street: CharField = CharField(_("street"), max_length=255, null=True)
district = CharField(_("district"), max_length=255, null=True) # noqa: DJ001 district: CharField = CharField(_("district"), max_length=255, null=True)
city = CharField(_("city"), max_length=100, null=True) # noqa: DJ001 city: CharField = CharField(_("city"), max_length=100, null=True)
region = CharField(_("region"), max_length=100, null=True) # noqa: DJ001 region: CharField = CharField(_("region"), max_length=100, null=True)
postal_code = CharField(_("postal code"), max_length=20, null=True) # noqa: DJ001 postal_code: CharField = CharField(_("postal code"), max_length=20, null=True)
country = CharField(_("country"), max_length=40, null=True) # noqa: DJ001 country: CharField = CharField(_("country"), max_length=40, null=True)
location = PointField( location: PointField = PointField(
geography=True, srid=4326, null=True, blank=True, help_text=_("geolocation point: (longitude, latitude)") geography=True, srid=4326, null=True, blank=True, help_text=_("geolocation point: (longitude, latitude)")
) )
raw_data = JSONField(blank=True, null=True, help_text=_("full JSON response from geocoder for this address")) raw_data: JSONField = JSONField(
blank=True, null=True, help_text=_("full JSON response from geocoder for this address")
)
api_response = JSONField(blank=True, null=True, help_text=_("stored JSON response from the geocoding service")) api_response: JSONField = JSONField(
blank=True, null=True, help_text=_("stored JSON response from the geocoding service")
)
user = ForeignKey(to="vibes_auth.User", on_delete=CASCADE, blank=True, null=True) user: ForeignKey = ForeignKey(to="vibes_auth.User", on_delete=CASCADE, blank=True, null=True)
objects = AddressManager() objects = AddressManager()

View file

@ -13,6 +13,7 @@ class IsOwnerOrReadOnly(permissions.BasePermission):
return obj.user == request.user return obj.user == request.user
# noinspection PyProtectedMember
class EvibesPermission(permissions.BasePermission): class EvibesPermission(permissions.BasePermission):
ACTION_PERM_MAP = { ACTION_PERM_MAP = {
"retrieve": "view", "retrieve": "view",

View file

@ -1,109 +1,3 @@
from rest_framework.fields import (
BooleanField,
CharField,
Field,
IntegerField,
JSONField,
ListField,
UUIDField,
)
from rest_framework.serializers import ListSerializer, Serializer
from .detail import * # noqa: F403 from .detail import * # noqa: F403
from .simple import * # noqa: F403 from .simple import * # noqa: F403
from .utility import * # noqa: F403
class CacheOperatorSerializer(Serializer):
key = CharField(required=True)
data = JSONField(required=False)
timeout = IntegerField(required=False)
class ContactUsSerializer(Serializer):
email = CharField(required=True)
name = CharField(required=True)
subject = CharField(required=True)
phone_number = CharField(required=False)
message = CharField(required=True)
class LanguageSerializer(Serializer):
code = CharField(required=True)
name = CharField(required=True)
flag = CharField()
class RecursiveField(Field):
def to_representation(self, value):
parent = self.parent
if isinstance(parent, ListSerializer):
parent = parent.parent
serializer_class = parent.__class__
return serializer_class(value, context=self.context).data
def to_internal_value(self, data):
return data
class AddOrderProductSerializer(Serializer):
product_uuid = CharField(required=True)
attributes = JSONField(required=False, default=dict)
class BulkAddOrderProductsSerializer(Serializer):
products = ListField(child=AddOrderProductSerializer(), required=True)
class RemoveOrderProductSerializer(Serializer):
product_uuid = CharField(required=True)
attributes = JSONField(required=False, default=dict)
class BulkRemoveOrderProductsSerializer(Serializer):
products = ListField(child=RemoveOrderProductSerializer(), required=True)
class AddWishlistProductSerializer(Serializer):
product_uuid = CharField(required=True)
class RemoveWishlistProductSerializer(Serializer):
product_uuid = CharField(required=True)
class BulkAddWishlistProductSerializer(Serializer):
product_uuids = ListField(child=CharField(required=True), allow_empty=False, max_length=64)
class BulkRemoveWishlistProductSerializer(Serializer):
product_uuids = ListField(child=CharField(required=True), allow_empty=False, max_length=64)
class BuyOrderSerializer(Serializer):
force_balance = BooleanField(required=False, default=False)
force_payment = BooleanField(required=False, default=False)
promocode_uuid = CharField(required=False)
shipping_address_uuid = CharField(required=False)
billing_address_uuid = CharField(required=False)
class BuyUnregisteredOrderSerializer(Serializer):
products = ListField(child=AddOrderProductSerializer(), required=True)
promocode_uuid = UUIDField(required=False)
customer_name = CharField(required=True)
customer_email = CharField(required=True)
customer_phone_number = CharField(required=True)
billing_customer_address_uuid = CharField(required=False)
shipping_customer_address_uuid = CharField(required=False)
payment_method = CharField(required=True)
class BuyAsBusinessOrderSerializer(Serializer):
products = ListField(child=AddOrderProductSerializer(), required=True)
business_inn = CharField(required=True)
business_email = CharField(required=True)
business_phone_number = CharField(required=True)
billing_business_address_uuid = CharField(required=False)
shipping_business_address_uuid = CharField(required=False)
payment_method = CharField(required=True)

View file

@ -1,10 +1,12 @@
import logging import logging
from contextlib import suppress from contextlib import suppress
from typing import Optional from typing import Any, Collection, Optional
from django.contrib.auth.models import AnonymousUser
from django.core.cache import cache from django.core.cache import cache
from django.db.models.functions import Length from django.db.models.functions import Length
from rest_framework.fields import JSONField, SerializerMethodField from rest_framework.fields import JSONField, SerializerMethodField
from rest_framework.request import Request
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework_recursive.fields import RecursiveField from rest_framework_recursive.fields import RecursiveField
@ -28,6 +30,7 @@ from core.models import (
) )
from core.serializers.simple import CategorySimpleSerializer, ProductSimpleSerializer from core.serializers.simple import CategorySimpleSerializer, ProductSimpleSerializer
from core.serializers.utility import AddressSerializer from core.serializers.utility import AddressSerializer
from vibes_auth.models import User
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -77,8 +80,11 @@ class CategoryDetailSerializer(ModelSerializer):
if filterable_results: if filterable_results:
return filterable_results return filterable_results
request = self.context.get("request") request: Request | None = self.context.get("request")
user = getattr(request, "user", None) user: User | AnonymousUser | None = getattr(request, "user") # noqa: B009
if user is None:
user = AnonymousUser()
attributes = obj.attributes.all() if user.has_perm("view_attribute") else obj.attributes.filter(is_active=True) attributes = obj.attributes.all() if user.has_perm("view_attribute") else obj.attributes.filter(is_active=True)
@ -100,12 +106,15 @@ class CategoryDetailSerializer(ModelSerializer):
} }
) )
if user is None:
user = AnonymousUser()
if not user.has_perm("view_attribute"): if not user.has_perm("view_attribute"):
cache.set(f"{obj.uuid}_filterable_results", filterable_results, 86400) cache.set(f"{obj.uuid}_filterable_results", filterable_results, 86400)
return filterable_results return filterable_results
def get_children(self, obj) -> list[dict]: def get_children(self, obj) -> Collection[Any]:
request = self.context.get("request") request = self.context.get("request")
if request is not None and request.user.has_perm("view_category"): if request is not None and request.user.has_perm("view_category"):
children = obj.children.all() children = obj.children.all()

View file

@ -1,5 +1,5 @@
from contextlib import suppress from contextlib import suppress
from typing import Optional from typing import Collection, Optional
from rest_framework.fields import JSONField, SerializerMethodField from rest_framework.fields import JSONField, SerializerMethodField
from rest_framework.relations import PrimaryKeyRelatedField from rest_framework.relations import PrimaryKeyRelatedField
@ -27,8 +27,8 @@ from core.serializers.utility import AddressSerializer
class AttributeGroupSimpleSerializer(ModelSerializer): class AttributeGroupSimpleSerializer(ModelSerializer):
parent = PrimaryKeyRelatedField(read_only=True) parent: PrimaryKeyRelatedField = PrimaryKeyRelatedField(read_only=True)
children = PrimaryKeyRelatedField(many=True, read_only=True) children: PrimaryKeyRelatedField = PrimaryKeyRelatedField(many=True, read_only=True)
class Meta: class Meta:
model = AttributeGroup model = AttributeGroup
@ -59,7 +59,7 @@ class CategorySimpleSerializer(ModelSerializer):
return obj.image.url return obj.image.url
return None return None
def get_children(self, obj) -> list[dict]: def get_children(self, obj) -> Collection:
request = self.context.get("request") request = self.context.get("request")
if request is not None and request.user.has_perm("view_category"): if request is not None and request.user.has_perm("view_category"):
children = obj.children.all() children = obj.children.all()
@ -111,7 +111,7 @@ class ProductTagSimpleSerializer(ModelSerializer):
class ProductImageSimpleSerializer(ModelSerializer): class ProductImageSimpleSerializer(ModelSerializer):
product = PrimaryKeyRelatedField(read_only=True) product: PrimaryKeyRelatedField = PrimaryKeyRelatedField(read_only=True)
class Meta: class Meta:
model = ProductImage model = ProductImage
@ -139,7 +139,7 @@ class AttributeSimpleSerializer(ModelSerializer):
class AttributeValueSimpleSerializer(ModelSerializer): class AttributeValueSimpleSerializer(ModelSerializer):
attribute = AttributeSimpleSerializer(read_only=True) attribute = AttributeSimpleSerializer(read_only=True)
product = PrimaryKeyRelatedField(read_only=True) product: PrimaryKeyRelatedField = PrimaryKeyRelatedField(read_only=True)
class Meta: class Meta:
model = AttributeValue model = AttributeValue
@ -246,7 +246,7 @@ class PromotionSimpleSerializer(ModelSerializer):
class WishlistSimpleSerializer(ModelSerializer): class WishlistSimpleSerializer(ModelSerializer):
user = PrimaryKeyRelatedField(read_only=True) user: PrimaryKeyRelatedField = PrimaryKeyRelatedField(read_only=True)
products = ProductSimpleSerializer(many=True, read_only=True) products = ProductSimpleSerializer(many=True, read_only=True)
class Meta: class Meta:
@ -259,7 +259,7 @@ class WishlistSimpleSerializer(ModelSerializer):
class FeedbackSimpleSerializer(ModelSerializer): class FeedbackSimpleSerializer(ModelSerializer):
order_product = PrimaryKeyRelatedField(read_only=True) order_product: PrimaryKeyRelatedField = PrimaryKeyRelatedField(read_only=True)
class Meta: class Meta:
model = Feedback model = Feedback
@ -285,7 +285,7 @@ class OrderProductSimpleSerializer(ModelSerializer):
class OrderSimpleSerializer(ModelSerializer): class OrderSimpleSerializer(ModelSerializer):
user = PrimaryKeyRelatedField(read_only=True) user: PrimaryKeyRelatedField = PrimaryKeyRelatedField(read_only=True)
promo_code = PromoCodeSimpleSerializer(read_only=True) promo_code = PromoCodeSimpleSerializer(read_only=True)
order_products = OrderProductSimpleSerializer(many=True, read_only=True) order_products = OrderProductSimpleSerializer(many=True, read_only=True)
billing_address = AddressSerializer(read_only=True, required=False) billing_address = AddressSerializer(read_only=True, required=False)

View file

@ -1,7 +1,17 @@
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, DictField, FloatField, IntegerField from rest_framework.fields import (
from rest_framework.serializers import ModelSerializer, Serializer BooleanField,
CharField,
DictField,
Field,
FloatField,
IntegerField,
JSONField,
ListField,
UUIDField,
)
from rest_framework.serializers import ListSerializer, ModelSerializer, Serializer
from core.models import Address from core.models import Address
@ -75,3 +85,99 @@ class DoFeedbackSerializer(Serializer):
def validate(self, data): def validate(self, data):
if data["action"] == "add" and not all([data["comment"], data["rating"]]): if data["action"] == "add" and not all([data["comment"], data["rating"]]):
raise ValidationError(_("you must provide a comment, rating, and order product uuid to add feedback.")) raise ValidationError(_("you must provide a comment, rating, and order product uuid to add feedback."))
class CacheOperatorSerializer(Serializer):
key = CharField(required=True)
data = JSONField(required=False) # type: ignore
timeout = IntegerField(required=False)
class ContactUsSerializer(Serializer):
email = CharField(required=True)
name = CharField(required=True)
subject = CharField(required=True)
phone_number = CharField(required=False)
message = CharField(required=True)
class LanguageSerializer(Serializer):
code = CharField(required=True)
name = CharField(required=True)
flag = CharField()
class RecursiveField(Field):
def to_representation(self, value):
parent = self.parent
if isinstance(parent, ListSerializer):
parent = parent.parent
serializer_class = parent.__class__
return serializer_class(value, context=self.context).data
def to_internal_value(self, data):
return data
class AddOrderProductSerializer(Serializer):
product_uuid = CharField(required=True)
attributes = JSONField(required=False, default=dict)
class BulkAddOrderProductsSerializer(Serializer):
products = ListField(child=AddOrderProductSerializer(), required=True)
class RemoveOrderProductSerializer(Serializer):
product_uuid = CharField(required=True)
attributes = JSONField(required=False, default=dict)
class BulkRemoveOrderProductsSerializer(Serializer):
products = ListField(child=RemoveOrderProductSerializer(), required=True)
class AddWishlistProductSerializer(Serializer):
product_uuid = CharField(required=True)
class RemoveWishlistProductSerializer(Serializer):
product_uuid = CharField(required=True)
class BulkAddWishlistProductSerializer(Serializer):
product_uuids = ListField(child=CharField(required=True), allow_empty=False, max_length=64)
class BulkRemoveWishlistProductSerializer(Serializer):
product_uuids = ListField(child=CharField(required=True), allow_empty=False, max_length=64)
class BuyOrderSerializer(Serializer):
force_balance = BooleanField(required=False, default=False)
force_payment = BooleanField(required=False, default=False)
promocode_uuid = CharField(required=False)
shipping_address_uuid = CharField(required=False)
billing_address_uuid = CharField(required=False)
class BuyUnregisteredOrderSerializer(Serializer):
products = ListField(child=AddOrderProductSerializer(), required=True)
promocode_uuid = UUIDField(required=False)
customer_name = CharField(required=True)
customer_email = CharField(required=True)
customer_phone_number = CharField(required=True)
billing_customer_address_uuid = CharField(required=False)
shipping_customer_address_uuid = CharField(required=False)
payment_method = CharField(required=True)
class BuyAsBusinessOrderSerializer(Serializer):
products = ListField(child=AddOrderProductSerializer(), required=True)
business_inn = CharField(required=True)
business_email = CharField(required=True)
business_phone_number = CharField(required=True)
billing_business_address_uuid = CharField(required=False)
shipping_business_address_uuid = CharField(required=False)
payment_method = CharField(required=True)

View file

@ -1,209 +1,212 @@
<!DOCTYPE html> <!DOCTYPE html>
<!--suppress JSSuspiciousNameCombination -->
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8"/>
<title>Maintenance</title> <title>Maintenance</title>
<meta name="viewport" content="width=device-height, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport"
<link rel="preconnect" href="https://fonts.googleapis.com"> content="width=device-height, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@500&display=swap" rel="stylesheet"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<style> <link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@500&display=swap" rel="stylesheet">
html, body { <style>
margin: 0; html, body {
padding: 0; margin: 0;
width: 100%; padding: 0;
height: 100%; width: 100%;
overflow: hidden; height: 100%;
font-family: 'Source Code Pro', monospace; overflow: hidden;
background: #2a2a3a; font-family: 'Source Code Pro', monospace;
color: #fff; background: #2a2a3a;
} color: #fff;
canvas { }
display: block;
} canvas {
</style> display: block;
}
</style>
</head> </head>
<body> <body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script> <script>
(function() { (function () {
let scene, camera, renderer; let scene, camera, renderer;
let particleSystem, textGroup, fontLoader; let particleSystem, textGroup, fontLoader;
const clock = new THREE.Clock(); const clock = new THREE.Clock();
let targetRotX = 0, targetRotY = 0; let targetRotX = 0, targetRotY = 0;
let currentRotX = 0, currentRotY = 0; let currentRotX = 0, currentRotY = 0;
let isMobile = 'ontouchstart' in window; let isMobile = 'ontouchstart' in window;
function init() { function init() {
scene = new THREE.Scene(); scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 2000); camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 2000);
camera.position.z = 70; camera.position.z = 70;
renderer = new THREE.WebGLRenderer({ antialias: true }); renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight); renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio); renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement); document.body.appendChild(renderer.domElement);
createGradientBackground(); createGradientBackground();
createParticleField(); createParticleField();
fontLoader = new THREE.FontLoader(); fontLoader = new THREE.FontLoader();
fontLoader.load('https://api.evibes.com/static/Source%20Code%20Pro%20ExtraLight_Regular.json', f => { fontLoader.load('https://api.evibes.com/static/Source%20Code%20Pro%20ExtraLight_Regular.json', f => {
create3DText(f); create3DText(f);
fitCameraToText(); fitCameraToText();
}); });
window.addEventListener('resize', onWindowResize); window.addEventListener('resize', onWindowResize);
if (isMobile) { if (isMobile) {
window.addEventListener('touchmove', onTouchMove, { passive: false }); window.addEventListener('touchmove', onTouchMove, {passive: false});
} else { } else {
window.addEventListener('mousemove', onMouseMove); window.addEventListener('mousemove', onMouseMove);
} }
onWindowResize(); onWindowResize();
animate(); animate();
} }
function createGradientBackground() { function createGradientBackground() {
const c = document.createElement('canvas'); const c = document.createElement('canvas');
c.width = 2; c.width = 2;
c.height = 2; c.height = 2;
const ctx = c.getContext('2d'); const ctx = c.getContext('2d');
const gradient = ctx.createLinearGradient(0, 0, 0, 2); const gradient = ctx.createLinearGradient(0, 0, 0, 2);
gradient.addColorStop(0, '#1E1E2A'); gradient.addColorStop(0, '#1E1E2A');
gradient.addColorStop(1, '#4C4C6A'); gradient.addColorStop(1, '#4C4C6A');
ctx.fillStyle = gradient; ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 2, 2); ctx.fillRect(0, 0, 2, 2);
const texture = new THREE.Texture(c); const texture = new THREE.Texture(c);
texture.needsUpdate = true; texture.needsUpdate = true;
scene.background = texture; scene.background = texture;
} }
function createParticleField() { function createParticleField() {
const particleCount = 15000; const particleCount = 15000;
const geometry = new THREE.BufferGeometry(); const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3); const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3); const colors = new Float32Array(particleCount * 3);
const colorStart = new THREE.Color(0x34000d); const colorStart = new THREE.Color(0x34000d);
const colorEnd = new THREE.Color(0x02066F); const colorEnd = new THREE.Color(0x02066F);
for (let i = 0; i < particleCount; i++) { for (let i = 0; i < particleCount; i++) {
positions[i * 3] = (Math.random() - 0.5) * 400; positions[i * 3] = (Math.random() - 0.5) * 400;
positions[i * 3 + 1] = (Math.random() - 0.5) * 400; positions[i * 3 + 1] = (Math.random() - 0.5) * 400;
positions[i * 3 + 2] = (Math.random() - 0.5) * 400; positions[i * 3 + 2] = (Math.random() - 0.5) * 400;
const mixRatio = i / particleCount; const mixRatio = i / particleCount;
const color = colorStart.clone().lerp(colorEnd, mixRatio); const color = colorStart.clone().lerp(colorEnd, mixRatio);
colors[i * 3] = color.r; colors[i * 3] = color.r;
colors[i * 3 + 1] = color.g; colors[i * 3 + 1] = color.g;
colors[i * 3 + 2] = color.b; colors[i * 3 + 2] = color.b;
} }
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({ const material = new THREE.PointsMaterial({
size: 0.7, size: 0.7,
vertexColors: true, vertexColors: true,
transparent: true, transparent: true,
opacity: 0.7, opacity: 0.7,
blending: THREE.AdditiveBlending blending: THREE.AdditiveBlending
}); });
particleSystem = new THREE.Points(geometry, material); particleSystem = new THREE.Points(geometry, material);
scene.add(particleSystem); scene.add(particleSystem);
} }
function create3DText(font) { function create3DText(font) {
textGroup = new THREE.Group(); textGroup = new THREE.Group();
const text1 = new THREE.TextGeometry("Well Be Back Soon", { const text1 = new THREE.TextGeometry("Well Be Back Soon", {
font, font,
size: 5, size: 5,
height: 1, height: 1,
curveSegments: 12, curveSegments: 12,
bevelEnabled: true, bevelEnabled: true,
bevelThickness: 0.2, bevelThickness: 0.2,
bevelSize: 0.1, bevelSize: 0.1,
bevelSegments: 5 bevelSegments: 5
}); });
const material1 = new THREE.MeshPhongMaterial({ color: 0xffffff, emissive: 0x111111 }); const material1 = new THREE.MeshPhongMaterial({color: 0xffffff, emissive: 0x111111});
const mesh1 = new THREE.Mesh(text1, material1); const mesh1 = new THREE.Mesh(text1, material1);
mesh1.geometry.center(); mesh1.geometry.center();
mesh1.position.y = 5; mesh1.position.y = 5;
textGroup.add(mesh1); textGroup.add(mesh1);
const text2 = new THREE.TextGeometry("An update is in progress. Please check back later.", { const text2 = new THREE.TextGeometry("An update is in progress. Please check back later.", {
font, font,
size: 2, size: 2,
height: 0.5, height: 0.5,
curveSegments: 12, curveSegments: 12,
bevelEnabled: false bevelEnabled: false
}); });
const material2 = new THREE.MeshPhongMaterial({ color: 0xffffff, emissive: 0x111111 }); const material2 = new THREE.MeshPhongMaterial({color: 0xffffff, emissive: 0x111111});
const mesh2 = new THREE.Mesh(text2, material2); const mesh2 = new THREE.Mesh(text2, material2);
mesh2.geometry.center(); mesh2.geometry.center();
mesh2.position.y = -5; mesh2.position.y = -5;
textGroup.add(mesh2); textGroup.add(mesh2);
scene.add(textGroup); scene.add(textGroup);
const ambientLight = new THREE.AmbientLight(0x404040, 2); const ambientLight = new THREE.AmbientLight(0x404040, 2);
scene.add(ambientLight); scene.add(ambientLight);
const spotLight = new THREE.SpotLight(0xffffff, 1); const spotLight = new THREE.SpotLight(0xffffff, 1);
spotLight.position.set(100, 100, 100); spotLight.position.set(100, 100, 100);
scene.add(spotLight); scene.add(spotLight);
} }
function fitCameraToText() { function fitCameraToText() {
if (!textGroup) return; if (!textGroup) return;
const box = new THREE.Box3().setFromObject(textGroup); const box = new THREE.Box3().setFromObject(textGroup);
const size = box.getSize(new THREE.Vector3()); const size = box.getSize(new THREE.Vector3());
const center = box.getCenter(new THREE.Vector3()); const center = box.getCenter(new THREE.Vector3());
const halfSizeToFitOnScreen = size.length() * 0.5; const halfSizeToFitOnScreen = size.length() * 0.5;
const halfFov = THREE.MathUtils.degToRad(camera.fov * 0.5); const halfFov = THREE.MathUtils.degToRad(camera.fov * 0.5);
let distance = halfSizeToFitOnScreen / Math.sin(halfFov); let distance = halfSizeToFitOnScreen / Math.sin(halfFov);
distance *= 1.5; distance *= 1.5;
camera.position.set(center.x, center.y, distance); camera.position.set(center.x, center.y, distance);
camera.lookAt(center); camera.lookAt(center);
} }
function animate() { function animate() {
requestAnimationFrame(animate); requestAnimationFrame(animate);
const delta = clock.getDelta(); const delta = clock.getDelta();
if (particleSystem) { if (particleSystem) {
particleSystem.rotation.y += 0.02 * delta; particleSystem.rotation.y += 0.02 * delta;
} }
currentRotX += (targetRotX - currentRotX) * 0.1; currentRotX += (targetRotX - currentRotX) * 0.1;
currentRotY += (targetRotY - currentRotY) * 0.1; currentRotY += (targetRotY - currentRotY) * 0.1;
if (textGroup) { if (textGroup) {
textGroup.rotation.x = currentRotY; textGroup.rotation.x = currentRotY;
textGroup.rotation.y = currentRotX; textGroup.rotation.y = currentRotX;
} }
renderer.render(scene, camera); renderer.render(scene, camera);
} }
function onWindowResize() { function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight; camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix(); camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight); renderer.setSize(window.innerWidth, window.innerHeight);
fitCameraToText(); fitCameraToText();
} }
function onMouseMove(event) { function onMouseMove(event) {
targetRotX = ((event.clientX / window.innerWidth) - 0.5) * 2 * 0.3; targetRotX = ((event.clientX / window.innerWidth) - 0.5) * 2 * 0.3;
targetRotY = ((event.clientY / window.innerHeight) - 0.5) * 2 * 0.3; targetRotY = ((event.clientY / window.innerHeight) - 0.5) * 2 * 0.3;
} }
function onTouchMove(event) { function onTouchMove(event) {
if (event.touches.length > 0) { if (event.touches.length > 0) {
const touch = event.touches[0]; const touch = event.touches[0];
targetRotX = ((touch.clientX / window.innerWidth) - 0.5) * 2 * 0.3; targetRotX = ((touch.clientX / window.innerWidth) - 0.5) * 2 * 0.3;
targetRotY = ((touch.clientY / window.innerHeight) - 0.5) * 2 * 0.3; targetRotY = ((touch.clientY / window.innerHeight) - 0.5) * 2 * 0.3;
} }
event.preventDefault(); event.preventDefault();
} }
init(); init();
})(); })();
</script> </script>
</body> </body>
</html> </html>

View file

@ -30,7 +30,7 @@ def update_products_task():
includes invoking the `update_stock` method of vendor classes and removing includes invoking the `update_stock` method of vendor classes and removing
stale products. Finally, it clears the flag in the cache. stale products. Finally, it clears the flag in the cache.
Just write integrations with your vendors' APIs into core/vendors/<vendor_name>.py and use it here :) Write integrations with your vendors' APIs into core/vendors/<vendor_name>.py and use it here :)
:return: A tuple consisting of a status boolean and a message string :return: A tuple consisting of a status boolean and a message string
:rtype: tuple[bool, str] :rtype: tuple[bool, str]
@ -64,7 +64,7 @@ def update_orderproducts_task():
`vendors_classes`. Each vendor class in the `vendors_classes` list is `vendors_classes`. Each vendor class in the `vendors_classes` list is
instantiated, and the `update_order_products_statuses` method of the instantiated, and the `update_order_products_statuses` method of the
respective vendor instance is executed to handle the update process. respective vendor instance is executed to handle the update process.
Just write integrations with your vendors' APIs into core/vendors/<vendor_name>.py and use it here :) Write integrations with your vendors' APIs into core/vendors/<vendor_name>.py and use it here :)
:return: A tuple containing a boolean indicating success and a string :return: A tuple containing a boolean indicating success and a string
message confirming the successful execution of the task. message confirming the successful execution of the task.
@ -104,7 +104,7 @@ def remove_stale_product_images():
The task scans the product images directory to locate subdirectories named after The task scans the product images directory to locate subdirectories named after
product UUIDs. It verifies whether each UUID is part of the database's current product UUIDs. It verifies whether each UUID is part of the database's current
product records. If a directory's UUID is not found in the database, it deletes product records. If a directory's UUID is not found in the database, it deletes
the directory, as it is considered stale. This helps in maintaining a clean storage the directory, as it is considered stale. This helps in maintaining clean storage
and removing unused image data. and removing unused image data.
:raises ValueError: If a directory name is not a valid UUID. :raises ValueError: If a directory name is not a valid UUID.
@ -203,7 +203,7 @@ def process_promotions() -> tuple[bool, str]:
if eligible_products.count() < 48: if eligible_products.count() < 48:
return False, "Not enough products to choose from [< 48]." return False, "Not enough products to choose from [< 48]."
selected_products = [] selected_products: list = []
while len(selected_products) < 48: while len(selected_products) < 48:
product = eligible_products.order_by("?").first() product = eligible_products.order_by("?").first()
@ -211,8 +211,9 @@ def process_promotions() -> tuple[bool, str]:
promotion = Promotion.objects.update_or_create( promotion = Promotion.objects.update_or_create(
name=promotion_name, defaults={"discount_percent": discount_percent, "is_active": True} name=promotion_name, defaults={"discount_percent": discount_percent, "is_active": True}
) )[0]
promotion.products.set(selected_products) for product in selected_products:
promotion.products.add(product)
return True, "Promotions updated successfully." return True, "Promotions updated successfully."

View file

@ -1,68 +0,0 @@
{% extends "admin/base_site.html" %}
{% load admin_list static i18n %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/changelists.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/forms.css' %}">
{{ media.css }}
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/constance.css' %}">
{% endblock %}
{% block extrahead %}
{% url 'admin:jsi18n' as jsi18nurl %}
<script type="text/javascript" src="{{ jsi18nurl|default:'../../jsi18n/' }}"></script>
{{ block.super }}
{{ media.js }}
<script type="text/javascript" src="{% static 'admin/js/constance.js' %}"></script>
{% if django_version < "5.1" %}
<script type="text/javascript" src="{% static 'admin/js/collapse.js' %}"></script>
{% endif %}
{% endblock %}
{% block bodyclass %}{{ block.super }} change-list{% endblock %}
{% block content %}
<div id="content-main" class="constance">
<div class="module" id="changelist">
<form id="changelist-form" action="" method="post" enctype="multipart/form-data">{% csrf_token %}
{% if form.non_field_errors %}
<ul class="errorlist">
{% for error in form.non_field_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{% if form.errors %}
<ul class="errorlist">
{% endif %}
{% for field in form.hidden_fields %}
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
{{ field }}
{% endfor %}
{% if form.errors %}
</ul>
{% endif %}
<fieldset class="module">
<h2>{% trans "configuration" %}</h2>
{% include "admin/constance/includes/results_list.html" %}
</fieldset>
<p class="paginator sticky-footer">
<input type="submit" name="_save" class="default" value="{% trans 'save' %}">
</p>
</form>
</div>
</div>
{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; {{ opts.verbose_name_plural|capfirst }}
</div>
{% endblock %}

Some files were not shown because too many files have changed in this diff Show more