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)
class PostTagAdmin(ModelAdmin): # type: ignore [misc, type-arg]
class PostTagAdmin(ModelAdmin): # type: ignore [type-arg]
list_display = ("tag_name", "name")
search_fields = ("tag_name", "name")
ordering = ("tag_name",)

View file

@ -11,6 +11,6 @@ class BlogConfig(AppConfig):
hide = False
# noinspection PyUnresolvedReferences
def ready(self):
def ready(self) -> None:
import blog.elasticsearch.documents
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
class PostDocument(ActiveOnlyMixin, BaseDocument):
class PostDocument(ActiveOnlyMixin, BaseDocument): # type: ignore [misc]
title = fields.TextField(
attr="title",
analyzer="standard",
@ -30,7 +30,7 @@ class PostDocument(ActiveOnlyMixin, BaseDocument):
model = Post
fields = ["uuid"]
def prepare_title(self, instance):
def prepare_title(self, instance: Post) -> str:
return getattr(instance, "title", "") or ""

View file

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

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

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 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <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
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

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 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <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
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

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 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"

View file

@ -1,9 +1,9 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: EVIBES 3.0.0\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"
"Last-Translator: EGOR GORBUNOV <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
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.

View file

@ -1,4 +1,7 @@
from typing import Any
from django import forms
from django.forms.renderers import BaseRenderer
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",)}
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_id = attrs.get("id", f"id_{name}")
init_js = f"""

View file

@ -20,16 +20,16 @@ class NiceModel(Model):
verbose_name=_("is active"),
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"))
modified = ModificationDateTimeField(_("modified"), help_text=_("when the object was last modified"))
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")) # type: ignore [no-untyped-call]
def save(
def save( # type: ignore [override]
self,
*,
force_insert: bool = False,
force_update: bool = False,
using: str | None = None,
update_fields: Collection | None = None,
update_fields: Collection[str] | None = None,
update_modified: bool = True,
) -> None:
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
# noinspection PyUnresolvedReferences
def ready(self):
def ready(self) -> None:
import core.elasticsearch.documents
import core.signals # noqa: F401

View file

@ -113,7 +113,7 @@ def process_query(
request: Request | None = None,
indexes: tuple[str, ...] = ("categories", "brands", "products"),
use_transliteration: bool = True,
) -> dict[str, list[dict]] | None:
) -> dict[str, list[dict[str, Any]]] | None:
if not query:
raise ValueError(_("no search term provided."))
@ -235,7 +235,7 @@ def process_query(
):
hit_cache.append(h)
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 = {}
brands_by_uuid = {}
@ -329,9 +329,9 @@ def _lang_analyzer(lang_code: str) -> str:
class ActiveOnlyMixin:
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)
@ -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:
lc = code.replace("-", "_").lower()
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))
def populate_index():
def populate_index() -> None:
for doc in registry.get_documents(set(registry.get_models())):
qs = doc().get_indexing_queryset()
doc().update(qs, parallel=True, refresh=True)
return None
def process_system_query(
@ -493,7 +494,7 @@ def process_system_query(
size_per_index: int = 25,
language_code: str | None = None,
use_transliteration: bool = True,
) -> dict[str, list[dict]]:
) -> dict[str, list[dict[str, Any]]]:
if not query:
raise ValueError(_("no search term provided."))
@ -526,7 +527,7 @@ def process_system_query(
**({"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:
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.registries import registry
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
class BaseDocument(Document):
class BaseDocument(Document): # type: ignore [misc]
name = fields.TextField(
attr="name",
analyzer="standard",
@ -39,10 +42,10 @@ class BaseDocument(Document):
"index": {"max_ngram_diff": 20},
}
def prepare_name(self, instance):
def prepare_name(self, instance: Model) -> str:
return getattr(instance, "name", "") or ""
def prepare_description(self, instance):
def prepare_description(self, instance: Model) -> str:
return getattr(instance, "description", "") or ""
@ -103,7 +106,7 @@ class ProductDocument(ActiveOnlyMixin, BaseDocument):
},
)
def get_queryset(self):
def get_queryset(self) -> QuerySet[Product]:
return (
super()
.get_queryset()
@ -156,7 +159,7 @@ add_multilang_fields(BrandDocument)
registry.register_document(BrandDocument)
class TestModelDocument(Document):
class TestModelDocument(Document): # type: ignore [misc]
class Index:
name = "testmodels"
@ -164,7 +167,7 @@ class TestModelDocument(Document):
model = TestModel
fields = ["title"]
ignore_signals = True
related_models: list = []
related_models: list[Any] = []
auto_refresh = False

View file

@ -1,6 +1,7 @@
import json
import logging
import uuid
from typing import Any
from django.core.exceptions import BadRequest
from django.db.models import (
@ -19,6 +20,7 @@ from django.db.models import (
When,
)
from django.db.models.functions import Coalesce
from django.http import HttpRequest
from django.utils.http import urlsafe_base64_decode
from django.utils.translation import gettext_lazy as _
from django_filters import (
@ -31,6 +33,8 @@ from django_filters import (
OrderingFilter,
UUIDFilter,
)
from graphene import Context
from rest_framework.request import Request
from core.elasticsearch import process_query
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")
class CaseInsensitiveListFilter(BaseInFilter, CharFilter):
def filter(self, qs, value):
class CaseInsensitiveListFilter(BaseInFilter, CharFilter): # type: ignore [misc]
def filter(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]:
if not value:
return qs
@ -61,7 +65,7 @@ class CaseInsensitiveListFilter(BaseInFilter, CharFilter):
# noinspection PyUnusedLocal
class ProductFilter(FilterSet):
class ProductFilter(FilterSet): # type: ignore [misc]
search = CharFilter(field_name="name", method="search_products", label=_("Search"))
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID"))
name = CharFilter(lookup_expr="icontains", label=_("Name"))
@ -121,7 +125,15 @@ class ProductFilter(FilterSet):
"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)
ordering_param = self.data.get("order_by", "")
if ordering_param:
@ -133,7 +145,7 @@ class ProductFilter(FilterSet):
.annotate(avg_rating=Avg("rating"))
.values("avg_rating")
)
self.queryset = self.queryset.annotate(
self.queryset: QuerySet[Product] = self.queryset.annotate(
rating=Coalesce(
Subquery(feedback_qs, 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:
return queryset
@ -156,17 +168,17 @@ class ProductFilter(FilterSet):
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"):
raise BadRequest(_("there must be a category_uuid to use include_subcategories flag"))
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):
queryset = queryset.filter(stocks__isnull=False, stocks__quantity__gt=0, stocks__price__gt=0)
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:
return queryset
@ -228,7 +240,7 @@ class ProductFilter(FilterSet):
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:
return queryset
@ -247,7 +259,7 @@ class ProductFilter(FilterSet):
return queryset.filter(category__uuid=value)
@staticmethod
def _infer_type(value):
def _infer_type(value: str) -> Any:
try:
parsed_value = json.loads(value)
if isinstance(parsed_value, list | dict):
@ -271,7 +283,7 @@ class ProductFilter(FilterSet):
return value
@property
def qs(self):
def qs(self) -> QuerySet[Product]:
qs = super().qs
ordering_param = self.data.get("order_by", "")
@ -320,7 +332,8 @@ class ProductFilter(FilterSet):
return qs.distinct()
class OrderFilter(FilterSet):
# noinspection PyUnusedLocal
class OrderFilter(FilterSet): # type: ignore [misc]
search = CharFilter(
method="filter_search",
label=_("Search (ID, product name or part number)"),
@ -367,7 +380,7 @@ class OrderFilter(FilterSet):
"max_buy_time",
]
def filter_search(self, queryset, _name, value):
def filter_search(self, queryset: QuerySet[Order], name: str, value: str):
return queryset.filter(
Q(human_readable_id__icontains=value)
| Q(order_products__product__name__icontains=value)
@ -375,7 +388,7 @@ class OrderFilter(FilterSet):
).distinct()
class WishlistFilter(FilterSet):
class WishlistFilter(FilterSet): # type: ignore [misc]
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
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"))
@ -395,7 +408,7 @@ class WishlistFilter(FilterSet):
# noinspection PyUnusedLocal
class CategoryFilter(FilterSet):
class CategoryFilter(FilterSet): # type: ignore [misc]
search = CharFilter(field_name="name", method="search_categories", label=_("Search"))
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
name = CharFilter(lookup_expr="icontains", label=_("Name"))
@ -424,7 +437,7 @@ class CategoryFilter(FilterSet):
"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:
return queryset
@ -432,7 +445,7 @@ class CategoryFilter(FilterSet):
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:
return queryset
@ -456,7 +469,7 @@ class CategoryFilter(FilterSet):
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 == "?":
def build_random_prefetch(depth):
@ -494,7 +507,7 @@ class CategoryFilter(FilterSet):
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_desc_products = Exists(
Product.objects.filter(
@ -509,7 +522,7 @@ class CategoryFilter(FilterSet):
return annotated.filter(has_products=True).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"):
return queryset.filter(parent=None)
@ -522,7 +535,7 @@ class CategoryFilter(FilterSet):
# noinspection PyUnusedLocal
class BrandFilter(FilterSet):
class BrandFilter(FilterSet): # type: ignore [misc]
search = CharFilter(field_name="name", method="search_brands", label=_("Search"))
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
name = CharFilter(lookup_expr="icontains", label=_("Name"))
@ -543,7 +556,7 @@ class BrandFilter(FilterSet):
model = Brand
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:
return queryset
@ -552,7 +565,7 @@ class BrandFilter(FilterSet):
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"))
product_uuid = UUIDFilter(
field_name="order_product__product__uuid",
@ -581,7 +594,7 @@ class FeedbackFilter(FilterSet):
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"))
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"))

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 Meta:
model = OrderProduct

View file

@ -1,10 +1,12 @@
from typing import Any
from graphene import Mutation
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)
@staticmethod
def mutate(**kwargs) -> None:
def mutate(**kwargs: Any) -> None:
pass

View file

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

View file

@ -3,7 +3,7 @@ from typing import Any
from constance import config
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.utils.translation import gettext_lazy as _
from graphene import (
@ -60,7 +60,7 @@ from payments.graphene.object_types import TransactionType
logger = logging.getLogger("django")
class SEOMetaType(ObjectType):
class SEOMetaType(ObjectType): # type: ignore [misc]
title = String()
description = String()
canonical = String()
@ -71,7 +71,7 @@ class SEOMetaType(ObjectType):
hreflang = String()
class AttributeType(DjangoObjectType):
class AttributeType(DjangoObjectType): # type: ignore [misc]
values = List(lambda: AttributeValueType, description=_("attribute values"))
class Meta:
@ -81,7 +81,7 @@ class AttributeType(DjangoObjectType):
filter_fields = ["uuid"]
description = _("attributes")
def resolve_values(self, info):
def resolve_values(self, info) -> QuerySet[AttributeValue]:
base_qs = AttributeValue.objects.filter(attribute=self)
product_uuid = getattr(info.context, "_product_uuid", None)
@ -91,7 +91,7 @@ class AttributeType(DjangoObjectType):
return base_qs
class AttributeGroupType(DjangoObjectType):
class AttributeGroupType(DjangoObjectType): # type: ignore [misc]
attributes = List(lambda: AttributeType, description=_("grouped attributes"))
class Meta:
@ -101,7 +101,7 @@ class AttributeGroupType(DjangoObjectType):
filter_fields = ["uuid"]
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)
qs = self.attributes.all()
@ -112,7 +112,7 @@ class AttributeGroupType(DjangoObjectType):
return qs
class BrandType(DjangoObjectType):
class BrandType(DjangoObjectType): # type: ignore [misc]
categories = List(lambda: CategoryType, description=_("categories"))
seo_meta = Field(SEOMetaType, description=_("SEO Meta snapshot"))
@ -123,18 +123,18 @@ class BrandType(DjangoObjectType):
filter_fields = ["uuid", "name"]
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"):
return self.categories.all()
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 ""
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 ""
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()
base = f"https://{config.BASE_DOMAIN}"
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)
possible_values = List(String, required=True)
class MinMaxPriceType(ObjectType):
class MinMaxPriceType(ObjectType): # type: ignore [misc]
min_price = Float()
max_price = Float()
class CategoryType(DjangoObjectType):
class CategoryType(DjangoObjectType): # type: ignore [misc]
children = List(
lambda: CategoryType,
description=_("categories"),
@ -340,7 +340,7 @@ class CategoryType(DjangoObjectType):
}
class VendorType(DjangoObjectType):
class VendorType(DjangoObjectType): # type: ignore [misc]
markup_percent = Float(description=_("markup percentage"))
class Meta:
@ -351,7 +351,7 @@ class VendorType(DjangoObjectType):
description = _("vendors")
class AddressType(DjangoObjectType):
class AddressType(DjangoObjectType): # type: ignore [misc]
latitude = Float(description=_("Latitude (Y coordinate)"))
longitude = Float(description=_("Longitude (X coordinate)"))
@ -381,7 +381,7 @@ class AddressType(DjangoObjectType):
return self.location.y if self.location else None
class FeedbackType(DjangoObjectType):
class FeedbackType(DjangoObjectType): # type: ignore [misc]
comment = String(description=_("comment"))
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.")
class OrderProductType(DjangoObjectType):
class OrderProductType(DjangoObjectType): # type: ignore [misc]
attributes = GenericScalar(description=_("attributes"))
notifications = GenericScalar(description=_("notifications"))
download_url = String(description=_("download url for this order product if applicable"))
@ -429,7 +429,7 @@ class OrderProductType(DjangoObjectType):
return self.download_url
class OrderType(DjangoObjectType):
class OrderType(DjangoObjectType): # type: ignore [misc]
order_products = DjangoFilterConnectionField(
OrderProductType, description=_("a list of order products in this order")
)
@ -482,7 +482,7 @@ class OrderType(DjangoObjectType):
return None
class ProductImageType(DjangoObjectType):
class ProductImageType(DjangoObjectType): # type: ignore [misc]
image = String(description=_("image url"))
class Meta:
@ -496,7 +496,7 @@ class ProductImageType(DjangoObjectType):
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"))
images = DjangoFilterConnectionField(ProductImageType, description=_("images"))
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"))
class Meta:
@ -616,7 +616,7 @@ class AttributeValueType(DjangoObjectType):
description = _("attribute value")
class PromoCodeType(DjangoObjectType):
class PromoCodeType(DjangoObjectType): # type: ignore [misc]
discount = Float()
discount_type = String()
@ -640,7 +640,7 @@ class PromoCodeType(DjangoObjectType):
return "percent" if self.discount_percent else "amount"
class PromotionType(DjangoObjectType):
class PromotionType(DjangoObjectType): # type: ignore [misc]
products = DjangoFilterConnectionField(ProductType, description=_("products on sale"))
class Meta:
@ -651,7 +651,7 @@ class PromotionType(DjangoObjectType):
description = _("promotions")
class StockType(DjangoObjectType):
class StockType(DjangoObjectType): # type: ignore [misc]
vendor = Field(VendorType, description=_("vendor"))
product = Field(ProductType, description=_("product"))
@ -663,7 +663,7 @@ class StockType(DjangoObjectType):
description = _("stocks")
class WishlistType(DjangoObjectType):
class WishlistType(DjangoObjectType): # type: ignore [misc]
products = DjangoFilterConnectionField(ProductType, description=_("wishlisted products"))
class Meta:
@ -673,7 +673,7 @@ class WishlistType(DjangoObjectType):
description = _("wishlists")
class ProductTagType(DjangoObjectType):
class ProductTagType(DjangoObjectType): # type: ignore [misc]
product_set = DjangoFilterConnectionField(ProductType, description=_("tagged products"))
class Meta:
@ -684,7 +684,7 @@ class ProductTagType(DjangoObjectType):
description = _("product tags")
class CategoryTagType(DjangoObjectType):
class CategoryTagType(DjangoObjectType): # type: ignore [misc]
category_set = DjangoFilterConnectionField(CategoryType, description=_("tagged categories"))
class Meta:
@ -695,7 +695,7 @@ class CategoryTagType(DjangoObjectType):
description = _("categories tags")
class ConfigType(ObjectType):
class ConfigType(ObjectType): # type: ignore [misc]
project_name = String(description=_("project name"))
base_domain = String(description=_("company email"))
company_name = String(description=_("company name"))
@ -712,7 +712,7 @@ class ConfigType(ObjectType):
description = _("company configuration")
class LanguageType(ObjectType):
class LanguageType(ObjectType): # type: ignore [misc]
code = String(description=_("language code"))
name = String(description=_("language name"))
flag = String(description=_("language flag, if exists :)"))
@ -721,40 +721,40 @@ class LanguageType(ObjectType):
description = _("supported languages")
class SearchProductsResultsType(ObjectType):
class SearchProductsResultsType(ObjectType): # type: ignore [misc]
uuid = UUID()
name = String()
slug = String()
image = String()
class SearchCategoriesResultsType(ObjectType):
class SearchCategoriesResultsType(ObjectType): # type: ignore [misc]
uuid = UUID()
name = String()
slug = String()
image = String()
class SearchBrandsResultsType(ObjectType):
class SearchBrandsResultsType(ObjectType): # type: ignore [misc]
uuid = UUID()
name = String()
slug = String()
image = String()
class SearchPostsResultsType(ObjectType):
class SearchPostsResultsType(ObjectType): # type: ignore [misc]
uuid = UUID()
name = String()
slug = String()
class SearchResultsType(ObjectType):
class SearchResultsType(ObjectType): # type: ignore [misc]
products = List(description=_("products search results"), of_type=SearchProductsResultsType)
categories = List(description=_("products search results"), of_type=SearchCategoriesResultsType)
brands = List(description=_("products search results"), of_type=SearchBrandsResultsType)
posts = List(description=_("posts search results"), of_type=SearchPostsResultsType)
class BulkProductInput(InputObjectType):
class BulkProductInput(InputObjectType): # type: ignore [misc]
uuid = UUID(required=True)
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 threading
import time
from typing import Any
import redis
from django.core.management.base import BaseCommand
@ -11,10 +12,10 @@ from redis.exceptions import ConnectionError # noqa: A004
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...")
def wait_for_db():
def wait_for_db() -> None:
db_up = False
while not db_up:
try:
@ -31,7 +32,7 @@ class Command(BaseCommand):
time.sleep(1)
self.stdout.write(self.style.SUCCESS("Database available!"))
def wait_for_redis():
def wait_for_redis() -> None:
redis_up = False
while not redis_up:
try:

View file

@ -1,7 +1,9 @@
import contextlib
import os
import re
from argparse import ArgumentParser
from tempfile import NamedTemporaryFile
from typing import Any
import polib
from django.apps import apps
@ -64,7 +66,7 @@ def load_po_sanitized(path: str) -> polib.POFile:
class Command(BaseCommand):
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(
"-l",
"--language",
@ -92,14 +94,14 @@ class Command(BaseCommand):
help="Root path prefix to adjust file links",
)
def handle(self, *args, **options) -> None:
langs: list[str] = options["target_languages"]
def handle(self, *args: list[Any], **options: dict[str, str | list[str]]) -> None:
langs: list[str] = options.get("target_languages", []) # type: ignore [assignment]
if "ALL" in langs:
langs = list(dict(settings.LANGUAGES).keys())
apps_to_scan: set[str] = set(options["target_apps"])
if "ALL" in apps_to_scan:
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())
# noinspection PyTypeChecker

View file

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

View file

@ -1,13 +1,15 @@
import os
import re
from argparse import ArgumentParser
from tempfile import NamedTemporaryFile
from typing import Any
import polib
import requests
from django.apps import apps
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
PLACEHOLDER_REGEXES = [
@ -23,7 +25,7 @@ def placeholderize(text: str) -> tuple[str, list[str]]:
"""
placeholders: list[str] = []
def _repl(match: re.Match) -> str:
def _repl(match: re.Match) -> str: # type: ignore [type-arg]
idx = len(placeholders)
placeholders.append(match.group(0))
return f"__PH_{idx}__"
@ -77,7 +79,7 @@ def load_po_sanitized(path: str) -> polib.POFile | None:
class Command(BaseCommand):
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(
"-l",
"--language",
@ -97,10 +99,10 @@ class Command(BaseCommand):
help="App label for translation, e.g. core, payments. Use ALL to translate all apps.",
)
def handle(self, *args, **options) -> None:
target_langs = options["target_languages"]
def handle(self, *args: list[Any], **options: dict[Any, Any]) -> None:
target_langs: list[str] = options["target_languages"] # type: ignore [assignment]
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"])
if "ALL" in target_apps:
target_apps = {
@ -152,9 +154,9 @@ class Command(BaseCommand):
default = e.msgid
if readline:
def hook():
readline.insert_text(default) # noqa: B023
readline.redisplay()
def hook() -> None:
readline.insert_text(default) # type: ignore [attr-defined] # noqa: B023
readline.redisplay() # 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