Django api rest filter manytomany - python

I am a quite New in Django and Django Api Rest FrameWork
I am trying to develop an Api with two models relationated with a ManyToMany relation
models.py
class Category(models.Model):
name = models.CharField(max_length=100)
class Apartment(models.Model):
id_2 = models.IntegerField(default= None)
neighborhood = models.CharField(max_length=100)
name = models.CharField(max_length=120)
district = models.CharField(max_length=120)
created = models.DateTimeField()
cats = models.ManyToManyField(Category)
address = models.CharField(max_length=200)
postal_code = models.IntegerField()
latitude = models.FloatField()
longitude = models.FloatField()
serializers.py
class CategorySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Category
fields = ( 'id', 'name')
class AptSerializer(serializers.HyperlinkedModelSerializer):
cats = CategorySerializer(many=True, read_only=True)
class Meta:
model = Apartment
fields = ('id_2', 'neighborhood', 'name','district','created','cats','address','postal_code','latitude','longitude')
view.py
class AptViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Apartment.objects.all()
serializer_class = AptSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('id_2', 'neighborhood', 'name', 'district','created','cats','address','postal_code','latitude','longitude')
routers.py
router.register(r'apartments', views.AptViewSet)
As you can see it implements DjangoFilterBackend so now i can filter with urls type
http://localhost:8000/apartments/?district=Eixample
and i get this
{
"id_2": 1044121532,
"neighborhood": "la Nova Esquerra de l'Eixample",
"name": "Hotel Exe AB Viladomat",
"district": "Eixample",
"created": "2001-02-13T00:00:00Z",
"cats": [
{
"id": 1073,
"name": "Allotjament"
},
{
"id": 1075,
"name": "Hotels"
},
{
"id": 1076,
"name": "3 estrelles"
}
],
"address": "Viladomat 197",
"postal_code": 8015,
"latitude": 41.383665702777776,
"longitude": 2.151834694444444
},
I'd like to make get request like
http://localhost:8000/apartments/?cats_name=Hotels
But it doesn't work, any idea in which will the road from now?
Write a custom Filter?
write another ViewSet for Category objects?
Thank You

The root QuerySet provided by the Manager describes all objects in the database table. Usually, though, you'll need to select only a subset of the complete set of objects.
But here your are passing the query parameters and to filter on whole queryset
views.py
class AptViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Apartment.objects.all()
serializer_class = AptSerializer
def get_queryset(self):
queryset = super(AptViewSet, self).get_queryset()
cats_name = self.request.query_params.get('cat_name', None)
if cats_name:
queryset = queryset.filter(cats__name=cats_name)
return queryset
If you can follow this post for writing the custom filters How to Pass kwarg to Filter

Check out this link on query parameter filtering.
You will overriding the get_queryset method in your serializer.

Related

how to get foreignkey names instead of sql reference number django view

I'm trying to build a simple rest API in Django. I have a transaction, account and stock table. Each transaction points to 1 account and 1 stock(on seperate tables using foreign keys). I'm trying to have my API gets the stock ticker for each transaction and not the sql reference number. Does anyone know how to get simple views to return values instead of table keys?
The serializers I'm using:
class TransactionSerializer(serializers.ModelSerializer):
class Meta:
model = Transaction
fields = "__all__"
class StockSerializer(serializers.ModelSerializer):
class Meta:
model = Stock
fields = "__all__"
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = "__all__"
The models for transaction and stock:
class Stock(models.Model):
ticker = models.CharField(max_length=10, unique=True)
name = models.CharField(max_length=200)
price = models.FloatField()
def __str__(self):
return self.ticker
class Transaction(models.Model):
account = models.ForeignKey(Account, on_delete=models.CASCADE)
stock = models.ForeignKey(Stock, on_delete=models.CASCADE)
amount = models.IntegerField()
price = models.FloatField()
type = models.CharField(max_length=10)
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.account
I'm leaving out the account/user model but its a mess and half tied into the django base user model.
The view I'm using is a basic APIView:
class TransactionView(APIView):
permission_classes = (IsAuthenticated,)
authentication_classes = (JWTAuthentication,)
def get(self, request, *args, **kwargs):
account = Account.objects.get(email=request.user.email)
transactions = Transaction.objects.filter(account=account)
serializer = TransactionSerializer(transactions, many=True)
return Response(serializer.data)
and the output I am getting from postman:
[
{
"id": 1,
"amount": 1,
"price": 146.1,
"type": "buy",
"date": "2022-10-05T04:12:10.414961Z",
"account": 1,
"stock": 1
},
{
"id": 2,
"amount": 1,
"price": 146.1,
"type": "buy",
"date": "2022-10-05T04:17:57.807945Z",
"account": 1,
"stock": 1
}
]
Also the url I am using for the endpoint:
path("transactions/", views.TransactionView.as_view(), name="transactions"),
Sorry for repetition but the basic issue I'm getting is when I call the API endpoint I am getting 1 for stock and 1 for account instead of the stock ticker and account name and I don't know how to get the view to return all the information without calling a second endpoint that returns the stock and a 3rd endpoint to return the account information.
I was under the impression that the model __str__ function would return information on the object but that isn't happening here.
You should define a "stock" field and an "account" field in your transaction serializer. (Assigning the relevant serializer as value).
Doing this will allow you to indicate to DRF how to serialize the related model.
class TransactionSerializer(serializers.ModelSerializer):
stock = StockSerializer()
account = AccountSerializer()
class Meta:
model = Transaction
fields = "__all__"

How do I fix 'MultipleObjectsReturned' error in Django rest framework

I am building an API using Django rest framework. I currently have an endpoint /api/v1/device-groups/ which returns all device group objects like this:
[
{
"device_group_name": "Default",
"group_uuid": "3812a299-3ab9-4c00-a711-d166fb01075e",
"color": "4286f4",
"is_default": true,
"customer": {
"customer_name": "Testcustomer",
"customer_uuid": "179fe73d-ec67-45ac-8dac-e2456ccd9b48"
}
}
]
Using the customer_uuid in the URL shows the device groups related to that customer
/api/v1/device-groups/179fe73d-ec67-45ac-8dac-e2456ccd9b48 returns data of the device groups attached to the specified customer_uuid
The problem I am facing is that whenever there are multiple device groups related to a customer_uuid it results in a MultipleObjectsReturned error. I want to list all the device groups that are related to the specified customer_uuid.
My serializers.py:
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Customer
fields = ('customer_name', 'customer_uuid')
class DeviceGroupSerializer(serializers.HyperlinkedModelSerializer):
customer = CustomerSerializer(many=False, read_only=True, source='customer_uuid')
class Meta:
model = DeviceGroup
fields = ('device_group_name', 'group_uuid', 'color', 'is_default', 'customer')
My views.py:
class DeviceGroupViewSet(viewsets.ModelViewSet):
serializer_class = DeviceGroupSerializer
lookup_field = 'customer_uuid'
def get_queryset(self):
queryset = DeviceGroup.objects.all()
customer_uuid = self.request.query_params.get('customer_uuid', None)
if customer_uuid is not None:
queryset = queryset.filter(customer_uuid=customer_uuid)
return queryset
What do I need to change so all device groups related to a customer_uuid are returned?

Django Rest Framework- retrieving a related field on reverse foreign key efficiently

I have the following models that represent a working group of users. Each working group has a leader and members:
class WorkingGroup(models.Model):
group_name = models.CharField(max_length=255)
leader = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
class WorkingGroupMember(models.Model):
group = models.ForeignKey(WorkingGroup, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
In DRF, I want to efficiently retrieve all groups (there are several hundred) as an array of the following json objects:
{
'id': <the_group_id>
'group_name': <the_group_name>
'leader': <id_of_leader>
'members': [<id_of_member_1>, <id_of_member_2>, ...]
}
To do so, I have set up the following serializer:
class WorkingGroupSerializer(serializers.ModelSerializer):
members = serializers.SerializerMethodField()
class Meta:
model = WorkingGroup
fields = ('id', 'group_name', 'leader', 'members',)
def get_members(self, obj):
return obj.workinggroupmember_set.all().values_list('user_id', flat=True)
So that in my view, I can do something like:
groups = WorkingGroup.objects.all().prefetch_related('workinggroupmember_set')
group_serializer = WorkingGroupSerializer(groups, many=True)
This works, and gives the desired result, however I am finding it does not scale well at all, as the prefetching workinggroupmember_set does not seem to be used inside of the get_members method (Silky is showing a single query to grab all WorkingGroup objects, and then a query for each workinggroupmember_set call in the get_members method). Is there a way to set up the members field in the serializer to grab a flattened/single field version of workinggroupmember_set without using a SerializerMethodField? Or some other way of doing this that lets me properly use prefetch?
Problem here that you are doing values_list on top of all which nullifies your prefetch_related. There is currently no way to do prefetch with values_list see https://code.djangoproject.com/ticket/26565. What you can do is to transition this into python code instead of SQL
class WorkingGroupSerializer(serializers.ModelSerializer):
members = serializers.SerializerMethodField()
class Meta:
model = WorkingGroup
fields = ('id', 'group_name', 'leader', 'members',)
def get_members(self, obj):
return [wgm.user_id for wgm in obj.workinggroupmember_set.all()]
In a recent project with DRF v3.9.1 and django 2.1, I needed to recursively expose all the children of an object, by having only a direct connection to the parent, which could have had multiple children.
Before, if I was to request the "tree" of an object, I was getting:
{
"uuid": "b85385c0e0a84785b6ca87ce50132659",
"name": "a",
"parent": null
}
By applying the serialization shown below I get:
{
"uuid": "b85385c0e0a84785b6ca87ce50132659",
"name": "a",
"parent": null
"children": [
{
"uuid": "efd26a820b4e4f7c8e56c812a7791fcb",
"name": "aa",
"parent": "b85385c0e0a84785b6ca87ce50132659"
"children": [
{
"uuid": "ca2441fc7abf49b6aa1f3ebbc2dae251",
"name": "aaa",
"parent": "efd26a820b4e4f7c8e56c812a7791fcb"
"children": [],
}
],
},
{
"uuid": "40e09c85775d4f1a8578bba9c812df0e",
"name": "ab",
"parent": "b85385c0e0a84785b6ca87ce50132659"
"children": [],
}
],
}
Here is the models.py of the recursive object:
class CategoryDefinition(BaseModelClass):
name = models.CharField(max_length=100)
parent = models.ForeignKey('self', related_name='children',
on_delete=models.CASCADE,
null=True, blank=True)
To get all the reverse objects in the foreign key, apply a field to the serializer class:
class DeepCategorySerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField()
class Meta:
model = models.CategoryDefinition
fields = '__all__'
def get_children(self, obj):
return [DeepCategorySerializer().to_representation(cat) for cat in obj.children.all()]
Then apply this serializer to a DRF view function or generics class, such as:
re_path(r'categories/(?P<pk>[\w\d]{32})/',
generics.RetrieveUpdateDestroyAPIView.as_view(
queryset=models.CategoryDefinition.objects.all(),
serializer_class=serializers.DeepCategorySerializer),
name='category-update'),

Can't get Django Rest Framework GET vs POST structure to differ

I have a model that looks like this:
class Pet(models.Model):
name = models.CharField(max_length=30)
description = models.CharField(max_length=200)
primary_contact = models.ForeignKey(Person)
My Serializer looks like this:
class PetSerializer(serializers.ModelSerializer):
class Meta:
model = Pet
fields = ('id', 'name', 'description', 'primary_contact')
My Viewset:
class PetViewSet(viewsets.ModelViewSet):
queryset = Pet.objects.all()
serializer_class = PetSerializer
My Problem:
If I do a "GET" at my endpoint, my result looks like this:
[
{
"id": 1,
"name": "Ripley",
"description": "Black / Tan Yorkie",
"primary_contact": 1
}
]
The primary_contact only brings back the ID of the Person object. This is exactly how I want the POSTing structure to look like. When I POST, I only want to supply the ID of the Person object. However, when I GET, I want the content to look like this:
[
{
"id": 1,
"name": "Ripley",
"description": "Black / Tan Yorkie",
"primary_contact": {
"id": 1,
"first_name": "MyFistName",
"last_name": "MyLastName",
"phone": "312-xxx-xxxx",
"email": "aarsan#abc123.com"
}
}
]
I can get the above structure by setting depth=2 in my serializer but then if I try to POST, it tries to create the primary_contact, which I not want to do since it already exists.
The workaround I've been using is creating a different endpoint for POST and GET when I have a foreign key, which I hope isn't the only way to do this.
You want a nested serializer. In this case you have to define a serializer for primary contact and link in with primary contact field of PetSerializer.
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = ('id', 'field2', 'field3', '...')
class PetSerializer(serializers.ModelSerializer):
primary_contact = ContactSerializer()
class Meta:
model = Pet
fields = ('id', 'name', 'description', 'primary_contact')
You can have several serializers in the same ViewSet, to achieve this you need to overwrite get_serializer_classmethod.
Take a look at this: http://www.django-rest-framework.org/api-guide/generic-views/#get_serializer_classself

Serializing ManyToMany relationship with intermediary model in Django Rest Framework

I'm having some trouble serializing many to many relationships with a through argument in DRF3
Very basically I have recipes and ingredients, combined through an intermediate model that specifies the amount and unit used of a particular ingredient.
These are my models:
from django.db import models
from dry_rest_permissions.generics import authenticated_users, allow_staff_or_superuser
from core.models import Tag, NutritionalValue
from usersettings.models import Profile
class IngredientTag(models.Model):
label = models.CharField(max_length=255)
def __str__(self):
return self.label
class Ingredient(models.Model):
recipe = models.ForeignKey('Recipe', on_delete=models.CASCADE)
ingredient_tag = models.ForeignKey(IngredientTag, on_delete=models.CASCADE)
amount = models.FloatField()
unit = models.CharField(max_length=255)
class RecipeNutrition(models.Model):
nutritional_value = models.ForeignKey(NutritionalValue, on_delete=models.CASCADE)
recipe = models.ForeignKey('Recipe', on_delete=models.CASCADE)
amount = models.FloatField()
class Recipe(models.Model):
name = models.CharField(max_length=255)
ingredients = models.ManyToManyField(IngredientTag, through=Ingredient)
tags = models.ManyToManyField(Tag, blank=True)
nutritions = models.ManyToManyField(NutritionalValue, through=RecipeNutrition)
owner = models.ForeignKey(Profile, on_delete=models.SET_NULL, blank=True, null=True)
def __str__(self):
return self.name
And these are currently my serializers:
from recipes.models import Recipe, IngredientTag, Ingredient
from rest_framework import serializers
class IngredientTagSerializer(serializers.ModelSerializer):
class Meta:
model = IngredientTag
fields = ('id', 'label')
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = ('amount', 'unit')
class RecipeSerializer(serializers.ModelSerializer):
class Meta:
model = Recipe
fields = ('id', 'url', 'name', 'ingredients', 'tags', 'nutritions', 'owner')
read_only_fields = ('owner',)
depth = 1
I've searched SO and the web quite a bit, but I can't figure it out. It would be great if someone could point me in the right direction.
I can get the list of ingredients to be returned like so:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"url": "http://localhost:8000/recipes/1/",
"name": "Hallo recept",
"ingredients": [
{
"id": 1,
"label": "Koek"
}
],
"tags": [],
"nutritions": [],
"owner": null
}
]
}
But what I want is for the amount and unit to also be returned!
I got what I wanted in the following way:
from recipes.models import Recipe, IngredientTag, Ingredient
from rest_framework import serializers
class IngredientTagSerializer(serializers.ModelSerializer):
class Meta:
model = IngredientTag
fields = ('id', 'label')
class IngredientSerializer(serializers.ModelSerializer):
ingredient_tag = IngredientTagSerializer()
class Meta:
model = Ingredient
fields = ('amount', 'unit', 'ingredient_tag')
class RecipeSerializer(serializers.ModelSerializer):
ingredients = IngredientSerializer(source='ingredient_set', many=True)
class Meta:
model = Recipe
fields = ('url', 'name', 'ingredients', 'tags', 'nutritions', 'owner')
read_only_fields = ('owner',)
depth = 1
using the ingredient_tag's ingredient_set as a source for IngredientSerializer resulted in the response I required:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"url": "http://localhost:8000/recipes/1/",
"name": "Hallo recept",
"ingredients": [
{
"amount": 200.0,
"unit": "g",
"ingredient_tag": {
"id": 1,
"label": "Koek"
}
},
{
"amount": 500.0,
"unit": "kg",
"ingredient_tag": {
"id": 3,
"label": "Sugar"
}
}
],
"tags": [],
"nutritions": [],
"owner": null
}
]
}
I don't know if this is the best way to go about it, so I'll wait til somebody who knows their DRF leaves a comment or perhaps someone posts something better before marking as answer.
While serializing the nested relations, you also have to serialize specifically those ManyToManyField.
Let me give you a small example:
class RecipeSerializer(serializers.ModelSerializer):
ingredients = serializers.SerializerMethodField()
def get_ingredients(self, obj):
serializer = IngredientSerializer(obj.ingredients)
return serializer.data
class Meta:
model = Recipe
fields = ('id', 'url', 'name', 'ingredients', 'tags', 'nutritions', 'owner')
read_only_fields = ('owner',)
depth = 1
Whatever your nested relation is (like ingredients, tags or nutritions), you can serialize them by creating a serializer method field. In that method, You can use your specific serializer so that it gives the json you want.
Be careful with the method name. If your ManyToManyField is "ingredients", your method name should be "ingredients" because DRF works with "get_".
For further information, check this:
Django Rest Framework - SerializerMethodField
Override the method to_representation of RecipeSerializer
and pass the instance of many to many filed to their serializer with many is True. or
tags = serializers.HyperlinkedRelatedField(
many=True,read_only=True,
)

Categories