Fetching nested objects with Django REST framework - django

I am attempting to fetch nested objects but not quite sure how to achieve this. My model is as shown:
class Brand(models.Model)
name = models.CharField(max_length=128)
class Size(models.Model)
name = models.CharField(max_length=128)
class Brand_Size(models.Model)
brand = models.ForeignKey(Brand)
size = models.ForeignKey(Size)
class Brand_Size_Location(models.Model)
location = models.ForeignKey(Location)
brand_size = models.ForeignKey(Brand_Size)
I filter objects in Brand_Size_Location by location which can occur 1..x. I want my serializer to output the results in terms of the model Brand (BrandSerializer). Since my resultset can be 1..x and furthermore the occurrence of Brand can be duplicates i would like to eliminate these aswell at the same time.

You should be able to do this fairly easily by including a serializer field in your BrandSerializer:
class BrandSerializer(serializers.ModelSerializer):
brand_sizes = BrandSizeSerializer(
source='brand_size_set',
read_only=True
)
class Meta:
model = Brand
fields = (
'id',
...add more fields you want here
'brand_sizes')
You can simlarly create the brand size serializer to nest the locations
Filtering on this relationship occurs in the view and will need a custom filter. Here's a basic example using a ListView:
class BrandFilter(django_filters.FilterSet):
location = django_filters.ModelMultipleChoiceFilter(
queryset=Brand_Size_Location.objects.all(),
name='brand_size__brand_size_location__location'
)
location_name = django_filters.CharFilter(
name='brand_size__brand_size_location__location__name'
)
class Meta:
model = Brand
fields = [
'location',
'location_name'
]
class BrandList(LoginRequiredMixin, generics.ListCreateAPIView):
model = Brand
serializer_class = BrandSerializer
filter_class = BrandFilter
You can then use query parameters to filter on the URL like:
http://somehost/api/brands/?location=42
which uses a PK to filter, or with the location name (assuming you have a name field in the Location model):
http://somehost/api/brands/?location_name=Atlantis

Related

Query set for many to many object relationship

Need to post many items onetime -many titles- in a titles list from mobile app to the backend, the problem is when use create method within the for loop it gets no record in database & that Type Error -Direct assignment to the forward side of a many-to-many set is prohibited. Use container.set() instead.- when try to use the set() method it's not showing any right case!!!!, the problem is when to use create in order to made many create objects in many-to-many relationship. please guide.
models.py
class Container(models.Model):
name = models.CharField(max_length=200, blank=False)
capacity = models.IntegerField( null=False ,blank=False)
class Item(models.Model):
title = models.CharField(max_length=200, blank=False)
container = models.ManyToManyField(container)
serializers.py
class ContainerSerializer(serializers.ModelSerializer):
class Meta:
model = Container
fields = '__all__'
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = '__all__'
Request
{
"container": {
"name": "container1",
"capacity": 10,
},
"Items":[
{"title":"tl1",},
{"title":"tl2",},
{"title":"tl3",}
]
}
view.py
#api_view(['POST'])
def additems(request):
data = request.data
container = Container.objects.create(
name = data['name']
capacity = data['capacity']
)
container.save()
for i in Items:
item = Item.objects.create(
container = container ,
title = i['title'] ,
)
item.save()
serializer = ContainerSerializer(container, many=False)
return response(serializer.data)
TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use container.set() instead.
Why getting this error ?!!! please guide ...
The error is occurring because you are attempting to create and assign a Container object to a ManyToManyField on the Item model using the create() method within a for loop. This will result in a new container being created for each iteration of the loop, which is not what you want. Additionally, when attempting to add Item objects to a many-to-many relationship with a Container object, you should use the add() method, not the set() method. You can refactor your view like this
#api_view(['POST'])
def additems(request):
data = request.data
# Create the container
container = Container.objects.create(
name=data['container']['name'],
capacity=data['container']['capacity']
)
# Create the items and add them to the container
items = data['Items']
for item_data in items:
item = Item.objects.create(title=item_data['title'])
container.item_set.add(item)
serializer = ContainerSerializer(container)
return Response(serializer.data)
In the ContainerSerializer, you need to specify the ItemSerializer as a nested serializer in order to properly serialize the Item objects that are associated with the Container through the many-to-many relationship.As shown below:
class ContainerSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True, read_only=True)
class Meta:
model = Container
fields = ['id', 'name', 'capacity', 'items']

django swagger api returned object url instead of readable name

I have an model which is for mapping book(item) to categories(tag),
it shows like this in the django admin page.
id item_uid tag_uid
407 Food Recipe
but in django swagger page, when I try to GET this mapping api with ID 407, it returned like this:
"id": 407,
"item_uid": "http://127.0.0.1:8000/items/237/";
"tag_uid": "http://127.0.0.1:8000/tags/361/"
as you can see, it mapped together correctly, but the response body showed the object url and it's object id, which is not readable for human users. I wonder that if there is anyway to make them like this:
"id": 407,
"item_uid": "Food";
"tag_uid": "Recipe"
edit: codes,
#models.py
class Map_item_tag(models.Model):
item_uid = models.ForeignKey(items, on_delete=models.CASCADE, verbose_name='Item UID')
tag_uid = models.ForeignKey(tags, on_delete=models.CASCADE, verbose_name='Tag UID')
#admin.py
#admin.register(Map_item_tag)
class map_item_tag_admin(ImportExportModelAdmin):
resource_class = map_item_tag_Resource
readonly_fields = ('id',)
list_display = ['id','item_uid','tag_uid']
#serializers.py
class Map_item_tag_Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Map_item_tag
fields = ['id','item_uid','tag_uid']
#views.py
class Map_item_tag_ViewSet(viewsets.ModelViewSet):
queryset = Map_item_tag.objects.all().order_by('item_uid')
serializer_class = Map_item_tag_Serializer
parser_classes = (FormParser, MultiPartParser)
permission_classes = [permissions.IsAuthenticated]
thank you for answering!
It seems you are using a HyperlinkedModelSerializer instead of a regular ModelSerializer
Try changing the serializer class to a ModelSerializer:
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = [] # list of fields you want to include in your Item serializer
class Map_item_tag_Serializer(serializers.ModelSerializer):
item_uid = ItemSerializer()
class Meta:
model = Map_item_tag
fields = ['id','item_uid','tag_uid']
In addition, I would advise you to use CamelCase notation for all your classes. For example: instead of using Map_item_tag_Serializer, change the name to MapItemTagSerializer. The same goes for all your other classes.
I would also avoid using using the _uuid suffix when using ForeignKey relationships. In the MapItemTag model, the ForeignKey relationship inherently means that the field will point to an object Item of Tag object. Hence, no need to specify the _uuid part again.
For example, the following changes would make the model a lot more readable:
class MapItemTag(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE, verbose_name='map_item')
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, verbose_name='map_tag')

How to retrieve a list of objects (including ForeignKey Field data) in django (DRF) without significantly increasing DB call times

I have three models in a django DRF project:
class ModelA(models.Model):
name = ....
other fields...
class ModelB(models.Model):
name = ....
other fields...
class ModelC(models.Model):
name = ....
model_a = FKField(ModelA)
model_b = FKField(ModelB)
I was using the default ModelViewSet serializers for each model.
On my react frontend, I'm displaying a table containing 100 objects of ModelC. The request took 300ms. The problem is that instead of displaying just the pk id of modelA and ModelB in my table, I want to display their names. I've tried the following ways to get that data when I use the list() method of the viewset (retreive all modelc objects), but it significantly increases call times:
Serializing the fields in ModelCSerializer
class ModelCSerializer(serializers.ModelSerializer):
model_a = ModelASerializer(read_only=True)
model_b = ModelBSerializer(read_only=True)
class Meta:
model = ModelC
fields = '__all__'
Creating a new serializer to only return the name of the FK object
class ModelCSerializer(serializers.ModelSerializer):
model_a = ModelANameSerializer(read_only=True) (serializer only returns id and name)
model_b = ModelBNameSerializer(read_only=True) (serializer only returns id and name)
class Meta:
model = ModelC
fields = '__all__'
StringRelatedField
class ModelCSerializer(serializers.ModelSerializer):
model_a = serializer.StringRelatedField()
model_b = serializer.StringRelatedField()
class Meta:
model = ModelC
fields = '__all__'
Every way returns the data I need (except number 3 takes more work to get the FKobject's id) but now my table request takes 5.5 seconds. Is there a way to do this without significantly increasing call times? I guess this is due to the DB looking up 3 objects for every object I retrieve.
Also I wouldn't be able to make the primary_key of ModelA & ModelB the name field because they aren't unique.
Thanks
EDIT Answer for my example thanks to bdbd below:
class ModelCViewSet(viewsets.ModelViewSet):
queryset = ModelC.objects.select_related('model_a', 'model_b').all()
# ...
You can use select_related for this to optimise your queries and make sure that every object in your ModelC does not do extra DB hits

Serialize relation both ways with Django rest_framework

I wonder how to serialize the mutual relation between objects both ways with "djangorestframework". Currently, the relation only shows one way with this:
class MyPolys(models.Model):
name = models.CharField(max_length=20)
text = models.TextField()
poly = models.PolygonField()
class MyPages2(models.Model):
name = models.CharField(max_length=20)
body = models.TextField()
mypolys = models.ManyToManyField(MyPolys)
# ...
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPages2
# ...
class MyPolyViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPolys.objects.all()
serializer_class = srlz.MyPolysSerializer
class MyPages2ViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPages2.objects.all()
serializer_class = srlz.MyPages2Serializer
The many-to-many relation shows up just fine in the api for MyPages2 but nor for MyPolys. How do I make rest_framework aware that the relation goes both ways and needs to be serialized both ways?
The question also applies to one-to-many relations btw.
So far, from reading the documentation and googling, I can't figure out how do that.
Just do it like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields =('id','name','text','poly')
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
mypolys = MyPolysSerializer(many=True,read_only=True)
class Meta:
model = testmodels.MyPages2
fields =('id','name','body','mypolys')
I figured it out! It appears that by adding a mypolys = models.ManyToManyField(MyPolys) to the MyPages2 class, Django has indeed automatically added a similar field called mypages2_set to the MyPolys class, so the serializer looks like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields = ('name', 'text', 'id', 'url', 'mypages2_set')
I found out by inspecting an instance of the class in the shell using ./manage.py shell:
pol = testmodels.MyPolys.objects.get(pk=1)
pol. # hit the tab key after '.'
Hitting the tab key after the '.' reveals additional fields and methods including mypages2_set.

Adding a count field not in models to GET request

I have a model of Gun, and each gun has multiple Loads
What I want to do is add a Load count to the gun GET request object, but not in the model it self. So the count is done when I do the GET request, and only for the GET request if possible. Otherwise I can just make the POST load_count = 0, because there is no such field and It should not be saved.
class Gun(models.Model):
name = models.CharField(max_length=128)
class Load(models.Model):
gun = models.ForeignKey(Gun, related_name='gun')
foo = ...
Serializers:
class GunSerializer(serializers.HyperlinkedModelSerializer):
??? load_count = Field() -- reverse lookup and count loads
id = serializers.Field()
class Meta:
model = Gun
fields = ('id',"name", ???? "load_count")
class LoadSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field()
class Meta:
model = Load
Try using an IntegerField for the set count:
class GunSerializer(serializers.HyperlinkedModelSerializer):
load_count = serializers.IntegerField(source='load_set.count')
class Meta:
model = Gun
fields = ('id', "name", "load_count")
However, you are using the related_name of gun in your Load model which I would recommend changing to loads (or just omitting and then use the default load_set and the example above will work). If you want to keep the related name of gun then define the load_count serializer field as:
load_count = serializers.IntegerField(source='gun.count')