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
# Host's scripts
scripts
scripts/Windows
scripts/Unix

1
.gitignore vendored
View file

@ -60,7 +60,6 @@ htmlcov/
.tox/
.nox/
.scrapy
.coverage.*
.cover
.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
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)
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
@ -20,7 +21,7 @@ cases and learning Django skills. The project is easy to customize, allowing for
## 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.
- **Asynchronous Task Processing**: Integrated Celery workers and beat scheduler for background tasks.
- **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
- Docker and Docker Compose installed on your machine - that's it!
- Docker and Docker Compose are installed on your machine.
### 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 >
```
3. Generate your .env file. Check and confirm the contents afterwards.
3. Generate your .env file. Check and confirm the contents afterward.
- Windows
```powershell
@ -85,29 +86,29 @@ cases and learning Django skills. The project is easy to customize, allowing for
6. Bring to production.
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
### 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`
before running installment scripts
### 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!
### .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.
## 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
2. www.your-domain.com

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,13 +1,13 @@
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 2.8.5\n"
"Report-Msgid-Bugs-To: <CONTACT@FUREUNOIR.COM> \n"
"POT-Creation-Date: 2025-05-27 13:42+0100\n"
"Project-Id-Version: EVIBES 2.8.9\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-18 12:55+0100\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
"Language: ja-JP\n"
"Language: ja-jp\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "(no content yet)"
msgstr "(内容はまだありません)"
#: blog/admin.py:40
#: blog/admin.py:41
msgid "rendered HTML"
msgstr "レンダリングされたHTML"
@ -25,49 +25,51 @@ msgstr "レンダリングされたHTML"
msgid "blog"
msgstr "ブログ"
#: blog/models.py:15
#: blog/models.py:17
msgid "post title"
msgstr "投稿タイトル"
#: blog/models.py:15
#: blog/models.py:17
msgid "title"
msgstr "タイトル"
#: blog/models.py:62
#: blog/models.py:64
msgid "post"
msgstr "ポスト"
#: blog/models.py:63
#: blog/models.py:65
msgid "posts"
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 ""
"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"
msgstr "投稿タグの内部タグ識別子"
#: blog/models.py:79
#: blog/models.py:83
msgid "tag name"
msgstr "タグ名"
#: blog/models.py:83
#: blog/models.py:87
msgid "user-friendly name for the post tag"
msgstr "投稿タグのユーザーフレンドリーな名前"
#: blog/models.py:84
#: blog/models.py:88
msgid "tag display name"
msgstr "タグ表示名"
#: blog/models.py:92
#: blog/models.py:96
msgid "post tag"
msgstr "投稿タグ"
#: blog/models.py:93
#: blog/models.py:97
msgid "post tags"
msgstr "投稿タグ"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,14 +7,14 @@ from django_extensions.db.fields import CreationDateTimeField, ModificationDateT
class NiceModel(Model):
id = None
uuid = UUIDField(
uuid: UUIDField = UUIDField(
verbose_name=_("unique id"),
help_text=_("unique id is used to surely identify any database object"),
primary_key=True,
default=uuid.uuid4,
editable=False,
)
is_active = BooleanField(
is_active: BooleanField = BooleanField(
default=True,
verbose_name=_("is active"),
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):
return instance.name
indented_title.short_description = _("name")
indented_title.admin_order_field = "name"
indented_title.short_description = _("name") # type: ignore
indented_title.admin_order_field = "name" # type: ignore
@admin.register(Brand)
@ -191,12 +191,12 @@ class ProductAdmin(BasicModelAdmin, TabbedTranslationAdmin):
def price(self, obj):
return obj.price
price.short_description = _("price")
price.short_description = _("price") # type: ignore
def rating(self, obj):
return obj.rating
rating.short_description = _("rating")
rating.short_description = _("rating") # type: ignore
fieldsets = (
(
@ -278,7 +278,7 @@ class OrderAdmin(BasicModelAdmin):
def is_business(self, obj):
return obj.is_business
is_business.short_description = _("is business")
is_business.short_description = _("is business") # type: ignore
def get_queryset(self, request):
qs = super().get_queryset(request)
@ -431,6 +431,6 @@ class ConstanceConfig:
admin.site.unregister([Config]) # 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.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()
# Collect results, guard against None values
results = {"products": [], "categories": [], "brands": [], "posts": []}
results: dict = {"products": [], "categories": [], "brands": [], "posts": []}
for hit in response.hits:
obj_uuid = getattr(hit, "uuid", None) or hit.meta.id
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"]
def filter_whole_categories(self, queryset, _name, value):
has_own_products = Exists(
Product.objects.filter(category=OuterRef('pk'))
)
has_own_products = Exists(Product.objects.filter(category=OuterRef("pk")))
has_desc_products = Exists(
Product.objects.filter(
is_active=True,
category__tree_id=OuterRef('tree_id'),
category__lft__gt=OuterRef('lft'),
category__rght__lt=OuterRef('rght'),
category__tree_id=OuterRef("tree_id"),
category__lft__gt=OuterRef("lft"),
category__rght__lt=OuterRef("rght"),
)
)
annotated = queryset.annotate(
has_products=has_own_products | has_desc_products
)
annotated = queryset.annotate(has_products=has_own_products | has_desc_products)
if value:
return annotated.filter(has_products=True).distinct()
return annotated.filter(has_products=False).distinct()

View file

@ -619,6 +619,7 @@ class ContactUs(BaseMutation):
return ContactUs(received=False, error=str(e))
# noinspection PyArgumentList
class Search(BaseMutation):
class Arguments:
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)
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)
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
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
]
def extract_placeholders(text: str) -> set[str]:
"""
Extract all placeholders from given text.
Extract all placeholders from the given text.
"""
phs: list[str] = []
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.
Raises CommandError if still unparsable.
"""
try:
with contextlib.suppress(Exception):
return polib.pofile(path)
except Exception:
# read raw text
try:
with open(path, encoding="utf-8") as f:
text = f.read()
except OSError as e:
raise CommandError(f"{path}: cannot read file ({e})")
# fix fuzzy flags and empty header entries
text = re.sub(r"^#,(?!\s)", "#, ", text, flags=re.MULTILINE)
parts = text.split("\n\n", 1)
header = parts[0]
rest = parts[1] if len(parts) > 1 else ""
rest = re.sub(r"^msgid \"\"\s*\nmsgstr \"\"\s*\n?", "", rest, flags=re.MULTILINE)
sanitized = header + "\n\n" + rest
tmp = NamedTemporaryFile(mode="w+", delete=False, suffix=".po", encoding="utf-8") # noqa: SIM115
try:
tmp.write(sanitized)
tmp.flush()
tmp.close()
return polib.pofile(tmp.name)
except Exception as e:
raise CommandError(f"{path}: syntax error after sanitization ({e})")
finally:
with contextlib.suppress(OSError):
os.unlink(tmp.name)
# read raw text
try:
with open(path, encoding="utf-8") as f:
text = f.read()
except OSError as e:
raise CommandError(f"{path}: cannot read file ({e})")
# fix fuzzy flags and empty header entries
text = re.sub(r"^#,(?!\s)", "#, ", text, flags=re.MULTILINE)
parts = text.split("\n\n", 1)
header = parts[0]
rest = parts[1] if len(parts) > 1 else ""
rest = re.sub(r"^msgid \"\"\s*\nmsgstr \"\"\s*\n?", "", rest, flags=re.MULTILINE)
sanitized = header + "\n\n" + rest
tmp = NamedTemporaryFile(mode="w+", delete=False, suffix=".po", encoding="utf-8") # noqa: SIM115
try:
tmp.write(sanitized)
tmp.flush()
tmp.close()
return polib.pofile(tmp.name)
except Exception as e:
raise CommandError(f"{path}: syntax error after sanitization ({e})")
finally:
with contextlib.suppress(OSError):
os.unlink(tmp.name)
class Command(BaseCommand):

View file

@ -145,6 +145,9 @@ class Command(BaseCommand):
self.stdout.write(f"{app_conf.label}: loading English PO…")
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]
if missing:
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["Language"] = target_lang
for e in entries:
prev = old_tgt.find(e.msgid) if old_tgt else None
for entry in entries:
prev = old_tgt.find(entry.msgid) if old_tgt else None
new_po.append(
polib.POEntry(
msgid=e.msgid,
msgid=entry.msgid,
msgstr=prev.msgstr if prev and prev.msgstr else "",
msgctxt=e.msgctxt,
comment=e.comment,
tcomment=e.tcomment,
occurrences=e.occurrences,
flags=e.flags,
msgctxt=entry.msgctxt,
comment=entry.comment,
tcomment=entry.tcomment,
occurrences=entry.occurrences,
flags=entry.flags,
)
)

View file

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

View file

@ -56,7 +56,7 @@ logger = logging.getLogger(__name__)
class AttributeGroup(ExportModelOperationsMixin("attribute_group"), NiceModel):
is_publicly_visible = True
parent = ForeignKey(
parent: ForeignKey = ForeignKey(
"self",
on_delete=CASCADE,
null=True,
@ -65,7 +65,7 @@ class AttributeGroup(ExportModelOperationsMixin("attribute_group"), NiceModel):
help_text=_("parent of this group"),
verbose_name=_("parent attribute group"),
)
name = CharField(
name: CharField = CharField(
max_length=255,
verbose_name=_("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):
is_publicly_visible = True
categories = ManyToManyField(
categories: ManyToManyField = ManyToManyField(
"core.Category",
related_name="attributes",
help_text=_("category of this attribute"),
verbose_name=_("categories"),
)
group = ForeignKey(
group: ForeignKey = ForeignKey(
"core.AttributeGroup",
on_delete=CASCADE,
related_name="attributes",
help_text=_("group of this attribute"),
verbose_name=_("attribute group"),
)
value_type = CharField(
value_type: CharField = CharField(
max_length=50,
choices=[
("string", _("string")),
@ -111,7 +111,7 @@ class Attribute(ExportModelOperationsMixin("attribute"), NiceModel):
verbose_name=_("value type"),
)
name = CharField(
name: CharField = CharField(
max_length=255,
help_text=_("name of this attribute"),
verbose_name=_("attribute's name"),
@ -129,14 +129,14 @@ class Attribute(ExportModelOperationsMixin("attribute"), NiceModel):
class AttributeValue(ExportModelOperationsMixin("attribute_value"), NiceModel):
is_publicly_visible = True
attribute = ForeignKey(
attribute: ForeignKey = ForeignKey(
"core.Attribute",
on_delete=CASCADE,
related_name="values",
help_text=_("attribute of this value"),
verbose_name=_("attribute"),
)
product = ForeignKey(
product: ForeignKey = ForeignKey(
"core.Product",
on_delete=CASCADE,
blank=False,
@ -145,7 +145,7 @@ class AttributeValue(ExportModelOperationsMixin("attribute_value"), NiceModel):
verbose_name=_("associated product"),
related_name="attributes",
)
value = TextField(
value: TextField = TextField(
verbose_name=_("attribute value"),
help_text=_("the specific value for this attribute"),
)
@ -169,7 +169,7 @@ class Category(ExportModelOperationsMixin("category"), NiceModel, MPTTModel):
validators=[validate_category_image_dimensions],
verbose_name=_("category image"),
)
markup_percent = IntegerField(
markup_percent: IntegerField = IntegerField(
default=0,
validators=[MinValueValidator(0), MaxValueValidator(100)],
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"),
)
name = CharField(
name: CharField = CharField(
max_length=255,
verbose_name=_("category name"),
help_text=_("provide a name for this category"),
unique=True,
)
description = TextField( # noqa: DJ001
description: TextField = TextField(
blank=True,
null=True,
help_text=_("add a detailed description for this category"),
verbose_name=_("category description"),
)
slug = AutoSlugField(
slug: AutoSlugField = AutoSlugField(
populate_from=("uuid", "name"),
allow_unicode=True,
unique=True,
editable=False,
null=True,
)
tags = ManyToManyField(
tags: ManyToManyField = ManyToManyField(
"core.CategoryTag",
blank=True,
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):
is_publicly_visible = True
name = CharField(
name: CharField = CharField(
max_length=255,
help_text=_("name of this brand"),
verbose_name=_("brand name"),
@ -252,13 +252,13 @@ class Brand(ExportModelOperationsMixin("brand"), NiceModel):
validators=[validate_category_image_dimensions],
verbose_name=_("brand big image"),
)
description = TextField( # noqa: DJ001
description: TextField = TextField(
blank=True,
null=True,
help_text=_("add a detailed description of the brand"),
verbose_name=_("brand description"),
)
categories = ManyToManyField(
categories: ManyToManyField = ManyToManyField(
"core.Category",
blank=True,
help_text=_("optional categories that this brand is associated with"),
@ -276,14 +276,14 @@ class Brand(ExportModelOperationsMixin("brand"), NiceModel):
class Product(ExportModelOperationsMixin("product"), NiceModel):
is_publicly_visible = True
category = ForeignKey(
category: ForeignKey = ForeignKey(
"core.Category",
on_delete=CASCADE,
help_text=_("category this product belongs to"),
verbose_name=_("category"),
related_name="products",
)
brand = ForeignKey(
brand: ForeignKey = ForeignKey(
"core.Brand",
on_delete=CASCADE,
blank=True,
@ -291,31 +291,31 @@ class Product(ExportModelOperationsMixin("product"), NiceModel):
help_text=_("optionally associate this product with a brand"),
verbose_name=_("brand"),
)
tags = ManyToManyField(
tags: ManyToManyField = ManyToManyField(
"core.ProductTag",
blank=True,
help_text=_("tags that help describe or group this product"),
verbose_name=_("product tags"),
)
is_digital = BooleanField(
is_digital: BooleanField = BooleanField(
default=False,
help_text=_("indicates whether this product is digitally delivered"),
verbose_name=_("is product digital"),
blank=False,
null=False,
)
name = CharField(
name: CharField = CharField(
max_length=255,
help_text=_("provide a clear identifying name for the product"),
verbose_name=_("product name"),
)
description = TextField( # noqa: DJ001
description: TextField = TextField(
blank=True,
null=True,
help_text=_("add a detailed description of the product"),
verbose_name=_("product description"),
)
partnumber = CharField( # noqa: DJ001
partnumber: CharField = CharField(
unique=True,
default=None,
blank=False,
@ -388,13 +388,13 @@ class Vendor(ExportModelOperationsMixin("vendor"), NiceModel):
help_text=_("stores credentials and endpoints required for vendor communication"),
verbose_name=_("authentication info"),
)
markup_percent = IntegerField(
markup_percent: IntegerField = IntegerField(
default=0,
validators=[MinValueValidator(0), MaxValueValidator(100)],
help_text=_("define the markup for products retrieved from this vendor"),
verbose_name=_("vendor markup percentage"),
)
name = CharField(
name: CharField = CharField(
max_length=255,
help_text=_("name of this vendor"),
verbose_name=_("vendor name"),
@ -417,13 +417,13 @@ class Vendor(ExportModelOperationsMixin("vendor"), NiceModel):
class Feedback(ExportModelOperationsMixin("feedback"), NiceModel):
is_publicly_visible = True
comment = TextField( # noqa: DJ001
comment: TextField = TextField(
blank=True,
null=True,
help_text=_("user-provided comments about their experience with the product"),
verbose_name=_("feedback comments"),
)
order_product = OneToOneField(
order_product: OneToOneField = OneToOneField(
"core.OrderProduct",
on_delete=CASCADE,
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"),
verbose_name=_("related order product"),
)
rating = FloatField(
rating: FloatField = FloatField(
blank=True,
null=True,
help_text=_("user-assigned rating for the product"),
@ -450,7 +450,7 @@ class Feedback(ExportModelOperationsMixin("feedback"), NiceModel):
class Order(ExportModelOperationsMixin("order"), NiceModel):
is_publicly_visible = False
billing_address = ForeignKey(
billing_address: ForeignKey = ForeignKey(
"core.Address",
on_delete=CASCADE,
blank=True,
@ -459,7 +459,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
help_text=_("the billing address used for this order"),
verbose_name=_("billing address"),
)
promo_code = ForeignKey(
promo_code: ForeignKey = ForeignKey(
"core.PromoCode",
on_delete=PROTECT,
blank=True,
@ -467,7 +467,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
help_text=_("optional promo code applied to this order"),
verbose_name=_("applied promo code"),
)
shipping_address = ForeignKey(
shipping_address: ForeignKey = ForeignKey(
"core.Address",
on_delete=CASCADE,
blank=True,
@ -476,7 +476,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
help_text=_("the shipping address used for this order"),
verbose_name=_("shipping address"),
)
status = CharField(
status: CharField = CharField(
default="PENDING",
max_length=64,
choices=ORDER_STATUS_CHOICES,
@ -495,7 +495,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
help_text=_("json representation of order attributes for this order"),
verbose_name=_("attributes"),
)
user = ForeignKey(
user: ForeignKey = ForeignKey(
"vibes_auth.User",
on_delete=CASCADE,
help_text=_("the user who placed the order"),
@ -504,14 +504,14 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
blank=True,
null=True,
)
buy_time = DateTimeField(
buy_time: DateTimeField = DateTimeField(
help_text=_("the timestamp when the order was finalized"),
verbose_name=_("buy time"),
default=None,
null=True,
blank=True,
)
human_readable_id = CharField(
human_readable_id: CharField = CharField(
max_length=8,
help_text=_("a human-readable identifier for the order"),
verbose_name=_("human readable id"),
@ -524,7 +524,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
verbose_name_plural = _("orders")
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
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")
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(
product=product,
@ -597,7 +597,12 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
name = "Product"
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"]:
raise ValueError(_("you cannot remove products from an order that is not a pending one"))
try:
@ -818,31 +823,31 @@ class Order(ExportModelOperationsMixin("order"), NiceModel):
class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel):
is_publicly_visible = False
buy_price = FloatField(
buy_price: FloatField = FloatField(
blank=True,
null=True,
help_text=_("the price paid by the customer for this product at purchase time"),
verbose_name=_("purchase price at order time"),
)
comments = TextField( # noqa: DJ001
comments: TextField = TextField(
blank=True,
null=True,
help_text=_("internal comments for admins about this ordered product"),
verbose_name=_("internal comments"),
)
notifications = JSONField(
notifications: JSONField = JSONField(
blank=True,
null=True,
help_text=_("json structure of notifications to display to users"),
verbose_name=_("user notifications"),
)
attributes = JSONField(
attributes: JSONField = JSONField(
blank=True,
null=True,
help_text=_("json representation of this item's attributes"),
verbose_name=_("ordered product attributes"),
)
order = ForeignKey(
order: ForeignKey = ForeignKey(
"core.Order",
on_delete=CASCADE,
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",
null=True,
)
product = ForeignKey(
product: ForeignKey = ForeignKey(
"core.Product",
on_delete=PROTECT,
blank=True,
@ -858,14 +863,14 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel):
help_text=_("the specific product associated with this order line"),
verbose_name=_("associated product"),
)
quantity = PositiveIntegerField(
quantity: PositiveIntegerField = PositiveIntegerField(
blank=False,
null=False,
default=1,
help_text=_("quantity of this specific product in the order"),
verbose_name=_("product quantity"),
)
status = CharField(
status: CharField = CharField(
max_length=128,
blank=False,
null=False,
@ -921,7 +926,7 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel):
return self.download.url
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"]:
raise ValueError(_(f"wrong action specified for feedback: {action}"))
if action == "remove" and self.feedback:
@ -938,14 +943,14 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel):
class ProductTag(ExportModelOperationsMixin("product_tag"), NiceModel):
is_publicly_visible = True
tag_name = CharField(
tag_name: CharField = CharField(
blank=False,
null=False,
max_length=255,
help_text=_("internal tag identifier for the product tag"),
verbose_name=_("tag name"),
)
name = CharField(
name: CharField = CharField(
max_length=255,
help_text=_("user-friendly name for the product tag"),
verbose_name=_("tag display name"),
@ -963,14 +968,14 @@ class ProductTag(ExportModelOperationsMixin("product_tag"), NiceModel):
class CategoryTag(ExportModelOperationsMixin("category_tag"), NiceModel):
is_publicly_visible = True
tag_name = CharField(
tag_name: CharField = CharField(
blank=False,
null=False,
max_length=255,
help_text=_("internal tag identifier for the product tag"),
verbose_name=_("tag name"),
)
name = CharField(
name: CharField = CharField(
max_length=255,
help_text=_("user-friendly name for the product tag"),
verbose_name=_("tag display name"),
@ -988,7 +993,7 @@ class CategoryTag(ExportModelOperationsMixin("category_tag"), NiceModel):
class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel):
is_publicly_visible = True
alt = CharField(
alt: CharField = CharField(
max_length=255,
help_text=_("provide alternative text for the image for accessibility"),
verbose_name=_("image alt text"),
@ -998,13 +1003,13 @@ class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel):
verbose_name=_("product image"),
upload_to=get_product_uuid_as_path,
)
priority = IntegerField(
priority: IntegerField = IntegerField(
default=1,
validators=[MinValueValidator(1)],
help_text=_("determines the order in which images are displayed"),
verbose_name=_("display priority"),
)
product = ForeignKey(
product: ForeignKey = ForeignKey(
"core.Product",
on_delete=CASCADE,
help_text=_("the product that this image represents"),
@ -1027,7 +1032,7 @@ class ProductImage(ExportModelOperationsMixin("product_image"), NiceModel):
class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel):
is_publicly_visible = False
code = CharField(
code: CharField = CharField(
max_length=20,
unique=True,
default=get_random_code,
@ -1042,7 +1047,7 @@ class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel):
help_text=_("fixed discount amount applied if percent is not used"),
verbose_name=_("fixed discount amount"),
)
discount_percent = IntegerField(
discount_percent: IntegerField = IntegerField(
validators=[MinValueValidator(1), MaxValueValidator(100)],
blank=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"),
verbose_name=_("usage timestamp"),
)
user = ForeignKey(
user: ForeignKey = ForeignKey(
"vibes_auth.User",
on_delete=CASCADE,
help_text=_("user assigned to this promocode if applicable"),
@ -1125,12 +1130,12 @@ class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel):
class Promotion(ExportModelOperationsMixin("promotion"), NiceModel):
is_publicly_visible = True
discount_percent = IntegerField(
discount_percent: IntegerField = IntegerField(
validators=[MinValueValidator(1), MaxValueValidator(100)],
help_text=_("percentage discount for the selected products"),
verbose_name=_("discount percentage"),
)
name = CharField(
name: CharField = CharField(
max_length=256,
unique=True,
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"),
verbose_name=_("promotion description"),
)
products = ManyToManyField(
products: ManyToManyField = ManyToManyField(
"core.Product",
blank=True,
help_text=_("select which products are included in this promotion"),
@ -1162,7 +1167,7 @@ class Promotion(ExportModelOperationsMixin("promotion"), NiceModel):
class Stock(ExportModelOperationsMixin("stock"), NiceModel):
is_publicly_visible = False
vendor = ForeignKey(
vendor: ForeignKey = ForeignKey(
"core.Vendor",
on_delete=CASCADE,
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"),
verbose_name=_("selling price"),
)
product = ForeignKey(
product: ForeignKey = ForeignKey(
"core.Product",
on_delete=CASCADE,
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"),
verbose_name=_("vendor purchase price"),
)
quantity = IntegerField(
quantity: IntegerField = IntegerField(
default=0,
help_text=_("available quantity of the product in stock"),
verbose_name=_("quantity in stock"),
)
sku = CharField(
sku: CharField = CharField(
max_length=255,
help_text=_("vendor-assigned SKU for identifying the product"),
verbose_name=_("vendor sku"),
@ -1217,13 +1222,13 @@ class Stock(ExportModelOperationsMixin("stock"), NiceModel):
class Wishlist(ExportModelOperationsMixin("wishlist"), NiceModel):
is_publicly_visible = False
products = ManyToManyField(
products: ManyToManyField = ManyToManyField(
"core.Product",
blank=True,
help_text=_("products that the user has marked as wanted"),
verbose_name=_("wishlisted products"),
)
user = OneToOneField(
user: OneToOneField = OneToOneField(
"vibes_auth.User",
on_delete=CASCADE,
blank=True,
@ -1278,8 +1283,8 @@ class Wishlist(ExportModelOperationsMixin("wishlist"), NiceModel):
class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceModel):
is_publicly_visible = False
order_product = OneToOneField(to=OrderProduct, on_delete=CASCADE, related_name="download")
num_downloads = IntegerField(default=0)
order_product: OneToOneField = OneToOneField(to=OrderProduct, on_delete=CASCADE, related_name="download")
num_downloads: IntegerField = IntegerField(default=0)
class Meta:
verbose_name = _("download")
@ -1293,13 +1298,15 @@ class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceMo
if self.order_product.status != "FINISHED":
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):
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)
class Meta:
@ -1326,22 +1333,26 @@ class Address(ExportModelOperationsMixin("address"), NiceModel):
help_text=_("address line for the customer"),
verbose_name=_("address line"),
)
street = CharField(_("street"), max_length=255, null=True) # noqa: DJ001
district = CharField(_("district"), max_length=255, null=True) # noqa: DJ001
city = CharField(_("city"), max_length=100, null=True) # noqa: DJ001
region = CharField(_("region"), max_length=100, null=True) # noqa: DJ001
postal_code = CharField(_("postal code"), max_length=20, null=True) # noqa: DJ001
country = CharField(_("country"), max_length=40, null=True) # noqa: DJ001
street: CharField = CharField(_("street"), max_length=255, null=True)
district: CharField = CharField(_("district"), max_length=255, null=True)
city: CharField = CharField(_("city"), max_length=100, null=True)
region: CharField = CharField(_("region"), max_length=100, null=True)
postal_code: CharField = CharField(_("postal code"), max_length=20, null=True)
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)")
)
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()

View file

@ -13,6 +13,7 @@ class IsOwnerOrReadOnly(permissions.BasePermission):
return obj.user == request.user
# noinspection PyProtectedMember
class EvibesPermission(permissions.BasePermission):
ACTION_PERM_MAP = {
"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 .simple 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)
from .utility import * # noqa: F403

View file

@ -1,10 +1,12 @@
import logging
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.db.models.functions import Length
from rest_framework.fields import JSONField, SerializerMethodField
from rest_framework.request import Request
from rest_framework.serializers import ModelSerializer
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.utility import AddressSerializer
from vibes_auth.models import User
logger = logging.getLogger(__name__)
@ -77,8 +80,11 @@ class CategoryDetailSerializer(ModelSerializer):
if filterable_results:
return filterable_results
request = self.context.get("request")
user = getattr(request, "user", None)
request: Request | None = self.context.get("request")
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)
@ -100,12 +106,15 @@ class CategoryDetailSerializer(ModelSerializer):
}
)
if user is None:
user = AnonymousUser()
if not user.has_perm("view_attribute"):
cache.set(f"{obj.uuid}_filterable_results", filterable_results, 86400)
return filterable_results
def get_children(self, obj) -> list[dict]:
def get_children(self, obj) -> Collection[Any]:
request = self.context.get("request")
if request is not None and request.user.has_perm("view_category"):
children = obj.children.all()

View file

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

View file

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

View file

@ -30,7 +30,7 @@ def update_products_task():
includes invoking the `update_stock` method of vendor classes and removing
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
:rtype: tuple[bool, str]
@ -64,7 +64,7 @@ def update_orderproducts_task():
`vendors_classes`. Each vendor class in the `vendors_classes` list is
instantiated, and the `update_order_products_statuses` method of the
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
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
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
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.
: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:
return False, "Not enough products to choose from [< 48]."
selected_products = []
selected_products: list = []
while len(selected_products) < 48:
product = eligible_products.order_by("?").first()
@ -211,8 +211,9 @@ def process_promotions() -> tuple[bool, str]:
promotion = Promotion.objects.update_or_create(
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."

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