From 368eac5406427e1d31f70c0b9b50c21bca5d554d Mon Sep 17 00:00:00 2001 From: TimeTGame Date: Thu, 27 Nov 2025 12:45:07 +0300 Subject: [PATCH 1/2] feat: Add a view of all parts of the API --- NoobRPG/NoobRPG/urls.py | 7 +++-- NoobRPG/entities/serializers.py | 39 --------------------------- NoobRPG/items/models.py | 14 ++++++++++ NoobRPG/items/serializers.py | 17 ++++++++++++ NoobRPG/items/urls.py | 11 ++++++++ NoobRPG/items/views.py | 26 ++++++++++++++++++ NoobRPG/locations/serializers.py | 1 + NoobRPG/locations/urls.py | 2 +- NoobRPG/locations/views.py | 15 +++++++++++ NoobRPG/rarity/serializers.py | 15 +++++++++++ NoobRPG/rarity/urls.py | 11 ++++++++ NoobRPG/rarity/views.py | 26 ++++++++++++++++++ NoobRPG/sellers_offers/models.py | 14 ++++++++++ NoobRPG/sellers_offers/serializers.py | 15 +++++++++++ NoobRPG/sellers_offers/urls.py | 11 ++++++++ NoobRPG/sellers_offers/views.py | 26 ++++++++++++++++++ 16 files changed, 208 insertions(+), 42 deletions(-) create mode 100644 NoobRPG/items/serializers.py create mode 100644 NoobRPG/items/urls.py create mode 100644 NoobRPG/items/views.py create mode 100644 NoobRPG/rarity/serializers.py create mode 100644 NoobRPG/rarity/urls.py create mode 100644 NoobRPG/rarity/views.py create mode 100644 NoobRPG/sellers_offers/serializers.py create mode 100644 NoobRPG/sellers_offers/urls.py create mode 100644 NoobRPG/sellers_offers/views.py diff --git a/NoobRPG/NoobRPG/urls.py b/NoobRPG/NoobRPG/urls.py index 5fde70d..cf7c7a5 100644 --- a/NoobRPG/NoobRPG/urls.py +++ b/NoobRPG/NoobRPG/urls.py @@ -4,6 +4,9 @@ urlpatterns = [ path('admin/', admin.site.urls), - path('entities/', include('entities.urls')), - path('locations/', include('locations.urls')), + path('api/v1/entities/', include('entities.urls')), + path('api/v1/items/', include('items.urls')), + path('api/v1/locations/', include('locations.urls')), + path('api/v1/rarity/', include('rarity.urls')), + path('api/v1/sellers_offers/', include('sellers_offers.urls')), ] diff --git a/NoobRPG/entities/serializers.py b/NoobRPG/entities/serializers.py index 268e3a1..7fe17eb 100644 --- a/NoobRPG/entities/serializers.py +++ b/NoobRPG/entities/serializers.py @@ -17,19 +17,6 @@ class Meta: Seller.offers.field.name, ] - def build_field(self, field_name, info, model_class, nested_depth): - field_class, field_kwargs = super().build_field( - field_name, - info, - model_class, - nested_depth, - ) - - if field_name == 'url': - field_kwargs['view_name'] = 'sellers-detail' - - return field_class, field_kwargs - class NPCSerializer(serializers.HyperlinkedModelSerializer): items_to_drop = serializers.StringRelatedField(many=True, read_only=True) @@ -50,19 +37,6 @@ class Meta: NPCModel.items_to_drop.field.name, ] - def build_field(self, field_name, info, model_class, nested_depth): - field_class, field_kwargs = super().build_field( - field_name, - info, - model_class, - nested_depth, - ) - - if field_name == 'url': - field_kwargs['view_name'] = 'npcs-detail' - - return field_class, field_kwargs - class PlayerSerializer(serializers.HyperlinkedModelSerializer): inventory = serializers.StringRelatedField(many=True, read_only=True) @@ -85,16 +59,3 @@ class Meta: Player.weapon.field.name, Player.start_location.field.name, ] - - def build_field(self, field_name, info, model_class, nested_depth): - field_class, field_kwargs = super().build_field( - field_name, - info, - model_class, - nested_depth, - ) - - if field_name == 'url': - field_kwargs['view_name'] = 'players-detail' - - return field_class, field_kwargs diff --git a/NoobRPG/items/models.py b/NoobRPG/items/models.py index 04406ef..ad89d7a 100644 --- a/NoobRPG/items/models.py +++ b/NoobRPG/items/models.py @@ -4,7 +4,21 @@ from rarity.models import Rarity +class ItemManager(models.Manager): + def all_fields(self) -> models.QuerySet: + queryset = ( + self.get_queryset() + .select_related( + Items.rarity.field.name, + ) + ) + return queryset.order_by( + Items.id.field.name, + ) + + class Items(models.Model): + objects = ItemManager() name = models.CharField( 'item name', max_length=100, diff --git a/NoobRPG/items/serializers.py b/NoobRPG/items/serializers.py new file mode 100644 index 0000000..6b62ba4 --- /dev/null +++ b/NoobRPG/items/serializers.py @@ -0,0 +1,17 @@ +__all__ = () + +from items.models import Items +from rest_framework import serializers + + +class ItemSerializer(serializers.HyperlinkedModelSerializer): + + class Meta: + model = Items + fields = [ + 'url', + Items.id.field.name, + Items.name.field.name, + Items.rarity.field.name, + Items.damage.field.name, + ] diff --git a/NoobRPG/items/urls.py b/NoobRPG/items/urls.py new file mode 100644 index 0000000..8cf0824 --- /dev/null +++ b/NoobRPG/items/urls.py @@ -0,0 +1,11 @@ +from django.urls import include, path +from items import views +from rest_framework import routers + + +router = routers.DefaultRouter() +router.register(r'', views.ItemViewSet, basename='items') + +urlpatterns = [ + path('', include(router.urls)), +] diff --git a/NoobRPG/items/views.py b/NoobRPG/items/views.py new file mode 100644 index 0000000..04c770c --- /dev/null +++ b/NoobRPG/items/views.py @@ -0,0 +1,26 @@ +__all__ = () + +from core.functions import get_filterable_fields +from django.core.exceptions import FieldError +from items.models import Items +from items.serializers import ItemSerializer +from rest_framework import permissions, viewsets + + +class ItemViewSet(viewsets.ModelViewSet): + queryset = Items.objects.all_fields() + serializer_class = ItemSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + queryset = Items.objects.all_fields() + allowed_params = get_filterable_fields(Items, depth=1) + + for param, value in self.request.query_params.items(): + if param in allowed_params: + try: + queryset = queryset.filter(**{param: value}) + except (TypeError, ValueError, FieldError): + continue + + return queryset diff --git a/NoobRPG/locations/serializers.py b/NoobRPG/locations/serializers.py index 23b0a04..0c835d3 100644 --- a/NoobRPG/locations/serializers.py +++ b/NoobRPG/locations/serializers.py @@ -8,6 +8,7 @@ class LocationSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Location fields = [ + 'url', Location.id.field.name, Location.name.field.name, Location.slug.field.name, diff --git a/NoobRPG/locations/urls.py b/NoobRPG/locations/urls.py index 6167687..18462c9 100644 --- a/NoobRPG/locations/urls.py +++ b/NoobRPG/locations/urls.py @@ -4,7 +4,7 @@ router = routers.DefaultRouter() -router.register(r'locations', views.LocationViewSet) +router.register(r'', views.LocationViewSet, basename='locations') urlpatterns = [ path('', include(router.urls)), diff --git a/NoobRPG/locations/views.py b/NoobRPG/locations/views.py index 6f2fe81..22f8090 100644 --- a/NoobRPG/locations/views.py +++ b/NoobRPG/locations/views.py @@ -1,5 +1,7 @@ __all__ = () +from core.functions import get_filterable_fields +from django.core.exceptions import FieldError from locations.models import Location from locations.serializers import LocationSerializer from rest_framework import permissions, viewsets @@ -9,3 +11,16 @@ class LocationViewSet(viewsets.ModelViewSet): queryset = Location.objects.all() serializer_class = LocationSerializer permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + queryset = Location.objects.all() + allowed_params = get_filterable_fields(Location, depth=1) + + for param, value in self.request.query_params.items(): + if param in allowed_params: + try: + queryset = queryset.filter(**{param: value}) + except (TypeError, ValueError, FieldError): + continue + + return queryset diff --git a/NoobRPG/rarity/serializers.py b/NoobRPG/rarity/serializers.py new file mode 100644 index 0000000..d7bb887 --- /dev/null +++ b/NoobRPG/rarity/serializers.py @@ -0,0 +1,15 @@ +__all__ = () + +from rarity.models import Rarity +from rest_framework import serializers + + +class RaritySerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Rarity + fields = [ + 'url', + Rarity.id.field.name, + Rarity.name.field.name, + Rarity.slug.field.name, + ] diff --git a/NoobRPG/rarity/urls.py b/NoobRPG/rarity/urls.py new file mode 100644 index 0000000..22a4afb --- /dev/null +++ b/NoobRPG/rarity/urls.py @@ -0,0 +1,11 @@ +from django.urls import include, path +from rarity import views +from rest_framework import routers + + +router = routers.DefaultRouter() +router.register(r'', views.RarityViewSet, basename='rarity') + +urlpatterns = [ + path('', include(router.urls)), +] diff --git a/NoobRPG/rarity/views.py b/NoobRPG/rarity/views.py new file mode 100644 index 0000000..370dc12 --- /dev/null +++ b/NoobRPG/rarity/views.py @@ -0,0 +1,26 @@ +__all__ = () + +from core.functions import get_filterable_fields +from django.core.exceptions import FieldError +from rarity.models import Rarity +from rarity.serializers import RaritySerializer +from rest_framework import permissions, viewsets + + +class RarityViewSet(viewsets.ModelViewSet): + queryset = Rarity.objects.all() + serializer_class = RaritySerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + queryset = Rarity.objects.all() + allowed_params = get_filterable_fields(Rarity, depth=1) + + for param, value in self.request.query_params.items(): + if param in allowed_params: + try: + queryset = queryset.filter(**{param: value}) + except (TypeError, ValueError, FieldError): + continue + + return queryset diff --git a/NoobRPG/sellers_offers/models.py b/NoobRPG/sellers_offers/models.py index 7ef3545..1bce1c2 100644 --- a/NoobRPG/sellers_offers/models.py +++ b/NoobRPG/sellers_offers/models.py @@ -4,7 +4,21 @@ from items.models import Items +class SellerOfferManager(models.Manager): + def all_fields(self) -> models.QuerySet: + queryset = ( + self.get_queryset() + .select_related( + SellerOffer.item.field.name, + ) + ) + return queryset.order_by( + SellerOffer.id.field.name, + ) + + class SellerOffer(models.Model): + objects = SellerOfferManager() item = models.ForeignKey( Items, on_delete=models.CASCADE, diff --git a/NoobRPG/sellers_offers/serializers.py b/NoobRPG/sellers_offers/serializers.py new file mode 100644 index 0000000..09bb9b7 --- /dev/null +++ b/NoobRPG/sellers_offers/serializers.py @@ -0,0 +1,15 @@ +__all__ = () + +from rest_framework import serializers +from sellers_offers.models import SellerOffer + + +class SellerOfferSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = SellerOffer + fields = [ + 'url', + SellerOffer.id.field.name, + SellerOffer.item.field.name, + SellerOffer.cost.field.name, + ] diff --git a/NoobRPG/sellers_offers/urls.py b/NoobRPG/sellers_offers/urls.py new file mode 100644 index 0000000..b3df250 --- /dev/null +++ b/NoobRPG/sellers_offers/urls.py @@ -0,0 +1,11 @@ +from django.urls import include, path +from rest_framework import routers +from sellers_offers import views + + +router = routers.DefaultRouter() +router.register(r'', views.SellerOfferViewSet, basename='sellers-offers') + +urlpatterns = [ + path('', include(router.urls)), +] diff --git a/NoobRPG/sellers_offers/views.py b/NoobRPG/sellers_offers/views.py new file mode 100644 index 0000000..0022c8c --- /dev/null +++ b/NoobRPG/sellers_offers/views.py @@ -0,0 +1,26 @@ +__all__ = () + +from core.functions import get_filterable_fields +from django.core.exceptions import FieldError +from rest_framework import permissions, viewsets +from sellers_offers.models import SellerOffer +from sellers_offers.serializers import SellerOfferSerializer + + +class SellerOfferViewSet(viewsets.ModelViewSet): + queryset = SellerOffer.objects.all_fields() + serializer_class = SellerOfferSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + queryset = SellerOffer.objects.all_fields() + allowed_params = get_filterable_fields(SellerOffer, depth=1) + + for param, value in self.request.query_params.items(): + if param in allowed_params: + try: + queryset = queryset.filter(**{param: value}) + except (TypeError, ValueError, FieldError): + continue + + return queryset From b1be5a1a5c8786106cbb3562237a5aad9c7552f8 Mon Sep 17 00:00:00 2001 From: TimeTGame Date: Thu, 27 Nov 2025 13:21:48 +0300 Subject: [PATCH 2/2] fix: Fixed errors in tests and for pipeline compliance --- NoobRPG/entities/serializers.py | 39 +++++++++++++++++++++++++++ NoobRPG/entities/tests.py | 6 ++--- NoobRPG/items/models.py | 7 ++--- NoobRPG/items/serializers.py | 13 +++++++++ NoobRPG/items/urls.py | 2 +- NoobRPG/locations/serializers.py | 13 +++++++++ NoobRPG/locations/urls.py | 2 +- NoobRPG/rarity/serializers.py | 13 +++++++++ NoobRPG/sellers_offers/models.py | 7 ++--- NoobRPG/sellers_offers/serializers.py | 13 +++++++++ 10 files changed, 100 insertions(+), 15 deletions(-) diff --git a/NoobRPG/entities/serializers.py b/NoobRPG/entities/serializers.py index 7fe17eb..268e3a1 100644 --- a/NoobRPG/entities/serializers.py +++ b/NoobRPG/entities/serializers.py @@ -17,6 +17,19 @@ class Meta: Seller.offers.field.name, ] + def build_field(self, field_name, info, model_class, nested_depth): + field_class, field_kwargs = super().build_field( + field_name, + info, + model_class, + nested_depth, + ) + + if field_name == 'url': + field_kwargs['view_name'] = 'sellers-detail' + + return field_class, field_kwargs + class NPCSerializer(serializers.HyperlinkedModelSerializer): items_to_drop = serializers.StringRelatedField(many=True, read_only=True) @@ -37,6 +50,19 @@ class Meta: NPCModel.items_to_drop.field.name, ] + def build_field(self, field_name, info, model_class, nested_depth): + field_class, field_kwargs = super().build_field( + field_name, + info, + model_class, + nested_depth, + ) + + if field_name == 'url': + field_kwargs['view_name'] = 'npcs-detail' + + return field_class, field_kwargs + class PlayerSerializer(serializers.HyperlinkedModelSerializer): inventory = serializers.StringRelatedField(many=True, read_only=True) @@ -59,3 +85,16 @@ class Meta: Player.weapon.field.name, Player.start_location.field.name, ] + + def build_field(self, field_name, info, model_class, nested_depth): + field_class, field_kwargs = super().build_field( + field_name, + info, + model_class, + nested_depth, + ) + + if field_name == 'url': + field_kwargs['view_name'] = 'players-detail' + + return field_class, field_kwargs diff --git a/NoobRPG/entities/tests.py b/NoobRPG/entities/tests.py index 5b08e31..e4990b4 100644 --- a/NoobRPG/entities/tests.py +++ b/NoobRPG/entities/tests.py @@ -30,7 +30,7 @@ def setUp(self): def test_npc_list_without_query_param(self): view = NPCsViewSet.as_view({'get': 'list'}) - request = self.factory.get('/entities/npcs/') + request = self.factory.get('/api/v1/entities/npcs/') force_authenticate(request, user=self.user) response = view(request) @@ -40,7 +40,7 @@ def test_npc_list_without_query_param(self): def test_npc_list_with_location_query_param(self): view = NPCsViewSet.as_view({'get': 'list'}) request = self.factory.get( - '/entities/npcs/?current_location__slug=forest', + '/api/v1/entities/npcs/?current_location__slug=forest', ) force_authenticate(request, user=self.user) response = view(request) @@ -52,7 +52,7 @@ def test_npc_list_with_location_query_param(self): def test_npc_list_with_nonexistent_location(self): view = NPCsViewSet.as_view({'get': 'list'}) request = self.factory.get( - '/entities/npcs/?current_location__slug=nonexistent', + '/api/v1/entities/npcs/?current_location__slug=nonexistent', ) force_authenticate(request, user=self.user) response = view(request) diff --git a/NoobRPG/items/models.py b/NoobRPG/items/models.py index ad89d7a..cd7f28f 100644 --- a/NoobRPG/items/models.py +++ b/NoobRPG/items/models.py @@ -6,11 +6,8 @@ class ItemManager(models.Manager): def all_fields(self) -> models.QuerySet: - queryset = ( - self.get_queryset() - .select_related( - Items.rarity.field.name, - ) + queryset = self.get_queryset().select_related( + Items.rarity.field.name, ) return queryset.order_by( Items.id.field.name, diff --git a/NoobRPG/items/serializers.py b/NoobRPG/items/serializers.py index 6b62ba4..6d8c5fa 100644 --- a/NoobRPG/items/serializers.py +++ b/NoobRPG/items/serializers.py @@ -15,3 +15,16 @@ class Meta: Items.rarity.field.name, Items.damage.field.name, ] + + def build_field(self, field_name, info, model_class, nested_depth): + field_class, field_kwargs = super().build_field( + field_name, + info, + model_class, + nested_depth, + ) + + if field_name == 'url': + field_kwargs['view_name'] = 'items-detail' + + return field_class, field_kwargs diff --git a/NoobRPG/items/urls.py b/NoobRPG/items/urls.py index 8cf0824..b33083d 100644 --- a/NoobRPG/items/urls.py +++ b/NoobRPG/items/urls.py @@ -7,5 +7,5 @@ router.register(r'', views.ItemViewSet, basename='items') urlpatterns = [ - path('', include(router.urls)), + path('items/', include(router.urls)), ] diff --git a/NoobRPG/locations/serializers.py b/NoobRPG/locations/serializers.py index 0c835d3..62436c5 100644 --- a/NoobRPG/locations/serializers.py +++ b/NoobRPG/locations/serializers.py @@ -13,3 +13,16 @@ class Meta: Location.name.field.name, Location.slug.field.name, ] + + def build_field(self, field_name, info, model_class, nested_depth): + field_class, field_kwargs = super().build_field( + field_name, + info, + model_class, + nested_depth, + ) + + if field_name == 'url': + field_kwargs['view_name'] = 'locations-detail' + + return field_class, field_kwargs diff --git a/NoobRPG/locations/urls.py b/NoobRPG/locations/urls.py index 18462c9..e9982e5 100644 --- a/NoobRPG/locations/urls.py +++ b/NoobRPG/locations/urls.py @@ -4,7 +4,7 @@ router = routers.DefaultRouter() -router.register(r'', views.LocationViewSet, basename='locations') +router.register(r'', views.LocationViewSet, basename='location') urlpatterns = [ path('', include(router.urls)), diff --git a/NoobRPG/rarity/serializers.py b/NoobRPG/rarity/serializers.py index d7bb887..9878550 100644 --- a/NoobRPG/rarity/serializers.py +++ b/NoobRPG/rarity/serializers.py @@ -13,3 +13,16 @@ class Meta: Rarity.name.field.name, Rarity.slug.field.name, ] + + def build_field(self, field_name, info, model_class, nested_depth): + field_class, field_kwargs = super().build_field( + field_name, + info, + model_class, + nested_depth, + ) + + if field_name == 'url': + field_kwargs['view_name'] = 'rarity-detail' + + return field_class, field_kwargs diff --git a/NoobRPG/sellers_offers/models.py b/NoobRPG/sellers_offers/models.py index 1bce1c2..9ded97f 100644 --- a/NoobRPG/sellers_offers/models.py +++ b/NoobRPG/sellers_offers/models.py @@ -6,11 +6,8 @@ class SellerOfferManager(models.Manager): def all_fields(self) -> models.QuerySet: - queryset = ( - self.get_queryset() - .select_related( - SellerOffer.item.field.name, - ) + queryset = self.get_queryset().select_related( + SellerOffer.item.field.name, ) return queryset.order_by( SellerOffer.id.field.name, diff --git a/NoobRPG/sellers_offers/serializers.py b/NoobRPG/sellers_offers/serializers.py index 09bb9b7..12352e2 100644 --- a/NoobRPG/sellers_offers/serializers.py +++ b/NoobRPG/sellers_offers/serializers.py @@ -13,3 +13,16 @@ class Meta: SellerOffer.item.field.name, SellerOffer.cost.field.name, ] + + def build_field(self, field_name, info, model_class, nested_depth): + field_class, field_kwargs = super().build_field( + field_name, + info, + model_class, + nested_depth, + ) + + if field_name == 'url': + field_kwargs['view_name'] = 'sellers-offers-detail' + + return field_class, field_kwargs