... | ... | @@ -271,7 +271,322 @@ It is also important to note that this permission will also checks that the user |
|
|
|
|
|
# The serializers
|
|
|
|
|
|
[Official DRF documentation about the serializers](https://www.django-rest-framework.org/api-guide/serializers/)
|
|
|
|
|
|
The data that is extracted from the Django objects need to be formatted so the response can be returned in the deisred format (probably JSON). For that, DRF uses serializers. Quickly, this is an objet that
|
|
|
1. Takes a Django object instance
|
|
|
2. Select the field to output
|
|
|
3. Depending of the field type, generate a string or a JSON object from the field data
|
|
|
4. Return a structured JSON object that contains information about all the fields
|
|
|
|
|
|
:warning: Even though it might seems simple, be careful because it can quickly gets complicated because of the many options to specify what and how to render an object.
|
|
|
|
|
|
|
|
|
## Default behavior
|
|
|
|
|
|
DRF provides some base serializers that have an automatic behavior on certain kind of fields. For example, for a *CharField*, the string is outputed ; for a *DateTimeField*, the date is casted to a string using [ISO-8601](https://en.wikipedia.org/wiki/ISO_8601) format ; for a *BooleanField*, the value is casted to `true` or `false` ; for an *IntegerField*, the value is casted to a string, ... Here is an example:
|
|
|
|
|
|
```python
|
|
|
import json
|
|
|
from datetime.datetime import now
|
|
|
from django.db import models
|
|
|
from rest_framework.serializers import ModelSerializer
|
|
|
|
|
|
# Model
|
|
|
class MyModel(models.Model):
|
|
|
field_char = models.CharField(default='this is a char')
|
|
|
field_date = models.DateTimeField(default=now())
|
|
|
field_bool = models.BooleanField(default=True)
|
|
|
field_int = models.IntegerField(default=10)
|
|
|
|
|
|
# Serializer
|
|
|
class MySerializer(ModelSerializer):
|
|
|
class Meta:
|
|
|
model = MyModel
|
|
|
|
|
|
# Serialization
|
|
|
obj = MyModel()
|
|
|
seria = MySerializer(obj)
|
|
|
print(json.dumps(seria.data), indent=2)
|
|
|
## {
|
|
|
## "field_char": "this is a char",
|
|
|
## "field_date": "2018-06-15T18:29:37Z",
|
|
|
## "field_bool": "true",
|
|
|
## "field_int": "10"
|
|
|
## }
|
|
|
```
|
|
|
|
|
|
For more complex Django fields such as *ForeignKey*, *OneToOneField* or *ManyToManyField*, there is also a default behavior which is detailed just below. For non-supported third-party fields, you will need either to find an extension that handle the serialization field or code yourself the *SerializerField*. Please refer to the [official DRF documentation on serializer fields](https://www.django-rest-framework.org/api-guide/fields/) for details.
|
|
|
|
|
|
|
|
|
## Django models relation serialization
|
|
|
|
|
|
Serializing the relation between Django models can be done in muliple ways and it the main difference between the different base serialization provided by DRF:
|
|
|
|
|
|
|
|
|
### rest_framework.serializers.ModelSerializer
|
|
|
|
|
|
Serializing such relation results in outputing **the primary key** of the related model. Consider the following example:
|
|
|
|
|
|
```python
|
|
|
import json
|
|
|
from django.db import models
|
|
|
from rest_framework.serializers import ModelSerializer
|
|
|
|
|
|
# Model
|
|
|
class RelatedModel(models.Model):
|
|
|
pass
|
|
|
|
|
|
class MyModel(models.Model):
|
|
|
field1 = ForeignKey('RelatedModel')
|
|
|
|
|
|
# Serializer
|
|
|
class MySerializer(ModelSerializer):
|
|
|
class Meta:
|
|
|
model = MyModel
|
|
|
|
|
|
# Serialization
|
|
|
rel_obj = RelatedModel()
|
|
|
print("rel_obj.pk = "+str(rel_obj.pk))
|
|
|
## rel_obj.pk = 1
|
|
|
obj = MyModel(field1=rel_obj)
|
|
|
seria = MySerializer(obj)
|
|
|
print(json.dumps(seria.data), indent=2)
|
|
|
## {
|
|
|
## "field1": "1"
|
|
|
## }
|
|
|
```
|
|
|
|
|
|
|
|
|
### rest_framework.serializers.HyperlinkedModelSerializer
|
|
|
|
|
|
Instead of simply outputting the `pk` field of the object and letting the user find the right url to use to get information on this related object, this serializer will use Django's URL system to resolve the url to use with this `pk` field and it will output the full URL. This means that the URL to see the details of the model need to exist. By default, the url used will be `resolve('<model_name>-details', kwargs={'pk': <pk>})` but this can be changed via some parameter to look for another URL name and use a different fileld fo the lookup. See the DRF documentation for those details. Here is an example of how it works:
|
|
|
|
|
|
```python
|
|
|
import json
|
|
|
from django.db import models
|
|
|
from rest_framework.serializers import HyperlinkedModelSerializer
|
|
|
|
|
|
# Model
|
|
|
class RelatedModel(models.Model):
|
|
|
pass
|
|
|
|
|
|
class MyModel(models.Model):
|
|
|
field1 = ForeignKey('RelatedModel')
|
|
|
|
|
|
# Serializer
|
|
|
class MySerializer(HyperlinkedModelSerializer):
|
|
|
class Meta:
|
|
|
model = MyModel
|
|
|
|
|
|
# Serialization
|
|
|
rel_obj = RelatedModel()
|
|
|
print("rel_obj.pk = "+str(rel_obj.pk))
|
|
|
## rel_obj.pk = 1
|
|
|
obj = MyModel(field1=rel_obj)
|
|
|
seria = MySerializer(obj)
|
|
|
print(json.dumps(seria.data), indent=2)
|
|
|
## {
|
|
|
## "field1": "https://example.net/relatedmodel/1/"
|
|
|
## }
|
|
|
```
|
|
|
|
|
|
|
|
|
### api.serializers.NamespacedHMSerializer
|
|
|
|
|
|
This serializer is a simple subclass of `rest_framework.serializers.HyperlinkedModelSerializer` to support the use of namespaced URL (which is not supported because considered not useful for an API and also I think to complex to implement for any namespacing). This serializer will behave exactly as the other one but all URL resolving for related models is done in the namespace 'api', where are all Re2o's API URLs. Without this modification, either the namespacing need to be remove for the whole API or the serializer will fail resolving the URL, so consider using this serializer instead of `rest_framework.serializers.HyperlinkedModelSerializer`.
|
|
|
|
|
|
|
|
|
## Control the outputed field
|
|
|
|
|
|
One can specify which fields to output by adding a `fields` attribute or an `exclude` attribute in the `Meta` class. The default is to output all fields (`fields = '__all__'`).
|
|
|
|
|
|
```python
|
|
|
import json
|
|
|
from datetime.datetime import now
|
|
|
from django.db import models
|
|
|
from rest_framework import serializers
|
|
|
|
|
|
# Model
|
|
|
class MyModel(models.Model):
|
|
|
field_char = models.CharField(default='this is a char')
|
|
|
field_date = models.DateTimeField(default=now())
|
|
|
field_bool = models.BooleanField(null=True)
|
|
|
field_int = models.IntegerField(default=10)
|
|
|
|
|
|
# Serializer
|
|
|
class MySerializer(serializers.ModelSerializer):
|
|
|
class Meta:
|
|
|
model = MyModel
|
|
|
fields = ('field_char', 'field_date')
|
|
|
# Alternatively use the following for the same result
|
|
|
exclude = ('field_bool', 'field_int')
|
|
|
|
|
|
# Serialization
|
|
|
rel_obj = RelatedModel()
|
|
|
obj = MyModel(field1=rel_obj)
|
|
|
seria = MySerializer(obj)
|
|
|
print(json.dumps(seria.data), indent=2)
|
|
|
## {
|
|
|
## "field_char": "this is a char",
|
|
|
## "field_date": "2018-06-15T18:29:37Z"
|
|
|
## }
|
|
|
```
|
|
|
|
|
|
|
|
|
## Specifying a non-default serializer field
|
|
|
|
|
|
[Official DRF documentation of serializer fields](https://www.django-rest-framework.org/api-guide/fields/)
|
|
|
|
|
|
If the default serializer field used for a field is not the one want, or if the behaviour need to be changed or even if there is no default serializer field, a custom serializer field can be used. Example:
|
|
|
|
|
|
```python
|
|
|
import json
|
|
|
from django.db import models
|
|
|
from rest_framework import serializers
|
|
|
from mac_address.fields import MACAddressField
|
|
|
|
|
|
# Model
|
|
|
class MyModel(models.Model):
|
|
|
field_char = models.CharField(default='this is a char')
|
|
|
field_mac = MACAddressField(default='0123456789ab')
|
|
|
field_bool = models.BooleanField(null=True)
|
|
|
field_int = models.IntegerField(default=10)
|
|
|
|
|
|
# Serializer
|
|
|
class MySerializer(serializers.ModelSerializer):
|
|
|
field_mac = serializers.CharField()
|
|
|
field_bool = serializers.NullBooleanField()
|
|
|
|
|
|
class Meta:
|
|
|
model = MyModel
|
|
|
|
|
|
# Serialization
|
|
|
rel_obj = RelatedModel()
|
|
|
obj = MyModel(field1=rel_obj)
|
|
|
seria = MySerializer(obj)
|
|
|
print(json.dumps(seria.data), indent=2)
|
|
|
## {
|
|
|
## "field_char": "this is a char",
|
|
|
## "field_mac": "01:23:45:67:89:AB",
|
|
|
## "field_bool": "false",
|
|
|
## "field_int": "10",
|
|
|
## }
|
|
|
```
|
|
|
|
|
|
|
|
|
## Add custom serializer fields
|
|
|
|
|
|
This custom addition of serializer field can also be used to add fields that do not correspond to a field in the model or to rename fields. Note that new serializer fields need to be explicitely include in the `fields` attribute. Example:
|
|
|
|
|
|
```python
|
|
|
import json
|
|
|
from datetime.datetime import now
|
|
|
from django.db import models
|
|
|
from rest_framework import serializers
|
|
|
|
|
|
# Model
|
|
|
class RelatedModel(models.Model):
|
|
|
field_date = models.DateTimeField(default=now())
|
|
|
|
|
|
class MyModel(models.Model):
|
|
|
field1 = ForeignKey('RelatedModel')
|
|
|
field_char = models.CharField(default='this is a char')
|
|
|
|
|
|
def get_now(self):
|
|
|
return now()
|
|
|
|
|
|
# Serializer
|
|
|
class MySerializer(serializers.ModelSerializer):
|
|
|
char = serializers.CharField(source='field_char')
|
|
|
related_month = serializers.SerializerMethodField('get_related_month')
|
|
|
related_year = serializer.SerializerMethodField()
|
|
|
related_date = serializer.DateTimeField(source='field1.field_date')
|
|
|
now = serializer.DateTimeField(source='get_now')
|
|
|
|
|
|
class Meta:
|
|
|
model = MyModel
|
|
|
fields = ('char', 'related_month', 'related_year', 'related_date', 'now')
|
|
|
|
|
|
def get_related_month(self, obj):
|
|
|
return str(obj.field1.field_date.month)
|
|
|
|
|
|
def related_year(self, obj):
|
|
|
return str(obj.field1.field_date.year)
|
|
|
|
|
|
# Serialization
|
|
|
rel_obj = RelatedModel()
|
|
|
obj = MyModel(field1=rel_obj)
|
|
|
seria = MySerializer(obj)
|
|
|
print(json.dumps(seria.data), indent=2)
|
|
|
## {
|
|
|
## "char": "this is a char",
|
|
|
## "related_month": "6",
|
|
|
## "related_year": "2018",
|
|
|
## "related_date": "2018-06-15T18:29:37Z",
|
|
|
## "now": "2018-06-15T20:55:54Z"
|
|
|
## }
|
|
|
```
|
|
|
|
|
|
|
|
|
## Using serializers as serializer fields
|
|
|
|
|
|
[Official DRF documentaiton on serializer relations](https://www.django-rest-framework.org/api-guide/relations/)
|
|
|
|
|
|
A nice addition to avoid code duplication is the possibility to use an already written serializer as a serializer field on a relation between Django models, which means that the value of the field will be the full-blown JSON object itself. For a better exaplnation, consider the following example
|
|
|
|
|
|
```python
|
|
|
import json
|
|
|
from django.db import models
|
|
|
from rest_framework.serializer import ModelSerializer
|
|
|
|
|
|
# Model
|
|
|
class RelatedModel(models.Model):
|
|
|
field_char = models.CharField(default='this is a char')
|
|
|
field_date = models.DateTimeField(default=now())
|
|
|
field_bool = models.BooleanField(default=True)
|
|
|
field_int = models.IntegerField(default=10)
|
|
|
|
|
|
class MyModel(models.Model):
|
|
|
field1 = ForeignKey('RelatedModel')
|
|
|
|
|
|
# Serializer
|
|
|
class RelatedSerializer(ModelSerializer):
|
|
|
class Meta:
|
|
|
model = RelatedModel
|
|
|
|
|
|
class MySerializer(ModelSerializer):
|
|
|
field1 = RelatedSerializer()
|
|
|
|
|
|
class Meta:
|
|
|
model = MyModel
|
|
|
|
|
|
# Serialization
|
|
|
rel_obj = RelatedModel()
|
|
|
obj = MyModel(field1=rel_obj)
|
|
|
seria = MySerializer(obj)
|
|
|
print(json.dumps(seria.data), indent=2)
|
|
|
## {
|
|
|
## "field1": {
|
|
|
## "field_char": "this is a char",
|
|
|
## "field_date": "2018-06-15T18:29:37Z",
|
|
|
## "field_bool": "true",
|
|
|
## "field_int": "10",
|
|
|
## }
|
|
|
## }
|
|
|
```
|
|
|
|
|
|
|
|
|
## Usage in Re2o
|
|
|
|
|
|
All models currently have an associated serializer in `api.serializers` that output all the relevant data. Some subclass can be used to only output a subset of the fields or output differently a field if needed. Example:
|
|
|
|
|
|
```python
|
|
|
class OriginV4RecordSerializer(IpListSerializer):
|
|
|
class Meta(IpListSerializer.Meta):
|
|
|
fields = ('ipv4',)
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
# The settings file
|
... | ... | |