Features:
1) Enhance lookup logic to support UUID validation for products in viewsets; 2) Add extensive filtering, sorting, and attributes documentation for product endpoints; 3) Define new OpenAPI parameters for querying products with detailed constraints. Fixes: 1) Add missing import for `UUID` in core viewsets. Extra: 1) Refactor and reorganize product API schema for clarity and consistency.
This commit is contained in:
parent
9941b45bd3
commit
f5c1d64d46
2 changed files with 67 additions and 39 deletions
|
|
@ -256,9 +256,51 @@ WISHLIST_SCHEMA = {
|
|||
),
|
||||
}
|
||||
|
||||
ATTRIBUTES_DESC = _(
|
||||
"Filter by one or more attribute name/value pairs. \n"
|
||||
"• **Syntax**: `attr_name=method-value[;attr2=method2-value2]…` \n"
|
||||
"• **Methods** (defaults to `icontains` if omitted): "
|
||||
"`iexact`, `exact`, `icontains`, `contains`, `isnull`, "
|
||||
"`startswith`, `istartswith`, `endswith`, `iendswith`, "
|
||||
"`regex`, `iregex`, `lt`, `lte`, `gt`, `gte`, `in` \n"
|
||||
"• **Value typing**: JSON is attempted first (so you can pass lists/dicts), "
|
||||
"`true`/`false` for booleans, integers, floats; otherwise treated as string. \n"
|
||||
"• **Base64**: prefix with `b64-` to URL-safe base64-encode the raw value. \n"
|
||||
"Examples: \n"
|
||||
"`color=exact-red`, `size=gt-10`, `features=in-[\"wifi\",\"bluetooth\"]`, \n"
|
||||
"`b64-description=icontains-aGVhdC1jb2xk`"
|
||||
)
|
||||
|
||||
PRODUCT_SCHEMA = {
|
||||
"list": extend_schema(
|
||||
summary=_("list all products (simple view)"),
|
||||
parameters=[
|
||||
OpenApiParameter("uuid", _("(exact) Product UUID"), OpenApiParameter.QUERY, type=str),
|
||||
OpenApiParameter("name", _("(icontains) Product name"), OpenApiParameter.QUERY, type=str),
|
||||
OpenApiParameter("categories", _("(list) Category names, case-insensitive"), OpenApiParameter.QUERY,
|
||||
type=str),
|
||||
OpenApiParameter("category_uuid", _("(exact) Category UUID"), OpenApiParameter.QUERY, type=str),
|
||||
OpenApiParameter("tags", _("(list) Tag names, case-insensitive"), OpenApiParameter.QUERY, type=str),
|
||||
OpenApiParameter("min_price", _("(gte) Minimum stock price"), OpenApiParameter.QUERY, type=float),
|
||||
OpenApiParameter("max_price", _("(lte) Maximum stock price"), OpenApiParameter.QUERY, type=float),
|
||||
OpenApiParameter("is_active", _("(exact) Only active products"), OpenApiParameter.QUERY, type=bool),
|
||||
OpenApiParameter("brand", _("(iexact) Brand name"), OpenApiParameter.QUERY, type=str),
|
||||
OpenApiParameter("attributes", ATTRIBUTES_DESC, OpenApiParameter.QUERY, type=str),
|
||||
OpenApiParameter("quantity", _("(gt) Minimum stock quantity"), OpenApiParameter.QUERY, type=int),
|
||||
OpenApiParameter("slug", _("(exact) Product slug"), OpenApiParameter.QUERY, type=str),
|
||||
OpenApiParameter("is_digital", _("(exact) Digital vs. physical"), OpenApiParameter.QUERY, type=bool),
|
||||
OpenApiParameter(
|
||||
name="order_by",
|
||||
description=_(
|
||||
"Comma-separated list of fields to sort by. "
|
||||
"Prefix with `-` for descending. \n"
|
||||
"**Allowed:** uuid, rating, name, slug, created, modified, price, random"
|
||||
),
|
||||
required=False,
|
||||
type=str,
|
||||
location=OpenApiParameter.QUERY,
|
||||
),
|
||||
],
|
||||
responses={
|
||||
status.HTTP_200_OK: ProductSimpleSerializer(many=True),
|
||||
**BASE_ERRORS,
|
||||
|
|
@ -266,46 +308,26 @@ PRODUCT_SCHEMA = {
|
|||
),
|
||||
"retrieve": extend_schema(
|
||||
summary=_("retrieve a single product (detailed view)"),
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="lookup",
|
||||
description=_("Product UUID or slug"),
|
||||
required=True,
|
||||
type=str,
|
||||
location=OpenApiParameter.PATH,
|
||||
),
|
||||
],
|
||||
responses={
|
||||
status.HTTP_200_OK: ProductDetailSerializer,
|
||||
**BASE_ERRORS,
|
||||
},
|
||||
parameters=[OpenApiParameter("lookup", _("Product UUID or slug"), OpenApiParameter.PATH, type=str)],
|
||||
responses={status.HTTP_200_OK: ProductDetailSerializer, **BASE_ERRORS},
|
||||
),
|
||||
"create": extend_schema(
|
||||
summary=_("create a product"),
|
||||
responses={
|
||||
status.HTTP_201_CREATED: ProductDetailSerializer,
|
||||
**BASE_ERRORS,
|
||||
},
|
||||
),
|
||||
"destroy": extend_schema(
|
||||
summary=_("delete a product"),
|
||||
responses={
|
||||
status.HTTP_204_NO_CONTENT: {},
|
||||
**BASE_ERRORS,
|
||||
},
|
||||
responses={status.HTTP_201_CREATED: ProductDetailSerializer, **BASE_ERRORS},
|
||||
),
|
||||
"update": extend_schema(
|
||||
summary=_("rewrite an existing product, preserving non-editable fields"),
|
||||
responses={
|
||||
status.HTTP_200_OK: ProductDetailSerializer,
|
||||
**BASE_ERRORS,
|
||||
},
|
||||
parameters=[OpenApiParameter("lookup", _("Product UUID or slug"), OpenApiParameter.PATH, type=str)],
|
||||
responses={status.HTTP_200_OK: ProductDetailSerializer, **BASE_ERRORS},
|
||||
),
|
||||
"partial_update": extend_schema(
|
||||
summary=_("update some fields of an existing product, preserving non-editable fields"),
|
||||
responses={
|
||||
status.HTTP_200_OK: ProductDetailSerializer,
|
||||
**BASE_ERRORS,
|
||||
},
|
||||
parameters=[OpenApiParameter("lookup", _("Product UUID or slug"), OpenApiParameter.PATH, type=str)],
|
||||
responses={status.HTTP_200_OK: ProductDetailSerializer, **BASE_ERRORS},
|
||||
),
|
||||
"destroy": extend_schema(
|
||||
summary=_("delete a product"),
|
||||
parameters=[OpenApiParameter("lookup", _("Product UUID or slug"), OpenApiParameter.PATH, type=str)],
|
||||
responses={status.HTTP_204_NO_CONTENT: {}, **BASE_ERRORS},
|
||||
),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from uuid import UUID
|
||||
|
||||
from django.http import Http404
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
|
|
@ -168,13 +170,17 @@ class ProductViewSet(EvibesViewSet):
|
|||
queryset = self.filter_queryset(self.get_queryset())
|
||||
lookup_value = self.kwargs[self.lookup_url_kwarg]
|
||||
|
||||
obj = (
|
||||
queryset.filter(uuid=lookup_value)
|
||||
.first()
|
||||
or
|
||||
queryset.filter(slug=lookup_value)
|
||||
.first()
|
||||
)
|
||||
obj = None
|
||||
|
||||
try:
|
||||
uuid_obj = UUID(lookup_value)
|
||||
obj = queryset.filter(uuid=uuid_obj).first()
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if not obj:
|
||||
obj = queryset.filter(slug=lookup_value).first()
|
||||
|
||||
if not obj:
|
||||
raise Http404(f"No Product found matching uuid or slug '{lookup_value}'")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue