Features: 1) Improved request processing in middleware by adding mutable QueryDict implementation; 2) Extended type annotations across various modules for enhanced type safety; 3) Refined JWT token lifetime configuration for environment-specific logic.
Fixes: 1) Addressed missing or incorrect imports and type hints with `# ty:ignore` markers; 2) Fixed search queryset error handling in filters module; 3) Resolved issues in viewsets with updated `@action` method usage. Extra: Removed unused classes and dependencies (e.g., `BaseMutation`, `basedpyright`, and related packages); streamlined GraphQL mutation implementations; cleaned up unused arguments in model `save` methods.
This commit is contained in:
parent
c3b4becc76
commit
13e7af52aa
26 changed files with 172 additions and 411 deletions
|
|
@ -23,19 +23,17 @@ lint:
|
||||||
- "**/*.py"
|
- "**/*.py"
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
- ".pre-commit-config.yaml"
|
- ".pre-commit-config.yaml"
|
||||||
- "pyrightconfig.json"
|
|
||||||
when: on_success
|
when: on_success
|
||||||
- when: never
|
- when: never
|
||||||
|
|
||||||
typecheck:
|
typecheck:
|
||||||
stage: typecheck
|
stage: typecheck
|
||||||
script:
|
script:
|
||||||
- uv run pyright
|
- uv run ty
|
||||||
rules:
|
rules:
|
||||||
- changes:
|
- changes:
|
||||||
- "**/*.py"
|
- "**/*.py"
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
- "pyrightconfig.json"
|
|
||||||
when: on_success
|
when: on_success
|
||||||
- when: never
|
- when: never
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ class Post(NiceModel):
|
||||||
verbose_name = _("post")
|
verbose_name = _("post")
|
||||||
verbose_name_plural = _("posts")
|
verbose_name_plural = _("posts")
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if self.file:
|
if self.file:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
_("markdown files are not supported yet - use markdown content instead")
|
_("markdown files are not supported yet - use markdown content instead")
|
||||||
|
|
@ -110,7 +110,7 @@ class Post(NiceModel):
|
||||||
"a markdown file or markdown content must be provided - mutually exclusive"
|
"a markdown file or markdown content must be provided - mutually exclusive"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
super().save(**kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class PostTag(NiceModel):
|
class PostTag(NiceModel):
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,13 @@ class NiceModel(Model):
|
||||||
|
|
||||||
def save(
|
def save(
|
||||||
self,
|
self,
|
||||||
*,
|
*args,
|
||||||
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[str] | None = None,
|
update_fields: Collection[str] | None = None,
|
||||||
update_modified: bool = True,
|
update_modified: bool = True,
|
||||||
) -> None:
|
) -> None: # ty:ignore[invalid-method-override]
|
||||||
self.update_modified = update_modified
|
self.update_modified = update_modified
|
||||||
return super().save(
|
return super().save(
|
||||||
force_insert=force_insert,
|
force_insert=force_insert,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import Any, ClassVar, Type
|
from typing import Any, Callable, ClassVar, Type
|
||||||
|
|
||||||
from constance.admin import Config
|
from constance.admin import Config
|
||||||
from constance.admin import ConstanceAdmin as BaseConstanceAdmin
|
from constance.admin import ConstanceAdmin as BaseConstanceAdmin
|
||||||
|
|
@ -145,6 +145,7 @@ class FieldsetsMixin:
|
||||||
|
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
class ActivationActionsMixin:
|
class ActivationActionsMixin:
|
||||||
|
message_user: Callable
|
||||||
actions_on_top = True
|
actions_on_top = True
|
||||||
actions_on_bottom = True
|
actions_on_bottom = True
|
||||||
actions = [
|
actions = [
|
||||||
|
|
@ -1086,10 +1087,8 @@ class ConstanceConfig:
|
||||||
_meta = Meta()
|
_meta = Meta()
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
site.unregister([Config]) # ty:ignore[invalid-argument-type]
|
||||||
site.unregister([Config])
|
site.register([ConstanceConfig], BaseConstanceAdmin) # ty:ignore[invalid-argument-type]
|
||||||
# noinspection PyTypeChecker
|
|
||||||
site.register([ConstanceConfig], BaseConstanceAdmin)
|
|
||||||
site.site_title = settings.PROJECT_NAME
|
site.site_title = settings.PROJECT_NAME
|
||||||
site.site_header = "eVibes"
|
site.site_header = "eVibes"
|
||||||
site.index_title = settings.PROJECT_NAME
|
site.index_title = settings.PROJECT_NAME
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from django_elasticsearch_dsl import fields
|
||||||
from django_elasticsearch_dsl.registries import registry
|
from django_elasticsearch_dsl.registries import registry
|
||||||
from elasticsearch import NotFoundError
|
from elasticsearch import NotFoundError
|
||||||
from elasticsearch.dsl import Q, Search
|
from elasticsearch.dsl import Q, Search
|
||||||
|
from elasticsearch.dsl.types import Hit
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
|
||||||
from engine.core.models import Brand, Category, Product
|
from engine.core.models import Brand, Category, Product
|
||||||
|
|
@ -199,7 +200,7 @@ def process_query(
|
||||||
minimum_should_match=1,
|
minimum_should_match=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
def build_search(idxs: list[str], size: int) -> Search:
|
def build_search(idxs: list[str], size: int) -> Search[Hit]:
|
||||||
return (
|
return (
|
||||||
Search(index=idxs)
|
Search(index=idxs)
|
||||||
.query(query_base)
|
.query(query_base)
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ class ProductFilter(FilterSet):
|
||||||
data: dict[Any, Any] | None = None,
|
data: dict[Any, Any] | None = None,
|
||||||
queryset: QuerySet[Product] | None = None,
|
queryset: QuerySet[Product] | None = None,
|
||||||
*,
|
*,
|
||||||
request: HttpRequest | Request | Context = None,
|
request: HttpRequest | Request | Context | None = None,
|
||||||
prefix: str | None = None,
|
prefix: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(data=data, queryset=queryset, request=request, prefix=prefix)
|
super().__init__(data=data, queryset=queryset, request=request, prefix=prefix)
|
||||||
|
|
@ -516,12 +516,12 @@ class CategoryFilter(FilterSet):
|
||||||
if not value:
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
uuids = [
|
s_result = process_query(query=value, indexes=("categories",))
|
||||||
category.get("uuid")
|
|
||||||
for category in process_query(query=value, indexes=("categories",))[
|
if not s_result:
|
||||||
"categories"
|
raise ValueError("Search is unprocessable")
|
||||||
]
|
|
||||||
]
|
uuids = [category.get("uuid") for category in s_result.get("categories", [])]
|
||||||
|
|
||||||
return queryset.filter(uuid__in=uuids)
|
return queryset.filter(uuid__in=uuids)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from graphene import Mutation
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMutation(Mutation):
|
|
||||||
def __init__(self, *args: list[Any], **kwargs: dict[Any, Any]) -> None:
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def mutate(**kwargs: Any) -> None:
|
|
||||||
pass
|
|
||||||
|
|
@ -1,179 +0,0 @@
|
||||||
from contextlib import suppress
|
|
||||||
|
|
||||||
from django.core.exceptions import PermissionDenied
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from graphene import UUID, Boolean, Field, InputObjectType, List, NonNull, String
|
|
||||||
|
|
||||||
from engine.core.graphene import BaseMutation
|
|
||||||
from engine.core.graphene.object_types import ProductType
|
|
||||||
from engine.core.models import (
|
|
||||||
Attribute,
|
|
||||||
AttributeGroup,
|
|
||||||
AttributeValue,
|
|
||||||
Brand,
|
|
||||||
Category,
|
|
||||||
Product,
|
|
||||||
ProductTag,
|
|
||||||
)
|
|
||||||
from engine.core.utils.messages import permission_denied_message
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_attributes(product, attributes):
|
|
||||||
for attr_input in attributes:
|
|
||||||
attribute = None
|
|
||||||
attr_uuid = attr_input.get("attribute_uuid")
|
|
||||||
if attr_uuid:
|
|
||||||
with suppress(Attribute.DoesNotExist):
|
|
||||||
attribute = Attribute.objects.get(uuid=attr_uuid)
|
|
||||||
if attribute is None:
|
|
||||||
group_name = attr_input.get("group_name")
|
|
||||||
attribute_name = attr_input.get("attribute_name")
|
|
||||||
value_type = attr_input.get("value_type") or "string"
|
|
||||||
if group_name and attribute_name:
|
|
||||||
group, _ = AttributeGroup.objects.get_or_create(name=group_name)
|
|
||||||
attribute, _ = Attribute.objects.get_or_create(
|
|
||||||
group=group,
|
|
||||||
name=attribute_name,
|
|
||||||
defaults={"value_type": value_type},
|
|
||||||
)
|
|
||||||
if attribute.value_type != value_type:
|
|
||||||
attribute.value_type = value_type
|
|
||||||
attribute.save(update_fields=["value_type"])
|
|
||||||
if attribute is not None:
|
|
||||||
AttributeValue.objects.update_or_create(
|
|
||||||
product=product,
|
|
||||||
attribute=attribute,
|
|
||||||
defaults={"value": str(attr_input.get("value", ""))},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_tags(product, tag_uuids):
|
|
||||||
tags = list(ProductTag.objects.filter(uuid__in=tag_uuids))
|
|
||||||
if tags:
|
|
||||||
product.tags.set(tags)
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeInput(InputObjectType):
|
|
||||||
attribute_uuid = UUID(required=False, name="attributeUuid")
|
|
||||||
group_name = String(required=False, name="groupName")
|
|
||||||
attribute_name = String(required=False, name="attributeName")
|
|
||||||
value_type = String(required=False, name="valueType")
|
|
||||||
value = String(required=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ProductInput(InputObjectType):
|
|
||||||
name = NonNull(String)
|
|
||||||
description = String(required=False)
|
|
||||||
is_digital = Boolean(required=False, name="isDigital")
|
|
||||||
partnumber = String(required=False)
|
|
||||||
sku = String(required=False)
|
|
||||||
|
|
||||||
category_uuid = NonNull(UUID, name="categoryUuid")
|
|
||||||
brand_uuid = UUID(required=False, name="brandUuid")
|
|
||||||
tag_uuids = List(UUID, required=False, name="tagUuids")
|
|
||||||
attributes = List(NonNull(AttributeInput), required=False)
|
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
|
||||||
class CreateProduct(BaseMutation):
|
|
||||||
class Meta:
|
|
||||||
description = _("create a product")
|
|
||||||
|
|
||||||
class Arguments:
|
|
||||||
product_data = NonNull(ProductInput, name="productData")
|
|
||||||
|
|
||||||
product = Field(ProductType)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def mutate(parent, info, product_data):
|
|
||||||
user = info.context.user
|
|
||||||
if not user.has_perm("core.add_product"):
|
|
||||||
raise PermissionDenied(permission_denied_message)
|
|
||||||
category = Category.objects.get(uuid=product_data["category_uuid"])
|
|
||||||
brand = None
|
|
||||||
if product_data.get("brand_uuid"):
|
|
||||||
with suppress(Brand.DoesNotExist):
|
|
||||||
brand = Brand.objects.get(uuid=product_data["brand_uuid"])
|
|
||||||
|
|
||||||
product = Product.objects.create(
|
|
||||||
name=product_data["name"],
|
|
||||||
description=product_data.get("description"),
|
|
||||||
is_digital=product_data.get("is_digital") or False,
|
|
||||||
partnumber=product_data.get("partnumber"),
|
|
||||||
sku=product_data.get("sku") or None,
|
|
||||||
category=category,
|
|
||||||
brand=brand,
|
|
||||||
)
|
|
||||||
|
|
||||||
resolve_tags(product, product_data.get("tag_uuids", []))
|
|
||||||
|
|
||||||
resolve_attributes(product, product_data.get("attributes", []))
|
|
||||||
|
|
||||||
return CreateProduct(product=product)
|
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
|
||||||
class UpdateProduct(BaseMutation):
|
|
||||||
class Meta:
|
|
||||||
description = _("create a product")
|
|
||||||
|
|
||||||
class Arguments:
|
|
||||||
product_uuid = UUID(required=True)
|
|
||||||
product_data = NonNull(ProductInput, name="productData")
|
|
||||||
|
|
||||||
product = Field(ProductType)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def mutate(parent, info, product_uuid, product_data):
|
|
||||||
user = info.context.user
|
|
||||||
if not user.has_perm("core.change_product"):
|
|
||||||
raise PermissionDenied(permission_denied_message)
|
|
||||||
product = Product.objects.get(uuid=product_uuid)
|
|
||||||
|
|
||||||
updates = {}
|
|
||||||
for field_in, model_field in (
|
|
||||||
("name", "name"),
|
|
||||||
("description", "description"),
|
|
||||||
("is_digital", "is_digital"),
|
|
||||||
("partnumber", "partnumber"),
|
|
||||||
("sku", "sku"),
|
|
||||||
):
|
|
||||||
if field_in in product_data and product_data[field_in] is not None:
|
|
||||||
updates[model_field] = product_data[field_in]
|
|
||||||
|
|
||||||
if product_data.get("category_uuid"):
|
|
||||||
product.category = Category.objects.get(uuid=product_data["category_uuid"])
|
|
||||||
if product_data.get("brand_uuid") is not None:
|
|
||||||
if product_data.get("brand_uuid"):
|
|
||||||
product.brand = Brand.objects.get(uuid=product_data["brand_uuid"])
|
|
||||||
else:
|
|
||||||
product.brand = None
|
|
||||||
|
|
||||||
for k, v in updates.items():
|
|
||||||
setattr(product, k, v)
|
|
||||||
product.save()
|
|
||||||
|
|
||||||
resolve_tags(product, product_data.get("tag_uuids", []))
|
|
||||||
|
|
||||||
resolve_attributes(product, product_data.get("attributes"))
|
|
||||||
|
|
||||||
return UpdateProduct(product=product)
|
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
|
||||||
class DeleteProduct(BaseMutation):
|
|
||||||
class Meta:
|
|
||||||
description = _("create a product")
|
|
||||||
|
|
||||||
class Arguments:
|
|
||||||
product_uuid = UUID(required=True)
|
|
||||||
|
|
||||||
ok = Boolean()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def mutate(parent, info, product_uuid):
|
|
||||||
user = info.context.user
|
|
||||||
if not user.has_perm("core.delete_product"):
|
|
||||||
raise PermissionDenied(permission_denied_message)
|
|
||||||
Product.objects.get(uuid=product_uuid).delete()
|
|
||||||
return DeleteProduct(ok=True)
|
|
||||||
|
|
@ -6,11 +6,10 @@ from django.core.cache import cache
|
||||||
from django.core.exceptions import BadRequest, PermissionDenied
|
from django.core.exceptions import BadRequest, PermissionDenied
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from graphene import UUID, Boolean, Field, Int, List, String
|
from graphene import UUID, Boolean, Field, Int, List, Mutation, String
|
||||||
from graphene.types.generic import GenericScalar
|
from graphene.types.generic import GenericScalar
|
||||||
|
|
||||||
from engine.core.elasticsearch import process_query
|
from engine.core.elasticsearch import process_query
|
||||||
from engine.core.graphene import BaseMutation
|
|
||||||
from engine.core.graphene.object_types import (
|
from engine.core.graphene.object_types import (
|
||||||
AddressType,
|
AddressType,
|
||||||
BulkProductInput,
|
BulkProductInput,
|
||||||
|
|
@ -32,7 +31,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
class CacheOperator(BaseMutation):
|
class CacheOperator(Mutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
description = _("cache I/O")
|
description = _("cache I/O")
|
||||||
|
|
||||||
|
|
@ -54,7 +53,7 @@ class CacheOperator(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
class RequestCursedURL(BaseMutation):
|
class RequestCursedURL(Mutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
description = _("request a CORSed URL")
|
description = _("request a CORSed URL")
|
||||||
|
|
||||||
|
|
@ -82,7 +81,7 @@ class RequestCursedURL(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
# noinspection PyUnusedLocal,PyTypeChecker
|
||||||
class AddOrderProduct(BaseMutation):
|
class AddOrderProduct(Mutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
description = _("add a product to the order")
|
description = _("add a product to the order")
|
||||||
|
|
||||||
|
|
@ -111,7 +110,7 @@ class AddOrderProduct(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
# noinspection PyUnusedLocal,PyTypeChecker
|
||||||
class RemoveOrderProduct(BaseMutation):
|
class RemoveOrderProduct(Mutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
description = _("remove a product from the order")
|
description = _("remove a product from the order")
|
||||||
|
|
||||||
|
|
@ -140,7 +139,7 @@ class RemoveOrderProduct(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
# noinspection PyUnusedLocal,PyTypeChecker
|
||||||
class RemoveAllOrderProducts(BaseMutation):
|
class RemoveAllOrderProducts(Mutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
description = _("remove all products from the order")
|
description = _("remove all products from the order")
|
||||||
|
|
||||||
|
|
@ -162,7 +161,7 @@ class RemoveAllOrderProducts(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
# noinspection PyUnusedLocal,PyTypeChecker
|
||||||
class RemoveOrderProductsOfAKind(BaseMutation):
|
class RemoveOrderProductsOfAKind(Mutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
description = _("remove a product from the order")
|
description = _("remove a product from the order")
|
||||||
|
|
||||||
|
|
@ -185,7 +184,7 @@ class RemoveOrderProductsOfAKind(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
# noinspection PyUnusedLocal,PyTypeChecker
|
||||||
class BuyOrder(BaseMutation):
|
class BuyOrder(Mutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
description = _("buy an order")
|
description = _("buy an order")
|
||||||
|
|
||||||
|
|
@ -256,7 +255,7 @@ class BuyOrder(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
# noinspection PyUnusedLocal,PyTypeChecker
|
||||||
class BulkOrderAction(BaseMutation):
|
class BulkOrderAction(Mutation):
|
||||||
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")
|
||||||
|
|
||||||
|
|
@ -308,7 +307,7 @@ class BulkOrderAction(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
# noinspection PyUnusedLocal,PyTypeChecker
|
||||||
class BulkWishlistAction(BaseMutation):
|
class BulkWishlistAction(Mutation):
|
||||||
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")
|
||||||
|
|
||||||
|
|
@ -349,7 +348,7 @@ class BulkWishlistAction(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
class BuyUnregisteredOrder(BaseMutation):
|
class BuyUnregisteredOrder(Mutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
description = _("purchase an order without account creation")
|
description = _("purchase an order without account creation")
|
||||||
|
|
||||||
|
|
@ -397,7 +396,7 @@ class BuyUnregisteredOrder(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
# noinspection PyUnusedLocal,PyTypeChecker
|
||||||
class AddWishlistProduct(BaseMutation):
|
class AddWishlistProduct(Mutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
description = _("add a product to the wishlist")
|
description = _("add a product to the wishlist")
|
||||||
|
|
||||||
|
|
@ -425,7 +424,7 @@ class AddWishlistProduct(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
# noinspection PyUnusedLocal,PyTypeChecker
|
||||||
class RemoveWishlistProduct(BaseMutation):
|
class RemoveWishlistProduct(Mutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
description = _("remove a product from the wishlist")
|
description = _("remove a product from the wishlist")
|
||||||
|
|
||||||
|
|
@ -453,7 +452,7 @@ class RemoveWishlistProduct(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
# noinspection PyUnusedLocal,PyTypeChecker
|
||||||
class RemoveAllWishlistProducts(BaseMutation):
|
class RemoveAllWishlistProducts(Mutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
description = _("remove all products from the wishlist")
|
description = _("remove all products from the wishlist")
|
||||||
|
|
||||||
|
|
@ -481,7 +480,7 @@ class RemoveAllWishlistProducts(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
# noinspection PyUnusedLocal,PyTypeChecker
|
||||||
class BuyWishlist(BaseMutation):
|
class BuyWishlist(Mutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
description = _("buy all products from the wishlist")
|
description = _("buy all products from the wishlist")
|
||||||
|
|
||||||
|
|
@ -531,7 +530,7 @@ class BuyWishlist(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
# noinspection PyUnusedLocal,PyTypeChecker
|
||||||
class BuyProduct(BaseMutation):
|
class BuyProduct(Mutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
description = _("buy a product")
|
description = _("buy a product")
|
||||||
|
|
||||||
|
|
@ -576,7 +575,7 @@ class BuyProduct(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
# noinspection PyUnusedLocal,PyTypeChecker
|
||||||
class FeedbackProductAction(BaseMutation):
|
class FeedbackProductAction(Mutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
description = _("add or delete a feedback for orderproduct")
|
description = _("add or delete a feedback for orderproduct")
|
||||||
|
|
||||||
|
|
@ -611,7 +610,7 @@ class FeedbackProductAction(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal,PyTypeChecker
|
# noinspection PyUnusedLocal,PyTypeChecker
|
||||||
class CreateAddress(BaseMutation):
|
class CreateAddress(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
raw_data = String(
|
raw_data = String(
|
||||||
required=True, description=_("original address string provided by the user")
|
required=True, description=_("original address string provided by the user")
|
||||||
|
|
@ -628,7 +627,7 @@ class CreateAddress(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
class DeleteAddress(BaseMutation):
|
class DeleteAddress(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
uuid = UUID(required=True)
|
uuid = UUID(required=True)
|
||||||
|
|
||||||
|
|
@ -655,7 +654,7 @@ class DeleteAddress(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
class AutocompleteAddress(BaseMutation):
|
class AutocompleteAddress(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
q = String()
|
q = String()
|
||||||
limit = Int()
|
limit = Int()
|
||||||
|
|
@ -676,7 +675,7 @@ class AutocompleteAddress(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
class ContactUs(BaseMutation):
|
class ContactUs(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
email = String(required=True)
|
email = String(required=True)
|
||||||
name = String(required=True)
|
name = String(required=True)
|
||||||
|
|
@ -707,7 +706,7 @@ class ContactUs(BaseMutation):
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyArgumentList PyUnusedLocal
|
# noinspection PyArgumentList PyUnusedLocal
|
||||||
class Search(BaseMutation):
|
class Search(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
query = String(required=True)
|
query = String(required=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,9 +79,6 @@ class AttributeGroup(ExportModelOperationsMixin("attribute_group"), NiceModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
is_publicly_visible = True
|
is_publicly_visible = True
|
||||||
attributes: QuerySet["Attribute"]
|
|
||||||
children: QuerySet["Self"]
|
|
||||||
|
|
||||||
parent = ForeignKey(
|
parent = ForeignKey(
|
||||||
"self",
|
"self",
|
||||||
on_delete=CASCADE,
|
on_delete=CASCADE,
|
||||||
|
|
@ -164,15 +161,7 @@ class Vendor(ExportModelOperationsMixin("vendor"), NiceModel):
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def save(
|
def save(self, *args, **kwargs) -> None:
|
||||||
self,
|
|
||||||
*,
|
|
||||||
force_insert: bool = False,
|
|
||||||
force_update: bool = False,
|
|
||||||
using: str | None = None,
|
|
||||||
update_fields: list[str] | tuple[str, ...] | None = None,
|
|
||||||
update_modified: bool = True,
|
|
||||||
) -> None:
|
|
||||||
users = self.users.filter(is_active=True)
|
users = self.users.filter(is_active=True)
|
||||||
users = users.exclude(attributes__icontains="is_business")
|
users = users.exclude(attributes__icontains="is_business")
|
||||||
if users.count() > 0:
|
if users.count() > 0:
|
||||||
|
|
@ -182,11 +171,11 @@ class Vendor(ExportModelOperationsMixin("vendor"), NiceModel):
|
||||||
user.attributes.update({"is_business": True})
|
user.attributes.update({"is_business": True})
|
||||||
user.save()
|
user.save()
|
||||||
return super().save(
|
return super().save(
|
||||||
force_insert=force_insert,
|
force_insert=kwargs.get("force_insert", False),
|
||||||
force_update=force_update,
|
force_update=kwargs.get("force_update", False),
|
||||||
using=using,
|
using=kwargs.get("using"),
|
||||||
update_fields=update_fields,
|
update_fields=kwargs.get("update_fields"),
|
||||||
update_modified=update_modified,
|
update_modified=kwargs.get("update_modified", True),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -704,7 +693,9 @@ class Product(ExportModelOperationsMixin("product"), NiceModel):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def discount_price(self) -> float | None:
|
def discount_price(self) -> float | None:
|
||||||
return self.promos.first().discount_percent if self.promos.exists() else None
|
return (
|
||||||
|
self.promos.first().discount_percent if self.promos.exists() else None
|
||||||
|
) # ty:ignore[possibly-missing-attribute]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rating(self) -> float:
|
def rating(self) -> float:
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import os
|
||||||
import traceback
|
import traceback
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from constance import config
|
from constance import config
|
||||||
|
|
@ -16,7 +15,6 @@ from django.core.exceptions import BadRequest
|
||||||
from django.db.models import Count, F, Sum
|
from django.db.models import Count, F, Sum
|
||||||
from django.http import (
|
from django.http import (
|
||||||
FileResponse,
|
FileResponse,
|
||||||
Http404,
|
|
||||||
HttpRequest,
|
HttpRequest,
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
HttpResponseRedirect,
|
HttpResponseRedirect,
|
||||||
|
|
@ -30,6 +28,7 @@ from django.utils.http import urlsafe_base64_decode
|
||||||
from django.utils.timezone import now as tz_now
|
from django.utils.timezone import now as tz_now
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.decorators.cache import cache_page
|
from django.views.decorators.cache import cache_page
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.decorators.vary import vary_on_headers
|
from django.views.decorators.vary import vary_on_headers
|
||||||
from django_ratelimit.decorators import ratelimit
|
from django_ratelimit.decorators import ratelimit
|
||||||
from drf_spectacular.utils import extend_schema_view
|
from drf_spectacular.utils import extend_schema_view
|
||||||
|
|
@ -404,14 +403,13 @@ class DownloadDigitalAssetView(APIView):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def favicon_view(
|
@csrf_exempt
|
||||||
request: HttpRequest, *args: list[Any], **kwargs: dict[str, Any]
|
def favicon_view(request: HttpRequest) -> HttpResponse | FileResponse:
|
||||||
) -> HttpResponse | FileResponse | None:
|
|
||||||
try:
|
try:
|
||||||
favicon_path = os.path.join(settings.BASE_DIR, "static/favicon.png")
|
favicon_path = os.path.join(settings.BASE_DIR, "static/favicon.png")
|
||||||
return FileResponse(open(favicon_path, "rb"), content_type="image/x-icon")
|
return FileResponse(open(favicon_path, "rb"), content_type="image/x-icon")
|
||||||
except FileNotFoundError as fnfe:
|
except FileNotFoundError:
|
||||||
raise Http404(_("favicon not found")) from fnfe
|
return HttpResponse(status=404, content=_("favicon not found"))
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import graphene
|
import graphene
|
||||||
|
from graphene import Mutation
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
|
||||||
from engine.core.graphene import BaseMutation
|
|
||||||
from engine.core.utils.messages import permission_denied_message
|
from engine.core.utils.messages import permission_denied_message
|
||||||
from engine.payments.graphene.object_types import TransactionType
|
from engine.payments.graphene.object_types import TransactionType
|
||||||
from engine.payments.models import Transaction
|
from engine.payments.models import Transaction
|
||||||
|
|
||||||
|
|
||||||
class Deposit(BaseMutation):
|
class Deposit(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
amount = graphene.Float(required=True)
|
amount = graphene.Float(required=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,10 @@ from django.db import IntegrityError
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
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 graphene import UUID, Boolean, Field, List, String
|
from graphene import UUID, Boolean, Field, List, Mutation, String
|
||||||
from graphene.types.generic import GenericScalar
|
from graphene.types.generic import GenericScalar
|
||||||
from graphene_file_upload.scalars import Upload
|
from graphene_file_upload.scalars import Upload
|
||||||
|
|
||||||
from engine.core.graphene import BaseMutation
|
|
||||||
from engine.core.utils.messages import permission_denied_message
|
from engine.core.utils.messages import permission_denied_message
|
||||||
from engine.core.utils.security import is_safe_key
|
from engine.core.utils.security import is_safe_key
|
||||||
from engine.vibes_auth.graphene.object_types import UserType
|
from engine.vibes_auth.graphene.object_types import UserType
|
||||||
|
|
@ -31,7 +30,7 @@ from engine.vibes_auth.validators import is_valid_email, is_valid_phone_number
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CreateUser(BaseMutation):
|
class CreateUser(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
email = String(required=True)
|
email = String(required=True)
|
||||||
password = String(required=True)
|
password = String(required=True)
|
||||||
|
|
@ -92,7 +91,7 @@ class CreateUser(BaseMutation):
|
||||||
raise BadRequest(str(e)) from e
|
raise BadRequest(str(e)) from e
|
||||||
|
|
||||||
|
|
||||||
class UpdateUser(BaseMutation):
|
class UpdateUser(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
uuid = UUID(required=True)
|
uuid = UUID(required=True)
|
||||||
email = String(required=False)
|
email = String(required=False)
|
||||||
|
|
@ -187,7 +186,7 @@ class UpdateUser(BaseMutation):
|
||||||
raise BadRequest(str(e)) from e
|
raise BadRequest(str(e)) from e
|
||||||
|
|
||||||
|
|
||||||
class DeleteUser(BaseMutation):
|
class DeleteUser(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
email = String()
|
email = String()
|
||||||
uuid = UUID()
|
uuid = UUID()
|
||||||
|
|
@ -212,7 +211,7 @@ class DeleteUser(BaseMutation):
|
||||||
raise PermissionDenied(permission_denied_message)
|
raise PermissionDenied(permission_denied_message)
|
||||||
|
|
||||||
|
|
||||||
class ObtainJSONWebToken(BaseMutation):
|
class ObtainJSONWebToken(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
email = String(required=True)
|
email = String(required=True)
|
||||||
password = String(required=True)
|
password = String(required=True)
|
||||||
|
|
@ -236,7 +235,7 @@ class ObtainJSONWebToken(BaseMutation):
|
||||||
raise PermissionDenied(f"invalid credentials provided: {e!s}") from e
|
raise PermissionDenied(f"invalid credentials provided: {e!s}") from e
|
||||||
|
|
||||||
|
|
||||||
class RefreshJSONWebToken(BaseMutation):
|
class RefreshJSONWebToken(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
refresh_token = String(required=True)
|
refresh_token = String(required=True)
|
||||||
|
|
||||||
|
|
@ -259,7 +258,7 @@ class RefreshJSONWebToken(BaseMutation):
|
||||||
raise PermissionDenied(f"invalid refresh token provided: {e!s}") from e
|
raise PermissionDenied(f"invalid refresh token provided: {e!s}") from e
|
||||||
|
|
||||||
|
|
||||||
class VerifyJSONWebToken(BaseMutation):
|
class VerifyJSONWebToken(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
token = String(required=True)
|
token = String(required=True)
|
||||||
|
|
||||||
|
|
@ -281,7 +280,7 @@ class VerifyJSONWebToken(BaseMutation):
|
||||||
return VerifyJSONWebToken(token_is_valid=False, user=None, detail=detail)
|
return VerifyJSONWebToken(token_is_valid=False, user=None, detail=detail)
|
||||||
|
|
||||||
|
|
||||||
class ActivateUser(BaseMutation):
|
class ActivateUser(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
uid = String(required=True)
|
uid = String(required=True)
|
||||||
token = String(required=True)
|
token = String(required=True)
|
||||||
|
|
@ -311,7 +310,7 @@ class ActivateUser(BaseMutation):
|
||||||
return ActivateUser(success=True)
|
return ActivateUser(success=True)
|
||||||
|
|
||||||
|
|
||||||
class ResetPassword(BaseMutation):
|
class ResetPassword(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
email = String(required=True)
|
email = String(required=True)
|
||||||
|
|
||||||
|
|
@ -330,7 +329,7 @@ class ResetPassword(BaseMutation):
|
||||||
return ResetPassword(success=True)
|
return ResetPassword(success=True)
|
||||||
|
|
||||||
|
|
||||||
class ConfirmResetPassword(BaseMutation):
|
class ConfirmResetPassword(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
uid = String(required=True)
|
uid = String(required=True)
|
||||||
token = String(required=True)
|
token = String(required=True)
|
||||||
|
|
@ -370,7 +369,7 @@ class ConfirmResetPassword(BaseMutation):
|
||||||
raise BadRequest(_(f"something went wrong: {e!s}")) from e
|
raise BadRequest(_(f"something went wrong: {e!s}")) from e
|
||||||
|
|
||||||
|
|
||||||
class UploadAvatar(BaseMutation):
|
class UploadAvatar(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
file = Upload(required=True)
|
file = Upload(required=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ class TokenRefreshSerializer(Serializer):
|
||||||
if api_settings.ROTATE_REFRESH_TOKENS:
|
if api_settings.ROTATE_REFRESH_TOKENS:
|
||||||
if api_settings.BLACKLIST_AFTER_ROTATION:
|
if api_settings.BLACKLIST_AFTER_ROTATION:
|
||||||
with suppress(AttributeError):
|
with suppress(AttributeError):
|
||||||
refresh.blacklist()
|
refresh.blacklist() # ty:ignore[possibly-missing-attribute]
|
||||||
|
|
||||||
refresh.set_jti()
|
refresh.set_jti()
|
||||||
refresh.set_exp()
|
refresh.set_exp()
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import logging
|
||||||
import traceback
|
import traceback
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from secrets import compare_digest
|
from secrets import compare_digest
|
||||||
from typing import Type
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.password_validation import validate_password
|
from django.contrib.auth.password_validation import validate_password
|
||||||
|
|
@ -53,7 +52,7 @@ class UserViewSet(
|
||||||
queryset = User.objects.filter(is_active=True)
|
queryset = User.objects.filter(is_active=True)
|
||||||
permission_classes = [AllowAny]
|
permission_classes = [AllowAny]
|
||||||
|
|
||||||
@action(detail=False, methods=["post"])
|
@action(detail=False, methods=("POST",))
|
||||||
@method_decorator(
|
@method_decorator(
|
||||||
ratelimit(key="ip", rate="4/h" if not settings.DEBUG else "888/h")
|
ratelimit(key="ip", rate="4/h" if not settings.DEBUG else "888/h")
|
||||||
)
|
)
|
||||||
|
|
@ -65,7 +64,7 @@ class UserViewSet(
|
||||||
send_reset_password_email_task.delay(user_pk=str(user.uuid))
|
send_reset_password_email_task.delay(user_pk=str(user.uuid))
|
||||||
return Response(status=status.HTTP_200_OK)
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
@action(detail=True, methods=["put"], permission_classes=[IsAuthenticated])
|
@action(detail=True, methods=("PUT",), permission_classes=[IsAuthenticated])
|
||||||
@method_decorator(
|
@method_decorator(
|
||||||
ratelimit(key="ip", rate="3/h" if not settings.DEBUG else "888/h")
|
ratelimit(key="ip", rate="3/h" if not settings.DEBUG else "888/h")
|
||||||
)
|
)
|
||||||
|
|
@ -81,24 +80,25 @@ class UserViewSet(
|
||||||
)
|
)
|
||||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
@action(detail=False, methods=["post"])
|
@action(detail=False, methods=("POST",))
|
||||||
@method_decorator(
|
@method_decorator(
|
||||||
ratelimit(key="ip", rate="5/h" if not settings.DEBUG else "888/h")
|
ratelimit(key="ip", rate="5/h" if not settings.DEBUG else "888/h")
|
||||||
)
|
)
|
||||||
def confirm_password_reset(self, request: Request, *args, **kwargs) -> Response:
|
def confirm_password_reset(self, request: Request, *args, **kwargs) -> Response:
|
||||||
try:
|
try:
|
||||||
if not compare_digest(
|
if not compare_digest(
|
||||||
request.data.get("password"), request.data.get("confirm_password")
|
str(request.data.get("password")),
|
||||||
|
str(request.data.get("confirm_password")),
|
||||||
):
|
):
|
||||||
return Response(
|
return Response(
|
||||||
{"error": _("passwords do not match")},
|
{"error": _("passwords do not match")},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
uuid = urlsafe_base64_decode(request.data.get("uidb_64")).decode()
|
uuid = urlsafe_base64_decode(str(request.data.get("uidb_64"))).decode()
|
||||||
user = User.objects.get(pk=uuid)
|
user = User.objects.get(pk=uuid)
|
||||||
|
|
||||||
validate_password(password=request.data.get("password"), user=user)
|
validate_password(password=str(request.data.get("password")), user=user)
|
||||||
|
|
||||||
password_reset_token = PasswordResetTokenGenerator()
|
password_reset_token = PasswordResetTokenGenerator()
|
||||||
if not password_reset_token.check_token(user, request.data.get("token")):
|
if not password_reset_token.check_token(user, request.data.get("token")):
|
||||||
|
|
@ -139,18 +139,18 @@ class UserViewSet(
|
||||||
serializer.data, status=status.HTTP_201_CREATED, headers=headers
|
serializer.data, status=status.HTTP_201_CREATED, headers=headers
|
||||||
)
|
)
|
||||||
|
|
||||||
@action(detail=False, methods=["post"])
|
@action(detail=False, methods=("POST",))
|
||||||
@method_decorator(
|
@method_decorator(
|
||||||
ratelimit(key="ip", rate="5/h" if not settings.DEBUG else "888/h")
|
ratelimit(key="ip", rate="5/h" if not settings.DEBUG else "888/h")
|
||||||
)
|
)
|
||||||
def activate(self, request: Request) -> Response:
|
def activate(self, request: Request) -> Response:
|
||||||
detail = ""
|
detail = ""
|
||||||
activation_error: Type[Exception] | None = None
|
activation_error: Exception | None = None
|
||||||
try:
|
try:
|
||||||
uuid = urlsafe_base64_decode(request.data.get("uidb_64")).decode()
|
uuid = urlsafe_base64_decode(str(request.data.get("uidb_64"))).decode()
|
||||||
user = User.objects.get(pk=uuid)
|
user = User.objects.get(pk=uuid)
|
||||||
if not user.check_token(
|
if not user.check_token(
|
||||||
urlsafe_base64_decode(request.data.get("token")).decode()
|
urlsafe_base64_decode(str(request.data.get("token"))).decode()
|
||||||
):
|
):
|
||||||
return Response(
|
return Response(
|
||||||
{"error": _("activation link is invalid!")},
|
{"error": _("activation link is invalid!")},
|
||||||
|
|
@ -175,7 +175,7 @@ class UserViewSet(
|
||||||
detail = str(traceback.format_exc())
|
detail = str(traceback.format_exc())
|
||||||
if user is None:
|
if user is None:
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
raise Exception from activation_error
|
raise Exception from activation_error # ty:ignore[possibly-unresolved-reference]
|
||||||
return Response(
|
return Response(
|
||||||
{"error": _("activation link is invalid!"), "detail": detail},
|
{"error": _("activation link is invalid!"), "detail": detail},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
|
@ -187,7 +187,7 @@ class UserViewSet(
|
||||||
response_data["access"] = str(tokens.access_token)
|
response_data["access"] = str(tokens.access_token)
|
||||||
return Response(response_data, status=status.HTTP_200_OK)
|
return Response(response_data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
@action(detail=True, methods=["put"], permission_classes=[IsAuthenticated])
|
@action(detail=True, methods=("PUT",), permission_classes=[IsAuthenticated])
|
||||||
def merge_recently_viewed(self, request: Request, *args, **kwargs) -> Response:
|
def merge_recently_viewed(self, request: Request, *args, **kwargs) -> Response:
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
if request.user != user:
|
if request.user != user:
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
from typing import Any
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
from storages.backends.ftp import FTPStorage
|
|
||||||
|
|
||||||
|
|
||||||
class AbsoluteFTPStorage(FTPStorage):
|
|
||||||
# noinspection PyProtectedMember
|
|
||||||
# noinspection PyUnresolvedReferences
|
|
||||||
|
|
||||||
def _get_config(self) -> Any:
|
|
||||||
cfg = super()._get_config()
|
|
||||||
url = urlparse(self.location)
|
|
||||||
cfg["path"] = url.path or cfg["path"]
|
|
||||||
return cfg
|
|
||||||
|
|
@ -17,6 +17,7 @@ from django.http import (
|
||||||
HttpResponseForbidden,
|
HttpResponseForbidden,
|
||||||
HttpResponsePermanentRedirect,
|
HttpResponsePermanentRedirect,
|
||||||
JsonResponse,
|
JsonResponse,
|
||||||
|
QueryDict,
|
||||||
)
|
)
|
||||||
from django.middleware.common import CommonMiddleware
|
from django.middleware.common import CommonMiddleware
|
||||||
from django.middleware.locale import LocaleMiddleware
|
from django.middleware.locale import LocaleMiddleware
|
||||||
|
|
@ -177,10 +178,21 @@ class CamelCaseMiddleWare:
|
||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
request.GET = underscoreize(
|
underscoreized_get = underscoreize(
|
||||||
request.GET,
|
{k: v for k, v in request.GET.lists()},
|
||||||
**JSON_UNDERSCOREIZE,
|
**JSON_UNDERSCOREIZE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
new_get = QueryDict(mutable=True)
|
||||||
|
for key, value in underscoreized_get.items():
|
||||||
|
if isinstance(value, list):
|
||||||
|
for val in value:
|
||||||
|
new_get.appendlist(key, val)
|
||||||
|
else:
|
||||||
|
new_get[key] = value
|
||||||
|
|
||||||
|
new_get._mutable = False
|
||||||
|
request.GET = new_get
|
||||||
|
|
||||||
response = self.get_response(request)
|
response = self.get_response(request)
|
||||||
return response
|
return response
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ class CustomPagination(PageNumberPagination):
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_paginated_response(self, data: dict[str, Any]) -> Response:
|
def get_paginated_response(self, data: dict[str, Any]) -> Response:
|
||||||
|
if not self.page:
|
||||||
|
raise RuntimeError
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"links": {
|
"links": {
|
||||||
|
|
@ -25,9 +27,7 @@ class CustomPagination(PageNumberPagination):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_paginated_response_schema(
|
def get_paginated_response_schema(self, schema: dict[str, Any]) -> dict[str, Any]:
|
||||||
self, data_schema: dict[str, Any]
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
return {
|
return {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -63,6 +63,6 @@ class CustomPagination(PageNumberPagination):
|
||||||
"example": 100,
|
"example": 100,
|
||||||
"description": "Total number of items",
|
"description": "Total number of items",
|
||||||
},
|
},
|
||||||
"data": data_schema,
|
"data": schema,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -348,7 +348,7 @@ LANGUAGE_URL_OVERRIDES: dict[str, str] = {
|
||||||
code.split("-")[0]: code for code, _ in LANGUAGES if "-" in code
|
code.split("-")[0]: code for code, _ in LANGUAGES if "-" in code
|
||||||
}
|
}
|
||||||
|
|
||||||
CURRENCY_CODE: str = dict(CURRENCIES_BY_LANGUAGES).get(LANGUAGE_CODE)
|
CURRENCY_CODE: str = dict(CURRENCIES_BY_LANGUAGES).get(LANGUAGE_CODE, "")
|
||||||
|
|
||||||
MODELTRANSLATION_FALLBACK_LANGUAGES: tuple[str, ...] = (LANGUAGE_CODE, "en-us", "de-de")
|
MODELTRANSLATION_FALLBACK_LANGUAGES: tuple[str, ...] = (LANGUAGE_CODE, "en-us", "de-de")
|
||||||
|
|
||||||
|
|
@ -517,7 +517,7 @@ if getenv("DBBACKUP_HOST") and getenv("DBBACKUP_USER") and getenv("DBBACKUP_PASS
|
||||||
STORAGES.update(
|
STORAGES.update(
|
||||||
{
|
{
|
||||||
"dbbackup": {
|
"dbbackup": {
|
||||||
"BACKEND": "evibes.ftpstorage.AbsoluteFTPStorage",
|
"BACKEND": "storages.backends.ftp.FTPStorage",
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
"location": (
|
"location": (
|
||||||
f"ftp://{getenv('DBBACKUP_USER')}:{getenv('DBBACKUP_PASS')}@{getenv('DBBACKUP_HOST')}:21/{raw_path}"
|
f"ftp://{getenv('DBBACKUP_USER')}:{getenv('DBBACKUP_PASS')}@{getenv('DBBACKUP_HOST')}:21/{raw_path}"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from os import getenv
|
from os import getenv
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from django.utils.text import format_lazy
|
from django.utils.text import format_lazy
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
@ -12,7 +13,7 @@ from evibes.settings.base import (
|
||||||
SECRET_KEY,
|
SECRET_KEY,
|
||||||
)
|
)
|
||||||
|
|
||||||
REST_FRAMEWORK: dict[str, str | int | list[str] | tuple[str, ...] | dict[str, bool]] = {
|
REST_FRAMEWORK: dict[str, Any] = {
|
||||||
"DEFAULT_PAGINATION_CLASS": "evibes.pagination.CustomPagination",
|
"DEFAULT_PAGINATION_CLASS": "evibes.pagination.CustomPagination",
|
||||||
"PAGE_SIZE": 30,
|
"PAGE_SIZE": 30,
|
||||||
"DEFAULT_AUTHENTICATION_CLASSES": [
|
"DEFAULT_AUTHENTICATION_CLASSES": [
|
||||||
|
|
@ -30,11 +31,14 @@ REST_FRAMEWORK: dict[str, str | int | list[str] | tuple[str, ...] | dict[str, bo
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON_UNDERSCOREIZE = REST_FRAMEWORK.get("JSON_UNDERSCOREIZE", {})
|
JSON_UNDERSCOREIZE: dict[str, Any] = REST_FRAMEWORK.get("JSON_UNDERSCOREIZE", ())
|
||||||
|
|
||||||
|
access_lifetime = timedelta(hours=8) if not DEBUG else timedelta(hours=88)
|
||||||
|
refresh_lifetime = timedelta(days=8)
|
||||||
|
|
||||||
SIMPLE_JWT: dict[str, timedelta | str | bool] = {
|
SIMPLE_JWT: dict[str, timedelta | str | bool] = {
|
||||||
"ACCESS_TOKEN_LIFETIME": timedelta(hours=8) if not DEBUG else timedelta(hours=88),
|
"ACCESS_TOKEN_LIFETIME": access_lifetime,
|
||||||
"REFRESH_TOKEN_LIFETIME": timedelta(days=8),
|
"REFRESH_TOKEN_LIFETIME": refresh_lifetime,
|
||||||
"ROTATE_REFRESH_TOKENS": True,
|
"ROTATE_REFRESH_TOKENS": True,
|
||||||
"BLACKLIST_AFTER_ROTATION": True,
|
"BLACKLIST_AFTER_ROTATION": True,
|
||||||
"UPDATE_LAST_LOGIN": True,
|
"UPDATE_LAST_LOGIN": True,
|
||||||
|
|
@ -90,15 +94,14 @@ The API supports multiple response formats:
|
||||||
Current API version: %(version)s
|
Current API version: %(version)s
|
||||||
""") # noqa: E501, F405
|
""") # noqa: E501, F405
|
||||||
|
|
||||||
_access_seconds = SIMPLE_JWT.get("ACCESS_TOKEN_LIFETIME").total_seconds()
|
|
||||||
if not DEBUG:
|
if not DEBUG:
|
||||||
_access_lifetime = int(_access_seconds // 60)
|
_access_lifetime = int(access_lifetime.total_seconds() // 60)
|
||||||
_access_unit = "minutes"
|
_access_unit = "minutes"
|
||||||
else:
|
else:
|
||||||
_access_lifetime = int(_access_seconds // 3600)
|
_access_lifetime = int(access_lifetime.total_seconds() // 3600)
|
||||||
_access_unit = "hours"
|
_access_unit = "hours"
|
||||||
|
|
||||||
_refresh_hours = int(SIMPLE_JWT.get("REFRESH_TOKEN_LIFETIME").total_seconds() // 3600)
|
_refresh_hours = int(refresh_lifetime.total_seconds() // 3600)
|
||||||
|
|
||||||
SPECTACULAR_DESCRIPTION = format_lazy(
|
SPECTACULAR_DESCRIPTION = format_lazy(
|
||||||
_SPECTACULAR_DESCRIPTION_TEMPLATE,
|
_SPECTACULAR_DESCRIPTION_TEMPLATE,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django_elasticsearch_dsl.registries import registry
|
from django_elasticsearch_dsl.registries import registry
|
||||||
from django_elasticsearch_dsl.signals import CelerySignalProcessor
|
from django_elasticsearch_dsl.signals import (
|
||||||
|
CelerySignalProcessor, # ty:ignore[possibly-missing-import]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SelectiveSignalProcessor(CelerySignalProcessor):
|
class SelectiveSignalProcessor(CelerySignalProcessor):
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ urlpatterns = [
|
||||||
path(
|
path(
|
||||||
r"favicon.ico",
|
r"favicon.ico",
|
||||||
favicon_view,
|
favicon_view,
|
||||||
|
name="favicon",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
r"graphql/",
|
r"graphql/",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import re
|
import re
|
||||||
from typing import Any, MutableMapping
|
from typing import Any, Collection, MutableMapping
|
||||||
|
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
from drf_orjson_renderer.renderers import ORJSONRenderer
|
from drf_orjson_renderer.renderers import ORJSONRenderer
|
||||||
|
|
@ -43,12 +43,12 @@ def camelize(obj: Any) -> Any:
|
||||||
|
|
||||||
|
|
||||||
def camelize_serializer_fields(result, generator, request, public):
|
def camelize_serializer_fields(result, generator, request, public):
|
||||||
ignore_fields = JSON_UNDERSCOREIZE.get("ignore_fields") or ()
|
ignore_fields: Collection[Any] = JSON_UNDERSCOREIZE.get("ignore_fields", ())
|
||||||
ignore_keys = JSON_UNDERSCOREIZE.get("ignore_keys") or ()
|
ignore_keys: Collection[Any] = JSON_UNDERSCOREIZE.get("ignore_keys", ())
|
||||||
|
|
||||||
def has_middleware_installed():
|
def has_middleware_installed():
|
||||||
try:
|
try:
|
||||||
from djangorestframework_camel_case.middleware import CamelCaseMiddleWare
|
from evibes.middleware import CamelCaseMiddleWare
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -104,7 +104,7 @@ def camelize_serializer_fields(result, generator, request, public):
|
||||||
class CamelCaseRenderer(ORJSONRenderer):
|
class CamelCaseRenderer(ORJSONRenderer):
|
||||||
def render(
|
def render(
|
||||||
self, data: Any, media_type: str | None = None, renderer_context: Any = None
|
self, data: Any, media_type: str | None = None, renderer_context: Any = None
|
||||||
) -> bytes:
|
) -> bytes: # ty:ignore[invalid-method-override]
|
||||||
if data is None:
|
if data is None:
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,8 @@ worker = [
|
||||||
"celery-prometheus-exporter==1.7.0",
|
"celery-prometheus-exporter==1.7.0",
|
||||||
]
|
]
|
||||||
linting = [
|
linting = [
|
||||||
|
"ty==0.0.3",
|
||||||
"ruff==0.14.9",
|
"ruff==0.14.9",
|
||||||
"basedpyright>=1.36.1",
|
|
||||||
"pyright==1.1.407",
|
|
||||||
"celery-types>=0.23.0",
|
"celery-types>=0.23.0",
|
||||||
"django-stubs==5.2.8",
|
"django-stubs==5.2.8",
|
||||||
"djangorestframework-stubs==3.16.6",
|
"djangorestframework-stubs==3.16.6",
|
||||||
|
|
@ -112,7 +111,15 @@ python-version = "3.13"
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 88
|
line-length = 88
|
||||||
target-version = "py312"
|
target-version = "py312"
|
||||||
exclude = ["media", "static", "storefront"]
|
exclude = [
|
||||||
|
"Dockerfiles",
|
||||||
|
"monitoring",
|
||||||
|
"scripts",
|
||||||
|
"static",
|
||||||
|
"storefront",
|
||||||
|
"tmp",
|
||||||
|
"media",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
select = ["E4", "E7", "E9", "F", "B", "Q", "I"]
|
select = ["E4", "E7", "E9", "F", "B", "Q", "I"]
|
||||||
|
|
@ -125,41 +132,25 @@ known-first-party = ["evibes", "engine"]
|
||||||
quote-style = "double"
|
quote-style = "double"
|
||||||
indent-style = "space"
|
indent-style = "space"
|
||||||
|
|
||||||
[tool.pyright]
|
[tool.ty.environment]
|
||||||
typeCheckingMode = "strict"
|
python-version = "3.12"
|
||||||
pythonVersion = "3.12"
|
|
||||||
useLibraryCodeForTypes = true
|
|
||||||
reportMissingTypeStubs = true
|
|
||||||
reportGeneralTypeIssues = false
|
|
||||||
reportRedeclaration = false
|
|
||||||
exclude = [
|
|
||||||
"**/__pycache__/**",
|
|
||||||
"**/.venv/**",
|
|
||||||
"**/.uv/**",
|
|
||||||
"media/**",
|
|
||||||
"static/**",
|
|
||||||
"storefront/**",
|
|
||||||
"**/migrations/**",
|
|
||||||
]
|
|
||||||
extraPaths = ["./evibes", "./engine"]
|
|
||||||
|
|
||||||
[tool.basedpyright]
|
[tool.ty.terminal]
|
||||||
typeCheckingMode = "strict"
|
output-format = "concise"
|
||||||
pythonVersion = "3.12"
|
|
||||||
useLibraryCodeForTypes = true
|
[tool.ty.rules]
|
||||||
reportMissingTypeStubs = true
|
possibly-unresolved-reference = "warn"
|
||||||
reportGeneralTypeIssues = false
|
|
||||||
reportRedeclaration = false
|
[[tool.ty.overrides]]
|
||||||
exclude = [
|
exclude = [
|
||||||
"**/__pycache__/**",
|
"Dockerfiles/**",
|
||||||
"**/.venv/**",
|
"monitoring/**",
|
||||||
"**/.uv/**",
|
"scripts/**",
|
||||||
"media/**",
|
|
||||||
"static/**",
|
"static/**",
|
||||||
"storefront/**",
|
"storefront/**",
|
||||||
"**/migrations/**",
|
"tmp/**",
|
||||||
|
"media/**",
|
||||||
]
|
]
|
||||||
extraPaths = ["./evibes", "./engine"]
|
|
||||||
|
|
||||||
[tool.django-stubs]
|
[tool.django-stubs]
|
||||||
django_settings_module = "evibes.settings.__init__"
|
django_settings_module = "evibes.settings.__init__"
|
||||||
|
|
|
||||||
81
uv.lock
81
uv.lock
|
|
@ -257,18 +257,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
|
{ url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "basedpyright"
|
|
||||||
version = "1.36.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "nodejs-wheel-binaries" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/32/29/d42d543a1637e692ac557bfc6d6fcf50e9a7061c1cb4da403378d6a70453/basedpyright-1.36.1.tar.gz", hash = "sha256:20c9a24e2a4c95d5b6d46c78a6b6c7e3dc7cbba227125256431d47c595b15fd4", size = 22834851, upload-time = "2025-12-11T14:55:47.463Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c0/7f/f0133313bffa303d32aa74468981eb6b2da7fadda6247c9aa0aeab8391b1/basedpyright-1.36.1-py3-none-any.whl", hash = "sha256:3d738484fe9681cdfe35dd98261f30a9a7aec64208bc91f8773a9aaa9b89dd16", size = 11881725, upload-time = "2025-12-11T14:55:43.805Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bcrypt"
|
name = "bcrypt"
|
||||||
version = "5.0.0"
|
version = "5.0.0"
|
||||||
|
|
@ -1420,12 +1408,11 @@ jupyter = [
|
||||||
{ name = "jupyter" },
|
{ name = "jupyter" },
|
||||||
]
|
]
|
||||||
linting = [
|
linting = [
|
||||||
{ name = "basedpyright" },
|
|
||||||
{ name = "celery-types" },
|
{ name = "celery-types" },
|
||||||
{ name = "django-stubs" },
|
{ name = "django-stubs" },
|
||||||
{ name = "djangorestframework-stubs" },
|
{ name = "djangorestframework-stubs" },
|
||||||
{ name = "pyright" },
|
|
||||||
{ name = "ruff" },
|
{ name = "ruff" },
|
||||||
|
{ name = "ty" },
|
||||||
{ name = "types-docutils" },
|
{ name = "types-docutils" },
|
||||||
{ name = "types-paramiko" },
|
{ name = "types-paramiko" },
|
||||||
{ name = "types-pillow" },
|
{ name = "types-pillow" },
|
||||||
|
|
@ -1448,7 +1435,6 @@ worker = [
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "aiogram", specifier = "==3.23.0" },
|
{ name = "aiogram", specifier = "==3.23.0" },
|
||||||
{ name = "aiosmtpd", specifier = "==1.4.6" },
|
{ name = "aiosmtpd", specifier = "==1.4.6" },
|
||||||
{ name = "basedpyright", marker = "extra == 'linting'", specifier = ">=1.36.1" },
|
|
||||||
{ name = "celery", marker = "extra == 'worker'", specifier = "==5.6.0" },
|
{ name = "celery", marker = "extra == 'worker'", specifier = "==5.6.0" },
|
||||||
{ name = "celery-prometheus-exporter", marker = "extra == 'worker'", specifier = "==1.7.0" },
|
{ name = "celery-prometheus-exporter", marker = "extra == 'worker'", specifier = "==1.7.0" },
|
||||||
{ name = "celery-types", marker = "extra == 'linting'", specifier = ">=0.23.0" },
|
{ name = "celery-types", marker = "extra == 'linting'", specifier = ">=0.23.0" },
|
||||||
|
|
@ -1514,7 +1500,6 @@ requires-dist = [
|
||||||
{ name = "pygraphviz", marker = "sys_platform != 'win32' and extra == 'graph'", specifier = "==1.14" },
|
{ name = "pygraphviz", marker = "sys_platform != 'win32' and extra == 'graph'", specifier = "==1.14" },
|
||||||
{ name = "pyjwt", specifier = "==2.10.1" },
|
{ name = "pyjwt", specifier = "==2.10.1" },
|
||||||
{ name = "pymdown-extensions", specifier = "==10.19.1" },
|
{ name = "pymdown-extensions", specifier = "==10.19.1" },
|
||||||
{ name = "pyright", marker = "extra == 'linting'", specifier = "==1.1.407" },
|
|
||||||
{ name = "pytest", specifier = "==9.0.2" },
|
{ name = "pytest", specifier = "==9.0.2" },
|
||||||
{ name = "pytest-django", specifier = "==4.11.1" },
|
{ name = "pytest-django", specifier = "==4.11.1" },
|
||||||
{ name = "python-slugify", specifier = "==8.0.4" },
|
{ name = "python-slugify", specifier = "==8.0.4" },
|
||||||
|
|
@ -1524,6 +1509,7 @@ requires-dist = [
|
||||||
{ name = "sentry-sdk", extras = ["django", "celery", "opentelemetry"], specifier = "==2.48.0" },
|
{ name = "sentry-sdk", extras = ["django", "celery", "opentelemetry"], specifier = "==2.48.0" },
|
||||||
{ name = "six", specifier = "==1.17.0" },
|
{ name = "six", specifier = "==1.17.0" },
|
||||||
{ name = "swapper", specifier = "==1.4.0" },
|
{ name = "swapper", specifier = "==1.4.0" },
|
||||||
|
{ name = "ty", marker = "extra == 'linting'", specifier = "==0.0.3" },
|
||||||
{ name = "types-docutils", marker = "extra == 'linting'", specifier = "==0.22.3.20251115" },
|
{ name = "types-docutils", marker = "extra == 'linting'", specifier = "==0.22.3.20251115" },
|
||||||
{ name = "types-paramiko", marker = "extra == 'linting'", specifier = "==4.0.0.20250822" },
|
{ name = "types-paramiko", marker = "extra == 'linting'", specifier = "==4.0.0.20250822" },
|
||||||
{ name = "types-pillow", marker = "extra == 'linting'", specifier = "==10.2.0.20240822" },
|
{ name = "types-pillow", marker = "extra == 'linting'", specifier = "==10.2.0.20240822" },
|
||||||
|
|
@ -2541,31 +2527,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" },
|
{ url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nodeenv"
|
|
||||||
version = "1.9.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nodejs-wheel-binaries"
|
|
||||||
version = "24.12.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/35/d806c2ca66072e36dc340ccdbeb2af7e4f1b5bcc33f1481f00ceed476708/nodejs_wheel_binaries-24.12.0.tar.gz", hash = "sha256:f1b50aa25375e264697dec04b232474906b997c2630c8f499f4caf3692938435", size = 8058, upload-time = "2025-12-11T21:12:26.856Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c3/3b/9d6f044319cd5b1e98f07c41e2465b58cadc1c9c04a74c891578f3be6cb5/nodejs_wheel_binaries-24.12.0-py2.py3-none-macosx_13_0_arm64.whl", hash = "sha256:7564ddea0a87eff34e9b3ef71764cc2a476a8f09a5cccfddc4691148b0a47338", size = 55125859, upload-time = "2025-12-11T21:11:58.132Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/48/a5/f5722bf15c014e2f476d7c76bce3d55c341d19122d8a5d86454db32a61a4/nodejs_wheel_binaries-24.12.0-py2.py3-none-macosx_13_0_x86_64.whl", hash = "sha256:8ff929c4669e64613ceb07f5bbd758d528c3563820c75d5de3249eb452c0c0ab", size = 55309035, upload-time = "2025-12-11T21:12:01.754Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a9/61/68d39a6f1b5df67805969fd2829ba7e80696c9af19537856ec912050a2be/nodejs_wheel_binaries-24.12.0-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:6ebacefa8891bc456ad3655e6bce0af7e20ba08662f79d9109986faeb703fd6f", size = 59661017, upload-time = "2025-12-11T21:12:05.268Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/16/a1/31aad16f55a5e44ca7ea62d1367fc69f4b6e1dba67f58a0a41d0ed854540/nodejs_wheel_binaries-24.12.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:3292649a03682ccbfa47f7b04d3e4240e8c46ef04dc941b708f20e4e6a764f75", size = 60159770, upload-time = "2025-12-11T21:12:08.696Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c4/5e/b7c569aa1862690ca4d4daf3a64cafa1ea6ce667a9e3ae3918c56e127d9b/nodejs_wheel_binaries-24.12.0-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7fb83df312955ea355ba7f8cbd7055c477249a131d3cb43b60e4aeb8f8c730b1", size = 61653561, upload-time = "2025-12-11T21:12:12.575Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/71/87/567f58d7ba69ff0208be849b37be0f2c2e99c69e49334edd45ff44f00043/nodejs_wheel_binaries-24.12.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2473c819448fedd7b036dde236b09f3c8bbf39fbbd0c1068790a0498800f498b", size = 62238331, upload-time = "2025-12-11T21:12:16.143Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6a/9d/c6492188ce8de90093c6755a4a63bb6b2b4efb17094cb4f9a9a49c73ed3b/nodejs_wheel_binaries-24.12.0-py2.py3-none-win_amd64.whl", hash = "sha256:2090d59f75a68079fabc9b86b14df8238b9aecb9577966dc142ce2a23a32e9bb", size = 41342076, upload-time = "2025-12-11T21:12:20.618Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/af/cd3290a647df567645353feed451ef4feaf5844496ced69c4dcb84295ff4/nodejs_wheel_binaries-24.12.0-py2.py3-none-win_arm64.whl", hash = "sha256:d0c2273b667dd7e3f55e369c0085957b702144b1b04bfceb7ce2411e58333757", size = 39048104, upload-time = "2025-12-11T21:12:23.495Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "notebook"
|
name = "notebook"
|
||||||
version = "7.5.1"
|
version = "7.5.1"
|
||||||
|
|
@ -3220,19 +3181,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/35/76/c34426d532e4dce7ff36e4d92cb20f4cbbd94b619964b93d24e8f5b5510f/pynacl-1.6.1-cp38-abi3-win_arm64.whl", hash = "sha256:5953e8b8cfadb10889a6e7bd0f53041a745d1b3d30111386a1bb37af171e6daf", size = 183970, upload-time = "2025-11-10T16:02:05.786Z" },
|
{ url = "https://files.pythonhosted.org/packages/35/76/c34426d532e4dce7ff36e4d92cb20f4cbbd94b619964b93d24e8f5b5510f/pynacl-1.6.1-cp38-abi3-win_arm64.whl", hash = "sha256:5953e8b8cfadb10889a6e7bd0f53041a745d1b3d30111386a1bb37af171e6daf", size = 183970, upload-time = "2025-11-10T16:02:05.786Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyright"
|
|
||||||
version = "1.1.407"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "nodeenv" },
|
|
||||||
{ name = "typing-extensions" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a6/1b/0aa08ee42948b61745ac5b5b5ccaec4669e8884b53d31c8ec20b2fcd6b6f/pyright-1.1.407.tar.gz", hash = "sha256:099674dba5c10489832d4a4b2d302636152a9a42d317986c38474c76fe562262", size = 4122872, upload-time = "2025-10-24T23:17:15.145Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/dc/93/b69052907d032b00c40cb656d21438ec00b3a471733de137a3f65a49a0a0/pyright-1.1.407-py3-none-any.whl", hash = "sha256:6dd419f54fcc13f03b52285796d65e639786373f433e243f8b94cf93a7444d21", size = 5997008, upload-time = "2025-10-24T23:17:13.159Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "9.0.2"
|
version = "9.0.2"
|
||||||
|
|
@ -3762,6 +3710,31 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
|
{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ty"
|
||||||
|
version = "0.0.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/bb/cd/aee86c0da3240960d6b7e807f3a41c89bae741495d81ca303200b0103dc9/ty-0.0.3.tar.gz", hash = "sha256:831259e22d3855436701472d4c0da200cd45041bc677eae79415d684f541de8a", size = 4769098, upload-time = "2025-12-18T02:16:49.773Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/ef/2d0d18e8fe6b673d3e1ea642f18404d7edfa9d08310f7203e8f0e7dc862e/ty-0.0.3-py3-none-linux_armv6l.whl", hash = "sha256:cd035bb75acecb78ac1ba8c4cc696f57a586e29d36e84bd691bc3b5b8362794c", size = 9763890, upload-time = "2025-12-18T02:16:56.879Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/67/0ae31574619a7264df8cf8e641f246992db22ac1720c2a72953aa31cbe61/ty-0.0.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7708eaf73485e263efc7ef339f8e4487d3f5885779edbeec504fd72e4521c376", size = 9558276, upload-time = "2025-12-18T02:16:45.453Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/f7/3b9c033e80910972fca3783e4a52ba9cb7cd5c8b6828a87986646d64082b/ty-0.0.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3113a633f46ec789f6df675b7afc5d3ab20c247c92ae4dbb9aa5b704768c18b2", size = 9094451, upload-time = "2025-12-18T02:17:01.155Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/29/9a90ed6bef00142a088965100b5e0a5d11805b9729c151ca598331bbd92b/ty-0.0.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a451f3f73a04bf18e551b1ebebb79b20fac5f09740a353f7e07b5f607b217c4f", size = 9568049, upload-time = "2025-12-18T02:16:28.643Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2f/ab/8daeb12912c2de8a3154db652931f4ad0d27c555faebcaf34af08bcfd0d2/ty-0.0.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f6e926b6de0becf0452e1afad75cb71f889a4777cd14269e5447d46c01b2770", size = 9547711, upload-time = "2025-12-18T02:16:54.464Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/54/f5c1f293f647beda717fee2448cc927ac0d05f66bebe18647680a67e1d67/ty-0.0.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160e7974150f9f359c31d5808214676d1baa05321ab5a7b29fb09f4906dbdb38", size = 9983225, upload-time = "2025-12-18T02:17:05.672Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/95/34/065962cfa2e87c10db839512229940a366b8ca1caffa2254a277b1694e5a/ty-0.0.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:726576df31d4e76934ffc64f2939d4a9bc195c7427452c8c159261ad00bd1b5e", size = 10851148, upload-time = "2025-12-18T02:16:38.354Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/27/e2a8cbfc33999eef882ccd1b816ed615293f96e96f6df60cd12f84b69ca2/ty-0.0.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5014cf4744c94d9ea7b43314199ddaf52564a80b3d006e4ba0fe982bc42f4e8b", size = 10564441, upload-time = "2025-12-18T02:17:03.584Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/6d/dcce3e222e59477c1f2b3a012cc76428d7032248138cd5544ad7f1cda7bd/ty-0.0.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a9a51dc040f2718725f34ae6ef51fe8f8bd689e21bd3e82f4e71767034928de", size = 10358651, upload-time = "2025-12-18T02:16:26.091Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/36/b6d0154b83a5997d607bf1238200271c17223f68aab2c778ded5424f9c1e/ty-0.0.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e6188eddd3a228c449261bb398e8621d33b92c1fc03599afdfad4388327a48", size = 10120457, upload-time = "2025-12-18T02:16:51.864Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/46/05dc826674ee1a451406e4c253c71700a6f707bae88b706a4c9e9bba6919/ty-0.0.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5cc55e08d5d18edf1c5051af02456bd359716f07aae0a305e4cefe7735188540", size = 9551642, upload-time = "2025-12-18T02:16:33.518Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/8a/f90b60d103fd5ec04ecbac091a64e607e6cd37cec6e718bba17cb2022644/ty-0.0.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:34b2d589412a81d1fd6d7fe461353068496c2bf1f7113742bd6d88d1d57ec3ad", size = 9572234, upload-time = "2025-12-18T02:16:31.013Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e8/72/5d3c6d34562d019ba7f3102b2a6d0c8e9e24ef39e70f09645c36a66765b7/ty-0.0.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8a065eb2959f141fe4adafc14d57463cfa34f6cc4844a4ed56b2dce1a53a419a", size = 9701682, upload-time = "2025-12-18T02:16:41.379Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ef/44/bda434f788b320c9550a48c549e4a8c507e3d8a6ccb04ba5bd098307ba1e/ty-0.0.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e7177421f830a493f98d22f86d940b5a38788866e6062f680881f19be35ba3bb", size = 10213714, upload-time = "2025-12-18T02:16:35.648Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/a6/b76a787938026c3d209131e5773de32cf6fc41210e0dd97874aafa20f394/ty-0.0.3-py3-none-win32.whl", hash = "sha256:e3e590bf5f33cb118a53c6d5242eedf7924d45517a5ee676c7a16be3a1389d2f", size = 9160441, upload-time = "2025-12-18T02:16:43.404Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fe/db/da60eb8252768323aee0ce69a08b95011088c003f80204b12380fe562fd2/ty-0.0.3-py3-none-win_amd64.whl", hash = "sha256:5af25b1fed8a536ce8072a9ae6a70cd2b559aa5294d43f57071fbdcd31dd2b0e", size = 10034265, upload-time = "2025-12-18T02:16:47.602Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/9c/9045cebdfc394c6f8c1e73a99d3aeda1bc639aace392e8ff4d695f1fab73/ty-0.0.3-py3-none-win_arm64.whl", hash = "sha256:29078b3100351a8b37339771615f13b8e4a4ff52b344d33f774f8d1a665a0ca5", size = 9513095, upload-time = "2025-12-18T02:16:59.073Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "types-cffi"
|
name = "types-cffi"
|
||||||
version = "1.17.0.20250915"
|
version = "1.17.0.20250915"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue