Django REST Framework | many-to-many relation returning "detail: not found"

Question:

I’m using Django REST framework. I’ve created a model for items in an inventory system and also a through table (named subassembly) for many-to-many relationship between items themselves, so an item can be a subpart of other items and vice versa.

I’m just not sure I’ve done it right and I can’t seem to get any results. When I visit the backend at a URL such as http://localhost:8000/api/subassemblies/2/, the response is

{"detail": "Not found."}

but I’m hoping to see all of an item’s subparts, or "children". PUT or any other type of request has the same outcome.

If it matters, when accessing the subassemblies from the admin page, I can create relationships between items just fine. But only one at a time though. And I need to be able to edit all of an item’s subparts in one go (at least, frontend-wise). Currently, the request body is structured like so:

    {   
        "parent_id": 2,
        "children": [
            { "child_id": 5, "qty": 2 },
            { "child_id": 4, "qty": 3 },
        ]
    }

This also allows me to use .set() on a particular item’s children which is useful because I think it also removes any prior children that are not included in the new set.

views.py

class SubassemblyDetail(generics.RetrieveUpdateDestroyAPIView):
    """
    Retrieve, update, or delete a particular items subassembly
    """
    queryset = Subassembly.objects.all()
    serializer_class = SubassemblySerializer

    def get_queryset(self):
        item_id = self.kwargs['pk']
        item = Item.objects.get(pk=item_id)
        return item.children.all()

models.py

class Item(models.Model):
    # ... (various other fields)
    children = models.ManyToManyField('self', through='Subassembly', blank=True)

class Subassembly(models.Model):
    parent_id = models.ForeignKey(Item, related_name='parent_item', on_delete=models.CASCADE)
    child_id = models.ForeignKey(Item, related_name='child_item', on_delete=models.CASCADE)
    child_qty = models.PositiveSmallIntegerField(default=1)

serializers.py

class SubassemblySerializer(ModelSerializer):
    parent_id = get_primary_key_related_model(ShortItemSerializer)
    child_id = get_primary_key_related_model(ShortItemSerializer)

    class Meta:
        model = Subassembly
        fields = '__all__'

    def update(self, instance, validated_data):
        children = validated_data.pop('children')

        child_list = []
        qty_list = []

        for child in children:
            pk = child['child_id']
            qty = child['qty']
            obj = Item.objects.get(id=pk)

            child_list.append(obj)
            qty_list.append(qty)
        instance.children.set(child_list, through_defaults={'qty': qty_list})
        instance.save()
        return instance

    def delete(self, instance):
        instance.children.clear()

For referencing the parent and child item objects, I’m using a mixin or something from this post so I can do so with just their primary keys.

Also, the item serializer code currently has no reference to the subassembly through table or its serializer.
I tried adding something like that but the error was still there and it also made the children field required when creating an item, which I don’t want.
That code looked like this:

class ItemSerializer(ModelSerializer):
    # ... (other fields)
    children = SubassemblySerializer(many=true)    

    def create(self, validated_data):
        return Item.objects.create(**validated_data)

    class Meta:
        model = Item
        fields = '__all__'

urls.py

urlpatterns = [
    path('', views.RouteList.as_view()),
    path('items/', views.ItemList.as_view(), name='item-list'),
    path('items/<int:pk>/', views.ItemDetail.as_view(), name='item-detail'),
    path('suppliers/', views.SupplierList.as_view(), name='supplier-list'),
    path('suppliers/<int:pk>/', views.SupplierDetail.as_view(), name='supplier-detail'),
    path('subassemblies/<int:pk>/', views.SubassemblyDetail.as_view(), name='subassembly-detail')
]

The other urls all work fine, it’s just the subassemblies url that returns this error.

Asked By: Jeffery Zhan

||

Answers:

If, I understand correctly, your problem involves retrieving children from an Item object. My suspicion is that the Item object isn’t being retrieved. The way to diagnose this is as follows:

Enter the Django interactive shell using the following command:

python manage.py shell

From within the shell do the following:

# Import Item    
from <your_model_name>.models import Item

# Retrieve an Item object
item = Item.objects.get(id=2)

# Retrieve children
children = item.children.all()

# Get the count of children
print(children.count())

If children.count() is 0, then it means that your item object doesn’t have any children. If you can rule this possibility out, it means your views or serializers are the issue.

From a cursory glance, the get_queryset() method in SubAssemblyDetail returns an Item queryset, which is the likely issue. Under-the-hood SubAssemblyDetail executes the retrieve() method see: https://www.cdrf.co/3.13/rest_framework.generics/RetrieveUpdateDestroyAPIView.html

Answered By: Hamster Hooey

Solved! Thanks to Hamster Hooey – the issue was that in my get_queryset() method under the SubassemblyDetail view, I was returning an item queryset rather than a subassembly queryset, which seems to be unacceptable to Django.

So rather than item.children.all(), I did Subassembly.objects.filter(parent_id=item_id) and it all showed up fine.

Answered By: Jeffery Zhan