I'm trying to serialize a model with RelatedFields as in the docs (http://www.django-rest-framework.org/api-guide/relations) but I keep getting an AttributeError.
The Error:
AttributeError at /testapi/foo/
'Foo' object has no attribute 'bar1'
The Models:
class Foo(models.Model):
foo_id = models.AutoField(primary_key=True)
name = models.TextField()
zip_code = models.TextField()
class Bar(models.Model):
user = models.OneToOneField(User)
arbitrary_field1 = models.ForeignKey(Foo, related_name='bar1')
arbitrary_field2 = models.ForeignKey(Foo, related_name='bar2')
The Serializer:
class FooSerializer(serializers.ModelSerializer):
bar1 = serializers.RelatedField()
bar2 = serializers.RelatedField()
class Meta:
model = Foo
fields = (
'foo_id',
'name',
'zip_code',
'bar1',
'bar2',
)
I may be completely wrong with this answer as I don't have a test environment set up right now but I'll do my best.
Ok, tried it in a test environment and the solution below is working for me.
I do not believe the RelatedField is needed in this case, since you are specifying the related_name for both the arbitrary fields you'd just need to reference the related name in the fields tuple instead of specifying them with RelateField in the serializer.
So you'd probably be fine with just this:
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = (
'foo_id',
'name',
'zip_code',
'bar1',
'bar2',
)
This will return serialized data similar to this:
{
....
"bar1": [
1,
2,
3,
4
],
"bar2": [
1,
2,
3
]
....
}
Just a side note, but if you don't want just the pk's of the relationship you can try setting the depth variable in the Meta class of the Foo serializer to 1 or whatever number you'd like, example from the docs http://www.django-rest-framework.org/api-guide/serializers#specifying-nested-serialization.
Or if you want a more custom representation of that relationship you can just create a serializer for the Bar model with whatever fields you want to be included or excluded and use that serializer in the Foo serializer, just like this example in the docs http://www.django-rest-framework.org/api-guide/relations#example.
Edit:
Here is the DRF documentation on reverse relationships http://www.django-rest-framework.org/api-guide/relations#reverse-relations
Related
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')
I am trying to make a nested serializer but when I run the following code it gives me an empty list. I tried to replicate the solution of this question and my problem is exactly similar
The only difference is in that answer serializer.Serializer is used but I am using Model Serializer
class hhhSerializer(serializers.Modelserializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(read_only=True)
class Meta:
model = ItemBatch
fields = ('id','name')
class dispatchhistorySerializer(serializers.ModelSerializer):
truck_name = ReadOnlyField(source='truck_name.name')
truck_type = ReadOnlyField(source='truck_type.name')
items = hhhSerializer(many=True)
class Meta:
model = DispatchPlan
fields = "__all__"
Output:
"id": 35,
"truck_name": "24 ft mxl 14 Ton",
"truck_type": "Container",
"items": [
{},
{},
{},
{},
{},
{},
{},
{},
{},
{}
],
You have to declare the field explicitly at DispatchHistorySerializer.Meta.fields; now, as personal recommendation avoid always "all" in the field list
This code should work (I had renamed your classes to comform python convention)
class HhhSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(read_only=True)
class Meta:
model = ItemBatch
fields = ('id','name')
class DispatchHistorySerializer(serializers.ModelSerializer):
truck_name = ReadOnlyField(source='truck_name.name')
truck_type = ReadOnlyField(source='truck_type.name')
items = HhhSerializer(many=True) # 2) here we say how to serialize 'items'
class Meta:
model = DispatchPlan
fields = ('id', 'truck_name', 'truck_type', 'items',) # 1) here we say: include 'items' please
EDIT: if using ModelSerializer, define which model in the Meta class; if it isn't a ModelSerializer, use a simple Serializer instead
I have a model like so:
class MyModel(models.Model):
thing = models.ForeignKey('Thing')
Serializers and ViewSet like so:
class ThingSerializer(serializers.ModelSerializer):
class Meta:
model = Thing
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
For MyModel list endpoint, DRF return objects like:
[
{ id: 1, thing: 1 },
{ id: 2, thing: 1 },
{ id: 3, thing: 2 },
{ id: 4, thing: 4 }
]
Is there a way to tell DRF to automatically include "_id" on the end of ForeignKey fields that are just IDs and not the actual related object? e.g.
[
{ id: 1, thing_id: 1 },
{ id: 2, thing_id: 1 },
{ id: 3, thing_id: 2 },
{ id: 4, thing_id: 4 }
]
Found same request and solution here:
https://github.com/tomchristie/django-rest-framework/issues/3121
https://gist.github.com/ostcar/eb78515a41ab41d1755b
The AppendIdSerializerMixin.get_fields override suffices for output of JSON objects (with _id appended) but when writing back to the API, it's a little more complicated and the logic in IdPrimaryKeyRelatedField and IdManyRelatedField handles that.
class IdManyRelatedField(relations.ManyRelatedField):
field_name_suffix = '_ids'
def bind(self, field_name, parent):
self.source = field_name[:-len(self.field_name_suffix)]
super(IdManyRelatedField, self).bind(field_name, parent)
class IdPrimaryKeyRelatedField(relations.PrimaryKeyRelatedField):
"""
Field that the field name to FIELD_NAME_id.
Only works together the our ModelSerializer.
"""
many_related_field_class = IdManyRelatedField
field_name_suffix = '_id'
def bind(self, field_name, parent):
"""
Called when the field is bound to the serializer.
Changes the source so that the original field name is used (removes
the _id suffix).
"""
if field_name:
self.source = field_name[:-len(self.field_name_suffix)]
super(IdPrimaryKeyRelatedField, self).bind(field_name, parent)
class AppendIdSerializerMixin(object):
'''
Append '_id' to FK field names
https://gist.github.com/ostcar/eb78515a41ab41d1755b
'''
serializer_related_field = IdPrimaryKeyRelatedField
def get_fields(self):
fields = super(AppendIdSerializerMixin, self).get_fields()
new_fields = type(fields)()
for field_name, field in fields.items():
if getattr(field, 'field_name_suffix', None):
field_name += field.field_name_suffix
new_fields[field_name] = field
return new_fields
class MyModelSerializer(AppendIdSerializerMixin, serializers.ModelSerializer):
class Meta:
model = MyModel
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
This has worked for me as of today in July 2022.
def ParentModel():
...
def ChildModel():
parent = models.ForeignKey(ParentModel)
serializers.py
def ChildSerializer():
parent_id = serializers.PrimaryKeyRelatedField(
source="parent",
queryset=ParentModel.objects.all(),
)
class Meta:
exclude = ["parent"]
fields = "__all__"
The source="parent" in the serializer field, is what allows this rename to happen; it needs to match the ForeignKey field name in ChildModel.
Note: The exclude = ["parent"] is required if you are using fields = "__all__" so that DRF doesn't require or return the default field name.
Note 2: If using UUID for your ID field then you'll need to add pk_field=UUIDField(format="hex_verbose"), to the serializer field you're renaming to {field}_id
You can use db_column model field option in your model:
class MyModel(models.Model):
thing = models.ForeignKey('Thing', db_column='thing_id')
Or if you don't want to change your model, you can do so by changing source serializer field in your serializer:
class ThingSerializer(serializers.ModelSerializer):
thing_id = serializers.IntegerField(source='thing')
class Meta:
model = Thing
fields = ('thing_id','other_field', 'another_field')
ok matt simply add that parameter to your model class:
thing = models.ForeignKey(Thing, related_name='thing_id')
It appears to be rather complicated since it's not something DRF allows to configure. But like always, you can override things.
Everything seems to happens in the model_meta.py file. In this file, you can replace
forward_relations[field.name] = RelationInfo(
by
forward_relations[field.name + '_id'] = RelationInfo(
Careful, you need to do it twice in that function.
Once you've done that, you have still work to do as the ModelSeralizer depends on the real model_meta. It appears you need to replace those three lines:
https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/serializers.py#L865
https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/serializers.py#L944
https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/serializers.py#L1425
Then, you need to implement a MyModelSerializer which overrides ModelSerializer and the three methods: create, get_fields, get_unique_together_validators. I tested it on GET requests and it works.
As you can see, it's a significant amount of code rewriting which implies difficulties for maintaining upgrades. Then, I would strongly recommend to think twice before doing so. In the mean time, you can still open an issue on the DRF project for making it more configurable (and maintainable).
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.
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',
)