Merge branch 'main' into storefront-nuxt
This commit is contained in:
commit
244d94831e
16 changed files with 285 additions and 149 deletions
0
engine/core/graphene/dashboard_mutations/__init__.py
Normal file
0
engine/core/graphene/dashboard_mutations/__init__.py
Normal file
179
engine/core/graphene/dashboard_mutations/product.py
Normal file
179
engine/core/graphene/dashboard_mutations/product.py
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
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): # type: ignore[misc]
|
||||
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): # type: ignore[misc]
|
||||
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): # type: ignore [override]
|
||||
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"]) # type: ignore[index]
|
||||
brand = None
|
||||
if product_data.get("brand_uuid"):
|
||||
with suppress(Brand.DoesNotExist): # type: ignore[name-defined]
|
||||
brand = Brand.objects.get(uuid=product_data["brand_uuid"]) # type: ignore[index]
|
||||
|
||||
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): # type: ignore [override]
|
||||
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"]) # type: ignore[index]
|
||||
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"]) # type: ignore[index]
|
||||
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): # type: ignore [override]
|
||||
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)
|
||||
|
|
@ -15,13 +15,12 @@ from engine.core.graphene import BaseMutation
|
|||
from engine.core.graphene.object_types import (
|
||||
AddressType,
|
||||
BulkProductInput,
|
||||
FeedbackType,
|
||||
OrderType,
|
||||
ProductType,
|
||||
SearchResultsType,
|
||||
WishlistType,
|
||||
FeedbackType,
|
||||
)
|
||||
from engine.core.models import Address, Category, Order, Product, Wishlist, OrderProduct
|
||||
from engine.core.models import Address, Order, OrderProduct, Wishlist
|
||||
from engine.core.utils import format_attributes, is_url_safe
|
||||
from engine.core.utils.caching import web_cache
|
||||
from engine.core.utils.emailing import contact_us_email
|
||||
|
|
@ -105,7 +104,7 @@ class AddOrderProduct(BaseMutation):
|
|||
raise Http404(_(f"order {order_uuid} not found")) from dne
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
# noinspection PyUnusedLocal,PyTypeChecker
|
||||
class RemoveOrderProduct(BaseMutation):
|
||||
class Meta:
|
||||
description = _("remove a product from the order")
|
||||
|
|
@ -118,7 +117,7 @@ class RemoveOrderProduct(BaseMutation):
|
|||
order = Field(OrderType)
|
||||
|
||||
@staticmethod
|
||||
def mutate(parent, info, product_uuid, order_uuid, attributes=None) -> AddOrderProduct | None: # type: ignore [override]
|
||||
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)
|
||||
|
|
@ -127,7 +126,7 @@ class RemoveOrderProduct(BaseMutation):
|
|||
|
||||
order = order.remove_product(product_uuid=product_uuid, attributes=format_attributes(attributes))
|
||||
|
||||
return AddOrderProduct(order=order)
|
||||
return RemoveOrderProduct(order=order)
|
||||
except Order.DoesNotExist as dne:
|
||||
raise Http404(_(f"order {order_uuid} not found")) from dne
|
||||
|
||||
|
|
@ -577,67 +576,6 @@ 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)
|
||||
description = String()
|
||||
category_uuid = UUID(required=True)
|
||||
|
||||
product = Field(ProductType)
|
||||
|
||||
@staticmethod
|
||||
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)
|
||||
product = Product.objects.create(name=name, description=description, category=category)
|
||||
return CreateProduct(product=product)
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal,PyTypeChecker
|
||||
class UpdateProduct(BaseMutation):
|
||||
class Arguments:
|
||||
uuid = UUID(required=True)
|
||||
name = String()
|
||||
description = String()
|
||||
category_uuid = UUID()
|
||||
|
||||
product = Field(ProductType)
|
||||
|
||||
@staticmethod
|
||||
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)
|
||||
product = Product.objects.get(uuid=uuid)
|
||||
if name:
|
||||
product.name = name
|
||||
if description:
|
||||
product.description = description
|
||||
if category_uuid:
|
||||
product.category = Category.objects.get(uuid=category_uuid)
|
||||
product.save()
|
||||
return UpdateProduct(product=product)
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal,PyTypeChecker
|
||||
class DeleteProduct(BaseMutation):
|
||||
class Arguments:
|
||||
uuid = UUID(required=True)
|
||||
|
||||
ok = Boolean()
|
||||
|
||||
@staticmethod
|
||||
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)
|
||||
product = Product.objects.get(uuid=uuid)
|
||||
product.delete()
|
||||
return DeleteProduct(ok=True)
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal,PyTypeChecker
|
||||
class CreateAddress(BaseMutation):
|
||||
class Arguments:
|
||||
|
|
|
|||
|
|
@ -31,9 +31,7 @@ from engine.core.graphene.mutations import (
|
|||
CacheOperator,
|
||||
ContactUs,
|
||||
CreateAddress,
|
||||
CreateProduct,
|
||||
DeleteAddress,
|
||||
DeleteProduct,
|
||||
FeedbackProductAction,
|
||||
RemoveAllOrderProducts,
|
||||
RemoveAllWishlistProducts,
|
||||
|
|
@ -42,7 +40,6 @@ from engine.core.graphene.mutations import (
|
|||
RemoveWishlistProduct,
|
||||
RequestCursedURL,
|
||||
Search,
|
||||
UpdateProduct,
|
||||
)
|
||||
from engine.core.graphene.object_types import (
|
||||
AddressType,
|
||||
|
|
@ -369,9 +366,6 @@ class Mutation(ObjectType):
|
|||
reset_password = ResetPassword.Field()
|
||||
confirm_reset_password = ConfirmResetPassword.Field()
|
||||
buy_product = BuyProduct.Field()
|
||||
create_product = CreateProduct.Field()
|
||||
update_product = UpdateProduct.Field()
|
||||
delete_product = DeleteProduct.Field()
|
||||
create_address = CreateAddress.Field()
|
||||
delete_address = DeleteAddress.Field()
|
||||
autocomplete_address = AutocompleteAddress.Field()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,33 @@
|
|||
from django.test import TestCase
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from engine.vibes_auth.models import User
|
||||
from engine.vibes_auth.serializers import TokenObtainPairSerializer
|
||||
|
||||
|
||||
class DRFCoreViewsTests(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.client = APIClient()
|
||||
self.superuser_password = "Str0ngPass!word1"
|
||||
self.superuser = User.objects.create(
|
||||
email="test-superuser@email.com",
|
||||
password=self.superuser_password,
|
||||
is_active=True,
|
||||
is_verified=True,
|
||||
is_superuser=True,
|
||||
is_staff=True,
|
||||
)
|
||||
self.user_password = "Str0ngPass!word2"
|
||||
self.user = User.objects.create(
|
||||
email="test-superuser@email.com", password=self.user_password, is_active=True, is_verified=True
|
||||
)
|
||||
|
||||
def _get_authorization_token(self, user):
|
||||
serializer = TokenObtainPairSerializer(
|
||||
data={"email": user.email, "password": self.superuser_password if user.is_superuser else self.user_password}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return serializer.validated_data["access_token"]
|
||||
|
||||
# TODO: create tests for every possible HTTP method in core module with DRF stack
|
||||
|
|
|
|||
|
|
@ -13,3 +13,6 @@ class GraphQLCoreTests(TestCase):
|
|||
response = self.client.post(url, data=payload, content_type="application/json")
|
||||
self.assertEqual(response.status_code, 200, response.json())
|
||||
return response.json()
|
||||
|
||||
|
||||
# TODO: create tests for every possible HTTP method in core module with Graphene stack
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import logging
|
|||
import traceback
|
||||
from typing import Any
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
from rest_framework import status
|
||||
from rest_framework.request import Request
|
||||
|
|
@ -18,21 +19,13 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
@extend_schema_view(**DEPOSIT_SCHEMA)
|
||||
class DepositView(APIView):
|
||||
"""Handles deposit operations.
|
||||
|
||||
This class provides an API endpoint to handle deposit transactions.
|
||||
It supports the creation of a deposit transaction after validating the
|
||||
provided data. If the user is not authenticated, an appropriate response
|
||||
is returned. On successful validation and execution, a response
|
||||
with the transaction details is provided.
|
||||
|
||||
Attributes:
|
||||
No attributes are declared at the class-level for this view.
|
||||
|
||||
Methods:
|
||||
post: Processes the deposit request, validates the request data, ensures
|
||||
user authentication, and creates a transaction.
|
||||
"""
|
||||
__doc__ = _( # type: ignore [assignment]
|
||||
"This class provides an API endpoint to handle deposit transactions.\n"
|
||||
"It supports the creation of a deposit transaction after validating the "
|
||||
"provided data. If the user is not authenticated, an appropriate response "
|
||||
"is returned. On successful validation and execution, a response "
|
||||
"with the transaction details is provided."
|
||||
)
|
||||
|
||||
def post(self, request: Request, *args: list[Any], **kwargs: dict[Any, Any]) -> Response:
|
||||
logger.debug(request.__dict__)
|
||||
|
|
@ -52,33 +45,25 @@ class DepositView(APIView):
|
|||
|
||||
@extend_schema(exclude=True)
|
||||
class CallbackAPIView(APIView):
|
||||
"""
|
||||
Handles incoming callback requests to the API.
|
||||
|
||||
This class processes and routes incoming HTTP POST requests to the appropriate
|
||||
gateway handler based on the provided gateway parameter. It is designed to handle
|
||||
callback events coming from external systems and provide an appropriate HTTP response
|
||||
indicating success or failure.
|
||||
|
||||
Attributes:
|
||||
No additional attributes are defined for this class beyond what is
|
||||
inherited from APIView.
|
||||
|
||||
Methods:
|
||||
post(request, *args, **kwargs): Processes POST requests and routes them
|
||||
based on the specified gateway. Handles exceptions gracefully by returning
|
||||
a server error response if an unknown gateway or other issues occur.
|
||||
"""
|
||||
__doc__ = _( # type: ignore [assignment]
|
||||
"Handles incoming callback requests to the API.\n"
|
||||
"This class processes and routes incoming HTTP POST requests to the appropriate "
|
||||
"pgateway handler based on the provided gateway parameter. It is designed to handle "
|
||||
"callback events coming from external systems and provide an appropriate HTTP response "
|
||||
"indicating success or failure."
|
||||
)
|
||||
|
||||
def post(self, request: Request, *args: list[Any], **kwargs: dict[Any, Any]) -> Response:
|
||||
try:
|
||||
transaction = Transaction.objects.get(uuid=str(kwargs.get("uuid")))
|
||||
if not transaction.gateway:
|
||||
raise UnknownGatewayError()
|
||||
gateway = transaction.gateway.get_integration_class_object(raise_exc=True)
|
||||
gateway.process_callback(request.data)
|
||||
raise UnknownGatewayError(_(f"Transaction {transaction.uuid} has no gateway"))
|
||||
gateway_integration = transaction.gateway.get_integration_class_object(raise_exc=True)
|
||||
if not gateway_integration:
|
||||
raise UnknownGatewayError(_(f"Gateway {transaction.gateway} has no integration"))
|
||||
gateway_integration.process_callback(request.data)
|
||||
return Response(status=status.HTTP_202_ACCEPTED)
|
||||
except Exception as e:
|
||||
return Response(
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR, data={"error": f"{e}; {traceback.format_exc()}"}
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR, data={"error": str(e), "detail": traceback.format_exc()}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -204,9 +204,8 @@ class ObtainJSONWebToken(BaseMutation):
|
|||
serializer = TokenObtainPairSerializer(data={"email": email, "password": password})
|
||||
try:
|
||||
serializer.is_valid(raise_exception=True)
|
||||
user = User.objects.get(email=email)
|
||||
return ObtainJSONWebToken(
|
||||
user=user,
|
||||
user=serializer.validated_data["user"],
|
||||
refresh_token=serializer.validated_data["refresh"],
|
||||
access_token=serializer.validated_data["access"],
|
||||
)
|
||||
|
|
@ -227,9 +226,9 @@ class RefreshJSONWebToken(BaseMutation):
|
|||
try:
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return RefreshJSONWebToken(
|
||||
user=serializer.validated_data["user"],
|
||||
access_token=serializer.validated_data["access"],
|
||||
refresh_token=serializer.validated_data["refresh"],
|
||||
user=User.objects.get(uuid=serializer.validated_data["user"]["uuid"]),
|
||||
)
|
||||
except Exception as e:
|
||||
raise PermissionDenied(f"invalid refresh token provided: {e!s}") from e
|
||||
|
|
@ -247,10 +246,8 @@ class VerifyJSONWebToken(BaseMutation):
|
|||
serializer = TokenVerifySerializer(data={"token": token})
|
||||
with suppress(Exception):
|
||||
serializer.is_valid(raise_exception=True)
|
||||
user_uuid = serializer.validated_data["user"]["uuid"]
|
||||
user = User.objects.get(pk=user_uuid)
|
||||
# noinspection PyTypeChecker
|
||||
return VerifyJSONWebToken(token_is_valid=True, user=user)
|
||||
return VerifyJSONWebToken(token_is_valid=True, user=serializer.validated_data["user"])
|
||||
detail = traceback.format_exc() if settings.DEBUG else ""
|
||||
# noinspection PyTypeChecker
|
||||
return VerifyJSONWebToken(token_is_valid=False, user=None, detail=detail)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from contextlib import suppress
|
||||
from typing import Iterable
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from asgiref.sync import sync_to_async
|
||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
||||
from drf_spectacular_websocket.decorators import extend_ws_schema
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.utils import timezone
|
||||
from drf_spectacular_websocket.decorators import extend_ws_schema
|
||||
|
||||
from engine.vibes_auth.choices import SenderType, ThreadStatus
|
||||
from engine.vibes_auth.docs.drf.messaging import (
|
||||
STAFF_INBOX_CONSUMER_SCHEMA,
|
||||
THREAD_CONSUMER_SCHEMA,
|
||||
|
|
@ -23,7 +22,6 @@ from engine.vibes_auth.messaging.services import (
|
|||
send_message,
|
||||
)
|
||||
from engine.vibes_auth.models import ChatThread, User
|
||||
from engine.vibes_auth.choices import SenderType, ThreadStatus
|
||||
|
||||
MAX_MESSAGE_LENGTH = 1028
|
||||
USER_SUPPORT_GROUP_NAME = "User Support"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from contextlib import suppress
|
||||
from typing import Optional
|
||||
|
||||
from aiogram import Bot, Dispatcher, Router, types
|
||||
from aiogram.enums import ParseMode
|
||||
|
|
@ -25,14 +22,14 @@ def is_telegram_enabled() -> bool:
|
|||
return bool(settings.TELEGRAM_TOKEN)
|
||||
|
||||
|
||||
def _get_bot() -> Optional["Bot"]:
|
||||
def _get_bot() -> Bot | None:
|
||||
if not is_telegram_enabled():
|
||||
logger.warning("Telegram forwarder disabled: missing aiogram or TELEGRAM_TOKEN")
|
||||
return None
|
||||
return Bot(token=settings.TELEGRAM_TOKEN, parse_mode=ParseMode.HTML) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def build_router() -> Optional["Router"]:
|
||||
def build_router() -> Router | None:
|
||||
if not is_telegram_enabled():
|
||||
return None
|
||||
|
||||
|
|
@ -160,7 +157,7 @@ async def forward_thread_message_to_assigned_staff(thread_uuid: str, text: str)
|
|||
if not is_telegram_enabled():
|
||||
return
|
||||
|
||||
def _resolve_chat_and_chat_id() -> tuple[Optional[int], Optional[str]]:
|
||||
def _resolve_chat_and_chat_id() -> tuple[int | None, str | None]:
|
||||
try:
|
||||
t = ChatThread.objects.select_related("assigned_to").get(uuid=thread_uuid)
|
||||
except ChatThread.DoesNotExist:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ class DRFAuthViewsTests(TestCase):
|
|||
stranger = User.objects.create_user(email="stranger@example.com", password="Str0ngPass!word", is_active=True)
|
||||
|
||||
access = str(RefreshToken.for_user(stranger).access_token)
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.client.credentials(HTTP_X_EVIBES_AUTH=f"Bearer {access}")
|
||||
|
||||
url = reverse("vibes_auth:users-upload-avatar", kwargs={"pk": owner.pk})
|
||||
|
|
@ -137,6 +138,7 @@ class DRFAuthViewsTests(TestCase):
|
|||
stranger = User.objects.create_user(email="stranger@example.com", password="Str0ngPass!word", is_active=True)
|
||||
|
||||
access = str(RefreshToken.for_user(stranger).access_token)
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.client.credentials(HTTP_X_EVIBES_AUTH=f"Bearer {access}")
|
||||
|
||||
url = reverse("vibes_auth:users-merge-recently-viewed", kwargs={"pk": owner.pk})
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@ class TokenObtainPairView(TokenViewBase):
|
|||
|
||||
serializer_class = TokenObtainPairSerializer # type: ignore [assignment]
|
||||
_serializer_class = TokenObtainPairSerializer # type: ignore [assignment]
|
||||
permission_classes: list[Type[BasePermission]] = [
|
||||
permission_classes: list[Type[BasePermission]] = [ # type: ignore [assignment]
|
||||
AllowAny,
|
||||
]
|
||||
authentication_classes: list[str] = []
|
||||
authentication_classes: list[str] = [] # type: ignore [assignment]
|
||||
|
||||
@method_decorator(ratelimit(key="ip", rate="10/h"))
|
||||
def post(self, request: Request, *args: list[Any], **kwargs: dict[Any, Any]) -> Response:
|
||||
|
|
@ -59,10 +59,10 @@ class TokenRefreshView(TokenViewBase):
|
|||
|
||||
serializer_class = TokenRefreshSerializer # type: ignore [assignment]
|
||||
_serializer_class = TokenRefreshSerializer # type: ignore [assignment]
|
||||
permission_classes: list[Type[BasePermission]] = [
|
||||
permission_classes: list[Type[BasePermission]] = [ # type: ignore [assignment]
|
||||
AllowAny,
|
||||
]
|
||||
authentication_classes: list[str] = []
|
||||
authentication_classes: list[str] = [] # type: ignore [assignment]
|
||||
|
||||
@method_decorator(ratelimit(key="ip", rate="10/h"))
|
||||
def post(self, request: Request, *args: list[Any], **kwargs: dict[Any, Any]) -> Response:
|
||||
|
|
@ -77,10 +77,10 @@ class TokenVerifyView(TokenViewBase):
|
|||
|
||||
serializer_class = TokenVerifySerializer # type: ignore [assignment]
|
||||
_serializer_class = TokenVerifySerializer # type: ignore [assignment]
|
||||
permission_classes: list[Type[BasePermission]] = [
|
||||
permission_classes: list[Type[BasePermission]] = [ # type: ignore [assignment]
|
||||
AllowAny,
|
||||
]
|
||||
authentication_classes: list[str] = []
|
||||
authentication_classes: list[str] = [] # type: ignore [assignment]
|
||||
|
||||
def post(self, request: Request, *args: list[Any], **kwargs: dict[Any, Any]) -> Response:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ class RateLimitMiddleware:
|
|||
def __call__(self, request):
|
||||
return self.get_response(request)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def process_exception(self, request, exception):
|
||||
if isinstance(exception, RatelimitedError):
|
||||
return JsonResponse(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from evibes.settings.base import EVIBES_VERSION, BASE_DOMAIN, STOREFRONT_DOMAIN
|
||||
from evibes.settings.base import EVIBES_VERSION, STOREFRONT_DOMAIN
|
||||
from evibes.settings.constance import CONSTANCE_CONFIG
|
||||
|
||||
JAZZMIN_SETTINGS = {
|
||||
|
|
@ -12,30 +12,51 @@ JAZZMIN_SETTINGS = {
|
|||
"login_logo_dark": "logo.png",
|
||||
"site_logo_classes": "",
|
||||
"site_icon": "favicon.ico",
|
||||
"welcome_sign": "Whoa! Only admins allowed here!",
|
||||
"welcome_sign": _("Whoa! Only admins allowed here!"),
|
||||
"copyright": f"eVibes {EVIBES_VERSION} by Wiseless",
|
||||
"search_model": None,
|
||||
"user_avatar": "avatar",
|
||||
"topmenu_links": [
|
||||
{"name": _("Home"), "url": "admin:index"},
|
||||
{"name": _("Storefront"), "url": f"https://{STOREFRONT_DOMAIN}", "new_window": True}, # type: ignore [index]
|
||||
{
|
||||
"name": "GraphQL Docs",
|
||||
"url": f"https://api.{BASE_DOMAIN}/graphql", # type: ignore [index]
|
||||
"new_window": True,
|
||||
"name": _("Home"),
|
||||
"url": "admin:index",
|
||||
"new_window": False,
|
||||
},
|
||||
{
|
||||
"name": "REST Docs",
|
||||
"url": f"https://api.{BASE_DOMAIN}/docs/swagger", # type: ignore [index]
|
||||
"new_window": True,
|
||||
"name": _("Storefront"),
|
||||
"url": f"https://{STOREFRONT_DOMAIN}",
|
||||
"new_window": False,
|
||||
},
|
||||
{
|
||||
"name": "GraphQL Docs",
|
||||
"url": "graphql-platform",
|
||||
"new_window": False,
|
||||
},
|
||||
{
|
||||
"name": "Swagger",
|
||||
"url": "swagger-ui-platform",
|
||||
"new_window": False,
|
||||
},
|
||||
{
|
||||
"name": "Redoc",
|
||||
"url": "redoc-ui-platform",
|
||||
"new_window": False,
|
||||
},
|
||||
{
|
||||
"name": _("Taskboard"),
|
||||
"url": "https://plane.wiseless.xyz/spaces/issues/dd33cb0ab9b04ef08a10f7eefae6d90c/?board=kanban",
|
||||
"new_window": True,
|
||||
},
|
||||
{"name": "GitLab", "url": "https://gitlab.com/wiseless/evibes", "new_window": True},
|
||||
{"name": _("Support"), "url": "https://t.me/fureunoir", "new_window": True},
|
||||
{
|
||||
"name": "GitLab",
|
||||
"url": "https://gitlab.com/wiseless/evibes",
|
||||
"new_window": True,
|
||||
},
|
||||
{
|
||||
"name": _("Support"),
|
||||
"url": "https://t.me/fureunoir",
|
||||
"new_window": True,
|
||||
},
|
||||
],
|
||||
"usermenu_links": [],
|
||||
"show_sidebar": True,
|
||||
|
|
@ -58,6 +79,6 @@ JAZZMIN_SETTINGS = {
|
|||
}
|
||||
|
||||
JAZZMIN_UI_TWEAKS = {
|
||||
"theme": "cyborg",
|
||||
"dark_mode_theme": "cyborg",
|
||||
"theme": "flatly",
|
||||
"dark_mode_theme": "darkly",
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue