Features: 1) Introduce ALLOW validation for generic actions in permissions; 2) Add AllowAny permission class to seo_meta endpoints; 3) Update SEO URL patterns and breadcrumbs with language codes; 4) Improve compatibility of attributes in serializers with ListField and DictField.;
Fixes: 1) Correct dynamic URL generation for images in Open Graph (`og_image` and `logo_url`); 2) Address potential missing or empty `crumbs` handling in breadcrumbs generation; Extra: 1) Refactor redundant action names (e.g., `seo` to `seo_meta`) for better consistency; 2) Clean up and optimize serializer structures and Graphene object fields; 3) Improve readability and maintainability of action and object implementations.;
This commit is contained in:
parent
733b249643
commit
ea22ca69a3
4 changed files with 49 additions and 23 deletions
|
|
@ -554,7 +554,7 @@ class ProductType(DjangoObjectType):
|
||||||
description = (self.description or "")[:180]
|
description = (self.description or "")[:180]
|
||||||
|
|
||||||
first_img = self.images.order_by("priority").first()
|
first_img = self.images.order_by("priority").first()
|
||||||
og_image = _abs(info.context, first_img.image.url) if first_img else ""
|
og_image = graphene_abs(info.context, first_img.image.url) if first_img else ""
|
||||||
|
|
||||||
og = {
|
og = {
|
||||||
"title": title,
|
"title": title,
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,9 @@ class EvibesPermission(permissions.BasePermission):
|
||||||
app_label = model._meta.app_label
|
app_label = model._meta.app_label
|
||||||
model_name = model._meta.model_name
|
model_name = model._meta.model_name
|
||||||
|
|
||||||
|
if view.additional.get(action) == "ALLOW":
|
||||||
|
return True
|
||||||
|
|
||||||
if action == "create" and view.additional.get("create") == "ALLOW":
|
if action == "create" and view.additional.get("create") == "ALLOW":
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ class RecursiveField(Field):
|
||||||
|
|
||||||
class AddOrderProductSerializer(Serializer):
|
class AddOrderProductSerializer(Serializer):
|
||||||
product_uuid = CharField(required=True)
|
product_uuid = CharField(required=True)
|
||||||
attributes = JSONField(required=False, default=dict)
|
attributes = ListField(required=False, child=DictField(), default=list)
|
||||||
|
|
||||||
|
|
||||||
class BulkAddOrderProductsSerializer(Serializer):
|
class BulkAddOrderProductsSerializer(Serializer):
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ from drf_spectacular.utils import extend_schema_view
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.renderers import MultiPartRenderer
|
from rest_framework.renderers import MultiPartRenderer
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
@ -283,7 +284,7 @@ class CategoryViewSet(EvibesViewSet):
|
||||||
action_serializer_classes = {
|
action_serializer_classes = {
|
||||||
"list": CategorySimpleSerializer,
|
"list": CategorySimpleSerializer,
|
||||||
}
|
}
|
||||||
additional = {"seo": "ALLOW"}
|
additional = {"seo_meta": "ALLOW"}
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super().get_queryset()
|
qs = super().get_queryset()
|
||||||
|
|
@ -291,8 +292,15 @@ class CategoryViewSet(EvibesViewSet):
|
||||||
return qs
|
return qs
|
||||||
return qs.filter(is_active=True)
|
return qs.filter(is_active=True)
|
||||||
|
|
||||||
@action(detail=True, methods=["get"], url_path="seo")
|
@action(
|
||||||
def seo(self, request, **kwargs):
|
detail=True,
|
||||||
|
methods=["get"],
|
||||||
|
url_path="meta",
|
||||||
|
permission_classes=[
|
||||||
|
AllowAny,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def seo_meta(self, request, **kwargs):
|
||||||
lookup_key = getattr(self, "lookup_url_kwarg", "pk")
|
lookup_key = getattr(self, "lookup_url_kwarg", "pk")
|
||||||
lookup_val = kwargs.get(lookup_key)
|
lookup_val = kwargs.get(lookup_key)
|
||||||
|
|
||||||
|
|
@ -304,13 +312,14 @@ class CategoryViewSet(EvibesViewSet):
|
||||||
title = f"{category.name} | {config.PROJECT_NAME}"
|
title = f"{category.name} | {config.PROJECT_NAME}"
|
||||||
description = (category.description or "")[:180]
|
description = (category.description or "")[:180]
|
||||||
canonical = f"https://{config.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/catalog/{category.slug}"
|
canonical = f"https://{config.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/catalog/{category.slug}"
|
||||||
|
og_image = request.build_absolute_uri(category.image.url) if getattr(category, "image", None) else ""
|
||||||
|
|
||||||
og = {
|
og = {
|
||||||
"title": title,
|
"title": title,
|
||||||
"description": description,
|
"description": description,
|
||||||
"type": "website",
|
"type": "website",
|
||||||
"url": canonical,
|
"url": canonical,
|
||||||
"image": category.image.url if getattr(category, "image", None) else "",
|
"image": og_image,
|
||||||
}
|
}
|
||||||
tw = {"card": "summary_large_image", "title": title, "description": description}
|
tw = {"card": "summary_large_image", "title": title, "description": description}
|
||||||
|
|
||||||
|
|
@ -379,7 +388,7 @@ class BrandViewSet(EvibesViewSet):
|
||||||
action_serializer_classes = {
|
action_serializer_classes = {
|
||||||
"list": BrandSimpleSerializer,
|
"list": BrandSimpleSerializer,
|
||||||
}
|
}
|
||||||
additional = {"seo": "ALLOW"}
|
additional = {"seo_meta": "ALLOW"}
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = Brand.objects.all()
|
queryset = Brand.objects.all()
|
||||||
|
|
@ -393,8 +402,15 @@ class BrandViewSet(EvibesViewSet):
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
@action(detail=True, methods=["get"], url_path="seo")
|
@action(
|
||||||
def seo(self, request, **kwargs):
|
detail=True,
|
||||||
|
methods=["get"],
|
||||||
|
url_path="meta",
|
||||||
|
permission_classes=[
|
||||||
|
AllowAny,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def seo_meta(self, request, **kwargs):
|
||||||
lookup_key = getattr(self, "lookup_url_kwarg", "pk")
|
lookup_key = getattr(self, "lookup_url_kwarg", "pk")
|
||||||
lookup_val = kwargs.get(lookup_key)
|
lookup_val = kwargs.get(lookup_key)
|
||||||
brand = get_object_or_404(Brand, slug=str(lookup_val))
|
brand = get_object_or_404(Brand, slug=str(lookup_val))
|
||||||
|
|
@ -406,9 +422,11 @@ class BrandViewSet(EvibesViewSet):
|
||||||
canonical = f"https://{config.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/brand/{brand.slug}"
|
canonical = f"https://{config.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/brand/{brand.slug}"
|
||||||
|
|
||||||
logo_url = (
|
logo_url = (
|
||||||
brand.big_logo.url
|
request.build_absolute_uri(brand.big_logo.url)
|
||||||
if getattr(brand, "big_logo", None)
|
if getattr(brand, "big_logo", None)
|
||||||
else (brand.small_logo.url if getattr(brand, "small_logo", None) else None)
|
else request.build_absolute_uri(brand.small_logo.url)
|
||||||
|
if getattr(brand, "small_logo", None)
|
||||||
|
else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
og = {
|
og = {
|
||||||
|
|
@ -416,7 +434,7 @@ class BrandViewSet(EvibesViewSet):
|
||||||
"description": description,
|
"description": description,
|
||||||
"type": "website",
|
"type": "website",
|
||||||
"url": canonical,
|
"url": canonical,
|
||||||
"image": logo_url or "",
|
"image": logo_url,
|
||||||
}
|
}
|
||||||
tw = {"card": "summary_large_image", "title": title, "description": description}
|
tw = {"card": "summary_large_image", "title": title, "description": description}
|
||||||
|
|
||||||
|
|
@ -479,7 +497,7 @@ class ProductViewSet(EvibesViewSet):
|
||||||
}
|
}
|
||||||
lookup_field = "lookup_value"
|
lookup_field = "lookup_value"
|
||||||
lookup_url_kwarg = "lookup_value"
|
lookup_url_kwarg = "lookup_value"
|
||||||
additional = {"seo": "ALLOW"}
|
additional = {"seo_meta": "ALLOW"}
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super().get_queryset()
|
qs = super().get_queryset()
|
||||||
|
|
@ -530,30 +548,35 @@ class ProductViewSet(EvibesViewSet):
|
||||||
name = "Product"
|
name = "Product"
|
||||||
return Response(status=status.HTTP_404_NOT_FOUND, data={"detail": _(f"{name} does not exist: {uuid}")})
|
return Response(status=status.HTTP_404_NOT_FOUND, data={"detail": _(f"{name} does not exist: {uuid}")})
|
||||||
|
|
||||||
@action(detail=True, methods=["get"], url_path="seo")
|
@action(
|
||||||
def seo(self, request, slug):
|
detail=True,
|
||||||
p = get_object_or_404(Product.objects.select_related("brand", "category"), slug=slug)
|
methods=["get"],
|
||||||
|
url_path="meta",
|
||||||
|
permission_classes=[
|
||||||
|
AllowAny,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def seo_meta(self, request, **kwargs):
|
||||||
|
p = self.get_object()
|
||||||
images = list(p.images.all()[:6])
|
images = list(p.images.all()[:6])
|
||||||
rating = {"value": p.rating, "count": p.feedbacks_count}
|
rating = {"value": p.rating, "count": p.feedbacks_count}
|
||||||
title = f"{p.name} | {config.PROJECT_NAME}"
|
title = f"{p.name} | {config.PROJECT_NAME}"
|
||||||
description = (p.description or "")[:180]
|
description = (p.description or "")[:180]
|
||||||
canonical = f"https://{config.BASE_DOMAIN}/products/{p.slug}"
|
canonical = f"https://{config.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/product/{p.slug}"
|
||||||
|
|
||||||
og = {
|
og = {
|
||||||
"title": title,
|
"title": title,
|
||||||
"description": description,
|
"description": description,
|
||||||
"type": "product",
|
"type": "product",
|
||||||
"url": canonical,
|
"url": canonical,
|
||||||
"image": images[0].image.url if images else "",
|
"image": request.build_absolute_uri(images[0].image.url) if images else "",
|
||||||
}
|
}
|
||||||
tw = {"card": "summary_large_image", "title": title, "description": description}
|
tw = {"card": "summary_large_image", "title": title, "description": description}
|
||||||
|
|
||||||
crumbs = []
|
crumbs = [("Home", f"https://{config.BASE_DOMAIN}/")]
|
||||||
if p.category:
|
if p.category:
|
||||||
crumbs.append(("Home", f"https://{config.BASE_DOMAIN}/"))
|
|
||||||
for c in p.category.get_ancestors(include_self=True):
|
for c in p.category.get_ancestors(include_self=True):
|
||||||
crumbs.append((c.name, f"https://{config.BASE_DOMAIN}/c/{c.slug}"))
|
crumbs.append((c.name, f"https://{config.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/catalog/{c.slug}"))
|
||||||
crumbs.append((p.name, canonical))
|
crumbs.append((p.name, canonical))
|
||||||
|
|
||||||
json_ld = [org_schema(), website_schema()]
|
json_ld = [org_schema(), website_schema()]
|
||||||
if crumbs:
|
if crumbs:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue