django-rest-framework-tricks¶
Collection of various tricks for Django REST framework.
Prerequisites¶
- Django 1.8, 1.9, 1.10 and 1.11.
- Python 2.7, 3.4, 3.5, 3.6
Dependencies¶
- djangorestframework
Installation¶
Install latest stable version from PyPI:
pip install django-rest-framework-tricks
or latest stable version from GitHub:
pip install https://github.com/barseghyanartur/django-rest-framework-tricks/archive/stable.tar.gz
Add
rest_framework
andrest_framework_tricks
toINSTALLED_APPS
:INSTALLED_APPS = ( # ... # REST framework 'rest_framework', # REST framework tricks (this package) 'rest_framework_tricks', # ... )
Main features and highlights¶
- Nested serializers: Nested serializers for non-relational fields.
Usage examples¶
Nested serializers¶
Nested serializers for non-relational fields.
Our imaginary Book
model consists of the following (non-relational) Django
model fields:
title
:CharField
description
:TextField
summary
:TextField
publication_date
:DateTimeField
state
:CharField
(with choices)isbn
:CharField
price
:DecimalField
pages
:IntegerField
stock_count
:IntegerField
In our REST API, we want to split serializer into parts using nested serializers and have the following structure:
{
"id": "",
"title": "",
"description": "",
"summary": "",
"publishing_information": {
"publication_date": "",
"isbn": "",
"pages": ""
},
"stock_information": {
"stock_count": "",
"price": "",
"state": ""
}
}
Sample model¶
The only customisation here is that we declare two NestedProxyField
fields
on the Book
model level for to be used in BookSerializer
serializer.
Note, that the change does not cause model change (no migrations or whatever).
Required imports¶
from django.db import models
from rest_framework_tricks.models.fields import NestedProxyField
Model definition¶
BOOK_PUBLISHING_STATUS_PUBLISHED = 'published'
BOOK_PUBLISHING_STATUS_NOT_PUBLISHED = 'not_published'
BOOK_PUBLISHING_STATUS_IN_PROGRESS = 'in_progress'
BOOK_PUBLISHING_STATUS_CANCELLED = 'cancelled'
BOOK_PUBLISHING_STATUS_REJECTED = 'rejected'
BOOK_PUBLISHING_STATUS_CHOICES = (
(BOOK_PUBLISHING_STATUS_PUBLISHED, "Published"),
(BOOK_PUBLISHING_STATUS_NOT_PUBLISHED, "Not published"),
(BOOK_PUBLISHING_STATUS_IN_PROGRESS, "In progress"),
(BOOK_PUBLISHING_STATUS_CANCELLED, "Cancelled"),
(BOOK_PUBLISHING_STATUS_REJECTED, "Rejected"),
)
BOOK_PUBLISHING_STATUS_DEFAULT = BOOK_PUBLISHING_STATUS_PUBLISHED
class Book(models.Model):
"""Book."""
title = models.CharField(max_length=100)
description = models.TextField(null=True, blank=True)
summary = models.TextField(null=True, blank=True)
publication_date = models.DateField()
state = models.CharField(max_length=100,
choices=BOOK_PUBLISHING_STATUS_CHOICES,
default=BOOK_PUBLISHING_STATUS_DEFAULT)
isbn = models.CharField(max_length=100, unique=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
pages = models.PositiveIntegerField(default=200)
stock_count = models.PositiveIntegerField(default=30)
# List the fields for `PublishingInformationSerializer` nested
# serializer. This does not cause a model change.
publishing_information = NestedProxyField(
'publication_date',
'isbn',
'pages',
)
# List the fields for `StockInformationSerializer` nested serializer.
# This does not cause a model change.
stock_information = NestedProxyField(
'stock_count',
'price',
'state',
)
class Meta(object):
"""Meta options."""
ordering = ["isbn"]
def __str__(self):
return self.title
Sample serializers¶
At first, we add nested_proxy_field
property to the Meta
class
definitions of PublishingInformationSerializer
and
StockInformationSerializer
nested serializers.
Then we define our (main) BookSerializer
class, which is going to be
used a serializer_class
of the BookViewSet
. We inherit the
BookSerializer
from
rest_framework_tricks.serializers.HyperlinkedModelSerializer
instead of the one of the Django REST framework.
Required imports¶
from rest_framework import serializers
from rest_framework_tricks.serializers import (
HyperlinkedModelSerializer,
)
from .models import Book
Defining the serializers¶
Nested serializer
class PublishingInformationSerializer(serializers.ModelSerializer):
"""Publishing information serializer."""
publication_date = serializers.DateField(required=False)
isbn = serializers.CharField(required=False)
pages = serializers.IntegerField(required=False)
class Meta(object):
"""Meta options."""
model = Book
fields = (
'publication_date',
'isbn',
'pages',
)
# Note, that this should be set to True to identify that
# this serializer is going to be used as `NestedProxyField`.
nested_proxy_field = True
Nested serializer
class StockInformationSerializer(serializers.ModelSerializer):
"""Stock information serializer."""
class Meta(object):
"""Meta options."""
model = Book
fields = (
'stock_count',
'price',
'state',
)
# Note, that this should be set to True to identify that
# this serializer is going to be used as `NestedProxyField`.
nested_proxy_field = True
Main serializer to be used in the ViewSet
# Note, that we are importing the ``HyperlinkedModelSerializer`` from
# the `rest_framework_tricks.serializers`.
class BookSerializer(HyperlinkedModelSerializer):
"""Book serializer."""
publishing_information = PublishingInformationSerializer(required=False)
stock_information = StockInformationSerializer(required=False)
class Meta(object):
"""Meta options."""
model = Book
fields = (
'url',
'id',
'title',
'description',
'summary',
'publishing_information',
'stock_information',
)
Sample ViewSet¶
Absolutely no customisations here.
Required imports¶
from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import AllowAny
from .models import Book
from .serializers import BookSerializer
ViewSet definition¶
class BookViewSet(ModelViewSet):
"""Book ViewSet."""
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [AllowAny]
Sample OPTIONS call¶
OPTIONS /books/api/books/
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"name": "Book List",
"description": "Book ViewSet.",
"renders": [
"application/json",
"text/html"
],
"parses": [
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data"
],
"actions": {
"POST": {
"id": {
"type": "integer",
"required": false,
"read_only": true,
"label": "ID"
},
"title": {
"type": "string",
"required": true,
"read_only": false,
"label": "Title",
"max_length": 100
},
"description": {
"type": "string",
"required": false,
"read_only": false,
"label": "Description"
},
"summary": {
"type": "string",
"required": false,
"read_only": false,
"label": "Summary"
},
"publishing_information": {
"type": "nested object",
"required": false,
"read_only": false,
"label": "Publishing information",
"children": {
"publication_date": {
"type": "date",
"required": false,
"read_only": false,
"label": "Publication date"
},
"isbn": {
"type": "string",
"required": false,
"read_only": false,
"label": "Isbn"
},
"pages": {
"type": "integer",
"required": false,
"read_only": false,
"label": "Pages"
}
}
},
"stock_information": {
"type": "nested object",
"required": false,
"read_only": false,
"label": "Stock information",
"children": {
"stock_count": {
"type": "integer",
"required": false,
"read_only": false,
"label": "Stock count"
},
"price": {
"type": "decimal",
"required": true,
"read_only": false,
"label": "Price"
},
"state": {
"type": "choice",
"required": false,
"read_only": false,
"label": "State",
"choices": [
{
"value": "published",
"display_name": "Published"
},
{
"value": "not_published",
"display_name": "Not published"
},
{
"value": "in_progress",
"display_name": "In progress"
},
{
"value": "cancelled",
"display_name": "Cancelled"
},
{
"value": "rejected",
"display_name": "Rejected"
}
]
}
}
}
}
}
}
Testing¶
Project is covered with tests.
To test with all supported Python/Django versions type:
tox
To test against specific environment, type:
tox -e py36-django110
To test just your working environment type:
./runtests.py
To run a single test in your working environment type:
./runtests.py src/rest_framework_tricks/tests/test_nested_proxy_field.py
Or:
./manage.py test rest_framework_tricks.tests.test_nested_proxy_field
It’s assumed that you have all the requirements installed. If not, first install the test requirements:
pip install -r examples/requirements/test.txt
Writing documentation¶
Keep the following hierarchy.
=====
title
=====
header
======
sub-header
----------
sub-sub-header
~~~~~~~~~~~~~~
sub-sub-sub-header
^^^^^^^^^^^^^^^^^^
sub-sub-sub-sub-header
++++++++++++++++++++++
sub-sub-sub-sub-sub-header
**************************
Author¶
Artur Barseghyan <artur.barseghyan@gmail.com>