Merge branch 'main' into storefront-nuxt

This commit is contained in:
Egor Pavlovich Gorbunov 2025-10-14 16:51:24 +03:00
commit 1a6a9f666e
292 changed files with 12872 additions and 11522 deletions

View file

@ -34,7 +34,7 @@ class PostAdmin(SummernoteModelAdminMixin, FieldsetsMixin, ActivationActionsMixi
@register(PostTag) @register(PostTag)
class PostTagAdmin(ModelAdmin): # type: ignore [misc, type-arg] class PostTagAdmin(ModelAdmin): # type: ignore [type-arg]
list_display = ("tag_name", "name") list_display = ("tag_name", "name")
search_fields = ("tag_name", "name") search_fields = ("tag_name", "name")
ordering = ("tag_name",) ordering = ("tag_name",)

View file

@ -11,6 +11,6 @@ class BlogConfig(AppConfig):
hide = False hide = False
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
def ready(self): def ready(self) -> None:
import blog.elasticsearch.documents import blog.elasticsearch.documents
import blog.signals # noqa: F401 import blog.signals # noqa: F401

View file

@ -6,7 +6,7 @@ from core.elasticsearch import COMMON_ANALYSIS, ActiveOnlyMixin, add_multilang_f
from core.elasticsearch.documents import BaseDocument from core.elasticsearch.documents import BaseDocument
class PostDocument(ActiveOnlyMixin, BaseDocument): class PostDocument(ActiveOnlyMixin, BaseDocument): # type: ignore [misc]
title = fields.TextField( title = fields.TextField(
attr="title", attr="title",
analyzer="standard", analyzer="standard",
@ -30,7 +30,7 @@ class PostDocument(ActiveOnlyMixin, BaseDocument):
model = Post model = Post
fields = ["uuid"] fields = ["uuid"]
def prepare_title(self, instance): def prepare_title(self, instance: Post) -> str:
return getattr(instance, "title", "") or "" return getattr(instance, "title", "") or ""

View file

@ -4,7 +4,7 @@ from blog.models import Post
from core.filters import CaseInsensitiveListFilter from core.filters import CaseInsensitiveListFilter
class PostFilter(FilterSet): class PostFilter(FilterSet): # type: ignore [misc]
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact") uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
slug = CharFilter(field_name="slug", lookup_expr="exact") slug = CharFilter(field_name="slug", lookup_expr="exact")
author = UUIDFilter(field_name="author__uuid", lookup_expr="exact") author = UUIDFilter(field_name="author__uuid", lookup_expr="exact")

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -2,12 +2,12 @@
# Copyright (C) 2025 EGOR <FUREUNOIR> GORBUNOV # Copyright (C) 2025 EGOR <FUREUNOIR> GORBUNOV
# This file is distributed under the same license as the EVIBES package. # This file is distributed under the same license as the EVIBES package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025. # EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -2,12 +2,12 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -2,12 +2,12 @@
# Copyright (C) 2025 EGOR <FUREUNOIR> GORBUNOV # Copyright (C) 2025 EGOR <FUREUNOIR> GORBUNOV
# This file is distributed under the same license as the EVIBES package. # This file is distributed under the same license as the EVIBES package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025. # EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -2,12 +2,12 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -2,12 +2,12 @@
# Copyright (C) 2025 EGOR <FUREUNOIR> GORBUNOV # Copyright (C) 2025 EGOR <FUREUNOIR> GORBUNOV
# This file is distributed under the same license as the EVIBES package. # This file is distributed under the same license as the EVIBES package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025. # EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 3.0.0\n" "Project-Id-Version: EVIBES 3.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 15:47+0300\n" "POT-Creation-Date: 2025-10-13 13:56+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -7,7 +7,7 @@ from blog.serializers import PostSerializer
from core.permissions import EvibesPermission from core.permissions import EvibesPermission
class PostViewSet(ReadOnlyModelViewSet): class PostViewSet(ReadOnlyModelViewSet): # type: ignore [type-arg]
""" """
Encapsulates operations for managing and retrieving Post entities in a read-only model view set. Encapsulates operations for managing and retrieving Post entities in a read-only model view set.

View file

@ -1,4 +1,7 @@
from typing import Any
from django import forms from django import forms
from django.forms.renderers import BaseRenderer
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -7,7 +10,10 @@ class MarkdownEditorWidget(forms.Textarea):
css = {"all": ("https://cdnjs.cloudflare.com/ajax/libs/easymde/2.14.0/easymde.min.css",)} css = {"all": ("https://cdnjs.cloudflare.com/ajax/libs/easymde/2.14.0/easymde.min.css",)}
js = ("https://cdnjs.cloudflare.com/ajax/libs/easymde/2.14.0/easymde.min.js",) js = ("https://cdnjs.cloudflare.com/ajax/libs/easymde/2.14.0/easymde.min.js",)
def render(self, name, value, attrs=None, renderer=None): def render(self, name: str, value: str, attrs: dict[Any, Any] | None = None, renderer: BaseRenderer | None = None):
if not attrs:
attrs = {}
attrs["class"] = "markdown-editor"
textarea_html = super().render(name, value, attrs, renderer) textarea_html = super().render(name, value, attrs, renderer)
textarea_id = attrs.get("id", f"id_{name}") textarea_id = attrs.get("id", f"id_{name}")
init_js = f""" init_js = f"""

View file

@ -20,16 +20,16 @@ class NiceModel(Model):
verbose_name=_("is active"), verbose_name=_("is active"),
help_text=_("if set to false, this object can't be seen by users without needed permission"), help_text=_("if set to false, this object can't be seen by users without needed permission"),
) )
created = CreationDateTimeField(_("created"), help_text=_("when the object first appeared on the database")) created = CreationDateTimeField(_("created"), help_text=_("when the object first appeared on the database")) # type: ignore [no-untyped-call]
modified = ModificationDateTimeField(_("modified"), help_text=_("when the object was last modified")) modified = ModificationDateTimeField(_("modified"), help_text=_("when the object was last modified")) # type: ignore [no-untyped-call]
def save( def save( # type: ignore [override]
self, self,
*, *,
force_insert: bool = False, force_insert: bool = False,
force_update: bool = False, force_update: bool = False,
using: str | None = None, using: str | None = None,
update_fields: Collection | None = None, update_fields: Collection[str] | None = None,
update_modified: bool = True, update_modified: bool = True,
) -> None: ) -> None:
self.update_modified = update_modified self.update_modified = update_modified

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,6 @@ class CoreConfig(AppConfig):
hide = False hide = False
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
def ready(self): def ready(self) -> None:
import core.elasticsearch.documents import core.elasticsearch.documents
import core.signals # noqa: F401 import core.signals # noqa: F401

View file

@ -113,7 +113,7 @@ def process_query(
request: Request | None = None, request: Request | None = None,
indexes: tuple[str, ...] = ("categories", "brands", "products"), indexes: tuple[str, ...] = ("categories", "brands", "products"),
use_transliteration: bool = True, use_transliteration: bool = True,
) -> dict[str, list[dict]] | None: ) -> dict[str, list[dict[str, Any]]] | None:
if not query: if not query:
raise ValueError(_("no search term provided.")) raise ValueError(_("no search term provided."))
@ -235,7 +235,7 @@ def process_query(
): ):
hit_cache.append(h) hit_cache.append(h)
if getattr(h, "uuid", None): if getattr(h, "uuid", None):
uuids_by_index.setdefault(h.meta.index, []).append(str(h.uuid)) uuids_by_index.setdefault(h.meta.index, []).append({"uuid": str(h.uuid)})
products_by_uuid = {} products_by_uuid = {}
brands_by_uuid = {} brands_by_uuid = {}
@ -329,9 +329,9 @@ def _lang_analyzer(lang_code: str) -> str:
class ActiveOnlyMixin: class ActiveOnlyMixin:
def get_queryset(self) -> QuerySet[Any]: def get_queryset(self) -> QuerySet[Any]:
return super().get_queryset().filter(is_active=True) return super().get_queryset().filter(is_active=True) # type: ignore [no-any-return, misc]
def should_index_object(self, obj) -> bool: def should_index_object(self, obj) -> bool: # type: ignore [no-untyped-def]
return getattr(obj, "is_active", False) return getattr(obj, "is_active", False)
@ -436,7 +436,7 @@ COMMON_ANALYSIS = {
} }
def add_multilang_fields(cls) -> None: def add_multilang_fields(cls: Any) -> None:
for code, _lang in settings.LANGUAGES: for code, _lang in settings.LANGUAGES:
lc = code.replace("-", "_").lower() lc = code.replace("-", "_").lower()
name_field = f"name_{lc}" name_field = f"name_{lc}"
@ -480,10 +480,11 @@ def add_multilang_fields(cls) -> None:
setattr(cls, f"prepare_{desc_field}", make_prepare(desc_field)) setattr(cls, f"prepare_{desc_field}", make_prepare(desc_field))
def populate_index(): def populate_index() -> None:
for doc in registry.get_documents(set(registry.get_models())): for doc in registry.get_documents(set(registry.get_models())):
qs = doc().get_indexing_queryset() qs = doc().get_indexing_queryset()
doc().update(qs, parallel=True, refresh=True) doc().update(qs, parallel=True, refresh=True)
return None
def process_system_query( def process_system_query(
@ -493,7 +494,7 @@ def process_system_query(
size_per_index: int = 25, size_per_index: int = 25,
language_code: str | None = None, language_code: str | None = None,
use_transliteration: bool = True, use_transliteration: bool = True,
) -> dict[str, list[dict]]: ) -> dict[str, list[dict[str, Any]]]:
if not query: if not query:
raise ValueError(_("no search term provided.")) raise ValueError(_("no search term provided."))
@ -526,7 +527,7 @@ def process_system_query(
**({"fuzziness": fuzzy} if fuzzy else {}), **({"fuzziness": fuzzy} if fuzzy else {}),
) )
results: dict[str, list[dict]] = {idx: [] for idx in indexes} results: dict[str, list[dict[str, Any]]] = {idx: [] for idx in indexes}
for idx in indexes: for idx in indexes:
s = Search(index=[idx]).query(mm).extra(size=size_per_index, track_total_hits=False) s = Search(index=[idx]).query(mm).extra(size=size_per_index, track_total_hits=False)

View file

@ -1,3 +1,6 @@
from typing import Any
from django.db.models import Model, QuerySet
from django_elasticsearch_dsl import Document, fields from django_elasticsearch_dsl import Document, fields
from django_elasticsearch_dsl.registries import registry from django_elasticsearch_dsl.registries import registry
from health_check.db.models import TestModel from health_check.db.models import TestModel
@ -6,7 +9,7 @@ from core.elasticsearch import COMMON_ANALYSIS, ActiveOnlyMixin, add_multilang_f
from core.models import Brand, Category, Product from core.models import Brand, Category, Product
class BaseDocument(Document): class BaseDocument(Document): # type: ignore [misc]
name = fields.TextField( name = fields.TextField(
attr="name", attr="name",
analyzer="standard", analyzer="standard",
@ -39,10 +42,10 @@ class BaseDocument(Document):
"index": {"max_ngram_diff": 20}, "index": {"max_ngram_diff": 20},
} }
def prepare_name(self, instance): def prepare_name(self, instance: Model) -> str:
return getattr(instance, "name", "") or "" return getattr(instance, "name", "") or ""
def prepare_description(self, instance): def prepare_description(self, instance: Model) -> str:
return getattr(instance, "description", "") or "" return getattr(instance, "description", "") or ""
@ -103,7 +106,7 @@ class ProductDocument(ActiveOnlyMixin, BaseDocument):
}, },
) )
def get_queryset(self): def get_queryset(self) -> QuerySet[Product]:
return ( return (
super() super()
.get_queryset() .get_queryset()
@ -156,7 +159,7 @@ add_multilang_fields(BrandDocument)
registry.register_document(BrandDocument) registry.register_document(BrandDocument)
class TestModelDocument(Document): class TestModelDocument(Document): # type: ignore [misc]
class Index: class Index:
name = "testmodels" name = "testmodels"
@ -164,7 +167,7 @@ class TestModelDocument(Document):
model = TestModel model = TestModel
fields = ["title"] fields = ["title"]
ignore_signals = True ignore_signals = True
related_models: list = [] related_models: list[Any] = []
auto_refresh = False auto_refresh = False

View file

@ -1,6 +1,7 @@
import json import json
import logging import logging
import uuid import uuid
from typing import Any
from django.core.exceptions import BadRequest from django.core.exceptions import BadRequest
from django.db.models import ( from django.db.models import (
@ -19,6 +20,7 @@ from django.db.models import (
When, When,
) )
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.http import HttpRequest
from django.utils.http import urlsafe_base64_decode from django.utils.http import urlsafe_base64_decode
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_filters import ( from django_filters import (
@ -31,6 +33,8 @@ from django_filters import (
OrderingFilter, OrderingFilter,
UUIDFilter, UUIDFilter,
) )
from graphene import Context
from rest_framework.request import Request
from core.elasticsearch import process_query from core.elasticsearch import process_query
from core.models import Address, Brand, Category, Feedback, Order, Product, Stock, Wishlist from core.models import Address, Brand, Category, Feedback, Order, Product, Stock, Wishlist
@ -38,8 +42,8 @@ from core.models import Address, Brand, Category, Feedback, Order, Product, Stoc
logger = logging.getLogger("django") logger = logging.getLogger("django")
class CaseInsensitiveListFilter(BaseInFilter, CharFilter): class CaseInsensitiveListFilter(BaseInFilter, CharFilter): # type: ignore [misc]
def filter(self, qs, value): def filter(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]:
if not value: if not value:
return qs return qs
@ -61,7 +65,7 @@ class CaseInsensitiveListFilter(BaseInFilter, CharFilter):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
class ProductFilter(FilterSet): class ProductFilter(FilterSet): # type: ignore [misc]
search = CharFilter(field_name="name", method="search_products", label=_("Search")) search = CharFilter(field_name="name", method="search_products", label=_("Search"))
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID")) uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID"))
name = CharFilter(lookup_expr="icontains", label=_("Name")) name = CharFilter(lookup_expr="icontains", label=_("Name"))
@ -121,7 +125,15 @@ class ProductFilter(FilterSet):
"order_by", "order_by",
] ]
def __init__(self, data=None, queryset=None, *, request=None, prefix=None): # noinspection PyTypeHints
def __init__(
self,
data: dict[Any, Any] | None = None,
queryset: QuerySet[Product] | None = None,
*,
request: HttpRequest | Request | Context = None,
prefix: str | None = None,
) -> None:
super().__init__(data=data, queryset=queryset, request=request, prefix=prefix) super().__init__(data=data, queryset=queryset, request=request, prefix=prefix)
ordering_param = self.data.get("order_by", "") ordering_param = self.data.get("order_by", "")
if ordering_param: if ordering_param:
@ -133,7 +145,7 @@ class ProductFilter(FilterSet):
.annotate(avg_rating=Avg("rating")) .annotate(avg_rating=Avg("rating"))
.values("avg_rating") .values("avg_rating")
) )
self.queryset = self.queryset.annotate( self.queryset: QuerySet[Product] = self.queryset.annotate(
rating=Coalesce( rating=Coalesce(
Subquery(feedback_qs, output_field=FloatField()), Subquery(feedback_qs, output_field=FloatField()),
Value(0, output_field=FloatField()), Value(0, output_field=FloatField()),
@ -148,7 +160,7 @@ class ProductFilter(FilterSet):
) )
) )
def search_products(self, queryset: QuerySet[Product], name, value): def search_products(self, queryset: QuerySet[Product], name: str, value: str) -> QuerySet[Product]:
if not value: if not value:
return queryset return queryset
@ -156,17 +168,17 @@ class ProductFilter(FilterSet):
return queryset.filter(uuid__in=uuids) return queryset.filter(uuid__in=uuids)
def filter_include_flag(self, queryset, name, value): def filter_include_flag(self, queryset: QuerySet[Product], name: str, value: str) -> QuerySet[Product]:
if not self.data.get("category_uuid"): if not self.data.get("category_uuid"):
raise BadRequest(_("there must be a category_uuid to use include_subcategories flag")) raise BadRequest(_("there must be a category_uuid to use include_subcategories flag"))
return queryset return queryset
def filter_include_personal_ordered(self, queryset, name, value): def filter_include_personal_ordered(self, queryset: QuerySet[Product], name: str, value: str) -> QuerySet[Product]:
if self.data.get("include_personal_ordered", False): if self.data.get("include_personal_ordered", False):
queryset = queryset.filter(stocks__isnull=False, stocks__quantity__gt=0, stocks__price__gt=0) queryset = queryset.filter(stocks__isnull=False, stocks__quantity__gt=0, stocks__price__gt=0)
return queryset return queryset
def filter_attributes(self, queryset, name, value): def filter_attributes(self, queryset: QuerySet[Product], name: str, value: str) -> QuerySet[Product]:
if not value: if not value:
return queryset return queryset
@ -228,7 +240,7 @@ class ProductFilter(FilterSet):
return queryset return queryset
def filter_category(self, queryset, name, value): def filter_category(self, queryset: QuerySet[Product], name: str, value: str) -> QuerySet[Product]:
if not value: if not value:
return queryset return queryset
@ -247,7 +259,7 @@ class ProductFilter(FilterSet):
return queryset.filter(category__uuid=value) return queryset.filter(category__uuid=value)
@staticmethod @staticmethod
def _infer_type(value): def _infer_type(value: str) -> Any:
try: try:
parsed_value = json.loads(value) parsed_value = json.loads(value)
if isinstance(parsed_value, list | dict): if isinstance(parsed_value, list | dict):
@ -271,7 +283,7 @@ class ProductFilter(FilterSet):
return value return value
@property @property
def qs(self): def qs(self) -> QuerySet[Product]:
qs = super().qs qs = super().qs
ordering_param = self.data.get("order_by", "") ordering_param = self.data.get("order_by", "")
@ -320,7 +332,8 @@ class ProductFilter(FilterSet):
return qs.distinct() return qs.distinct()
class OrderFilter(FilterSet): # noinspection PyUnusedLocal
class OrderFilter(FilterSet): # type: ignore [misc]
search = CharFilter( search = CharFilter(
method="filter_search", method="filter_search",
label=_("Search (ID, product name or part number)"), label=_("Search (ID, product name or part number)"),
@ -367,7 +380,7 @@ class OrderFilter(FilterSet):
"max_buy_time", "max_buy_time",
] ]
def filter_search(self, queryset, _name, value): def filter_search(self, queryset: QuerySet[Order], name: str, value: str):
return queryset.filter( return queryset.filter(
Q(human_readable_id__icontains=value) Q(human_readable_id__icontains=value)
| Q(order_products__product__name__icontains=value) | Q(order_products__product__name__icontains=value)
@ -375,7 +388,7 @@ class OrderFilter(FilterSet):
).distinct() ).distinct()
class WishlistFilter(FilterSet): class WishlistFilter(FilterSet): # type: ignore [misc]
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact") uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
user_email = CharFilter(field_name="user__email", lookup_expr="iexact", label=_("User email")) user_email = CharFilter(field_name="user__email", lookup_expr="iexact", label=_("User email"))
user = UUIDFilter(field_name="user__uuid", lookup_expr="exact", label=_("User UUID")) user = UUIDFilter(field_name="user__uuid", lookup_expr="exact", label=_("User UUID"))
@ -395,7 +408,7 @@ class WishlistFilter(FilterSet):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
class CategoryFilter(FilterSet): class CategoryFilter(FilterSet): # type: ignore [misc]
search = CharFilter(field_name="name", method="search_categories", label=_("Search")) search = CharFilter(field_name="name", method="search_categories", label=_("Search"))
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact") uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
name = CharFilter(lookup_expr="icontains", label=_("Name")) name = CharFilter(lookup_expr="icontains", label=_("Name"))
@ -424,7 +437,7 @@ class CategoryFilter(FilterSet):
"whole", "whole",
] ]
def search_categories(self, queryset: QuerySet[Product], name, value): def search_categories(self, queryset: QuerySet[Category], name: str, value: str) -> QuerySet[Category]:
if not value: if not value:
return queryset return queryset
@ -432,7 +445,7 @@ class CategoryFilter(FilterSet):
return queryset.filter(uuid__in=uuids) return queryset.filter(uuid__in=uuids)
def filter_order_by(self, queryset, _name, value): def filter_order_by(self, queryset: QuerySet[Category], name: str, value: str) -> QuerySet[Category]:
if not value: if not value:
return queryset return queryset
@ -456,7 +469,7 @@ class CategoryFilter(FilterSet):
qs = queryset.order_by(order_expression).prefetch_related(None) qs = queryset.order_by(order_expression).prefetch_related(None)
def create_ordered_tree_prefetch(max_depth=10): def create_ordered_tree_prefetch(max_depth=10) -> Prefetch | None:
if field == "?": if field == "?":
def build_random_prefetch(depth): def build_random_prefetch(depth):
@ -494,7 +507,7 @@ class CategoryFilter(FilterSet):
return qs return qs
def filter_whole_categories(self, queryset, _name, value): def filter_whole_categories(self, queryset: QuerySet[Category], name: str, value: str) -> QuerySet[Category]:
has_own_products = Exists(Product.objects.filter(category=OuterRef("pk"))) has_own_products = Exists(Product.objects.filter(category=OuterRef("pk")))
has_desc_products = Exists( has_desc_products = Exists(
Product.objects.filter( Product.objects.filter(
@ -509,7 +522,7 @@ class CategoryFilter(FilterSet):
return annotated.filter(has_products=True).distinct() return annotated.filter(has_products=True).distinct()
return annotated.filter(has_products=False).distinct() return annotated.filter(has_products=False).distinct()
def filter_parent_uuid(self, queryset, _name, value): def filter_parent_uuid(self, queryset: QuerySet[Category], name: str, value: str):
if value in ("", "null", "None"): if value in ("", "null", "None"):
return queryset.filter(parent=None) return queryset.filter(parent=None)
@ -522,7 +535,7 @@ class CategoryFilter(FilterSet):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
class BrandFilter(FilterSet): class BrandFilter(FilterSet): # type: ignore [misc]
search = CharFilter(field_name="name", method="search_brands", label=_("Search")) search = CharFilter(field_name="name", method="search_brands", label=_("Search"))
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact") uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
name = CharFilter(lookup_expr="icontains", label=_("Name")) name = CharFilter(lookup_expr="icontains", label=_("Name"))
@ -543,7 +556,7 @@ class BrandFilter(FilterSet):
model = Brand model = Brand
fields = ["uuid", "name", "slug", "priority"] fields = ["uuid", "name", "slug", "priority"]
def search_brands(self, queryset: QuerySet[Product], name, value): def search_brands(self, queryset: QuerySet[Brand], name: str, value: str) -> QuerySet[Brand]:
if not value: if not value:
return queryset return queryset
@ -552,7 +565,7 @@ class BrandFilter(FilterSet):
return queryset.filter(uuid__in=uuids) return queryset.filter(uuid__in=uuids)
class FeedbackFilter(FilterSet): class FeedbackFilter(FilterSet): # type: ignore [misc]
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID")) uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID"))
product_uuid = UUIDFilter( product_uuid = UUIDFilter(
field_name="order_product__product__uuid", field_name="order_product__product__uuid",
@ -581,7 +594,7 @@ class FeedbackFilter(FilterSet):
fields = ["uuid", "product_uuid", "user_uuid", "order_by"] fields = ["uuid", "product_uuid", "user_uuid", "order_by"]
class AddressFilter(FilterSet): class AddressFilter(FilterSet): # type: ignore [misc]
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID")) uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID"))
user_uuid = UUIDFilter(field_name="user__uuid", lookup_expr="exact", label=_("User UUID")) user_uuid = UUIDFilter(field_name="user__uuid", lookup_expr="exact", label=_("User UUID"))
user_email = CharFilter(field_name="user__email", lookup_expr="iexact", label=_("User email")) user_email = CharFilter(field_name="user__email", lookup_expr="iexact", label=_("User email"))

View file

@ -22,6 +22,16 @@ class VendorForm(forms.ModelForm):
} }
class CRMForm(forms.ModelForm):
class Meta:
model = Product
fields = "__all__"
widgets = {
"authentication": JSONTableWidget(),
"attributes": JSONTableWidget(),
}
class OrderProductForm(forms.ModelForm): class OrderProductForm(forms.ModelForm):
class Meta: class Meta:
model = OrderProduct model = OrderProduct

View file

@ -1,10 +1,12 @@
from typing import Any
from graphene import Mutation from graphene import Mutation
class BaseMutation(Mutation): # type: ignore [misc] class BaseMutation(Mutation): # type: ignore [misc]
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args: list[Any], **kwargs: dict[Any, Any]) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@staticmethod @staticmethod
def mutate(**kwargs) -> None: def mutate(**kwargs: Any) -> None:
pass pass

View file

@ -1,4 +1,5 @@
import logging import logging
from typing import Any
import requests import requests
from django.core.cache import cache from django.core.cache import cache
@ -31,6 +32,7 @@ from payments.graphene.object_types import TransactionType
logger = logging.getLogger("django") logger = logging.getLogger("django")
# noinspection PyUnusedLocal
class CacheOperator(BaseMutation): class CacheOperator(BaseMutation):
class Meta: class Meta:
description = _("cache I/O") description = _("cache I/O")
@ -46,10 +48,11 @@ class CacheOperator(BaseMutation):
data = GenericScalar(description=_("cached data")) data = GenericScalar(description=_("cached data"))
@staticmethod @staticmethod
def mutate(_parent, info, key, data=None, timeout=None): def mutate(parent, info, key, data=None, timeout=None) -> dict[Any, Any]: # type: ignore [override]
return camelize(web_cache(info.context, key, data, timeout)) return camelize(web_cache(info.context, key, data, timeout))
# noinspection PyUnusedLocal
class RequestCursedURL(BaseMutation): class RequestCursedURL(BaseMutation):
class Meta: class Meta:
description = _("request a CORSed URL") description = _("request a CORSed URL")
@ -60,7 +63,7 @@ class RequestCursedURL(BaseMutation):
data = GenericScalar(description=_("camelized JSON data from the requested URL")) data = GenericScalar(description=_("camelized JSON data from the requested URL"))
@staticmethod @staticmethod
def mutate(_parent, info, url): def mutate(parent, info, url) -> dict[str, Any]: # type: ignore [override]
if not is_url_safe(url): if not is_url_safe(url):
raise BadRequest(_("only URLs starting with http(s):// are allowed")) raise BadRequest(_("only URLs starting with http(s):// are allowed"))
try: try:
@ -75,6 +78,7 @@ class RequestCursedURL(BaseMutation):
return {"data": {"error": str(e)}} return {"data": {"error": str(e)}}
# noinspection PyUnusedLocal,PyTypeChecker
class AddOrderProduct(BaseMutation): class AddOrderProduct(BaseMutation):
class Meta: class Meta:
description = _("add a product to the order") description = _("add a product to the order")
@ -87,7 +91,7 @@ class AddOrderProduct(BaseMutation):
order = Field(OrderType) order = Field(OrderType)
@staticmethod @staticmethod
def mutate(_parent, info, product_uuid, order_uuid, attributes=None): def mutate(parent, info, product_uuid, order_uuid, attributes=None): # type: ignore [override]
user = info.context.user user = info.context.user
try: try:
order = Order.objects.get(uuid=order_uuid) order = Order.objects.get(uuid=order_uuid)
@ -101,6 +105,7 @@ class AddOrderProduct(BaseMutation):
raise Http404(_(f"order {order_uuid} not found")) from dne raise Http404(_(f"order {order_uuid} not found")) from dne
# noinspection PyUnusedLocal
class RemoveOrderProduct(BaseMutation): class RemoveOrderProduct(BaseMutation):
class Meta: class Meta:
description = _("remove a product from the order") description = _("remove a product from the order")
@ -113,7 +118,7 @@ class RemoveOrderProduct(BaseMutation):
order = Field(OrderType) order = Field(OrderType)
@staticmethod @staticmethod
def mutate(_parent, info, product_uuid, order_uuid, attributes=None): def mutate(parent, info, product_uuid, order_uuid, attributes=None) -> AddOrderProduct | None: # type: ignore [override]
user = info.context.user user = info.context.user
try: try:
order = Order.objects.get(uuid=order_uuid) order = Order.objects.get(uuid=order_uuid)
@ -127,6 +132,7 @@ class RemoveOrderProduct(BaseMutation):
raise Http404(_(f"order {order_uuid} not found")) from dne raise Http404(_(f"order {order_uuid} not found")) from dne
# noinspection PyUnusedLocal,PyTypeChecker
class RemoveAllOrderProducts(BaseMutation): class RemoveAllOrderProducts(BaseMutation):
class Meta: class Meta:
description = _("remove all products from the order") description = _("remove all products from the order")
@ -137,7 +143,7 @@ class RemoveAllOrderProducts(BaseMutation):
order = Field(OrderType) order = Field(OrderType)
@staticmethod @staticmethod
def mutate(_parent, info, order_uuid): def mutate(parent, info, order_uuid): # type: ignore [override]
user = info.context.user user = info.context.user
order = Order.objects.get(uuid=order_uuid) order = Order.objects.get(uuid=order_uuid)
if not (user.has_perm("core.delete_orderproduct") or user == order.user): if not (user.has_perm("core.delete_orderproduct") or user == order.user):
@ -148,6 +154,7 @@ class RemoveAllOrderProducts(BaseMutation):
return RemoveAllOrderProducts(order=order) return RemoveAllOrderProducts(order=order)
# noinspection PyUnusedLocal,PyTypeChecker
class RemoveOrderProductsOfAKind(BaseMutation): class RemoveOrderProductsOfAKind(BaseMutation):
class Meta: class Meta:
description = _("remove a product from the order") description = _("remove a product from the order")
@ -159,7 +166,7 @@ class RemoveOrderProductsOfAKind(BaseMutation):
order = Field(OrderType) order = Field(OrderType)
@staticmethod @staticmethod
def mutate(_parent, info, product_uuid, order_uuid): def mutate(parent, info, product_uuid, order_uuid): # type: ignore [override]
user = info.context.user user = info.context.user
order = Order.objects.get(uuid=order_uuid) order = Order.objects.get(uuid=order_uuid)
if not (user.has_perm("core.delete_orderproduct") or user == order.user): if not (user.has_perm("core.delete_orderproduct") or user == order.user):
@ -170,6 +177,7 @@ class RemoveOrderProductsOfAKind(BaseMutation):
return RemoveOrderProductsOfAKind(order=order) return RemoveOrderProductsOfAKind(order=order)
# noinspection PyUnusedLocal,PyTypeChecker
class BuyOrder(BaseMutation): class BuyOrder(BaseMutation):
class Meta: class Meta:
description = _("buy an order") description = _("buy an order")
@ -189,7 +197,7 @@ class BuyOrder(BaseMutation):
@staticmethod @staticmethod
def mutate( def mutate(
_parent, parent,
info, info,
order_uuid=None, order_uuid=None,
order_hr_id=None, order_hr_id=None,
@ -199,7 +207,7 @@ class BuyOrder(BaseMutation):
shipping_address=None, shipping_address=None,
billing_address=None, billing_address=None,
chosen_products=None, chosen_products=None,
): ): # type: ignore [override]
if not any([order_uuid, order_hr_id]) or all([order_uuid, order_hr_id]): if not any([order_uuid, order_hr_id]) or all([order_uuid, order_hr_id]):
raise BadRequest(_("please provide either order_uuid or order_hr_id - mutually exclusive")) raise BadRequest(_("please provide either order_uuid or order_hr_id - mutually exclusive"))
user = info.context.user user = info.context.user
@ -232,6 +240,7 @@ class BuyOrder(BaseMutation):
raise Http404(_(f"order {order_uuid} not found")) from dne raise Http404(_(f"order {order_uuid} not found")) from dne
# noinspection PyUnusedLocal,PyTypeChecker
class BulkOrderAction(BaseMutation): class BulkOrderAction(BaseMutation):
class Meta: class Meta:
description = _("perform an action on a list of products in the order") description = _("perform an action on a list of products in the order")
@ -246,13 +255,13 @@ class BulkOrderAction(BaseMutation):
@staticmethod @staticmethod
def mutate( def mutate(
_parent, parent,
info, info,
action, action,
products, products,
order_uuid=None, order_uuid=None,
order_hr_id=None, order_hr_id=None,
): ): # type: ignore [override]
if not any([order_uuid, order_hr_id]) or all([order_uuid, order_hr_id]): if not any([order_uuid, order_hr_id]) or all([order_uuid, order_hr_id]):
raise BadRequest(_("please provide either order_uuid or order_hr_id - mutually exclusive")) raise BadRequest(_("please provide either order_uuid or order_hr_id - mutually exclusive"))
user = info.context.user user = info.context.user
@ -279,6 +288,7 @@ class BulkOrderAction(BaseMutation):
raise Http404(_(f"order {order_uuid} not found")) from dne raise Http404(_(f"order {order_uuid} not found")) from dne
# noinspection PyUnusedLocal,PyTypeChecker
class BulkWishlistAction(BaseMutation): class BulkWishlistAction(BaseMutation):
class Meta: class Meta:
description = _("perform an action on a list of products in the wishlist") description = _("perform an action on a list of products in the wishlist")
@ -292,12 +302,12 @@ class BulkWishlistAction(BaseMutation):
@staticmethod @staticmethod
def mutate( def mutate(
_parent, parent,
info, info,
action, action,
products, products,
wishlist_uuid=None, wishlist_uuid=None,
): ): # type: ignore [override]
if not wishlist_uuid: if not wishlist_uuid:
raise BadRequest(_("please provide wishlist_uuid value")) raise BadRequest(_("please provide wishlist_uuid value"))
user = info.context.user user = info.context.user
@ -319,6 +329,7 @@ class BulkWishlistAction(BaseMutation):
raise Http404(_(f"wishlist {wishlist_uuid} not found")) from dne raise Http404(_(f"wishlist {wishlist_uuid} not found")) from dne
# noinspection PyUnusedLocal
class BuyUnregisteredOrder(BaseMutation): class BuyUnregisteredOrder(BaseMutation):
class Meta: class Meta:
description = _("purchase an order without account creation") description = _("purchase an order without account creation")
@ -338,7 +349,7 @@ class BuyUnregisteredOrder(BaseMutation):
@staticmethod @staticmethod
def mutate( def mutate(
_parent, parent,
info, info,
products, products,
customer_name, customer_name,
@ -349,7 +360,7 @@ class BuyUnregisteredOrder(BaseMutation):
customer_shipping_address=None, customer_shipping_address=None,
promocode_uuid=None, promocode_uuid=None,
is_business=False, is_business=False,
): ): # type: ignore [override]
order = Order.objects.create(status="MOMENTAL") order = Order.objects.create(status="MOMENTAL")
transaction = order.buy_without_registration( transaction = order.buy_without_registration(
products=products, products=products,
@ -362,9 +373,11 @@ class BuyUnregisteredOrder(BaseMutation):
payment_method=payment_method, payment_method=payment_method,
is_business=is_business, is_business=is_business,
) )
# noinspection PyTypeChecker
return BuyUnregisteredOrder(transaction=transaction) return BuyUnregisteredOrder(transaction=transaction)
# noinspection PyUnusedLocal,PyTypeChecker
class AddWishlistProduct(BaseMutation): class AddWishlistProduct(BaseMutation):
class Meta: class Meta:
description = _("add a product to the wishlist") description = _("add a product to the wishlist")
@ -376,7 +389,7 @@ class AddWishlistProduct(BaseMutation):
wishlist = Field(WishlistType) wishlist = Field(WishlistType)
@staticmethod @staticmethod
def mutate(_parent, info, product_uuid, wishlist_uuid): def mutate(parent, info, product_uuid, wishlist_uuid): # type: ignore [override]
user = info.context.user user = info.context.user
try: try:
wishlist = Wishlist.objects.get(uuid=wishlist_uuid) wishlist = Wishlist.objects.get(uuid=wishlist_uuid)
@ -392,6 +405,7 @@ class AddWishlistProduct(BaseMutation):
raise Http404(_(f"wishlist {wishlist_uuid} not found")) from dne raise Http404(_(f"wishlist {wishlist_uuid} not found")) from dne
# noinspection PyUnusedLocal,PyTypeChecker
class RemoveWishlistProduct(BaseMutation): class RemoveWishlistProduct(BaseMutation):
class Meta: class Meta:
description = _("remove a product from the wishlist") description = _("remove a product from the wishlist")
@ -403,7 +417,7 @@ class RemoveWishlistProduct(BaseMutation):
wishlist = Field(WishlistType) wishlist = Field(WishlistType)
@staticmethod @staticmethod
def mutate(_parent, info, product_uuid, wishlist_uuid): def mutate(parent, info, product_uuid, wishlist_uuid): # type: ignore [override]
user = info.context.user user = info.context.user
try: try:
wishlist = Wishlist.objects.get(uuid=wishlist_uuid) wishlist = Wishlist.objects.get(uuid=wishlist_uuid)
@ -419,6 +433,7 @@ class RemoveWishlistProduct(BaseMutation):
raise Http404(_(f"wishlist {wishlist_uuid} not found")) from dne raise Http404(_(f"wishlist {wishlist_uuid} not found")) from dne
# noinspection PyUnusedLocal,PyTypeChecker
class RemoveAllWishlistProducts(BaseMutation): class RemoveAllWishlistProducts(BaseMutation):
class Meta: class Meta:
description = _("remove all products from the wishlist") description = _("remove all products from the wishlist")
@ -429,7 +444,7 @@ class RemoveAllWishlistProducts(BaseMutation):
wishlist = Field(WishlistType) wishlist = Field(WishlistType)
@staticmethod @staticmethod
def mutate(_parent, info, wishlist_uuid): def mutate(parent, info, wishlist_uuid): # type: ignore [override]
user = info.context.user user = info.context.user
try: try:
wishlist = Wishlist.objects.get(uuid=wishlist_uuid) wishlist = Wishlist.objects.get(uuid=wishlist_uuid)
@ -446,6 +461,7 @@ class RemoveAllWishlistProducts(BaseMutation):
raise Http404(_(f"wishlist {wishlist_uuid} not found")) from dne raise Http404(_(f"wishlist {wishlist_uuid} not found")) from dne
# noinspection PyUnusedLocal,PyTypeChecker
class BuyWishlist(BaseMutation): class BuyWishlist(BaseMutation):
class Meta: class Meta:
description = _("buy all products from the wishlist") description = _("buy all products from the wishlist")
@ -459,7 +475,7 @@ class BuyWishlist(BaseMutation):
transaction = Field(TransactionType, required=False) transaction = Field(TransactionType, required=False)
@staticmethod @staticmethod
def mutate(_parent, info, wishlist_uuid, force_balance=False, force_payment=False): def mutate(parent, info, wishlist_uuid, force_balance=False, force_payment=False): # type: ignore [override]
user = info.context.user user = info.context.user
try: try:
wishlist = Wishlist.objects.get(uuid=wishlist_uuid) wishlist = Wishlist.objects.get(uuid=wishlist_uuid)
@ -489,6 +505,7 @@ class BuyWishlist(BaseMutation):
raise Http404(_(f"wishlist {wishlist_uuid} not found")) from dne raise Http404(_(f"wishlist {wishlist_uuid} not found")) from dne
# noinspection PyUnusedLocal,PyTypeChecker
class BuyProduct(BaseMutation): class BuyProduct(BaseMutation):
class Meta: class Meta:
description = _("buy a product") description = _("buy a product")
@ -507,13 +524,13 @@ class BuyProduct(BaseMutation):
@staticmethod @staticmethod
def mutate( def mutate(
_parent, parent,
info, info,
product_uuid, product_uuid,
attributes=None, attributes=None,
force_balance=False, force_balance=False,
force_payment=False, force_payment=False,
): ): # type: ignore [override]
user = info.context.user user = info.context.user
order = Order.objects.create(user=user, status="MOMENTAL") order = Order.objects.create(user=user, status="MOMENTAL")
order.add_product(product_uuid=product_uuid, attributes=format_attributes(attributes)) order.add_product(product_uuid=product_uuid, attributes=format_attributes(attributes))
@ -527,6 +544,7 @@ class BuyProduct(BaseMutation):
raise TypeError(_(f"wrong type came from order.buy() method: {type(instance)!s}")) raise TypeError(_(f"wrong type came from order.buy() method: {type(instance)!s}"))
# noinspection PyUnusedLocal,PyTypeChecker
class FeedbackProductAction(BaseMutation): class FeedbackProductAction(BaseMutation):
class Meta: class Meta:
description = _("add or delete a feedback for orderproduct") description = _("add or delete a feedback for orderproduct")
@ -540,7 +558,7 @@ class FeedbackProductAction(BaseMutation):
feedback = Field(FeedbackType, required=False) feedback = Field(FeedbackType, required=False)
@staticmethod @staticmethod
def mutate(_parent, info, order_product_uuid, action, comment=None, rating=None): def mutate(parent, info, order_product_uuid, action, comment=None, rating=None): # type: ignore [override]
user = info.context.user user = info.context.user
try: try:
order_product = OrderProduct.objects.get(uuid=order_product_uuid) order_product = OrderProduct.objects.get(uuid=order_product_uuid)
@ -559,6 +577,7 @@ class FeedbackProductAction(BaseMutation):
raise Http404(_(f"order product {order_product_uuid} not found")) from dne raise Http404(_(f"order product {order_product_uuid} not found")) from dne
# noinspection PyUnusedLocal,PyTypeChecker
class CreateProduct(BaseMutation): class CreateProduct(BaseMutation):
class Arguments: class Arguments:
name = String(required=True) name = String(required=True)
@ -568,7 +587,7 @@ class CreateProduct(BaseMutation):
product = Field(ProductType) product = Field(ProductType)
@staticmethod @staticmethod
def mutate(_parent, info, name, category_uuid, description=None): def mutate(parent, info, name, category_uuid, description=None): # type: ignore [override]
if not info.context.user.has_perm("core.add_product"): if not info.context.user.has_perm("core.add_product"):
raise PermissionDenied(permission_denied_message) raise PermissionDenied(permission_denied_message)
category = Category.objects.get(uuid=category_uuid) category = Category.objects.get(uuid=category_uuid)
@ -576,6 +595,7 @@ class CreateProduct(BaseMutation):
return CreateProduct(product=product) return CreateProduct(product=product)
# noinspection PyUnusedLocal,PyTypeChecker
class UpdateProduct(BaseMutation): class UpdateProduct(BaseMutation):
class Arguments: class Arguments:
uuid = UUID(required=True) uuid = UUID(required=True)
@ -586,7 +606,7 @@ class UpdateProduct(BaseMutation):
product = Field(ProductType) product = Field(ProductType)
@staticmethod @staticmethod
def mutate(_parent, info, uuid, name=None, description=None, category_uuid=None): def mutate(parent, info, uuid, name=None, description=None, category_uuid=None): # type: ignore [override]
user = info.context.user user = info.context.user
if not user.has_perm("core.change_product"): if not user.has_perm("core.change_product"):
raise PermissionDenied(permission_denied_message) raise PermissionDenied(permission_denied_message)
@ -601,6 +621,7 @@ class UpdateProduct(BaseMutation):
return UpdateProduct(product=product) return UpdateProduct(product=product)
# noinspection PyUnusedLocal,PyTypeChecker
class DeleteProduct(BaseMutation): class DeleteProduct(BaseMutation):
class Arguments: class Arguments:
uuid = UUID(required=True) uuid = UUID(required=True)
@ -608,7 +629,7 @@ class DeleteProduct(BaseMutation):
ok = Boolean() ok = Boolean()
@staticmethod @staticmethod
def mutate(_parent, info, uuid): def mutate(parent, info, uuid): # type: ignore [override]
user = info.context.user user = info.context.user
if not user.has_perm("core.delete_product"): if not user.has_perm("core.delete_product"):
raise PermissionDenied(permission_denied_message) raise PermissionDenied(permission_denied_message)
@ -617,6 +638,7 @@ class DeleteProduct(BaseMutation):
return DeleteProduct(ok=True) return DeleteProduct(ok=True)
# noinspection PyUnusedLocal,PyTypeChecker
class CreateAddress(BaseMutation): class CreateAddress(BaseMutation):
class Arguments: class Arguments:
raw_data = String(required=True, description=_("original address string provided by the user")) raw_data = String(required=True, description=_("original address string provided by the user"))
@ -624,13 +646,14 @@ class CreateAddress(BaseMutation):
address = Field(AddressType) address = Field(AddressType)
@staticmethod @staticmethod
def mutate(_parent, info, raw_data): def mutate(parent, info, raw_data): # type: ignore [override]
user = info.context.user if info.context.user.is_authenticated else None user = info.context.user if info.context.user.is_authenticated else None
address = Address.objects.create(raw_data=raw_data, user=user) address = Address.objects.create(raw_data=raw_data, user=user)
return CreateAddress(address=address) return CreateAddress(address=address)
# noinspection PyUnusedLocal
class DeleteAddress(BaseMutation): class DeleteAddress(BaseMutation):
class Arguments: class Arguments:
uuid = UUID(required=True) uuid = UUID(required=True)
@ -638,7 +661,7 @@ class DeleteAddress(BaseMutation):
success = Boolean() success = Boolean()
@staticmethod @staticmethod
def mutate(_parent, info, uuid): def mutate(parent, info, uuid): # type: ignore [override]
try: try:
address = Address.objects.get(uuid=uuid) address = Address.objects.get(uuid=uuid)
if ( if (
@ -647,6 +670,7 @@ class DeleteAddress(BaseMutation):
or info.context.user == address.user or info.context.user == address.user
): ):
address.delete() address.delete()
# noinspection PyTypeChecker
return DeleteAddress(success=True) return DeleteAddress(success=True)
raise PermissionDenied(permission_denied_message) raise PermissionDenied(permission_denied_message)
@ -656,6 +680,7 @@ class DeleteAddress(BaseMutation):
raise Http404(_(f"{name} does not exist: {uuid}")) from dne raise Http404(_(f"{name} does not exist: {uuid}")) from dne
# noinspection PyUnusedLocal
class AutocompleteAddress(BaseMutation): class AutocompleteAddress(BaseMutation):
class Arguments: class Arguments:
q = String() q = String()
@ -664,7 +689,7 @@ class AutocompleteAddress(BaseMutation):
suggestions = GenericScalar() suggestions = GenericScalar()
@staticmethod @staticmethod
def mutate(_parent, info, q, limit): def mutate(parent, info, q, limit): # type: ignore [override]
if 1 > limit > 10: if 1 > limit > 10:
raise BadRequest(_("limit must be between 1 and 10")) raise BadRequest(_("limit must be between 1 and 10"))
try: try:
@ -672,9 +697,11 @@ class AutocompleteAddress(BaseMutation):
except Exception as e: except Exception as e:
raise BadRequest(f"geocoding error: {e!s}") from e raise BadRequest(f"geocoding error: {e!s}") from e
# noinspection PyTypeChecker
return AutocompleteAddress(suggestions=suggestions) return AutocompleteAddress(suggestions=suggestions)
# noinspection PyUnusedLocal
class ContactUs(BaseMutation): class ContactUs(BaseMutation):
class Arguments: class Arguments:
email = String(required=True) email = String(required=True)
@ -687,7 +714,7 @@ class ContactUs(BaseMutation):
error = String() error = String()
@staticmethod @staticmethod
def mutate(_parent, info, email, name, subject, message, phone_number=None): def mutate(parent, info, email, name, subject, message, phone_number=None): # type: ignore [override]
try: try:
contact_us_email.delay( contact_us_email.delay(
{ {
@ -698,12 +725,14 @@ class ContactUs(BaseMutation):
"message": message, "message": message,
} }
) )
# noinspection PyTypeChecker
return ContactUs(received=True) return ContactUs(received=True)
except Exception as e: except Exception as e:
# noinspection PyTypeChecker
return ContactUs(received=False, error=str(e)) return ContactUs(received=False, error=str(e))
# noinspection PyArgumentList # noinspection PyArgumentList PyUnusedLocal
class Search(BaseMutation): class Search(BaseMutation):
class Arguments: class Arguments:
query = String(required=True) query = String(required=True)
@ -714,9 +743,10 @@ class Search(BaseMutation):
description = _("elasticsearch - works like a charm") description = _("elasticsearch - works like a charm")
@staticmethod @staticmethod
def mutate(_parent, info, query): def mutate(parent, info, query): # type: ignore [override]
data = process_query(query=query, request=info.context) data = process_query(query=query, request=info.context)
# noinspection PyTypeChecker
return Search( return Search(
results=SearchResultsType( results=SearchResultsType(
products=data["products"], products=data["products"],

View file

@ -3,7 +3,7 @@ from typing import Any
from constance import config from constance import config
from django.core.cache import cache from django.core.cache import cache
from django.db.models import Max, Min from django.db.models import Max, Min, QuerySet
from django.db.models.functions import Length from django.db.models.functions import Length
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from graphene import ( from graphene import (
@ -60,7 +60,7 @@ from payments.graphene.object_types import TransactionType
logger = logging.getLogger("django") logger = logging.getLogger("django")
class SEOMetaType(ObjectType): class SEOMetaType(ObjectType): # type: ignore [misc]
title = String() title = String()
description = String() description = String()
canonical = String() canonical = String()
@ -71,7 +71,7 @@ class SEOMetaType(ObjectType):
hreflang = String() hreflang = String()
class AttributeType(DjangoObjectType): class AttributeType(DjangoObjectType): # type: ignore [misc]
values = List(lambda: AttributeValueType, description=_("attribute values")) values = List(lambda: AttributeValueType, description=_("attribute values"))
class Meta: class Meta:
@ -81,7 +81,7 @@ class AttributeType(DjangoObjectType):
filter_fields = ["uuid"] filter_fields = ["uuid"]
description = _("attributes") description = _("attributes")
def resolve_values(self, info): def resolve_values(self, info) -> QuerySet[AttributeValue]:
base_qs = AttributeValue.objects.filter(attribute=self) base_qs = AttributeValue.objects.filter(attribute=self)
product_uuid = getattr(info.context, "_product_uuid", None) product_uuid = getattr(info.context, "_product_uuid", None)
@ -91,7 +91,7 @@ class AttributeType(DjangoObjectType):
return base_qs return base_qs
class AttributeGroupType(DjangoObjectType): class AttributeGroupType(DjangoObjectType): # type: ignore [misc]
attributes = List(lambda: AttributeType, description=_("grouped attributes")) attributes = List(lambda: AttributeType, description=_("grouped attributes"))
class Meta: class Meta:
@ -101,7 +101,7 @@ class AttributeGroupType(DjangoObjectType):
filter_fields = ["uuid"] filter_fields = ["uuid"]
description = _("groups of attributes") description = _("groups of attributes")
def resolve_attributes(self: AttributeGroup, info): def resolve_attributes(self: AttributeGroup, info) -> QuerySet[Attribute]:
product_uuid = getattr(info.context, "_product_uuid", None) product_uuid = getattr(info.context, "_product_uuid", None)
qs = self.attributes.all() qs = self.attributes.all()
@ -112,7 +112,7 @@ class AttributeGroupType(DjangoObjectType):
return qs return qs
class BrandType(DjangoObjectType): class BrandType(DjangoObjectType): # type: ignore [misc]
categories = List(lambda: CategoryType, description=_("categories")) categories = List(lambda: CategoryType, description=_("categories"))
seo_meta = Field(SEOMetaType, description=_("SEO Meta snapshot")) seo_meta = Field(SEOMetaType, description=_("SEO Meta snapshot"))
@ -123,18 +123,18 @@ class BrandType(DjangoObjectType):
filter_fields = ["uuid", "name"] filter_fields = ["uuid", "name"]
description = _("brands") description = _("brands")
def resolve_categories(self: Brand, info): def resolve_categories(self: Brand, info) -> QuerySet[Category]:
if info.context.user.has_perm("core.view_category"): if info.context.user.has_perm("core.view_category"):
return self.categories.all() return self.categories.all()
return self.categories.filter(is_active=True) return self.categories.filter(is_active=True)
def resolve_big_logo(self: Brand, info): def resolve_big_logo(self: Brand, info) -> str | None:
return info.context.build_absolute_uri(self.big_logo.url) if self.big_logo else "" return info.context.build_absolute_uri(self.big_logo.url) if self.big_logo else ""
def resolve_small_logo(self: Brand, info): def resolve_small_logo(self: Brand, info) -> str | None:
return info.context.build_absolute_uri(self.small_logo.url) if self.small_logo else "" return info.context.build_absolute_uri(self.small_logo.url) if self.small_logo else ""
def resolve_seo_meta(self: Brand, info): def resolve_seo_meta(self: Brand, info) -> dict[str, str | list[Any] | dict[str, str] | None]:
lang = graphene_current_lang() lang = graphene_current_lang()
base = f"https://{config.BASE_DOMAIN}" base = f"https://{config.BASE_DOMAIN}"
canonical = f"{base}/{lang}/brand/{self.slug}" canonical = f"{base}/{lang}/brand/{self.slug}"
@ -177,17 +177,17 @@ class BrandType(DjangoObjectType):
} }
class FilterableAttributeType(ObjectType): class FilterableAttributeType(ObjectType): # type: ignore [misc]
attribute_name = String(required=True) attribute_name = String(required=True)
possible_values = List(String, required=True) possible_values = List(String, required=True)
class MinMaxPriceType(ObjectType): class MinMaxPriceType(ObjectType): # type: ignore [misc]
min_price = Float() min_price = Float()
max_price = Float() max_price = Float()
class CategoryType(DjangoObjectType): class CategoryType(DjangoObjectType): # type: ignore [misc]
children = List( children = List(
lambda: CategoryType, lambda: CategoryType,
description=_("categories"), description=_("categories"),
@ -340,7 +340,7 @@ class CategoryType(DjangoObjectType):
} }
class VendorType(DjangoObjectType): class VendorType(DjangoObjectType): # type: ignore [misc]
markup_percent = Float(description=_("markup percentage")) markup_percent = Float(description=_("markup percentage"))
class Meta: class Meta:
@ -351,7 +351,7 @@ class VendorType(DjangoObjectType):
description = _("vendors") description = _("vendors")
class AddressType(DjangoObjectType): class AddressType(DjangoObjectType): # type: ignore [misc]
latitude = Float(description=_("Latitude (Y coordinate)")) latitude = Float(description=_("Latitude (Y coordinate)"))
longitude = Float(description=_("Longitude (X coordinate)")) longitude = Float(description=_("Longitude (X coordinate)"))
@ -381,7 +381,7 @@ class AddressType(DjangoObjectType):
return self.location.y if self.location else None return self.location.y if self.location else None
class FeedbackType(DjangoObjectType): class FeedbackType(DjangoObjectType): # type: ignore [misc]
comment = String(description=_("comment")) comment = String(description=_("comment"))
rating = Int(description=_("rating value from 1 to 10, inclusive, or 0 if not set.")) rating = Int(description=_("rating value from 1 to 10, inclusive, or 0 if not set."))
@ -393,7 +393,7 @@ class FeedbackType(DjangoObjectType):
description = _("represents feedback from a user.") description = _("represents feedback from a user.")
class OrderProductType(DjangoObjectType): class OrderProductType(DjangoObjectType): # type: ignore [misc]
attributes = GenericScalar(description=_("attributes")) attributes = GenericScalar(description=_("attributes"))
notifications = GenericScalar(description=_("notifications")) notifications = GenericScalar(description=_("notifications"))
download_url = String(description=_("download url for this order product if applicable")) download_url = String(description=_("download url for this order product if applicable"))
@ -429,7 +429,7 @@ class OrderProductType(DjangoObjectType):
return self.download_url return self.download_url
class OrderType(DjangoObjectType): class OrderType(DjangoObjectType): # type: ignore [misc]
order_products = DjangoFilterConnectionField( order_products = DjangoFilterConnectionField(
OrderProductType, description=_("a list of order products in this order") OrderProductType, description=_("a list of order products in this order")
) )
@ -482,7 +482,7 @@ class OrderType(DjangoObjectType):
return None return None
class ProductImageType(DjangoObjectType): class ProductImageType(DjangoObjectType): # type: ignore [misc]
image = String(description=_("image url")) image = String(description=_("image url"))
class Meta: class Meta:
@ -496,7 +496,7 @@ class ProductImageType(DjangoObjectType):
return info.context.build_absolute_uri(self.image.url) if self.image else "" return info.context.build_absolute_uri(self.image.url) if self.image else ""
class ProductType(DjangoObjectType): class ProductType(DjangoObjectType): # type: ignore [misc]
category = Field(CategoryType, description=_("category")) category = Field(CategoryType, description=_("category"))
images = DjangoFilterConnectionField(ProductImageType, description=_("images")) images = DjangoFilterConnectionField(ProductImageType, description=_("images"))
feedbacks = DjangoFilterConnectionField(FeedbackType, description=_("feedbacks")) feedbacks = DjangoFilterConnectionField(FeedbackType, description=_("feedbacks"))
@ -605,7 +605,7 @@ class ProductType(DjangoObjectType):
} }
class AttributeValueType(DjangoObjectType): class AttributeValueType(DjangoObjectType): # type: ignore [misc]
value = String(description=_("attribute value")) value = String(description=_("attribute value"))
class Meta: class Meta:
@ -616,7 +616,7 @@ class AttributeValueType(DjangoObjectType):
description = _("attribute value") description = _("attribute value")
class PromoCodeType(DjangoObjectType): class PromoCodeType(DjangoObjectType): # type: ignore [misc]
discount = Float() discount = Float()
discount_type = String() discount_type = String()
@ -640,7 +640,7 @@ class PromoCodeType(DjangoObjectType):
return "percent" if self.discount_percent else "amount" return "percent" if self.discount_percent else "amount"
class PromotionType(DjangoObjectType): class PromotionType(DjangoObjectType): # type: ignore [misc]
products = DjangoFilterConnectionField(ProductType, description=_("products on sale")) products = DjangoFilterConnectionField(ProductType, description=_("products on sale"))
class Meta: class Meta:
@ -651,7 +651,7 @@ class PromotionType(DjangoObjectType):
description = _("promotions") description = _("promotions")
class StockType(DjangoObjectType): class StockType(DjangoObjectType): # type: ignore [misc]
vendor = Field(VendorType, description=_("vendor")) vendor = Field(VendorType, description=_("vendor"))
product = Field(ProductType, description=_("product")) product = Field(ProductType, description=_("product"))
@ -663,7 +663,7 @@ class StockType(DjangoObjectType):
description = _("stocks") description = _("stocks")
class WishlistType(DjangoObjectType): class WishlistType(DjangoObjectType): # type: ignore [misc]
products = DjangoFilterConnectionField(ProductType, description=_("wishlisted products")) products = DjangoFilterConnectionField(ProductType, description=_("wishlisted products"))
class Meta: class Meta:
@ -673,7 +673,7 @@ class WishlistType(DjangoObjectType):
description = _("wishlists") description = _("wishlists")
class ProductTagType(DjangoObjectType): class ProductTagType(DjangoObjectType): # type: ignore [misc]
product_set = DjangoFilterConnectionField(ProductType, description=_("tagged products")) product_set = DjangoFilterConnectionField(ProductType, description=_("tagged products"))
class Meta: class Meta:
@ -684,7 +684,7 @@ class ProductTagType(DjangoObjectType):
description = _("product tags") description = _("product tags")
class CategoryTagType(DjangoObjectType): class CategoryTagType(DjangoObjectType): # type: ignore [misc]
category_set = DjangoFilterConnectionField(CategoryType, description=_("tagged categories")) category_set = DjangoFilterConnectionField(CategoryType, description=_("tagged categories"))
class Meta: class Meta:
@ -695,7 +695,7 @@ class CategoryTagType(DjangoObjectType):
description = _("categories tags") description = _("categories tags")
class ConfigType(ObjectType): class ConfigType(ObjectType): # type: ignore [misc]
project_name = String(description=_("project name")) project_name = String(description=_("project name"))
base_domain = String(description=_("company email")) base_domain = String(description=_("company email"))
company_name = String(description=_("company name")) company_name = String(description=_("company name"))
@ -712,7 +712,7 @@ class ConfigType(ObjectType):
description = _("company configuration") description = _("company configuration")
class LanguageType(ObjectType): class LanguageType(ObjectType): # type: ignore [misc]
code = String(description=_("language code")) code = String(description=_("language code"))
name = String(description=_("language name")) name = String(description=_("language name"))
flag = String(description=_("language flag, if exists :)")) flag = String(description=_("language flag, if exists :)"))
@ -721,40 +721,40 @@ class LanguageType(ObjectType):
description = _("supported languages") description = _("supported languages")
class SearchProductsResultsType(ObjectType): class SearchProductsResultsType(ObjectType): # type: ignore [misc]
uuid = UUID() uuid = UUID()
name = String() name = String()
slug = String() slug = String()
image = String() image = String()
class SearchCategoriesResultsType(ObjectType): class SearchCategoriesResultsType(ObjectType): # type: ignore [misc]
uuid = UUID() uuid = UUID()
name = String() name = String()
slug = String() slug = String()
image = String() image = String()
class SearchBrandsResultsType(ObjectType): class SearchBrandsResultsType(ObjectType): # type: ignore [misc]
uuid = UUID() uuid = UUID()
name = String() name = String()
slug = String() slug = String()
image = String() image = String()
class SearchPostsResultsType(ObjectType): class SearchPostsResultsType(ObjectType): # type: ignore [misc]
uuid = UUID() uuid = UUID()
name = String() name = String()
slug = String() slug = String()
class SearchResultsType(ObjectType): class SearchResultsType(ObjectType): # type: ignore [misc]
products = List(description=_("products search results"), of_type=SearchProductsResultsType) products = List(description=_("products search results"), of_type=SearchProductsResultsType)
categories = List(description=_("products search results"), of_type=SearchCategoriesResultsType) categories = List(description=_("products search results"), of_type=SearchCategoriesResultsType)
brands = List(description=_("products search results"), of_type=SearchBrandsResultsType) brands = List(description=_("products search results"), of_type=SearchBrandsResultsType)
posts = List(description=_("posts search results"), of_type=SearchPostsResultsType) posts = List(description=_("posts search results"), of_type=SearchPostsResultsType)
class BulkProductInput(InputObjectType): class BulkProductInput(InputObjectType): # type: ignore [misc]
uuid = UUID(required=True) uuid = UUID(required=True)
attributes = GenericScalar(required=False) attributes = GenericScalar(required=False)

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

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

@ -1,6 +1,7 @@
import os import os
import threading import threading
import time import time
from typing import Any
import redis import redis
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
@ -11,10 +12,10 @@ from redis.exceptions import ConnectionError # noqa: A004
class Command(BaseCommand): class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args: list[Any], **options: dict[Any, Any]) -> None:
self.stdout.write("Waiting for services...") self.stdout.write("Waiting for services...")
def wait_for_db(): def wait_for_db() -> None:
db_up = False db_up = False
while not db_up: while not db_up:
try: try:
@ -31,7 +32,7 @@ class Command(BaseCommand):
time.sleep(1) time.sleep(1)
self.stdout.write(self.style.SUCCESS("Database available!")) self.stdout.write(self.style.SUCCESS("Database available!"))
def wait_for_redis(): def wait_for_redis() -> None:
redis_up = False redis_up = False
while not redis_up: while not redis_up:
try: try:

View file

@ -1,7 +1,9 @@
import contextlib import contextlib
import os import os
import re import re
from argparse import ArgumentParser
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from typing import Any
import polib import polib
from django.apps import apps from django.apps import apps
@ -64,7 +66,7 @@ def load_po_sanitized(path: str) -> polib.POFile:
class Command(BaseCommand): class Command(BaseCommand):
help = "Scan target-language .po files and report any placeholder mismatches, grouped by app." help = "Scan target-language .po files and report any placeholder mismatches, grouped by app."
def add_arguments(self, parser): def add_arguments(self, parser: ArgumentParser) -> None:
parser.add_argument( parser.add_argument(
"-l", "-l",
"--language", "--language",
@ -92,14 +94,14 @@ class Command(BaseCommand):
help="Root path prefix to adjust file links", help="Root path prefix to adjust file links",
) )
def handle(self, *args, **options) -> None: def handle(self, *args: list[Any], **options: dict[str, str | list[str]]) -> None:
langs: list[str] = options["target_languages"] langs: list[str] = options.get("target_languages", []) # type: ignore [assignment]
if "ALL" in langs: if "ALL" in langs:
langs = list(dict(settings.LANGUAGES).keys()) langs = list(dict(settings.LANGUAGES).keys())
apps_to_scan: set[str] = set(options["target_apps"]) apps_to_scan: set[str] = set(options["target_apps"])
if "ALL" in apps_to_scan: if "ALL" in apps_to_scan:
apps_to_scan = set(TRANSLATABLE_APPS) apps_to_scan = set(TRANSLATABLE_APPS)
root_path: str = options.get("root_path") or "/app/" root_path: str = options.get("root_path") or "/app/" # type: ignore [assignment]
configs = list(apps.get_app_configs()) configs = list(apps.get_app_configs())
# noinspection PyTypeChecker # noinspection PyTypeChecker

View file

@ -1,4 +1,5 @@
from collections import defaultdict from collections import defaultdict
from typing import Any
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
@ -6,17 +7,16 @@ from core.models import Category, Product, Stock
class Command(BaseCommand): class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args: list[Any], **options: dict[Any, Any]) -> None:
self.stdout.write(self.style.SUCCESS("Starting clearing unwanted data...")) self.stdout.write(self.style.SUCCESS("Starting clearing unwanted data..."))
# 1. Clean up duplicate Stock entries per product and vendor: # 1. Clean up duplicate Stock entries per product and vendor:
# Group stocks by (product, vendor) # Group stocks by (product, vendor)
stocks_by_group = defaultdict(list) stocks_by_group = defaultdict(list)
for stock in Stock.objects.all().order_by("modified"): for stock in Stock.objects.all().order_by("modified"):
key = (stock.product_id, stock.vendor) stocks_by_group[stock.product_pk].append(stock)
stocks_by_group[key].append(stock)
stock_deletions = [] stock_deletions: list[str] = []
for group in stocks_by_group.values(): for group in stocks_by_group.values():
if len(group) <= 1: if len(group) <= 1:
continue continue
@ -32,20 +32,20 @@ class Command(BaseCommand):
# Mark all stocks (except the designated one) for deletion. # Mark all stocks (except the designated one) for deletion.
for s in group: for s in group:
if s.id != record_to_keep.id: if s.uuid != record_to_keep.uuid:
stock_deletions.append(s.id) stock_deletions.append(str(s.uuid))
if stock_deletions: if stock_deletions:
Stock.objects.filter(id__in=stock_deletions).delete() Stock.objects.filter(uuid__in=stock_deletions).delete()
self.stdout.write(self.style.SUCCESS(f"Deleted {len(stock_deletions)} duplicate stock entries.")) self.stdout.write(self.style.SUCCESS(f"Deleted {len(stock_deletions)} duplicate stock entries."))
# 2. Clean up duplicate Category entries based on name (case-insensitive) # 2. Clean up duplicate Category entries based on name (case-insensitive)
category_groups = defaultdict(list) category_groups = defaultdict(list)
for cat in Category.objects.all().order_by("modified"): for cat in Category.objects.all().order_by("modified"):
key = cat.name.lower() key: str = cat.name.lower()
category_groups[key].append(cat) category_groups[key].append(cat)
categories_to_delete = [] categories_to_delete: list[str] = []
total_product_updates = 0 total_product_updates = 0
for cat_list in category_groups.values(): for cat_list in category_groups.values():
if len(cat_list) <= 1: if len(cat_list) <= 1:
@ -59,13 +59,13 @@ class Command(BaseCommand):
keep_category = max(cat_list, key=lambda c: c.modified) keep_category = max(cat_list, key=lambda c: c.modified)
for duplicate in cat_list: for duplicate in cat_list:
if duplicate.id == keep_category.id: if duplicate.uuid == keep_category.uuid:
continue continue
total_product_updates += Product.objects.filter(category=duplicate).update(category=keep_category) total_product_updates += Product.objects.filter(category=duplicate).update(category=keep_category)
categories_to_delete.append(duplicate.id) categories_to_delete.append(str(duplicate.uuid))
if categories_to_delete: if categories_to_delete:
Category.objects.filter(id__in=categories_to_delete).delete() Category.objects.filter(uuid__in=categories_to_delete).delete()
self.stdout.write( self.stdout.write(
self.style.SUCCESS( self.style.SUCCESS(
f"Replaced category for {total_product_updates} product(s) " f"Replaced category for {total_product_updates} product(s) "
@ -74,7 +74,7 @@ class Command(BaseCommand):
) )
# 3. For Products without stocks: set is_active = False. # 3. For Products without stocks: set is_active = False.
inactive_products = Product.objects.filter(stock__isnull=True) inactive_products = Product.objects.filter(stocks__isnull=True)
count_inactive = inactive_products.count() count_inactive = inactive_products.count()
if count_inactive: if count_inactive:
inactive_products.update(is_active=False) inactive_products.update(is_active=False)

View file

@ -1,13 +1,15 @@
import os import os
import re import re
from argparse import ArgumentParser
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from typing import Any
import polib import polib
import requests import requests
from django.apps import apps from django.apps import apps
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from core.management.commands import RootDirectory, DEEPL_TARGET_LANGUAGES_MAPPING, TRANSLATABLE_APPS from core.management.commands import DEEPL_TARGET_LANGUAGES_MAPPING, TRANSLATABLE_APPS, RootDirectory
# Patterns to identify placeholders # Patterns to identify placeholders
PLACEHOLDER_REGEXES = [ PLACEHOLDER_REGEXES = [
@ -23,7 +25,7 @@ def placeholderize(text: str) -> tuple[str, list[str]]:
""" """
placeholders: list[str] = [] placeholders: list[str] = []
def _repl(match: re.Match) -> str: def _repl(match: re.Match) -> str: # type: ignore [type-arg]
idx = len(placeholders) idx = len(placeholders)
placeholders.append(match.group(0)) placeholders.append(match.group(0))
return f"__PH_{idx}__" return f"__PH_{idx}__"
@ -77,7 +79,7 @@ def load_po_sanitized(path: str) -> polib.POFile | None:
class Command(BaseCommand): class Command(BaseCommand):
help = "Merge msgid/msgstr from en_GB PO into target-language POs via DeepL, preserving placeholders." help = "Merge msgid/msgstr from en_GB PO into target-language POs via DeepL, preserving placeholders."
def add_arguments(self, parser): def add_arguments(self, parser: ArgumentParser) -> None:
parser.add_argument( parser.add_argument(
"-l", "-l",
"--language", "--language",
@ -97,10 +99,10 @@ class Command(BaseCommand):
help="App label for translation, e.g. core, payments. Use ALL to translate all apps.", help="App label for translation, e.g. core, payments. Use ALL to translate all apps.",
) )
def handle(self, *args, **options) -> None: def handle(self, *args: list[Any], **options: dict[Any, Any]) -> None:
target_langs = options["target_languages"] target_langs: list[str] = options["target_languages"] # type: ignore [assignment]
if "ALL" in target_langs: if "ALL" in target_langs:
target_langs = DEEPL_TARGET_LANGUAGES_MAPPING.keys() target_langs = DEEPL_TARGET_LANGUAGES_MAPPING.keys() # type: ignore [assignment]
target_apps = set(options["target_apps"]) target_apps = set(options["target_apps"])
if "ALL" in target_apps: if "ALL" in target_apps:
target_apps = { target_apps = {
@ -152,9 +154,9 @@ class Command(BaseCommand):
default = e.msgid default = e.msgid
if readline: if readline:
def hook(): def hook() -> None:
readline.insert_text(default) # noqa: B023 readline.insert_text(default) # type: ignore [attr-defined] # noqa: B023
readline.redisplay() readline.redisplay() # type: ignore [attr-defined]
readline.set_pre_input_hook(hook) # type: ignore [attr-defined] readline.set_pre_input_hook(hook) # type: ignore [attr-defined]

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