Custom Serializer and ViewSet for ManyToManyField in DRF [duplicate] - python

This question already has answers here:
Include intermediary (through model) in responses in Django Rest Framework
(4 answers)
Closed 3 years ago.
I have a M2M relationship between the Entity and EntityGroup and I want to save the corresponding entity index to the EntityGroup just like an entity array to database.
Since I used a custom through Model with additional field index inside, I need to serialize the index to the corresponding entity to the response, how should I implement that?
I'm new to django and django-rest-framework and it seems there are not similar M2M examples after a few google search. Here is my thought, a serializer could only serialize the only one model's fields with ForeignKey relationship, a viewset could have a custom model based on queryset which could merge the relationships for a few models. So I need to implement a more expandable viewset with a custom queryset inside?
Here is my code:
models.py
class Entity(models.Model):
uuid = models.CharField()
name = models.CharField()
class EntityGroup(models.Model):
name = models.CharField()
entities = models.ManyToManyField(Entity,
through='EntityGroupRelationship',
through_fields=('group', 'entity'),
related_name='groups'
)
class EntityGroupRelationship(models.Model):
entity = models.ForeignKey(Entity, on_delete=models.CASCADE)
group = models.ForeignKey(EntityGroup, on_delete=models.CASCADE)
index = models.PositiveIntegerField()
serializers.py
class EntitySerializer(serializers.ModelSerializer):
class Meta:
model = Entity
fields = '__all__'
class EntityGroupRelationshipSerializer(serializers.ModelSerializer):
class Meta:
model = EntityGroupRelationship
fields = '__all__'
class EntityGroupSerializer(serializers.ModelSerializer):
entities = EntitySerializer(many=True)
class Meta:
model = EntityGroup
fields = '__all__'
views.py
class EntityGroupViewSet(BaseModelViewSet):
queryset = EntityGroup.objects.all()
serializer_class = EntityGroupSerializer
class EntityGroupRelationshipViewSet(BaseModelViewSet):
queryset = EntityGroupRelationship.objects.all()
serializer_class = EntityGroupRelationshipSerializer
Current response
[
{
"id": 1,
"entities": [{
"id": 1,
"name": "",
}]
},
...
]
Expected respponse
[
{
"id": 1,
"entities": [
{
"index": 1,
"info": {
"id": 1,
"name": "",
}
}
]
},
...
]

You are using incorrect serializer for that. Use EntityGroupRelationshipSerializer instead of EntitySerializer. Also you need to pass correct fields
class EntitySerializer(serializers.ModelSerializer):
class Meta:
model = Entity
fields = '__all__'
class EntityGroupRelationshipSerializer(serializers.ModelSerializer):
entity = EntitySerializer()
class Meta:
model = EntityGroupRelationship
fields = ('index', 'entity')
class EntityGroupSerializer(serializers.ModelSerializer):
entities = EntityGroupRelationshipSerializer(many=True) # here should be EntityGroupRelationshipSerializer
class Meta:
model = EntityGroup
fields = '__all__'

Related

How to retrieve models data having nested relationship using serializers in Django Rest Framework?

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)

Adding foreign key data to serializer?

I'm trying to use the Django Rest-Framework to produce some JSON that shows all the user's posts, but also shows the images for that post. Image is a foreign key to Post. Here are the models:
models.py
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.CharField(max_length=200)
class Image(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
img = models.CharField(max_length=120)
views_count = models.IntegerField(default=0)
views.py
class GetPosts(ListAPIView):
serializer_class = PostSerializer
def get_queryset(self):
requested_user = get_requested_user(self)
return Post.objects.filter(user=requested_user).order_by('-created_at')
def get_requested_user(self):
filter_kwargs = {'username': self.kwargs['username']}
return get_object_or_404(User.objects.all(), **filter_kwargs)
serializers.py
class PostSerializer(serializers.ModelSerializer):
image_img = serializers.RelatedField(source='Image', read_only=True)
class Meta:
model = Post
fields = ('status', 'image_img ')
In the serializers.py, I'd like to show all of the fields for Image (img, views_count) What I get with my current code is this:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"status": "I am number 1"
}
]
}
Which contains the user's posts, but not the user's posts and each post's images. Note: Query url looks like this: /api/posts/user/
You should use Nested serializer here,
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('img',)
class PostSerializer(serializers.ModelSerializer):
image_img = ImageSerializer(source='image_set', many=True)
class Meta:
model = Post
fields = '__all__'
Hence the response will be like,
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"status": "I am number 1",
"image_img": [
{"img": "image_url"},
{"img": "image_url"},
....
]
}
]
}
How to display all field of model class in serializer?
From the doc,
You can also set the fields attribute to the special value '__all__' to indicate that all fields in the model should be used.
Reference
1. DRF- Nested Realtions
2. source argument
3. Specifying which fields to include

How can I realize a APIView to get my requirement data?

I have two models:
class TypeModel(models.Model):
type = models.CharField(max_length=12)
class Amodel(models.Model):
name = models.CharField(max_length=12)
type = models.ForeignModel(to=TypeModel)
in_use = models.BooleanField(default=False)
the Amodel is the main model, it has a type field refer to TypeModel.
in the serializers.py:
class AmodelSerializer(ModelSerializer):
class Meta:
model = Amodel
fields = "__all__"
class TypeModelSerializer(ModelSerializer):
class Meta:
model = TypeModel
fields = "__all__"
My requirement is fetch the Amodel data like this, it should be divided by type, I want to get this type data:
[
{
"type": {
name: 'type1'
},
"count": {
"used": 12, # the in_use is `True`
"unused": 24
}
},
...
]
Calculate the used and unused count.
for amodel in amodel_qs:
if amodel.in_use: used_count+=1
else: unused_count +=1

Nested field serializer - Data missing

Related to this Topic
Hi,
I cannot follow the answer at the attached topic, because an ID is missing after serialization.
Model.py
class Owner(models.Model):
name = models.CharField(db_index=True, max_length=200)
class Car(models.Model):
name = models.CharField(db_index=True, max_length=200)
LCVS = models.ForeignKey(Owner)
View.py
class OwnerViewSet(viewsets.ModelViewSet):
queryset = Owner.objects.all()
serializer_class = OwnerSerializer
class CarViewSet(viewsets.ModelViewSet):
serializer_class = CarSerializer
queryset = Car.objects.all()
Serializer.py
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
class CarSerializer(serializers.ModelSerializer):
owner = OwnerSerializer()
class Meta:
model = Car
fields = ('id', 'name', 'owner')
def create(self, validated_data):
tmp_owner = Owner.objects.get(id=validated_data["car"]["id"])
car = Car.objects.create(name=self.data['name'],owner=tmp_owner)
return car
Now i send the following request :
Request URL:http://localhost:9000/api/v1/cars
Request Method:POST
Request Paylod :
{
"name": "Car_test",
"ower": {
"id":1,
"name": "Owner_test"
}
}
But, here the validated_data don't contain the owner ID !
Traceback | Local vars
validated_data {u'Owner': OrderedDict([(u'name', u'Owner_test')]), u'name': u'Car_test'}
#Kevin Brown :
Workful ! Thanks
I'll validate your answer but I get a new problem...
Now when I try to put a new Owner, an error raise :
{
"id": [
"This field is required."
]
}
I had to create a new serializer ?
Any AutoFields on your model (which is what the automatically generated id key is) are set to read-only by default when Django REST Framework is creating fields in the background. You can confirm this by doing
repr(CarSerializer())
and seeing the field generated with read_only=True set. You can override this with the extra_kwargs Meta option which will allow you to override it and set read_only=False.
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
extra_kwargs = {
"id": {
"read_only": False,
"required": False,
},
}
This will include the id field in the validated_data when you need it.

Django Rest Framework 3.0: Saving Nested, Many-To-One Relationship

I'm attempting to build a nested relationship using Django Rest Framework 3.0. I've created my serializers and have attempted to override the create() function. My models are defined as follows:
class Item(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
name = models.CharField(max_length=200)
description = models.CharField(max_length=1000)
categories = models.ManyToManyField(Category, null=True, blank=True)
class Price(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
item = models.ForeignKey(Item, related_name='prices')
name = models.CharField(max_length=100)
cost = models.FloatField()
As you'll note, I can have multiple prices for my items. My serializers are defined as follows:
class PriceSerializer(serializers.ModelSerializer):
class Meta:
model = Price
owner = serializers.Field(source='owner.username')
exclude = ('user',)
class ItemSerializer(serializers.ModelSerializer):
prices = PriceSerializer(many=True, required=False)
categories = CategorySerializer(many=True, required=False)
class Meta:
model = Item
owner = serializers.Field(source='owner.username')
fields = ('id', 'name', 'description', 'prices', 'categories')
def create(self, validated_data):
user = validated_data.get('user')
# Get our categories
category_data = validated_data.pop('categories')
# Create our item
item = Item.objects.create(**validated_data)
# Process the categories. We create any new categories, or return the ID of existing
# categories.
for category in category_data:
category['name'] = category['name'].title()
category, created = Category.objects.get_or_create(user=user, **category)
item.categories.add(category.id)
item.save()
return item
When I try and POST a new item:
{
"name": "Testing",
"description": "This is a test",
"categories": [
{
"name": "foo"
},
{
"name": "bar"
}
],
"prices": [
{
"name": "Red",
"cost": 10
}
]
}
I get the following error:
{
"prices": [
{
"item": [
"This field is required."
]
}
]
}
Presumably because the Price serializer has no idea what the ID of the new item is. I've tried overriding this functionality in the create() function of my serializer, but it appears as though the serializer's validation is being hit before I have the opportunity to create the item and associate it with the price.
So - How do I create a new item, get the item ID, and then create each of the new prices?
The problem is that your PriceSerializer is looking for the item key because it is specified on the Price model. This isn't immediately obvious because you are using Meta.exclude instead of Meta.fields.
class PriceSerializer(serializers.ModelSerializer):
class Meta:
model = Price
exclude = ('user',)
Is the same as writing
class PriceSerializer(serializers.ModelSerializer):
class Meta:
model = Price
fields = ('id', 'item', 'name', 'cost', )
Which makes it very clear what your issue is. Because your item field on the model does not have empty=True (or null=True) set, Django REST Framework automatically generates it as a PrimaryKeyRelatedField with required=True. This is why you are getting the This field is required is required error, because Django REST Framework cannot automatically detect that it is coming from a parent serializer which already has that field.
You can get around this by removing the field from the serializer, as it doesn't appear to ever be needed.
class PriceSerializer(serializers.ModelSerializer):
class Meta:
model = Price
fields = ('id', 'name', 'cost', )
This will no longer display the item field though, but I suspect this isn't actually an issue for you.

Categories

Resources