Features: 1) Add RecentProductConnection to support recently viewed products in GraphQL; 2) Implement recently_viewed field in UserType with reverse-chronological product ordering; 3) Add recently_viewed field to UserSerializer and return data with ProductSimpleSerializer.

Fixes: 1) Fix `resolve_recently_viewed` to handle empty UUIDs and avoid breaking queries.

Extra: Refactor imports in `graphene/object_types.py` and `serializers.py` for better clarity; Adjust minor formatting in `TokenObtainSerializer`.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-05-26 14:54:21 +03:00
parent f76b000e07
commit 89f6594751
2 changed files with 37 additions and 6 deletions

View file

@ -3,8 +3,10 @@ from django.utils.translation import gettext_lazy as _
from graphene import Field, List, String, relay
from graphene.types.generic import GenericScalar
from graphene_django import DjangoObjectType
from graphql_relay.connection.array_connection import connection_from_array
from core.graphene.object_types import OrderType, WishlistType
from core.graphene.object_types import OrderType, ProductType, WishlistType
from core.models import Product
from evibes.settings import LANGUAGE_CODE, LANGUAGES
from payments.graphene.object_types import BalanceType
from vibes_auth.models import User
@ -26,8 +28,16 @@ class PermissionType(DjangoObjectType):
filter_fields = ["name", "id"]
class RecentProductConnection(relay.Connection):
class Meta:
node = ProductType
class UserType(DjangoObjectType):
recently_viewed = GenericScalar(description=_("recently viewed products' UUIDs"))
recently_viewed = relay.ConnectionField(
RecentProductConnection,
description=_("the products this user has viewed most recently (max 48), in reversechronological order"),
)
groups = List(lambda: GroupType, description=_("groups"))
user_permissions = List(lambda: PermissionType, description=_("permissions"))
orders = List(lambda: OrderType, description=_("orders"))
@ -89,8 +99,23 @@ class UserType(DjangoObjectType):
def resolve_orders(self, info):
return self.orders.all() if self.orders.count() >= 1 else []
def resolve_recently_viewed(self, info):
return [] or self.recently_viewed
def resolve_recently_viewed(self, info, **kwargs):
uuid_list = self.recently_viewed or []
if not uuid_list:
return connection_from_array([], kwargs)
qs = Product.objects.filter(uuid__in=uuid_list)
products_by_uuid = {str(p.uuid): p for p in qs}
ordered_products = [
products_by_uuid[u]
for u in uuid_list
if u in products_by_uuid
]
return connection_from_array(ordered_products, kwargs)
def resolve_groups(self, info):
return self.groups.all() if self.groups.count() >= 1 else []

View file

@ -21,6 +21,8 @@ from rest_framework_simplejwt.settings import api_settings
from rest_framework_simplejwt.token_blacklist.models import BlacklistedToken
from rest_framework_simplejwt.tokens import RefreshToken, Token, UntypedToken
from core.models import Product
from core.serializers import ProductSimpleSerializer
from core.utils.security import is_safe_key
from evibes import settings
from vibes_auth.models import User
@ -32,6 +34,7 @@ class UserSerializer(ModelSerializer):
avatar_url = SerializerMethodField(required=False, read_only=True)
password = CharField(write_only=True, required=False)
is_staff = BooleanField(read_only=True)
recently_viewed = SerializerMethodField(required=False, read_only=True)
@staticmethod
def get_avatar_url(obj) -> str:
@ -84,6 +87,9 @@ class UserSerializer(ModelSerializer):
validate_password(attrs["password"])
return attrs
def get_recently_viewed(self, obj) -> ProductSimpleSerializer.data:
return ProductSimpleSerializer(Product.objects.filter(uuid__in=([] or obj.recently_viewed)), many=True).data
class TokenObtainSerializer(Serializer):
username_field = User.USERNAME_FIELD
@ -177,8 +183,8 @@ class TokenVerifySerializer(Serializer):
token = UntypedToken(attrs["token"])
if (
api_settings.BLACKLIST_AFTER_ROTATION
and "rest_framework_simplejwt.token_blacklist" in settings.INSTALLED_APPS
api_settings.BLACKLIST_AFTER_ROTATION
and "rest_framework_simplejwt.token_blacklist" in settings.INSTALLED_APPS
):
jti = token.get(api_settings.JTI_CLAIM)
if BlacklistedToken.objects.filter(token__jti=jti).exists():