Django REST ModelSerializer --- General Question - django

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

Related

How to dynamically specify fields for nested serialization

When working with Django rest-framework, and object relationships, I often encounter situations in which I want to return different fields from a serializer depending on the context in which I'm calling that serializier. To provide an example, lets say I have some models representing artiste and album
class Artiste(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=30)
songs = models.ManyToManyField(Song, related_name='artiste')
albums = models.ManyToManyField(Album, related_name='artiste')
class Album(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=30)
description = models.TextField(blank=True, null=True)
These models being serialized by these serializers, which include field from the many to many relationship
class AlbumSerializer(serializers.ModelSerializer):
artiste = ArtisteSerializer(many=True)
class Meta:
model = Album
fields = ('id','title','description','artiste')
class ArtisteSerializer(serializers.ModelSerializer):
class Meta:
model = Artiste
fields = ('id', 'name', 'albums')
A request to my album endpoint might produce an object like this
"id": "5807bd12-254f-4508-b76f-02dc0740e809",
"title": "Test",
"description": "",
"artiste": [
{
"id": "31f09ef0-50ce-48b1-96a6-a6c234930ce5",
"name": "Alec Benjamin",
"albums": [
"5807bd12-254f-4508-b76f-02dc0740e809"
]
}
]
As you can see, using nested serialization ends up giving me more information that I actually want in this context. Given that I had more data for example, when looking at the information for an artiste, I'd love to have the information regarding all the albums that the artiste has made, but when looking at my album endpoint, I'd rather not have this information be included, especially here where the only album the artiste has made is the one I'm currently looking at anyways. As the system gets larger, this problem gets worse as i include songs, playlists etc
What I want to do, is to be able to specify what serialzier fields I want depending on the context in which I'm calling that serializer, as opposed to defining my serializer once and then always getting the same information even when its not needed.
my current approach to this is to use a SerializerMethodField and adding this to my album serializer
artiste = serializers.SerializerMethodField('get_artiste')
def get_artiste(self, obj):
return [(i.id ,i.name)for i in obj.artiste.all()]
Doing this allows me to explicitly state what I want to return in the context of my album serializer but this makes my artiste field read only, meaning that when trying to create an album, I cannot specify what artiste it belongs to. This issue also seems to be the case with StringRelatedField etc.
What is your approach to doing nested serialization? Is there any way to specify what fields should be returned from serialization in a specific situation without rewriting the serializer or making the field read only?
Sorry for the lengthy, probably stupid question and thanks for your help.
according to documentation serializers classes is very like regular django form classes so you only need to specify which fields you want that serializer to show and it will be done in your example it will be :
class ArtisteSerializer(serializers.ModelSerializer):
class Meta:
model = Artiste
fields = ('id', 'name')
and now this serializer will only show the related 'id' and 'name' of the given artiste model.
and there is much more that you can do here like special fields = '__all__'
which will show all fields of a given model or instead of using fields you can use exclude = ('album',) that just removes these fields from serializer .
also you can use django's models relationship here as well.
update :
to be able to use these serializers in different endpoints it is better to use python class inheritance create a base serializer and use different attributes on childs
class BaseArtisteSerializer(serializers.ModelSerializer):
class Meta:
model = Artiste
fields = ('id', 'name', 'artist')
class ArtisteSerializer(BaseArtisteSerializer):
class Meta(BaseArtisteSerializer.Meta):
fields = ('id', 'name')

usage of standard backward relation manager in serilizers

Question is about using standard Django backward related manager name in DRF.
I have following serializer
class ExtraUserSerializer(serializers.ModelSerializer):
boatmodel_set = serializers.PrimaryKeyRelatedField(many=True,
queryset=BoatModel.objects.all())
class Meta:
model = get_user_model()
fields = ("id", "username", 'boatmodel_set', )
This serializer represents primary model ExtraUser and boat_model set represents backward relationship to secondary model BoatModel. Related name “boatmodel_set” chosen simply because main core Django site uses this standard “secondarymodel_set” conventional backward related manager name so that in DRF part I had to use related_name = “ boatmodel_set” as well in order to not change code in the main part.
Question is - is it possible to keep related_name = “ boatmodel_set” but represent it in rendered json as , for example “boats”??
Thank you
Yes, you can just specify the source= parameter [drf-doc], and name the field differently, like:
class ExtraUserSerializer(serializers.ModelSerializer):
boats = serializers.PrimaryKeyRelatedField(
many=True,
queryset=BoatModel.objects.all(),
source='boatmodel_set'
)
class Meta:
model = get_user_model()
fields = ('id', 'username', 'boats')
Here the JSON-side will contain "boats": ..., whereas the model side will still use myuser.boatmodel_set.all().

Django ORM access User table through multiple models

views.py
I'm creating a queryset that I want to serialize and return as JSON. The queryset looks like this:
all_objects = Program.objects.all()
test_data = serializers.serialize("json", all_objects, use_natural_keys=True)
This pulls back everything except for the 'User' model (which is linked across two models).
models.py
from django.db import models
from django.contrib.auth.models import User
class Time(models.Model):
user = models.ForeignKey(User)
...
class CostCode(models.Model):
program_name = models.TextField()
...
class Program(models.Model):
time = models.ForeignKey(Time)
program_select = models.ForeignKey(CostCode)
...
Question
My returned data has Time, Program, and CostCode information, but I'm unable to query back the 'User' table. How can I get back say the 'username' (from User Table) in the same queryset?
Note: I've changed my queryset to all_objects = Time.objects.all() and this gets User info, but then it doesn't pull in 'CostCode'. My models also have ModelManagers that return the get_by_natural_key so the relevant fields appear in my JSON.
Ultimately, I want data from all four models to appear in my serialized JSON fields, I'm just missing 'username'.
Here's a picture of how the JSON object currently appears in Firebug:
Thanks for any help!
It seems a bit heavyweight at first glance but you could look at using Django REST Framework:
http://www.django-rest-framework.org/api-guide/serializers#modelserializer
You can define and use the serializer classes without having to do anything else with the framework. The serializer returns a python dict which can then be easily dumped to JSON.
To get all fields from each related model as nested dicts you could do:
class ProgramSerializer(serializers.ModelSerializer):
class Meta:
model = Program
depth = 2
all_objects = Program.objects.all()
serializer = ProgramSerializer(all_objects, many=True)
json_str = json.dumps(serializer.data)
To customise which fields are included for each model you will need to define a ModelSerializer class for each of your models, for example to output only the username for the time.user:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', )
class TimeSerializer(serializers.ModelSerializer):
"""
specifying the field here rather than relying on `depth` to automatically
render nested relations allows us to specify a custom serializer class
"""
user = UserSerializer()
class Meta:
model = Time
class ProgramSerializer(serializers.ModelSerializer):
time = TimeSerializer()
class Meta:
model = Program
depth = 1 # render nested CostCode with default output
all_objects = Program.objects.all()
serializer = ProgramSerializer(all_objects, many=True)
json_str = json.dumps(serializer.data)
What you really want is a "deep" serialization of objects which Django does not natively support. This is a common problem, and it is discussed in detail here: Serializing Foreign Key objects in Django. See that question for some alternatives.
Normally Django expects you to serialize the Time, CostCode, Program, and User objects separately (i.e. a separate JSON array for each) and to refer to them by IDs. The IDs can either be the numeric primary keys (PKs) or a "natural" key defined with natural_key.
You could use natural_key to return any fields you want, including user.username. Alternatively, you could define a custom serializer output whatever you want there. Either of these approaches will probably make it impossible to load the data back into a Django database, which may not be a problem for you.

django rest nested relation in post/put

I am new in django rest api developement. I have two models one is category and another is subcategories.
Here is my models
class Category(models.Model):
title = models.Charfield()
brief = models.TextField()
subcategories = model.ManyToManyField('Subcategory', blank=True)
My serializer class
class CategorySerializer(serializers.ModelSerializer):
title= serializer.Charfield()
subcategories = Relatedfield(many=True)
Now in view
def post(self, request, format = None):
data=request.DATA
serialize= CategorySerializer(data=request.DATA)
if serializer.valid():
serializer.save()
How to save nested data like {'title':"test",'subscategories':[{'description':'bla bla bla'},{'description':'test test'}]} in post method.
I have read this in documentation
Note: Nested serializers are only suitable for read-only
representations, as there are cases where they would have ambiguous or
non-obvious behavior if used when updating instances. For read-write
representations you should always use a flat representation, by using
one of the RelatedField subclasses.
Please let me suggest which is right way or solution to do nested relation post/put in django rest.
Have you tried creating a SubCategorySerializer and adding this as a field on CategorySerializer?
class SubcategorySerializer(serializers.ModelSerializer):
class Meta:
model = Subcategory
class CategorySerializer(serializers.ModelSerializer):
subcategories = SubcategorySerializer(many=True)
Docs: http://django-rest-framework.org/api-guide/relations.html#nested-relationships

Django Form with no required fields

I want to make a form used to filter searches without any field being required. For example given this code:
models.py:
class Message(models.Model):
happened = models.DateTimeField()
filename = models.CharField(max_length=512, blank=True, null=True)
message = models.TextField(blank=True, null=True)
dest = models.CharField(max_length=512, blank=True, null=True)
fromhost = models.ForeignKey(Hosts, related_name='to hosts', blank=True, null=True)
TYPE_CHOICES = ( (u'Info', u'Info'), (u'Error', u'Error'), (u'File', u'File'), (u'BPS', u'BPS'),)
type = models.CharField(max_length=7, choices=TYPE_CHOICES)
job = models.ForeignKey(Jobs)
views.py:
WHEN_CHOICES = ( (u'', ''), (1, u'Today'), (2, u'Two days'), (3, u'Three Days'), (7, u'Week'),(31, u'Month'),)
class MessageSearch(ModelForm): #Class that makes a form from a model that can be customized by placing info above the class Meta
message = forms.CharField(max_length=25, required=False)
job = forms.CharField(max_length=25, required=False)
happened = forms.CharField(max_length=14, widget=forms.Select(choices=WHEN_CHOICES), required=False)
class Meta:
model = Message
That's the code I have now. As you can see it makes a form based on a model. I redefined message in the form because I'm using an icontains filter so I didn't need a giant text box. I redefined the date mostly because I didn't want to have to mess around with dates (I hate working with dates! Who doesnt?) And I changed the jobs field because otherwise I was getting a drop down list of existing jobs and I really wanted to be able to search by common words. So I was able to mark all of those as not required
The problem is it's marking all my other fields as required because in the model they're not allowed to be blank.
Now in the model they can't be blank. If they're blank then the data is bad and I don't want it in the DB. However the form is only a filter form on a page to display the data. I'm never going to save from that form so I don't care if fields are blank or not. So is there an easy way to make all fields as required=false while still using the class Meta: model = Message format in the form? It's really handy that I can make a form directly from a model.
Also this is my first serious attempt at a django app so if something is absurdly wrong please be kind :)
You can create a custom ModelForm that suit your needs. This custom ModelForm will override the save method and set all fields to be non-required:
from django.forms import ModelForm
class SearchForm(ModelForm):
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
for key, field in self.fields.iteritems():
self.fields[key].required = False
So you could declare your forms by simply calling instead of the ModelForm, e.g.:
class MessageForm(SearchForm):
class Meta:
model = Message
You could also pass empty_permitted=True when you instantiate the form, e.g.,
form = MessageSearch(empty_permitted=True)
that way you can still have normal validation rules for when someone does enter data into the form.
I would give a try to the django-filter module :
http://django-filter.readthedocs.io/en/develop/
fields are not required. these are filters actually. It would look like this :
import django_filters
class MessageSearch(django_filters.FilterSet):
class Meta:
model = Message
fields = ['happened', 'filename', 'message', '...', ]
# django-filter has its own default widgets corresponding to the field
# type of the model, but you can tweak and subclass in a django way :
happened = django_filters.DateFromToRangeFilter()
mandatory, hidden filters can be defined if you want to narrow a list of model depending on something like user rights etc.
also : setup a filter on a 'reverse' relationship (the foreignkey is not in the filtered model : the model is referenced elsewhere in another table), is easy, just name the table where the foreign key of the filtered model field is :
# the 'tags' model has a fk like message = models.ForeignKey(Message...)
tags= django_filters.<some filter>(name='tags')
quick extendable and clean to setup.
please note I didn't wrote this module, I'm just very happy with it :)