Django REST Framework - Relationships between nested object lists - python

I couldn't find an answer to this problem online. That said, I am new to building REST APIs and may be doing something fundamentally incorrect.
What I'd like to do is POST a JSON object with characteristics like this:
{
"groceryStoreVisit": 100,
"fruits": [
{"id":10, "type":"apple", "quantity":4},
{"id":20, "type":"orange", "quantity":3},
{"id":30, "type":"banana", "quantity":6}
],
"nuts": [
{"id":40, "type":"cashew", "no_bags":2},
{"id":50, "type":"peanut", "no_bags":4}
],
"bags": [
{"id":0, "type":"paper", "contents":[10,20,30]},
{"id":1, "type":"plastic", "contents":[40,50]}
]}
My issue is that, upon trying to serialize the data, I'm getting an error like this:
{"bags":[{"fruits":["Object with id=10 does not exist."]}]}
My Bag serialization class looks like this:
class BagSerializer(serializers.ModelSerializer):
contents = serializers.SlugRelatedField(many=True, queryset=Fruit.objects.all(), slug_field='id')
class Meta:
model = Bag
fields = ('id', 'type', 'contents')
My GroceryStoreVisit (entire object) serialization class looks like this:
class GroceryStoreVisitSerializer(serializers.ModelSerializer):
fruits = FruitSerializer(many=True)
bags = BagSerializer(many=True)
class Meta:
model = GroceryStoreVisit
fields = ('groceryStoreVisit', 'fruits', 'nuts', 'bags')
def create(self, validated_data):
fruits_data = validated_data.pop('fruits')
nuts_data = validated_data.pop('nuts')
bags_data = validated_data.pop('bags')
groceryStoreVisit = GroceryStoreVisit.objects.create(**validated_data)
for fruit_data in fruits_data:
Fruit.objects.create(groceryStoreVisit=groceryStoreVisit, **fruit_data)
for nut_data in nuts_data:
Nut.objects.create(groceryStoreVisit=groceryStoreVisit, **nut_data)
for bag_data in bags_data:
Bag.objects.create(groceryStoreVisit=groceryStoreVisit, **bag_data)
return groceryStoreVisit
I'm not sure if this is enough information to give the picture about what I'm trying to accomplish. There's nothing exotic about the models or Fruit/Nut serializers. Please let me know if there's anything I can clarify to make the problem definition more comprehensible.
I want to validate that the Bag contents exist within the top-level GroceryStoreVisit object. Can I do this as I'm trying to?
Thanks for the help!

Related

Get Related Data from ManyToManyField in Django

Working with ManyToManyField I want to get data of all the users related to all the queried model object along with other field data in the model.
For example for the below model, I have 2 users related to this "ChatRoom"
class ChatRoomParticipants(models.Model):
user = models.ManyToManyField(User, related_name='chatroom_users')
room = models.ForeignKey(ChatRoom, on_delete=models.PROTECT)
With the below query
chatrooms = list(ChatRoomParticipants.objects.filter(user=user).values('user__user_uid', 'room__id', 'room__name'))
I'm able to fetch
[{'user__user_uid': UUID('f4253fbd-90d1-471f-b541-80813b51d610'), 'room__id': 4, 'room__name': 'f4253fbd-90d1-471f-b541-80813b51d610-872952bb-6c34-4e50-b6fd-7053dfa583de'}]
But I'm expecting something like
[{
'user__user_uid1': UUID('f4253fbd-90d1-471f-b541-80813b51d610'),
'user__user_uid2': UUID('872952bb-6c34-4e50-b6fd-7053dfa583de'),
'room__id': 4,
'room__name': 'f4253fbd-90d1-471f-b541-80813b51d610-872952bb-6c34-4e50-b6fd-7053dfa583de'
},
{
'user__user_uid1': UUID('f4253fbd-90d1-471f-b541-80813b51d610'),
'user__user_uid2': UUID('eecd66e7-4874-4b96-bde0-7dd37d0b83b3'),
'room__id': 5,
'room__name': 'f4253fbd-90d1-471f-b541-80813b51d610-eecd66e7-4874-4b96-bde0-7dd37d0b83b3'
},
{
'user__user_uid1': UUID('f4253fbd-90d1-471f-b541-80813b51d610'),
'user__user_uid2': UUID('4f4c0f3d-2292-4d06-afdc-1e95962ac5e6'),
'room__id': 6,
'room__name': 'f4253fbd-90d1-471f-b541-80813b51d610-4f4c0f3d-2292-4d06-afdc-1e95962ac5e6'
}]
I've searched and found I can do something like
user_data = chatrooms.users.all().values('user_uid')
But the above doesn't work well with filter and I would miss out data on room.
Note: I know that's not a correct method to do what I'm trying to achieve, if anyone can enlighten with what's the correct way to achieve the same data.
your example is somewhat confusing, but I think what you are looking for is to find the information of the users related to the same room.
chat_room = ChatRoomParticipants.objects.get(id=id_room)
users_chat = chat_room.user.all().values_list('user_uid', flat=True)
data = {
"room__id": chat_room.room.id
"room__name": chat_room.room.name
"users" : users_chat
}
for something more consistent you can use serializers

Unexpected error while request parsing using a serializer

While parsing my request data from front-end and converting into JSON format using a serializer. I am getting some unexpected errors.
while request parsing pattern using serializers given as mentioned below, it shows me the following error:(I found the below error using: contact_serializer.errors)
{'address': {u'non_field_errors': [u'Invalid data. Expected a dictionary, but got str.']}}
I do not think it will work like this. You have to remember here is that if you input the values like this, it will ultimately be stored in DB, and it is hard coded values. Even if you insist to do it like this, then use a list of dictionary like this:
request.data['phone_number'] = [{'number': '9999999999'}]
request.data['cont_email'] = [{'email':'tim#gmail.com'}]
And update the serializer like this:
class CrmContactSerializer(serializers.ModelSerializer):
phone_number = PhoneNumberSerializer(source = 'contact_number', many=True)
cont_email = ContactEmailSerializer(source = 'contact_email', many=True)
class Meta:
model = RestaurantContactAssociation
fields = ('id','phone_number','cont_email','contact')
def create(self, validated_data):
phone_number = validated_data.pop('contact_number')
cont_email = validated_data.pop('contact_email')
restaurant = super(CrmContactSerializer, self).create(validated_data)
phone_instance = PhoneNumber(**phone_number)
phone_instance.restaurant = restaurant
phone_instance.save()
email_instance = ContactEmail(**phone_number)
email_instance.restaurant = restaurant
email_instance.save()
return restaurant
Reason for many=True is that one restaurant can have multiple numbers or emails(as it has one to many relationship with respective models).
Now, if you think of proper way of implementing, you can make phone_number and cont_email read only fields, so that it will be used when only reading, not writing:
class CrmContactSerializer(serializers.ModelSerializer):
phone_number = PhoneNumberSerializer(source = 'contact_number', read_only=True)
cont_email = ContactEmailSerializer(source = 'contact_email', read_only=True)
class Meta:
model = RestaurantContactAssociation
fields = ('id','phone_number','cont_email','contact')
In that way, validation error can be handled for phone number and cont email.

Serialize OneToMany relation into array in Django

Suppose I have some django models:
class Restaurant(Model):
name = CharField(max_length=200)
class Food(Model):
restaurant = ForeignKey(Restaurant, on_delete=CASCADE)
name = CharField(max_length=200)
One Restaurant can have multiple foods.
And I want my json to look like this:
[
{
"name": "xxx",
"food": [{"name": "xxx"}, {"name": "xxx"}, ...]
}, ...
]
I can use something like:
restaurants = Restaurant.objects.all().values()
for restaurant in restaurants:
restaurant['food'] = Food.objects.filter(restaurant__id=restaurant['id']).values()
But this loses some optimization in SQL level, as this is using application level loop.
Is there a better way to do this?
check this
restaurant = Restaurant.objects.all()
result = []
for rest in restaurant:
data = {
'name': rest.name,
'food': rest.food_set.all().values('name')
}
result.appen(data)
Try it....
User django rest framework. using serialzers you can do it more effectively
#Nakamura You are correct, query inside the loop is not a good way of coding.
below is one way of solving
restaurants = Restaurant.objects.all().values()
foods = Food.objects.all()
for restaurant in restaurants:
restaurant['food'] = foods.filter(restaurant__id=restaurant['id']).values()
As you gave required JSON format i suggested this answer.

Django DRF multi model view

I wanna retrieve few models in a single request so i'll get:
{
"cars": [
{
"id": "1",
"name": "foo"
}
],
"trucks": [
{
"id": "1",
"name": "goo"
}
],
"bikes": [
{
"id": "1",
"name": "doo"
}
],
}
for that I've create a serializer:
class VehiclesSerializer(serializers.Serializer):
cars = CarSerializer(many=True, read_only=True)
trucks = TruckSerializer(many=True, read_only=True)
bikes = BikeSerializer(many=True, read_only=True)
and a view:
class VehiclesListView(generics.ListAPIView):
queryset = ???????
serializer_class = VehiclesSerializer
but as you can see, I haven't manage to figure out how to write the queryset.
Any help?
UPDATE:
Just to clarify my question. There is no Vehicle model.
That's why I'm NOT writing the regular
queryset = Vehicles.objects.all()
There are a few options I think. The two cleanest ones would be:
Have different endpoints for your models. This feels like the most RESTful approach to me.
Create a VehicleModel that is ForeignKey related to your other models. That should even work with the Serializer you have written.
The 2nd approach would look something like this for the models:
# models.py
class Vehicle(models.Model):
pass
class Truck(models.Model):
vehicle = models.ForeignKey(Vehicle, related_name='trucks')
....
And like this for the views:
#views.py
class VehiclesListView(generics.ListAPIView):
queryset = Vehicle.objects.prefetch_related('cars', 'trucks', 'bikes').all()
serializer_class = VehiclesSerializer
Note on the prefetch_related() in there: Django will use four DB queries to get your objects, one for each related model and one for the main model. If you use Vehicle.objects.all() by itself, the Serializer will create a DB requests for every Vehicle it encounters. See Django docs here
If you don't want to do that, you can always override ListAPIView.list with your custom logic, even bypass the serializer completely:
class VehiclesListView(generics.ListAPIView):
def list(self, request, *args, **kwargs):
cars = Cars.objects.values('id', 'name')
trucks = Trucks.objects.values('id', 'name')
bikes = Bikes.objects.values('id', 'name')
out = {
'trucks': trucks,
'cars': cars,
'bikes': bikes,
}
return Response(out)
Note on using values() instead of all(): You don't really use any other model fields in your serializer, so there's no use in querying extra fields. Docs
You should really read the API Documentation.
queryset = Vehicles.objects.all();
or to get random 100 or 10 objects:
Vehicles.objects.all().order_by('?')[:100]
or
Vehicles.objects.all().order_by('?')[:10]
queryset = Vehicles.objects.all()
Or if you need to filter on datetime or request properties.
def get_queryset(self):
return Vehicles.objects.all()
The best that I've got was without using generics:
class VehiclesListView(APIView):
def get(self, request, *args, **kwargs):
ser = VehiclesSerializer({
'cars': Car.objects.all(),
'trucks': Truck.objects.all(),
'bikes': Bike.objects.all()
})
return JsonResponse(ser.data)
Thanks all for your help :)
The easiest way is to use DjangoRestMultipleModels
The documentation is beautiful.
In your case the view would look like this:
from drf_multiple_model.views import MultipleModelAPIView
class VehicleView(MultipleModelAPIView):
queryList = [
(Car.objects.all(),CarSerializer), #(queryset,Serializer)
(Truck.objects.all(),TruckSerializer),
(Bike.objects.all(), BikeSerializer),
]
Hope this helps someone..

Django serialization of inherited model

I have a problem with serialization of Django inherited models. For example
class Animal(models.Model):
color = models.CharField(max_length=50)
class Dog(Animal):
name = models.CharField(max_length=50)
...
# now I want to serialize Dog model with Animal inherited fields obviously included
print serializers.serialize('xml', Dog.objects.all())
and only Dog model has been serialized.
I can do smth like
all_objects = list(Animal.objects.all()) + list(Dog.objects.all())
print serializers.serialize('xml', all_objects)
But it looks ugly and because my models are very big so I have to use SAX parser and with such output it's difficult to parse.
Any idea how to serialize django models with parent class?
**EDIT: ** It use to work ok before this patch has been applied. And the explanation why the patch exist "Model saving was too aggressive about creating new parent class instances during deserialization. Raw save on a model now skips saving of the parent class. " I think there should be an option to be able to serialize "local fields only" by default and second option - "all" - to serialize all inherited fields.
You found your answer in the documentation of the patch.
all_objects = list(Animal.objects.all()) + list(Dog.objects.all())
print serializers.serialize('xml', all_objects)
However, if you change Animal to be an abstract base class it will work:
class Animal(models.Model):
color = models.CharField(max_length=50)
class Meta:
abstract = True
class Dog(Animal):
name = models.CharField(max_length=50)
This works as of Django 1.0. See http://docs.djangoproject.com/en/dev/topics/db/models/.
You'll need a custom serializer to support inherited fields, as Django's serializer will only serialize local fields.
I ended up writing my own when dealing with this issue, feel free to copy it: https://github.com/zmathew/django-backbone/blob/master/backbone/serializers.py
In order to use it on its own, you need to do:
serializer = AllFieldsSerializer()
serializer.serialize(queryset, fields=fields)
print serializer.getvalue()
I had the same problem, and i wrote a 'small' queryset serializer which navigates up the inheritance tree and returns all the fields serialized.
It's far from perfect... but works for me :)
a = QuerySetSerializer(MyModel, myqueryset)
a.serialize()
And the snippet:
from __future__ import unicode_literals
import json
import inspect
from django.core import serializers
from django.db.models.base import Model as DjangoBaseModel
class QuerySetSerializer(object):
def __init__(self, model, initial_queryset):
"""
#param model: The model of your queryset
#param initial_queryset: The queryset to serialize
"""
self.model = model
self.initial_queryset = initial_queryset
self.inheritance_tree = self._discover_inheritance_tree()
def serialize(self):
list_of_querysets = self._join_inheritance_tree_objects()
merged_querysets = self._zip_queryset_list(list_of_querysets)
result = []
for related_objects in merged_querysets:
result.append(self._serialize_related_objects(related_objects))
return json.dumps(result)
def _serialize_related_objects(self, related_objects):
"""
In this method, we serialize each instance using the django's serializer function as shown in :
See https://docs.djangoproject.com/en/1.10/topics/serialization/#inherited-models
However, it returns a list with mixed objects... Here we join those related objects into one single dict
"""
serialized_objects = []
for related_object in related_objects:
serialized_object = self._serialize_object(related_object)
fields = serialized_object['fields']
fields['pk'] = serialized_object['pk']
serialized_objects.append(fields)
merged_related_objects = {k: v for d in serialized_objects for k, v in d.items()}
return merged_related_objects
def _serialize_object(self, obj):
data = serializers.serialize('json', [obj, ])
struct = json.loads(data)
return struct[0]
def _discover_inheritance_tree(self):
# We need to find the inheritance tree which excludes abstract classes,
# so we can then join them when serializing the instance
return [x for x in inspect.getmro(self.model) if x is not object and x is not DjangoBaseModel and not x._meta.abstract]
def _join_inheritance_tree_objects(self):
"""
Here we join the required querysets from the non abstract inherited models, which we need so we are able to
serialize them.
Lets say that MyUser inherits from Customer and customer inherits from django's User model
This will return [list(MyUser.objects.filter(...), list(Customer.objects.filter(...), list(User.objects.filter(...)
"""
initial_ids = self._get_initial_ids()
inheritance__querysets = [list(x.objects.filter(id__in=initial_ids).order_by("id")) for x in self.inheritance_tree]
return inheritance__querysets
def _zip_queryset_list(self, list_of_querysets):
"""
At this stage, we have something like:
(
[MyUser1, MyUser2, MyUser3],
[Customer1, Customer2, Customer3],
[User1, User2, User3]
)
And to make it easier to work with, we 'zip' the list of lists so it looks like:
(
[MyUser1, Customer1, User1],
[MyUser2, Customer2, User2],
[MyUser3, Customer3, User3],
)
"""
return zip(*list_of_querysets)
def _get_initial_ids(self):
"""
Returns a list of ids of the initial queryset
"""
return self.initial_queryset.order_by("id").values_list("id", flat=True)
You can define a custom Serializer:
class DogSerializer(serializers.ModelSerializer):
class Meta:
model = Dog
fields = ('color','name')
Use it like:
serializer = DogSerializer(Dog.objects.all(), many=True)
print serializer.data enter code here
Did you look at select_related() ?
as in
serializers.serialize('xml', Dog.objects.select_related().all())

Categories

Resources