I need to filter some Models in Django and return them trough REST, but I have some difficulties. I have 4 Models connected with Foreign key's like so:
class Standort(models.Model):
name = models.CharField(max_length=40)
farbe = models.CharField(max_length=20, default="Black")
class Gruppe(models.Model):
standort = models.ForeignKey(Standort)
name = models.CharField(max_length=40)
class Person(models.Model):
name = models.CharField(max_length=40)
gruppe = models.ForeignKey(Gruppe, related_name='personen')
class Eintrag(models.Model):
person = models.ForeignKey(Person, related_name='eintrage')
typ = models.ForeignKey(Typ)
datum = models.DateField()
and Iam serializing them like so:
class EintragSerializer(serializers.ModelSerializer):
class Meta:
model = Eintrag
fields = ('datum', 'typ')
class PersonenSerializer(serializers.ModelSerializer):
eintrage = EintragSerializer(read_only=True, many=True)
class Meta(object):
model = Person
fields = ('id', 'name', 'eintrage')
class GruppenPersonenEintraegeSerializer(serializers.ModelSerializer):
personen = PersonenSerializer(read_only=True, many=True)
class Meta(object):
model = Gruppe
fields = ('id', 'name', 'personnel')
and my view looks like this:
class GruppenPersonenEintraege(APIView):
def get(self, request, standort, jahr):
gruppen = Gruppe.objects.filter(standort=standort)
serializer = GruppenPersonenEintraegeSerializer(gruppen, many=True)
return Response(serializer.data)
The result looks like this:
[
{
"id": 2,
"name": "2.Schicht",
"personen": [
{
"id": 1,
"name": "Rolf der Tester",
"eintrage": [
{
"datum": "2017-02-16",
"typ": 3
},
{
"datum": "2017-02-15",
"typ": 3
},
{
"datum": "2018-04-05",
"typ": 2
}
]
}
]
},
{
"id": 3,
"name": "Test",
"personen": []
}
]
This is totally fine, my Problem is when i also want to filter the year of "eintrage.datum"by adding: .filter(standort=standort, personen__eintrage__datum__year=2017)afterGruppe.objects. Then the entry with "id": 2 is repeated 3 times and the one with "id": 3 isn't displayed at all. how do i filter just the entry's of the second nested dict?
To avoid "id":2 repeated multi times, you can just add a list(set()) surround the filter queryset result, the django restful framework can also treat the list the same way as queryset. Also notice that in django orm, the hash of a model instance is the prime_key of in db, so that's why the set can work on queryset.
As for "id":3 not showing, I also have no ideas as you did, maybe double better check the db again. A little bit more info will be more helpful.
Related
I'm new to Django REST Framework.
I have a nested model for saving orders of a restaurant. When I send a GET request I get the following response:
[
{
"menu": {
"id": 1,
"food_name": "food1"
},
"user": {
"id": 49,
"username": "A"
}
},
{
"menu": {
"id": 1,
"food_name": "food1"
},
"user": {
"id": 63,
"username": "B"
}
}
]
But I want to group users with the same menu like this:
[
{
"menu": {
"id": 1,
"food_name": "food1",
"users": {
"1": {
"id": 49,
"username": "A"
},
"2": {
"id": 63,
"username": "B"
}
}
}
}
]
Here is my code:
models.py
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
menu = models.ForeignKey(Menu, on_delete=models.CASCADE)
views.py
class OrderViewSet(viewsets.ModelViewSet):
queryset = Order.objects.all()
serializer_class = OrderSerializer
serilizers.py
class OrderSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
menu = MenuSerializer()
class Meta:
model = Order
fields = ['id', 'user', 'menu']
Thanks
Since your output suggests that you are working with menus, I suggest you create a separate viewset that works with menus and returns the expected output you want here.
Doing the expected results in the order viewset is possible, but is tricky to optimise using select_related and prefetch_related. But if you really want this in your order viewset, then you can do this with:
class MenuSerializer(serializers.ModelSerializer):
users = serializers.SerializerMethodField()
class Meta:
model = Menu
fields = ['id', 'food_name', 'users']
def get_users(self, obj):
users = User.objects.filter(pk__in=obj.order_set.all().values('user'))
return UserSerializer(users, many=True).data
class OrderSerializer(serializers.HyperlinkedModelSerializer):
menu = MenuSerializer()
class Meta:
model = Order
fields = ['id', 'menu']
You also need to change the queryset of the view if you want distinct results, otherwise you will get duplicates:
class OrderViewSet(viewsets.ModelViewSet):
queryset = Order.objects.all().distinct('menu')
serializer_class = OrderSerializer
Do note that this is not optimised, and each row in your order table will require hitting the database just to get the users through menu. distinct('menu') also only works on PostgreSQL.
You can nest User into Menu if you want to group by menu
class OrderSerializer(serializers.HyperlinkedModelSerializer):
menu = MenuSerializer(many=True)
class Meta:
model = Order
fields = ['id', 'menu']
class MenuSerializer(serializers.ModelSerializer):
user = UserSerializer(many=True)
class Meta:
model = Menu
fields = ['id', 'user']
I have a serializer that gives this data
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('id', 'name', 'cost', 'currency')
class UserSerializer(serializers.ModelSerializer):
posts = PostSerializer(many=True)
class Meta:
model = User
fields = ('id', 'name')
and it gives the response like this,
{
"id": 1,
"name": "joe",
"posts": [
{
"id": 20,
"name": "first post",
"cost": 20.00,
"currency": "USD"
},
{
"id": 21,
"name": "second post",
"cost": 30.00,
"currency": "USD"
}
]
}
However I want to change/add the fields of the response based on few conditions,
Eg. if cost is less than 25, make it zero and add a discount field for every post.
This is how I am doing.
class MyPostView(APIView):
def get(request):
query_set = User.objects.all()
user_and_posts = UserSerializer(query_set)
response_data = user_and_posts.data
# I am modifying the serializer data here :<
for post in response_data['posts']:
post['discount'] = 10 # some value
if post['cost'] < 25:
post['cost'] = 0
return Response(serializer.data, status=status.HTTP_200_OK)
To me modifying the primitive data like this is not looking right,
is there any alternate way in django rest to do this?
or could've done better with serializer?
In general, what's the best way to alter the response data we get from serializer and
format it in the way client wants? In languages like Java, we will have serializer for model and another serializer for output.. Can I do something similar?
If it is something that is model related and can be derived by manipulating model variables, I would advise to add properties into your model
class Post(models.Model):
name = models.CharField(max_length=64)
cost = models.DecimalField(max_digits=8, decimal_places=2)
currency = models.CharField(max_length=3)
#property
def alternative_cost(self):
if self.cost < 25:
return 0
else:
return self.cost
Then you have to add the newly created property to serializer.
i am explaining in detail about a problem that i am facing and seek help from this community. I followed this Django Rest Framework documentation but couldn't get the desired result for multi-level nesting
Consider the relationship of models as explained :
A user can have multiple Workspaces
A workspace can have multiple Projects
A Project can have multiple ProjectLists
ProjectList can have multiple Tasks
Tasks can have multiple updates
User
|
Workspaces (ForeignKey="User")
|____Projects (ForeignKey="Workspaces")
|____TodoList (ForeignKey="Projects")
|____Tasks (ForeignKey="TodoList")
|____Updates (ForeignKey="Tasks")
So what i want is to get all the data that a User has in a nested json format like this:
[
{
"workspace_id": "99a961ec-b89e-11e8-96f8-529269fb1459",
"workspace_owner": "1",
"workspace_title": "Indie Dev Works",
"projects": [
{
"project_id":"db09cfa0-b89e-11e8-96f8-529269fb1459",
"todo_list":[
{
"list_id": "9dc64e4c-b89f-11e8-96f8-529269fb1459",
"list_name":"Project list -1",
"tasks":[
"task_name":"Create HTML docs",
"updates":[
{
"id":"d5eb660e-b89f-11e8-96f8-529269fb1459",
"text":"Creating using PUG"
},
{
.....
.....
.....
.....
},
]
]
}
]
},
{
"project_id":".........",
....
....
....
..
..
.
},
]
}
]
So when my User logs in by entering email, i am trying to get all the Workspaces instance and then pass it to the serializers
in my views as mentioned below:
views.py
class InitializeHome(viewsets.ViewSet):
def list(self,request):
user_email = request.user.email
user_instance = utils.getUserInstance(user_email)
workspace_instance = WorkSpace.objects.filter(workspace_owner=user_instance)
testing_serializer = WorkSpaceSerializer(workspace_instance,many=True)
return Response(testing_serializer.data)
serializers.py
class UpdateSerializer(serializers.ModelSerializer):
class Meta:
# depth = 2
fields = '__all__'
model = Update
class TaskSerializer(serializers.ModelSerializer):
updates = UpdateSerializer(many=True,read_only=True)
class Meta:
# depth = 2
fields = '__all__'
model = Task
class ProjectTodoListSerializer(serializers.ModelSerializer):
tasks = TaskSerializer(many=True,read_only=True)
class Meta:
# depth = 2
fields = '__all__'
model = ProjectTodoListItem
class ProjectSerializer(serializers.ModelSerializer):
project_todo_list = ProjectTodoListItemSerializer(many=True,read_only=True)
class Meta:
# depth = 2
fields = '__all__'
model = Project
class WorkSpaceSerializer(serializers.ModelSerializer):
projects = ProjectSerializer(many=True,read_only=True)
class Meta:
# depth = 2
model = WorkSpace
fields = '__all__'
What I am getting is only this and none of the nested Arrays :
[
{
"id": 1,
"workspace_title": "Indie Dev Work",
"status": "ACTIVE",
"workspace_id": "26c60d80-c018-403c-84b2-d92f01f6fb7e",
"workspace_owner": 1
},
{
"id": 2,
"workspace_title": "Homework Space",
"status": "ACTIVE",
"workspace_id": "08c715cc-bd24-46d3-a1dd-14cf7ff28215",
"workspace_owner": 1
}
]
Found the answer.
By adding source="workspace_set" in all the Serializer classes, the results can be achieved:-
class WorkSpaceSerializer(serializers.ModelSerializer):
projects = ProjectSerializer(many=True,read_only=True,source="workspace_set")
class Meta:
# depth = 2
model = WorkSpace
fields = '__all__'
You can try this way too:
class WorkSpaceSerializer(serializers.ModelSerializer):
class Meta:
# depth = 2
model = WorkSpace
fields = '__all__'
def get_projects(self, obj): # get_YOUR_FIELD_NAME
return ProjectSerializer(obj.projects.all(), many=True).data
and same for other classes.
I'm not sure why your code doesn't work and i can't really test it right now but you can try naming the fields in your meta class (it should't make any difference but you can try it)
I have some models in my project and I need a especial response of the API, i'm using Django Rest framework.
class Goal(models.Model):
name = models.CharField()
# more fields
class Task(models.Model):
name = models.CharField()
goal = models.ForeignKey(Goal)
class UserTask(models.Model):
goal = models.ForeignKey(Goal)
user = models.ForeignKey(User)
# other fields
I have this response:
{
"name": "One goal",
"task": [
{
"name": "first task"
},
{
"name": "second tas"
}
]
}
But I need this:
{
"name": "One goal",
"task": [
{
"name": "first task",
"is_in_usertask": true
},
{
"name": "second tas",
"is_in_usertask": false
}
]
}
I saw this in DRF docs but I don't know how to filter UserTask by the current user (or other that is given in URL paramenter) and each Goal.
Edit:
# serializers
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
class GoalSerializer(serializers.ModelSerializer):
# related_name works fine
tasks = TaskSerializer(many=True)
class Meta:
model = Goal
try to use SerializerMethodField field as
class TaskSerializer(serializers.ModelSerializer):
is_in_usertask = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Task
fields = ('name', 'is_in_usertask')
def get_is_in_usertask(self, task):
return UserTask.objects.filter(user=self.context['request'].user, goal=task.goal).exists()
Take a look to this conversation: How to get Request.User in Django-Rest-Framework serializer?
You can't access to request.user directly
I have a models.py having:
class Other(models.Model):
name = models.CharField(max_length=200)
class ModelA(models.Model):
name = models.CharField(max_length=200)
other = models.ForeignKey(Other, on_delete=models.PROTECT)
in my rest API i want to retrieve as JsonResponse a json like this:
{
"modelA": {
"id": "modelA id automatically assigned by django model",
"name": "my modelA name",
"other": {
"id": "other thing id also automatically assigned by django model",
"name": "other thing name"
}
}
}
What is the most "pythonic" way to do it?
What you are looking for is nested serialization.
In your serializers.py you should use the serializer for the Other model inside the one for your ModelA.
In serializers.py:
from rest_framework import serializers
from .models import Other, ModelA
class OtherSerializer(serializers.ModelSerializer):
class Meta:
model = Other
fields = ('id', 'name')
class ModelASerializer(serializers.ModelSerializer):
other = OtherSerializer(read_only=True)
# The magic happens here.
# You use your already created OtherSerializer inside the one for ModelA
# And that will perform nested serialization
# Which will produce the result that you want
class Meta:
model = ModelA
fields = ('id', 'name', 'other')
# _________________________^
And now you get result like:
{
"id": 1,
"name": "my modelA name",
"other": {
"id": 1,
"name": "other thing name"
}
}