QuerySet Have output but end point triggering Not found error

Hello All,

View:

class ProductViewByCategoryByDetails(generics.RetrieveUpdateDestroyAPIView): 
    serializer_class = serializers.ProductSerializer
    
    def get_queryset(self):
        
        category_id = self.kwargs['pk']
        product_id = self.kwargs['product_id'] 

        # Retrieve the category object or raise 404 if not found
        category = get_object_or_404(models.Category, pk=category_id)
        print(category)

        # Filter products based on both category and product id
        queryset = models.Product.objects.filter(category=category, id=product_id)
        print(queryset)
        
        return queryset

Serializer:

class ProductSerializer(serializers.ModelSerializer):

    category = serializers.CharField(source = 'category.category_name')

    class Meta: 
        model = models.Product
        fields = "__all__"

    def create(self, validated_data):
        category = validated_data.pop('category') # Get the category name from validated data
        
        try:
            category_object = models.Category.objects.get(category_name=category['category_name'])
        except models.Category.DoesNotExist:
            raise serializers.ValidationError(f'Category "{category['category_name']}" does not exist. Please add the category first.')

        product = models.Product.objects.create(category=category_object, **validated_data)
        return product
    
    def update(self, instance, validated_data):

        if 'category' in validated_data.keys():
            category = validated_data.pop('category') 
            try:
                category_object = models.Category.objects.get(category_name=category['category_name'])
            except models.Category.DoesNotExist:
                raise serializers.ValidationError(f'Category "{category['category_name']}" does not exist. Please add the category first.')
            instance.category = category_object
        else:
            instance.category = validated_data.get('category', instance.category)
        
    
        instance.product_name = validated_data.get('product_name', instance.product_name)
        instance.description = validated_data.get('description', instance.description)
        instance.price = validated_data.get('price', instance.price)
        instance.stock = validated_data.get('stock', instance.stock)
        instance.save()

        return instance

Model:

class Product(models.Model):
    id = models.AutoField(primary_key=True)
    product_name = models.CharField(max_length=300)
    description = models.CharField(max_length=1000)
    price = models.FloatField()
    stock = models.IntegerField()
    category = models.ForeignKey(Category, related_name='category', on_delete= models.CASCADE)

    def __str__(self):
        return self.product_name

urls:

from django.urls import path, include
from rest_framework.routers import DefaultRouter

from ecommerce_app.api.views import ProductView, CategoryView, ProductViewset, ProductViewByCategory, ProductViewByCategoryByDetails

routers = DefaultRouter()

routers.register('category',CategoryView, basename='category')
routers.register('',ProductViewset, basename='product')

urlpatterns = [
    path('',include(routers.urls)),
    path('category/<int:pk>/products/',ProductViewByCategory.as_view(), name='product_by_category'),
    path('category/<int:pk>/products/<int:product_id>/',ProductViewByCategoryByDetails.as_view()),
]

Above is my when try to hit ‘category/int:pk/products/int:product_id/’ getting error “No Product matches the given query.” but have product in database and I am getting out put in shell.

>>> product = Product.objects.get(pk=5, category__category_name='Electronics')
>>> print(product)
Test
>>> print(product.id)
5

when hit same end point for product id 1 am getting output let attach screenshots,

but when hit for product id 5 no output,

logs:

Electronics
<QuerySet [<Product: Ultra HD Smart TV>]>
[12/Jul/2024 06:26:58] "GET /products/category/1/products/1/ HTTP/1.1" 200 12132
Electronics
<QuerySet [<Product: Test>]>
Not Found: /products/category/1/products/5/
[12/Jul/2024 06:27:41] "GET /products/category/1/products/5/ HTTP/1.1" 404 11700

So please review and help me where I am doing wrong here

For same setup I tested using unit test,

from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
from ecommerce_app.api import serializers
from ecommerce_app import models

class ProductViewByCategoryByDetailsTestCase(TestCase):
    def setUp(self):
        self.client = APIClient()
        self.category = models.Category.objects.create(category_name="Electronics")
        self.product1 = models.Product.objects.create(product_name="Laptop", category=self.category, price=1000, description='Laptop test case', stock=5)
        self.product2 = models.Product.objects.create(product_name="Laptop2", category=self.category, price=1000, description='Laptop test case2', stock=5)
        self.valid_url = reverse('product-detail', kwargs={'pk': self.category.pk, 'product_id': self.product2.pk})
        self.invalid_category_url = reverse('product-detail', kwargs={'pk': 999, 'product_id': self.product2.pk})
        self.invalid_product_url = reverse('product-detail', kwargs={'pk': self.category.pk, 'product_id': 999})

    def test_retrieve_product_valid(self):
        response = self.client.get(self.valid_url)
        print(response.getvalue())
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['product_name'], self.product2.product_name)
        self.assertEqual(response.data['category'], self.category.category_name)
        

    def test_retrieve_product_invalid_category(self):
        response = self.client.get(self.invalid_category_url)
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

    def test_retrieve_product_invalid_product(self):
        response = self.client.get(self.invalid_product_url)
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

When I create two product output getting failed,

(env) manojkumar19@manojkumars-mbp ecommerce % python manage.py test
Found 3 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.Electronics
<QuerySet []>
.Electronics
<QuerySet [<Product: Laptop2>]>
b'{"detail":"No Product matches the given query."}'
F
======================================================================
FAIL: test_retrieve_product_valid (ecommerce_app.tests.ProductViewByCategoryByDetailsTestCase.test_retrieve_product_valid)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/manojkumar19/Desktop/Django_learning/api_learning/project_ecommerce/ecommerce/ecommerce_app/tests.py", line 21, in test_retrieve_product_valid
    self.assertEqual(response.status_code, status.HTTP_200_OK)
AssertionError: 404 != 200

----------------------------------------------------------------------
Ran 3 tests in 0.008s

FAILED (failures=1)
Destroying test database for alias 'default'...

but when I have one product, test passed

(env) manojkumar19@manojkumars-mbp ecommerce % python manage.py test
Found 3 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.Electronics
<QuerySet []>
.Electronics
<QuerySet [<Product: Laptop>]>
b'{"id":1,"category":"Electronics","product_name":"Laptop","description":"Laptop test case","price":1000.0,"stock":5}'
.
----------------------------------------------------------------------
Ran 3 tests in 0.007s

OK

Can someone tell me what I am doing wrong here ? why query set unable to read the second product when it called over view ?

For this test:

Please repeat this, but also print(product.category_id)

hello Ken,

Thanks for replying

I did given test,

(env) manojkumar19@manojkumars-mbp ecommerce % python manage.py shell
Python 3.12.4 (v3.12.4:8e8a4baf65, Jun  6 2024, 17:33:18) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from ecommerce_app.models import Product 
>>> data = Product.objects.get(pk=5, category__category_name='Electronics')
>>> print(data.id)
5
>>> print(data.category_name)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Product' object has no attribute 'category_name'. Did you mean: 'category_id'?
>>> print(data.category_name)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Product' object has no attribute 'category_name'. Did you mean: 'category_id'?
>>> print(data)
Test
>>> print(data.category.category_name)
Electronics
>>> 

I believe it was working fine.

model:

class Category(models.Model):
    id = models.AutoField(primary_key=True)
    category_name = models.CharField(max_length=150)

    def __str__(self):
        return self.category_name

class Product(models.Model):
    id = models.AutoField(primary_key=True)
    product_name = models.CharField(max_length=300)
    description = models.CharField(max_length=1000)
    price = models.FloatField()
    stock = models.IntegerField()
    category = models.ForeignKey(Category, related_name='category', on_delete= models.CASCADE)

    def __str__(self):
        return self.product_name

Can you please explain what I am missing here ?

Same I tested in View also,

class ProductViewByCategoryByDetails(generics.RetrieveUpdateDestroyAPIView): 
    serializer_class = serializers.ProductSerializer
    
    def get_queryset(self):
        
        category_id = self.kwargs['pk']
        product_id = self.kwargs['product_id'] 

        # Retrieve the category object or raise 404 if not found
        category = get_object_or_404(models.Category, pk=category_id)
        print(category)

        # Filter products based on both category and product id
        queryset = models.Product.objects.get(id=product_id, category = category)
        print(queryset)
        print(queryset.id)
        print(queryset.category.category_name)        
        
        return queryset

log:


Electronics
Test
5
Electronics
Not Found: /products/category/1/products/5/
[12/Jul/2024 14:52:58] "GET /products/category/1/products/5/ HTTP/1.1" 404 11675

You did not try what I asked. I asked you to print category_id, not category_name.

(env) manojkumar19@manojkumars-mbp ecommerce % python manage.py shell
Python 3.12.4 (v3.12.4:8e8a4baf65, Jun  6 2024, 17:33:18) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from ecommerce_app.models import Product 
>>> data = Product.objects.get(pk=5, category__category_name='Electronics')
>>> print(data.id)
5
>>> print(data.category_name)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Product' object has no attribute 'category_name'. Did you mean: 'category_id'?
>>> print(data.category_name)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Product' object has no attribute 'category_name'. Did you mean: 'category_id'?
>>> print(data)
Test
>>> print(data.category.category_name)
Electronics
>>> print(data.category.id)
1
>>> 

Quoting from: Generic views - Django REST framework.

Used for read-write-delete endpoints to represent a single model instance .

This view relies upon the output of the get_object method for the one model instance it’s going to work on.

By default, it’s going to use the pk url argument to find the instance to use. However, you’re using the pk argument for the Category and not the Product.

So, you need to either correct your url argument usage or override the get_object method to return the one object to be serialized.

Thanks @KenWhitesell I changed my url like below

category/<int:category_id>/products/<int:pk>/

and It worked fine as I expected thanks for helping me on this