Serialize relation both ways with Django rest_framework - django

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.

Related

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')

Avoid nested objects when using nested serializers

I have two models, one contains the other in a foreignKey relationship, I wanted to make an API that would return the combine of these two models, so I attempted to use nested Serializers to add the related model as well, but the data are not all on the same level, the related models is a object inside the first.
Here are the Models
class ModelOne(models.Model):
last_counter = models.IntegerField()
class ModelTwo(models.Model):
model_one = models.ForeignKey(ModelOne, on_delete=models.CASCADE)
category = models.CharField(max_length=64)
counter_type = models.CharField(max_length=32)
Here are the serializers
class ModelOneSerializer(serializers.ModelSerializer):
class Meta:
model = ModelOne
fields = "__all__"
class ModelTwoSerializer(serializers.ModelSerializer):
model_one= ModelOneSerializer(read_only=True)
class Meta:
model = ModelTwo
fields = "__all__"
This would return from the API in the form of
{
"category" : ...,
"counter_type" : ...,
"model_one" : {
"last_counter" : ...
}
}
But I don't want the response to be like that, I want it more like this
{
"category" : ...,
"counter_type" : ...,
"last_counter" : ...,
}
Is there a way to achieve this through serializers?
Use SerializerMethodField
from rest_framework.fields import SerializerMethodField
class ModelTwoSerializer(serializers.ModelSerializer):
last_counter = SerializerMethodField()
class Meta:
model = ModelTwo
fields = "__all__"
def get_last_counter(self, obj):
return ModelOneSerializer(obj.model_one).data['last_counter']
When creating custom fields(field_one for example) with SerializerMethodField, you have to create a method called get_field_one, for this method to be automatically detected by the serializer.
You can achieve what you want to do using SerializerMethodField from drf fields:
SerializerMethodField is a read-only field that computes its value at request processing time, by calling a method on the serializer class it is attached to. For example for your case it will look like this. Notice that the computed last_counter is added on the serialized model fields.
from rest_framework.fields import SerializerMethodField
class ModelTwoSerializer(serializers.ModelSerializer):
last_counter = serializers.SerializerMethodField()
class Meta:
model = ModelTwo
fields = ["category", "counter_type", "last_counter"]
def get_last_counter(self, obj):
return int(obj.model_one.last_counter)
SerializerMethodField accepts method_name, but it’s usually more convenient to use the default pattern for naming those methods, which is get_. Just make sure you‘re not overburdening your method fields with any heavy-lifting operations.
You can read more on the official documentation:enter link description here

Update three-level nested django model using serializer

I am trying to update one of my models (which is a nested model - three level actually as you can see below) and I am getting the following error:
AssertionError: The .update() method does not support writable nestedfields by default. Write an explicit .update() method for serializer SystemSettingsSerializer, or set read_only=True on nested serializer fields.
All day I have been reading about nested models and nested serializers, trying to add update and create methods setting fields as read_only=True but no matter what I did, it just didn't work :( :(
These are my models:
class SystemSettings(models.Model):
# ... some fields
class Components(models.Model):
settings = models.ForeignKey(SystemSettings, related_name="Components")
class SysComponent(models.Model):
class Meta:
abstarct = True
index = models.PositiveIntegerField(primery_key=True)
is_active = models.BooleanField(default=False)
component = NotImplemented
class Foo(SysComponent):
component = models.ForeignKey(Components, related_name="Foo")
class Bar(SysComponent):
component = models.ForeignKey(Components, related_name="Bar")
task_id = models.PositiveIntegerField(default=0)
and serializers:
class SystemSettingsSerializer(ModelSerializer):
Components = ComponentsSerializer(many=True)
class Meta:
model = SystemSettings
fields = [# some fields,
Components]
class ComponentsSerializer(ModelSerializer):
Foo = FooSerializer(many=True)
Bar = BarSerializer(many=True)
class Meta:
model = Components
fields = ['Foo',
'Bar']
class FooSerializer(ModelSerializer):
class Meta:
model = Foo
class BarSerializer(ModelSerializer):
class Meta:
model = Bar
My logic is the following:
I am fetching the SystemSettings via GET and display it in a form.
The user changes it as much as he want and by clicking submit I send it back via PUT.
As I said I am getting the error above after clicking submit.
Any help would be appreciated.
EDIT: I am using django 1.7.8 by the way

Django Rest Framework - Reverse relations

how do you include related fields in the api?
class Foo(models.Model):
name = models.CharField(...)
class Bar(models.Model):
foo = models.ForeignKey(Foo)
description = models.CharField()
Each Foo has a couple of Bar's related to him, like images or what ever.
How do I get these Bar's displaying in the Foo's resource?
with tastypie its quit simple, im not sure with Django Rest Framework..
I got it working! Shweeet!
Ok this is what I did:
Created serializers, Views and URLS for the Bar object as described in the Quickstart docs of Django REST Framework.
Then in the Foo Serializer I did this:
class FooSerializer(serializers.HyperlinkedModelSerializer):
# note the name bar should be the same than the model Bar
bar = serializers.ManyHyperlinkedRelatedField(
source='bar_set', # this is the model class name (and add set, this is how you call the reverse relation of bar)
view_name='bar-detail' # the name of the URL, required
)
class Meta:
model = Listing
Actualy its real simple, the docs just dont show it well I would say..
These days you can achieve this by just simply adding the reverse relationship to the fields tuple.
In your case:
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = (
'name',
'bar_set',
)
Now the "bar"-set will be included in your Foo response.
I couldn't get the above working because I have a model called FooSomething.
I found the following worked for me.
# models.py
class FooSomething(models.Model):
name = models.CharField(...)
class Bar(models.Model):
foo = models.ForeignKey(FooSomething, related_name='foosomethings')
description = models.CharField()
# serializer.py
class FooSomethingSerializer(serializers.ModelSerializer):
foosomethings = serializers.StringRelatedField(many=True)
class Meta:
model = FooSomething
fields = (
'name',
'foosomethings',
)

Posting data to create related Tastypie resources simultaneously?

Given two related Django models A and B in a OneToMany relationship:
models.py
class A(models.Model):
name = models.CharField(max_length=5)
class B(models.Model):
name = models.CharField(max_length=5)
a = models.ForeignKey(A)
And given (potentially non-optimal) Tastypie resources:
api.py
class AResource(ModelResource):
bs = fields.ToManyField( 'projectname.api.BResource', 'bs', full = True)
class Meta:
queryset = A.objects.all()
class BResource(ModelResource):
a = fields.ToOneField( AResource, 'a', full = True)
class Meta:
queryset = B.objects.all()
Let's assume the database is empty so far. Now I have related external data, and would like to crowd the database it with both an instance of A and several instances of B.
What is the prettiest Tastypionic way to approach this problem? Is it possible to crowd both A and the Bs at once? Or do I need to crowd first A, and then crowd B supplying A's ID as the ForeignKey?
It would be great if someone could come up with an post example (using e.g. a python dictionary and httplib2, or curl).
Thanks a million.
The solution is here . Use the related name for tastypie fields which automatically populate the reverse relationship while creating multiple objects at once.
http://django-tastypie.readthedocs.org/en/v0.10.0/fields.html#tastypie.fields.RelatedField.related_name
RelatedField.related_name
Used to help automatically populate reverse relations when creating data. Defaults to None.
In order for this option to work correctly, there must be a field on the other Resource with this as an attribute/instance_name. Usually this just means adding a reflecting ToOneField pointing back.
Example:
class EntryResource(ModelResource):
authors = fields.ToManyField('path.to.api.resources.AuthorResource', 'author_set', related_name='entry')
class Meta:
queryset = Entry.objects.all()
resource_name = 'entry'
class AuthorResource(ModelResource):
entry = fields.ToOneField(EntryResource, 'entry')
class Meta:
queryset = Author.objects.all()
resource_name = 'author'
Use of related_name do the task. it maps the objects of related fields and automatically populates the relations when creating data.
as you did full=True on both side of your resources it will generate maximum recursion depth exceeded exception because both resources are full in each others.
Here is one solution involving ManyToMany instead of OneToMany relationships:
models.py
class B(models.Model):
name = models.CharField(max_length=5)
class A(models.Model):
name = models.CharField(max_length=5)
bs = models.ManyToManyField(B)
api.py
class BResource(ModelResource):
class Meta:
queryset = B.objects.all()
resource_name = 'b'
class AResource(ModelResource):
bs = fields.ToManyField( BResource, 'bs', related_name = 'a', full = True, null=True)
class Meta:
queryset = A.objects.all()
resource_name = 'a'
curl
curl -v -H "Content-Type: application/json" -X POST --data '{"name":"a_name1", "bs":[{"name":"b_name1"}, {"name": "b_name2"}]}' http:<my_path>/api/a/
httplib2.py
A working example to post data via a python script using the httplib2 package is based on a neat and simple solution posted by warren-runk:
post_dict(
url='http:<my_path>/api/a/',
dictionary={
'name' : 'a_name1',
'bs' : [
{'name' : 'b_name1'},
{'name' : 'b_name1'},
]
}
)
However, now an additional table to relate A and B is created in the database. There might be better solutions based on the OneToMany relationship of A and B?