Features: 1) Add async and sync capabilities to CamelCaseMiddleWare; 2) Include OpenAPI support for Enum name overrides in DRF settings; 3) Integrate OpenAPI types in DRF views for improved schema accuracy.

Fixes: 1) Correct `lookup_field` to `uuid` in various viewsets; 2) Replace `type=str` with `OpenApiTypes.STR` in path parameters of multiple DRF endpoints; 3) Add missing import `iscoroutinefunction` and `markcoroutinefunction`.

Extra: 1) Refactor `__call__` method in `CamelCaseMiddleWare` to separate sync and async logic; 2) Enhance documentation schema responses with precise types in multiple DRF views.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-12-19 17:27:36 +03:00
parent dc7f8be926
commit 29fb56be89
5 changed files with 41 additions and 17 deletions

View file

@ -1,6 +1,6 @@
from django.conf import settings
from django.http import FileResponse
from django.utils.translation import gettext_lazy as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema, inline_serializer
from rest_framework import status
from rest_framework.fields import CharField, DictField, JSONField, ListField
@ -32,6 +32,9 @@ CUSTOM_OPENAPI_SCHEMA = {
"OpenApi3 schema for this API. Format can be selected via content negotiation. "
"Language can be selected with Accept-Language and query parameter both."
),
responses={
status.HTTP_200_OK: OpenApiTypes.OBJECT,
},
)
}
@ -176,7 +179,7 @@ DOWNLOAD_DIGITAL_ASSET_SCHEMA = {
],
summary=_("download a digital asset from purchased digital order"),
responses={
status.HTTP_200_OK: FileResponse,
status.HTTP_200_OK: OpenApiTypes.BINARY,
status.HTTP_400_BAD_REQUEST: error,
},
)

View file

@ -234,7 +234,7 @@ CATEGORY_SCHEMA = {
name="lookup_value",
location="path",
description=_("Category UUID or slug"),
type=str,
type=OpenApiTypes.STR,
),
],
responses={status.HTTP_200_OK: CategoryDetailSerializer(), **BASE_ERRORS},
@ -284,7 +284,7 @@ CATEGORY_SCHEMA = {
name="lookup_value",
location="path",
description=_("Category UUID or slug"),
type=str,
type=OpenApiTypes.STR,
),
],
responses={
@ -369,7 +369,7 @@ ORDER_SCHEMA = {
name="lookup_value",
location="path",
description=_("Order UUID or human-readable id"),
type=str,
type=OpenApiTypes.STR,
),
],
responses={status.HTTP_200_OK: OrderDetailSerializer(), **BASE_ERRORS},
@ -1006,7 +1006,7 @@ BRAND_SCHEMA = {
name="lookup_value",
location="path",
description=_("Brand UUID or slug"),
type=str,
type=OpenApiTypes.STR,
),
],
responses={status.HTTP_200_OK: BrandDetailSerializer(), **BASE_ERRORS},
@ -1049,7 +1049,7 @@ BRAND_SCHEMA = {
name="lookup_value",
location="path",
description=_("Brand UUID or slug"),
type=str,
type=OpenApiTypes.STR,
),
],
responses={status.HTTP_200_OK: SeoSnapshotSerializer(), **BASE_ERRORS},

View file

@ -228,7 +228,7 @@ class CategoryViewSet(EvibesViewSet):
action_serializer_classes = {
"list": CategorySimpleSerializer,
}
lookup_field = "lookup_value"
lookup_field = "uuid"
lookup_url_kwarg = "lookup_value"
additional = {"seo_meta": "ALLOW"}
@ -356,7 +356,7 @@ class BrandViewSet(EvibesViewSet):
action_serializer_classes = {
"list": BrandSimpleSerializer,
}
lookup_field = "lookup_value"
lookup_field = "uuid"
lookup_url_kwarg = "lookup_value"
additional = {"seo_meta": "ALLOW"}
@ -658,7 +658,7 @@ class OrderViewSet(EvibesViewSet):
"performed and enforces permissions accordingly while interacting with order data."
)
lookup_field = "lookup_value"
lookup_field = "uuid"
lookup_url_kwarg = "lookup_value"
queryset = Order.objects.prefetch_related("order_products").all()
filter_backends = [DjangoFilterBackend]
@ -690,7 +690,7 @@ class OrderViewSet(EvibesViewSet):
return qs.filter(user=user)
def get_object(self):
lookup_val = self.kwargs[self.lookup_field]
lookup_val = self.kwargs[self.lookup_url_kwarg]
qs = self.get_queryset()
try:

View file

@ -3,6 +3,7 @@ import traceback
from os import getenv
from typing import Any, Callable, cast
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import (
BadRequest,
@ -81,11 +82,11 @@ class GrapheneJWTAuthorizationMiddleware:
return next(root, info, **args)
@staticmethod
def get_jwt_user(request: HttpRequest) -> "User" | AnonymousUser:
def get_jwt_user(request: HttpRequest) -> User | AnonymousUser:
jwt_authenticator = JWTAuthentication()
try:
user_obj, _ = jwt_authenticator.authenticate(request) # type: ignore[assignment]
user: "User" | AnonymousUser = cast(User, user_obj)
user: User | AnonymousUser = cast(User, user_obj)
except InvalidToken:
user = AnonymousUser()
except TypeError:
@ -175,10 +176,27 @@ class RateLimitMiddleware:
class CamelCaseMiddleWare:
async_capable = True
sync_capable = True
def __init__(self, get_response):
self.get_response = get_response
if iscoroutinefunction(get_response):
markcoroutinefunction(self) # ty:ignore[invalid-argument-type]
def __call__(self, request):
async def __call__(self, request):
if iscoroutinefunction(self.get_response):
self._underscoreize_request(request)
response = await self.get_response(request)
return response
return self._sync_call(request)
def _sync_call(self, request):
self._underscoreize_request(request)
response = self.get_response(request)
return response
def _underscoreize_request(self, request):
underscoreized_get = underscoreize(
{k: v for k, v in request.GET.lists()},
**JSON_UNDERSCOREIZE,
@ -194,6 +212,3 @@ class CamelCaseMiddleWare:
new_get._mutable = False
request.GET = new_get
response = self.get_response(request)
return response

View file

@ -143,4 +143,10 @@ SPECTACULAR_SETTINGS = {
"email": "contact@fureunoir.com",
"URL": "https://t.me/fureunoir",
},
"ENUM_NAME_OVERRIDES": {
"OrderStatusEnum": "engine.core.choices.ORDER_STATUS_CHOICES",
"OrderProductStatusEnum": "engine.core.choices.ORDER_PRODUCT_STATUS_CHOICES",
"TransactionStatusEnum": "engine.core.choices.TRANSACTION_STATUS_CHOICES",
"ThreadStatusEnum": "engine.vibes_auth.choices.ThreadStatus",
},
}