I am working on a Django REST framework API and are running into some issues with using the Serializers. I'll try to isolate the problem a little. Basically there are the Room and a Location models.
class Room(models.Model):
uuid = UUIDField(primary_key=True)
...
location = models.ForeignKey(Location, related_name='room')
...
class Location(models.Model):
uuid = UUIDField(primary_key=True)
name = models.CharField(max_length=255)
def __unicode__(self):
return unicode(self.name)
and the corresponding serializers,
class RoomSerializer(serializers.ModelSerializer):
location = serializers.RelatedField()
class Meta:
model = Room
fields = ('uuid', 'location')
class LocationSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Location
fields = ('uuid', 'name')
I've explored the many ways to change the display of uuid to the location name including the use of serializers.SerializerMethodField() and also using the above serializers.RelatedField(), and they do work. As a reference, see the JSON output below.
{
"uuid": "491ab09d-qqqq-wwww-eeee-5801dbac0fef",
"location": "Kota Kinabalu",
"created": "2014-09-03T07:52:45.399Z",
"modified": "2014-09-03T07:52:45.530Z"
}
However, I am not able to do a model.save() on Room since Django or rather the database (postgresql in my case) complains that the location_id is empty. I think its due to the fact that when I send a POST request to the API endpoint /api/rooms/ the location_id is left blank when I modify my RoomSerializer.
If one uses a default ModelViewSet, you would see that the Location field is missing from the default form.
How do I use serializers properly such that I could display the name of the location ("Kota Kinabalu" instead of a uuid string like 6e6acbbb-xxxx-yyyy-zzzz-1cf1a5bac22c) and I am still able to accept a correct POST request?
(I'm moving quik_silv comment into an answer)
You should use the SlugRelatedField provided by DRF: http://www.django-rest-framework.org/api-guide/relations/#slugrelatedfield
In this case all you have to do is add a field like: location = serializers.SlugRelatedField(slug_field='name', read_only=True). Note the use of the parameter read_only, which should fix the problem in creating model instances.
You should try using customized UUID related field. You can find an example here.
I know it had been a long time and you might have already got the solution. But I am posting here the solution how I implemented this. After reading a bit of docs, I came across a method named to_representation(). I used this method to convert the data which is being returned in form of primary key to any other form. You can refer about this method in the docs here.
Related
I am working through a tutorial that includes the building of an articles app. I have an Article model that I am serializing and I am curious about why I need to explicitly set certain fields when using a ModelSerializer.
Here is my model:
from django.db import models
from core.models import TimestampedModel
class Article(TimestampedModel):
slug = models.SlugField(db_index=True, max_length=255, unique=True)
title = models.CharField(db_index=True, max_length=255)
description = models.TextField()
body = models.TextField()
author = models.ForeignKey('profiles.Profile', on_delete=models.CASCADE, related_name='articles')
def __str__(self):
return self.title
Pretty standard stuff. Next step is to serialize the model data in my serializers.py file:
class ArticleSerializer(serializers.ModelSerializer):
author = ProfileSerializer(read_only=True) # Three fields from the Profile app
description = serializers.CharField(required=False)
slug = serializers.SlugField(required=False)
class Meta:
model = Article
fields = (
'author',
'body',
'createdAt',
'description',
'slug',
'title',
'updatedAt',
)
Specifically, why do I need to explicitly state the author, description, and slug fields if I am using serializers.ModelSerializer and pulling those fields in from my model in my class Meta: below?
Thanks!
In the Django-Rest-Framework documentation, drf-docs/model_serializer/specifying-which-fields-to-include it says:
If you only want a subset of the default fields to be used in a model serializer, you can do so using fields or exclude options, just as you would with a ModelForm. It is strongly recommended that you explicitly set all fields that should be serialized using the fields attribute. This will make it less likely to result in unintentionally exposing data when your models change.
Therefore by using fields = in the Serializer META, you can specify just the needed fields, and not returning vital fields like id, or exessive information like updated and created timestamps.
You can also instead of using fields, use exclude, which again takes in a tuple, but just excludes the fields you don't want.
These are especially useful when your database table contains a lot of information, returning all this information, especially if it is listed, can result in large return JSON's, where the frontend may only use a small percentage of the sent data.
DRF has designed their framework like this to specifically combat these problems.
In my opinion, we should define field in serializer for:
Your api use serializer don't need all data of your models. Then you can limit field can get by serializer. It faster if you have so much data.
You dont want public all field of your model. Example like id
Custom field in serializer like serializers.SerializerMethodField() must define in fields for work
Finally, iF you dont want, you can define serializer without define fields. Its will work normally
Problem:
I'm using Django Rest Framework and i want to fetch all models with the relationships included, like this:
TestModel.objects.all()
My model looks like this:
class TestModel(models.Model):
name = models.CharField(max_length=32)
related_model = models.ForeignKey(TestRelation)
Problem is, i only get the Primary Keys for related_model but i need the whole related_model!
I'm using the ListCreateAPIView, with the above queryset (TestModel.objects.all()) and the most basic form of the ModelSerializer.
I tried the PrimaryKeyRelatedField but i get the same result..
Thanks!
Just create serializer for your related model:
class TestRelationSerializer(serializers.ModelSerializer):
class Meta:
meta = TestRelation
and use is as field in TestModelSerializer:
class TestModelSerializer(serializers.ModelSerializer):
related_model = TestRelationSerializer()
You can also do it other way around, by using TestModelSerializer as field in TestRelationSerializer with many set to true:
class TestRelationSerializer(serializers.ModelSerializer):
testmodel_set = TestModelSerializer(many=True)
just remember, you can't do both at once due to infinite recursion it makes.
this is a simple question but I'm very new to django-rest-framework.
I was wondering if there is any way to access a method defined on the model from the serializer.?
Say I have a model
class Listing(models.Model):
listingid = models.BigIntegerField(primary_key=True)
mappingid = models.BigIntegerField()
projectlevelid = models.IntegerField()
subsellerid = models.IntegerField()
iscreatedbyadmin = models.BooleanField(default=None, null=True)
createdon = models.DateTimeField(auto_now_add=True, editable=False)
validationstatus = models.SmallIntegerField(default=0)
def is_project(self):
""" Returns True if listing is of Project Type (projectlevelid=6) else False"""
if self.projectlevelid == 6:
return True
else:
return False
def get_project_info(self):
"""Returns False if listing is not mapped to a project, else returns the project info"""
if self.is_project() == False:
return False
return models.Project.objects.get(projectid=self.mappingid)
Is it possible for the serializer
class ListingSerializer(serializers.ModelSerializer):
class Meta:
model = models.MasterListing
to have access to Listing.is_project i.e. for an object of the Listing model, can the serializer call its is_project method?
If so, can I set a field in the serializer such that if is_project returns true, the field is populated?
I am trying for something like this,
class ListingSerializer(serializers.ModelSerializer):
project = serializers.SomeRELATEDFieldTYPE() # this field if populated if the `is_project` is true
class Meta:
model = models.MasterListing
I understand I can do this using some combination of required=False and SerializerMethodField, but maybe there is a simpler way?.
Note: It is not possible for me to set a foreign key to the mappingid, since it depends on the projectlevelid. I also can't affect this relationship so no further normalization is possible. I know that there might be some way using content-types, but we are trying to avoid that if it is possible..
EDIT: I solved the problem, but not as the question specified.
I used this:
class ListingSerializer(serializers.ModelSerializer):
project = serializers.SerializerMethodField()
def get_project(self, obj):
"""Returns False if listing is not mapped to a project, else returns the project info"""
if str(obj.projectlevelid) == str(6):
projectObj = models.Project(projectid=obj.mappingid)
projectObjSerialized = ProjectSerializer(projectObj)
return projectObjSerialized.data
return False
class Meta:
model = models.MasterListing
So, the original question still stands: "Is it possible for the modelSerializer to access its models methods?"
Also, another problem that now appears is, can I make the serializer exclude fields on demand, i.e. can it exclude mappingid and projectlevelid if it is indeed a project?
For your first question source attribute is the answer, citing:
May be a method that only takes a self argument, such as
URLField('get_absolute_url')
For your second answer, yes it is also possible. Check the example it provides in their docs: http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
PS: I really love drf for its very complete documentation =).
EDIT
To use the source attribute you can just declare a new explicit field like so:
is_project = serializers.BooleanField(source='is_project')
With this, is_project field has the value of the is_project method of your instance. Having this, when creating the dynamic serializer (by modifying its init method) you can add the 'project' field if it's True.
#argaen is absolutely right, source is a DRF core argument, and would most definitely solve your problem. However, it's redundant to use source, if the field name is the same as the source. So the above answer won't require you specify source, since field name is_project is the same as source name is_project.
So in your case:
is_project = serializers.BooleanField()
I have some models that relates to User, but does not have a related name on user:
class Registration(models.Model):
user = models.OneToOneField('auth.User', related_name='+')
class ManyToOneModel(models.Model):
user = models.ForeignKey('auth.User', related_name='+')
I would like to make a serializer for User, which can have this as a nested resource. Is there a way to specify what the queryset/object is? This is an example of what I have - and it completly expectedly failes with 'User' object has no attribute 'registration':
class UserSerializer(serializers.Serializer):
pk = serializers.Field()
registration = RegistrationSerializer()
many_to_one_model = ManyToOneModelSerializer(many=True, required=False)
I guess you'd need to manually query for the related objects and then construct the serializers by hand. You'd then construct the final representation and pass that as the data parameter to a Response object.
It seems like you're making life difficult though. If you just define the related_name on your related models you could use ModelSerializer (or HyperlinkedModelSerializer) and it would all Just Work™. — Is there some reason why you can't do this?
I have a model with a field that is required but not entered by the user and i have a hard time saving the model without errors. My model definition looks like this:
class Goal(db.Model):
author = db.UserProperty(required=True)
description = db.StringProperty(multiline=True, required=True)
active = db.BooleanProperty(default=True)
date = db.DateTimeProperty(auto_now_add=True)
class GoalForm(djangoforms.ModelForm):
class Meta:
model = Goal
exclude = ['author', 'active']
And i use django-forms in appengine to create and validate the form. When i try to save the result of this form however....
def post(self):
data = GoalForm(data=self.request.POST)
if data.is_valid():
goal = data.save(commit=False)
goal.author = users.get_current_user()
goal.put()
self.redirect('/')
I get "ValueError: The Goal could not be created (Property author is required)"
Now i would think that by having commit=False, then adding the property for Goal, and then saving the object would allow me to do this but obviously it's not working. Any ideas?
Note that save() will raise a ValueError if the data in the form doesn't validate
You can find what you need about the save() method here:
http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method
Edit: Instead of goal.put(), do a goal.save()
Edit2: This should solve your problem:
goal = Goal(author='Mr') #example
data = GoalForm(data=self.request.POST, instance=goal)
I realize this is an old question, but for the sake of others searching for a similar answer, I'm posting the following:
Unless there's a reason I missed for not doing so, but I believe this is what you need:
class Goal(db.Model):
author = db.UserProperty(auto_current_user_add=True)
...
...
For reference:
Types and Property Classes:class UserProperty()
Your GoalForm should inherit from django.forms.ModelForm and be defined such that it only requires some fields:
class GoalForm(django.forms.ModelForm):
class Meta:
model = Goal
fields = ('description', 'etc')
Not sure if this is totally working in AppEngine though.
You should also save the form (still not sure about AppEngine):
data = GoalForm(data=self.request.POST)
if data.is_valid():
data.author = users.get_current_user()
data.put()