django-rest-framework-tricks¶
Collection of various tricks for Django REST framework.
Prerequisites¶
Django 2.2, 3.0, 3.1, 3.2, 4.0 and 4.1.
Python 3.7, 3.8, 3.9, 3.10 and 3.11.
Dependencies¶
djangorestframework: Initially written with 3.6.3, but nowadays tested with >=3.10,<3.14. May (still) work on earlier- or (even) support later- versions, although not guaranteed.
Installation¶
Install latest stable version from PyPI:
pip install django-rest-framework-tricks
or latest development version from GitHub:
pip install https://github.com/barseghyanartur/django-rest-framework-tricks/archive/master.tar.gz
Add
rest_framework
andrest_framework_tricks
toINSTALLED_APPS
:INSTALLED_APPS = ( # ... # REST framework 'rest_framework', # REST framework tricks (this package) 'rest_framework_tricks', # ... )
Documentation¶
Documentation is available on Read the Docs.
Main features and highlights¶
Nested serializers: Nested (writable) serializers for non-relational fields.
Ordering filter: Developer friendly names for ordering options (for instance, for related field names).
File field with restrictions: Restrict the file field (in size).
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 the Book serializer into parts using nested serializers to have the following structure:
{
"id": "",
"title": "",
"description": "",
"summary": "",
"publishing_information": {
"publication_date": "",
"isbn": "",
"pages": ""
},
"stock_information": {
"stock_count": "",
"price": "",
"state": ""
}
}
Sample model¶
The only variation from standard implementation 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 whatsoever).
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_CHOICES = (
(BOOK_PUBLISHING_STATUS_PUBLISHED, "Published"),
(BOOK_PUBLISHING_STATUS_NOT_PUBLISHED, "Not published"),
(BOOK_PUBLISHING_STATUS_IN_PROGRESS, "In progress"),
)
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:
"""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 as 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. There’s also a
rest_framework_tricks.serializers.ModelSerializer
available.
Required imports¶
from rest_framework import serializers
from rest_framework_tricks.serializers import (
HyperlinkedModelSerializer,
)
from .models import Book
Defining the serializers¶
Note
If you get validation errors about null-values, add allow_null=True
next to the required=False
for serializer field definitions.
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:
"""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:
"""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`. Names of the serializers
# should match the names of model properties set with ``NestedProxyField``
# fields.
class BookSerializer(HyperlinkedModelSerializer):
"""Book serializer."""
publishing_information = PublishingInformationSerializer(required=False)
stock_information = StockInformationSerializer(required=False)
class Meta:
"""Meta options."""
model = Book
fields = (
'url',
'id',
'title',
'description',
'summary',
'publishing_information',
'stock_information',
)
Sample ViewSet¶
Absolutely no variations from standard implementation 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"
}
]
}
}
}
}
}
}
Unlimited nesting depth¶
Unlimited nesting depth is supported.
Our imaginary Author
model could consist of the following (non-relational)
Django model fields:
salutation
:CharField
name
:CharField
email
:EmailField
birth_date
:DateField
biography
:TextField
phone_number
:CharField
website
:URLField
company
:CharField
company_phone_number
:CharField
company_email
:EmailField
company_website
:URLField
In our REST API, we could split the Author serializer into parts using nested serializers to have the following structure:
{
"id": "",
"salutation": "",
"name": "",
"birth_date": "",
"biography": "",
"contact_information": {
"personal_contact_information": {
"email": "",
"phone_number": "",
"website": ""
},
"business_contact_information": {
"company": "",
"company_email": "",
"company_phone_number": "",
"company_website": ""
}
}
}
Our model would have to be defined as follows (see Advanced usage examples
for complete model definition):
class Author(models.Model):
"""Author."""
# ...
# List the fields for `PersonalContactInformationSerializer` nested
# serializer. This does not cause a model change.
personal_contact_information = NestedProxyField(
'email',
'phone_number',
'website',
)
# List the fields for `BusinessContactInformationSerializer` nested
# serializer. This does not cause a model change.
business_contact_information = NestedProxyField(
'company',
'company_email',
'company_phone_number',
'company_website',
)
# List the fields for `ContactInformationSerializer` nested
# serializer. This does not cause a model change.
contact_information = NestedProxyField(
'personal_contact_information',
'business_contact_information',
)
# ...
See the Advanced usage examples for complete example.
Ordering filter¶
Developer friendly names for ordering options (for instance, for related field names) for making better APIs.
Sample model¶
Absolutely no variations from standard implementation here.
Required imports¶
from django.db import models
Model definition¶
class Profile(models.Model):
"""Profile."""
user = models.ForeignKey('auth.User')
biography = models.TextField()
hobbies = models.TextField()
Sample serializer¶
Absolutely no variations from standard implementation here.
Required imports¶
from rest_framework import serializers
from .models import Profile
Defining the serializers¶
class ProfileSerializer(serializers.ModelSerializer):
"""Profile serializer."""
username = serializers.CharField(source='user.username', read_only=True)
full_name = serializers.SerializerMethodField()
email = serializers.CharField(source='user.email', read_only=True)
class Meta(object):
model = Profile
fields = (
'id',
'username',
'full_name',
'email',
'biography',
'hobbies',
)
def get_full_name(self, obj):
return obj.user.get_full_name()
Sample ViewSet¶
The only variation from standard implementation here is that we
use rest_frameworks_tricks.filters.OrderingFilter
instead
of rest_framework.filters.OrderingFilter
.
Required imports¶
from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import AllowAny
from rest_framework_tricks.filters import OrderingFilter
from .models import Profile
from .serializers import ProfileSerializer
ViewSet definition¶
class ProfileViewSet(ModelViewSet):
"""Profile ViewSet."""
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
permission_classes = [AllowAny]
filter_backends = (OrderingFilter,)
ordering_fields = {
'id': 'id',
'username': 'user__username',
'email': 'user__email',
'full_name': ['user__first_name', 'user__last_name']
}
ordering = ('id',)
Sample GET calls¶
Note, that our ordering options are now equal to the field names in the serializer (JSON response). API becomes easier to use/understand that way.
GET /api/profile/?ordering=email
GET /api/profile/?ordering=-username
GET /api/profile/?ordering=full_name
GET /api/profile/?ordering=-full_name
File field with restrictions¶
Sample model¶
Absolutely no variations from standard implementation here.
Required imports¶
from django.db import models
Model definition¶
class Profile(models.Model):
"""Upload."""
username = models.CharField(max_length=255)
resume = models.FileField()
Sample serializer¶
Required imports¶
from rest_framework import serializers
from rest_framework_tricks.fields import ConstrainedFileField
from .models import Upload
Defining the serializers¶
class ProfileSerializer(serializers.ModelSerializer):
"""Profile serializer."""
username = serializers.CharField()
# Restrict resume to 5Mb
resume = ConstrainedFileField(max_upload_size=5_242_880)
class Meta(object):
model = Profile
fields = (
'id',
'username',
'resume',
)
Demo¶
Run demo locally¶
In order to be able to quickly evaluate the django-rest-framework-tricks
,
a demo app (with a quick installer) has been created (works on Ubuntu/Debian,
may work on other Linux systems as well, although not guaranteed). Follow the
instructions below to have the demo running within a minute.
Grab and run the latest rest_framework_tricks_demo_installer.sh
demo
installer:
wget -O - https://raw.github.com/barseghyanartur/django-rest-framework-tricks/master/examples/rest_framework_tricks_demo_installer.sh | bash
Open your browser and test the app.
http://127.0.0.1:8001/books/api/
Testing¶
Project is covered with tests.
To test with all supported Python/Django versions type:
tox
To test against specific environment, type:
tox -e py39-django32
To test just your working environment type:
pytest -vvv
To run a single test in your working environment type:
pytest -vvv src/rest_framework_tricks/tests/test_nested_proxy_field.py
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
**************************
License¶
GPL-2.0-only OR LGPL-2.1-or-later
Support¶
For any security issues contact me at the e-mail given in the Author section.
For overall issues, go to GitHub.
Project documentation¶
Contents:
Advanced usage examples¶
Contents:
Nested serializers¶
Unlimited nesting depth¶
Our imaginary Author
model consist of the following (non-relational)
Django model fields:
salutation
:CharField
name
:CharField
email
:EmailField
birth_date
:DateField
biography
:TextField
phone_number
:CharField
website
:URLField
company
:CharField
company_phone_number
:CharField
company_email
:EmailField
company_website
:URLField
In our REST API, we split the Author serializer into parts using nested serializers to have the following structure:
{
"id": "",
"salutation": "",
"name": "",
"birth_date": "",
"biography": "",
"contact_information": {
"personal_contact_information": {
"email": "",
"phone_number": "",
"website": ""
},
"business_contact_information": {
"company": "",
"company_email": "",
"company_phone_number": "",
"company_website": ""
}
}
}
Sample models¶
The only variation from standard implementation here is that we declare two
NestedProxyField
fields on the Author
model level for to be used in
AuthorSerializer
serializer.
Note, that the change does not cause model change (no migrations or whatsoever).
Required imports¶
from django.db import models
from rest_framework_tricks.models.fields import NestedProxyField
Model definition¶
class Author(models.Model):
"""Author."""
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
birth_date = models.DateField(null=True, blank=True)
biography = models.TextField(null=True, blank=True)
phone_number = models.CharField(max_length=200, null=True, blank=True)
website = models.URLField(null=True, blank=True)
company = models.CharField(max_length=200, null=True, blank=True)
company_phone_number = models.CharField(max_length=200,
null=True,
blank=True)
company_email = models.EmailField(null=True, blank=True)
company_website = models.URLField(null=True, blank=True)
# List the fields for `PersonalContactInformationSerializer` nested
# serializer. This does not cause a model change.
personal_contact_information = NestedProxyField(
'email',
'phone_number',
'website',
)
# List the fields for `BusinessContactInformationSerializer` nested
# serializer. This does not cause a model change.
business_contact_information = NestedProxyField(
'company',
'company_email',
'company_phone_number',
'company_website',
)
# List the fields for `ContactInformationSerializer` nested
# serializer. This does not cause a model change.
contact_information = NestedProxyField(
'personal_contact_information',
'business_contact_information',
)
class Meta(object):
"""Meta options."""
ordering = ["id"]
def __str__(self):
return self.name
Alternatively, you could rewrite the contact_information
definition
as follows (although at the moment it’s not the recommended approach):
# ...
# List the fields for `ContactInformationSerializer` nested
# serializer. This does not cause a model change.
contact_information = NestedProxyField(
{
'personal_contact_information': (
'email',
'phone_number',
'website',
)
},
{
'business_contact_information': (
'company',
'company_email',
'company_phone_number',
'company_website',
)
},
)
# ...
Sample serializers¶
At first, we add nested_proxy_field
property to the Meta
class
definitions of PersonalContactInformationSerializer
,
BusinessContactInformationSerializer
and ContactInformationSerializer
nested serializers.
Then we define our (main) AuthorSerializer
class, which is going to be
used a serializer_class
of the AuthorViewSet
. We inherit the
AuthorSerializer
from
rest_framework_tricks.serializers.HyperlinkedModelSerializer
instead of the one of the Django REST framework. There’s also a
rest_framework_tricks.serializers.ModelSerializer
available.
Required imports¶
from rest_framework import serializers
from rest_framework_tricks.serializers import (
HyperlinkedModelSerializer,
ModelSerializer,
)
Serializer definition¶
Note
If you get validation errors about null-values, add allow_null=True
next to the required=False
for serializer field definitions.
Nested serializer for `ContactInformationSerializer` nested serializer
class PersonalContactInformationSerializer(serializers.ModelSerializer):
"""Personal contact information serializer."""
class Meta(object):
"""Meta options."""
model = Author
fields = (
'email',
'phone_number',
'website',
)
nested_proxy_field = True
Nested serializer for `ContactInformationSerializer` nested serializer
class BusinessContactInformationSerializer(serializers.ModelSerializer):
"""Business contact information serializer."""
class Meta(object):
"""Meta options."""
model = Author
fields = (
'company',
'company_email',
'company_phone_number',
'company_website',
)
nested_proxy_field = True
Nested serializer for `AuthorSerializer` (main) serializer
class ContactInformationSerializer(serializers.ModelSerializer):
"""Contact information serializer."""
personal_contact_information = PersonalContactInformationSerializer(
required=False
)
business_contact_information = BusinessContactInformationSerializer(
required=False
)
class Meta(object):
"""Meta options."""
model = Author
fields = (
'personal_contact_information',
'business_contact_information',
)
nested_proxy_field = True
Main serializer to be used in the ViewSet
class AuthorSerializer(ModelSerializer):
"""Author serializer."""
contact_information = ContactInformationSerializer(required=False)
class Meta(object):
"""Meta options."""
model = Author
fields = (
'id',
'salutation',
'name',
'birth_date',
'biography',
'contact_information',
)
If you can’t make use of rest_framework_tricks serializers¶
If somehow you can’t make use of the
rest_framework_tricks.serializers.ModelSerializer
or
rest_framework_tricks.serializers.HyperlinkedModelSerializer
serializers,
there are handy functions to help you to make your serializer to work with
NestedProxyField
.
See the following example:
Required imports¶
from rest_framework import serializers
from rest_framework_tricks.serializers.nested_proxy import (
extract_nested_serializers,
set_instance_values,
)
Serializer definition¶
class BookSerializer(serializers.ModelSerializer):
"""BookSerializer."""
# ...
def create(self, validated_data):
"""Create.
:param validated_data:
:return:
"""
# Collect information on nested serializers
__nested_serializers, __nested_serializers_data = \
extract_nested_serializers(
self,
validated_data,
)
# Create instance, but don't save it yet
instance = self.Meta.model(**validated_data)
# Assign fields to the `instance` one by one
set_instance_values(
__nested_serializers,
__nested_serializers_data,
instance
)
# Save the instance and return
instance.save()
return instance
def update(self, instance, validated_data):
"""Update.
:param instance:
:param validated_data:
:return:
"""
# Collect information on nested serializers
__nested_serializers, __nested_serializers_data = \
extract_nested_serializers(
self,
validated_data,
)
# Update the instance
instance = super(ModelSerializer, self).update(
instance,
validated_data
)
# Assign fields to the `instance` one by one
set_instance_values(
__nested_serializers,
__nested_serializers_data,
instance
)
# Save the instance and return
instance.save()
return instance
Sample ViewSet¶
Absolutely no variations from standard implementation here.
Required imports¶
from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import AllowAny
from .models import Author
from .serializers import AuthorSerializer
ViewSet definition¶
class AuthorViewSet(ModelViewSet):
"""Author ViewSet."""
queryset = Author.objects.all()
serializer_class = AuthorSerializer
permission_classes = [AllowAny]
Sample URLs/router definition¶
Absolutely no variations from standard implementation here.
Required imports¶
from django.conf.urls import url, include
from rest_framework_extensions.routers import ExtendedDefaultRouter
from .viewsets import AuthorViewSet
ViewSet definition¶
router = ExtendedDefaultRouter()
authors = router.register(r'authors',
AuthorViewSet,
base_name='author')
urlpatterns = [
url(r'^api/', include(router.urls)),
]
Sample OPTIONS call¶
OPTIONS /books/api/authors/
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"name": "Author List",
"description": "Author 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"
},
"salutation": {
"type": "string",
"required": true,
"read_only": false,
"label": "Salutation",
"max_length": 10
},
"name": {
"type": "string",
"required": true,
"read_only": false,
"label": "Name",
"max_length": 200
},
"birth_date": {
"type": "date",
"required": false,
"read_only": false,
"label": "Birth date"
},
"biography": {
"type": "string",
"required": false,
"read_only": false,
"label": "Biography"
},
"contact_information": {
"type": "nested object",
"required": false,
"read_only": false,
"label": "Contact information",
"children": {
"personal_contact_information": {
"type": "nested object",
"required": false,
"read_only": false,
"label": "Personal contact information",
"children": {
"email": {
"type": "email",
"required": true,
"read_only": false,
"label": "Email",
"max_length": 254
},
"phone_number": {
"type": "string",
"required": false,
"read_only": false,
"label": "Phone number",
"max_length": 200
},
"website": {
"type": "url",
"required": false,
"read_only": false,
"label": "Website",
"max_length": 200
}
}
},
"business_contact_information": {
"type": "nested object",
"required": false,
"read_only": false,
"label": "Business contact information",
"children": {
"company": {
"type": "string",
"required": false,
"read_only": false,
"label": "Company",
"max_length": 200
},
"company_email": {
"type": "email",
"required": false,
"read_only": false,
"label": "Company email",
"max_length": 254
},
"company_phone_number": {
"type": "string",
"required": false,
"read_only": false,
"label": "Company phone number",
"max_length": 200
},
"company_website": {
"type": "url",
"required": false,
"read_only": false,
"label": "Company website",
"max_length": 200
}
}
}
}
}
}
}
}
Sample POST call¶
POST /books/api/authors/
HTTP 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"salutation": "At eve",
"name": "Shana Rodriquez",
"birth_date": "2016-04-05",
"biography": "Commodi facere voluptate ipsum veniam maxime obcaecati",
"contact_information": {
"personal_contact_information": {
"email": "somasesu@yahoo.com",
"phone_number": "+386-36-3715907",
"website": "http://www.xazyvufugasi.biz"
},
"business_contact_information": {
"company": "Hopkins and Mccoy Co",
"company_email": "vevuciqa@yahoo.com",
"company_phone_number": "+386-35-5689443",
"company_website": "http://www.xifyhefiqom.com.au"
}
}
}
Release history and notes¶
Sequence based identifiers are used for versioning (schema follows below):
major.minor[.revision]
It’s always safe to upgrade within the same minor version (for example, from 0.3 to 0.3.4).
Minor version changes might be backwards incompatible. Read the release notes carefully before upgrading (for example, when upgrading from 0.3.4 to 0.4).
All backwards incompatible changes are mentioned in this document.
0.2.14¶
2022-11-22
Tested against Django 4.1.
Tested against Django REST Framework 3.13.
0.2.13¶
2022-11-20
Tested against Django 4.0.
Drop Python 3.6 support.
Drop Django REST Framework 3.9.x support.
Add ConstrainedFileField (for limiting size of file uploads).
0.2.12¶
2021-12-06
Tested against Django 3.1 and 3.2.
Tested against Python 3.9 and 3.10.
Tested against Django REST Framework 3.12.
Drop Python 2.x support.
Drop Python 3.5 support.
Drop Django < 2.2 support.
Drop Django REST Framework < 3.9 support.
0.2.11¶
2019-12-27
Tested against Django 3.0.
Tested against Python 3.8.
Tested against Django REST Framework 3.11.
0.2.10¶
2019-04-12
Tested against Django 2.1 and Django 2.2.
Tested against Python 3.7.
Dropping support for Python 3.4.
Upgrade test suite.
Temporary remove PyPy from tox (because of failing tests).
0.2.9¶
2018-02-03
Make it possible to order by two (or more fields) at once, using the
OrderingFilter
.
0.2.8¶
2018-01-31
Fixes in docs.
0.2.7¶
2018-01-28
Fixes in docs.
0.2.6¶
2018-01-28
Added
OrderingFilter
, which makes it possible to specify mapping (ordering option -> ORM field) for making more developer friendly ordering options in the API. An example of such could be aProfile
model withForeignKey
relation toUser
model. In case if we want to order byemail
field in theProfileViewSet
, instead of ordering onuser__email
we could order just onemail
.
0.2.5¶
2017-12-30
Update example project (and the tests that are dependant on the example project) to work with Django 2.0.
0.2.4¶
2017-07-14
Fix issue #1 with non-required nested serializer fields.
0.2.3¶
2017-07-13
More tests.
Made tests DRY.
0.2.2¶
2017-07-04
Documentation improvements.
Tested against various Django REST framework versions (>=3.5.0,<=3.6.3).
0.2.1¶
2017-07-04
Minor fixes.
Documentation improvements.
0.2¶
2017-07-02
Handle unlimited nesting depth for nested serializers of non-relational fields.
Documentation improvements.
0.1.8¶
2017-07-01
Initial beta release.
Package¶
Contents:
rest_framework_tricks package¶
Subpackages¶
rest_framework_tricks.filters package¶
Submodules¶
rest_framework_tricks.filters.ordering module¶
Ordering filter.
- class rest_framework_tricks.filters.ordering.OrderingFilter[source]¶
Bases:
OrderingFilter
Ordering filter improved.
Example:
>>> from rest_framework_tricks.filters import OrderingFilter >>> >>> class BooksViewSet(mixins.RetrieveModelMixin, >>> mixins.ListModelMixin, >>> viewsets.GenericViewSet): >>> >>> serializer_class = BookSerializer >>> filter_backends = ( >>> OrderingFilter, >>> ) >>> ordering_fields = { >>> 'email': 'user__email', >>> 'full_name': 'user__first_name', >>> 'last_login': 'user__last_login', >>> 'is_active': 'user__is_active', >>> }
Then it can be used in a view set as follows:
GET /books/api/proxy-books/?ordering=email
- get_ordering(request, queryset, view)[source]¶
Get ordering.
Important: list returned in this method is used directly in the filter_queryset method like:
>>> queryset.order_by(*ordering)
Ordering is set by a comma delimited ?ordering=… query parameter.
The ordering query parameter can be overridden by setting the ordering_param value on the OrderingFilter or by specifying an ORDERING_PARAM value in the API settings.
Module contents¶
Filters.
- class rest_framework_tricks.filters.OrderingFilter[source]¶
Bases:
OrderingFilter
Ordering filter improved.
Example:
>>> from rest_framework_tricks.filters import OrderingFilter >>> >>> class BooksViewSet(mixins.RetrieveModelMixin, >>> mixins.ListModelMixin, >>> viewsets.GenericViewSet): >>> >>> serializer_class = BookSerializer >>> filter_backends = ( >>> OrderingFilter, >>> ) >>> ordering_fields = { >>> 'email': 'user__email', >>> 'full_name': 'user__first_name', >>> 'last_login': 'user__last_login', >>> 'is_active': 'user__is_active', >>> }
Then it can be used in a view set as follows:
GET /books/api/proxy-books/?ordering=email
- get_ordering(request, queryset, view)[source]¶
Get ordering.
Important: list returned in this method is used directly in the filter_queryset method like:
>>> queryset.order_by(*ordering)
Ordering is set by a comma delimited ?ordering=… query parameter.
The ordering query parameter can be overridden by setting the ordering_param value on the OrderingFilter or by specifying an ORDERING_PARAM value in the API settings.
rest_framework_tricks.models package¶
Subpackages¶
rest_framework_tricks.models.fields package¶
Submodules¶
rest_framework_tricks.models.fields.nested_proxy module¶
Nested proxy field.
- rest_framework_tricks.models.fields.nested_proxy.NestedProxyField(*fields, **options)[source]¶
NestedProxyField field.
Example:
>>> from django.db import models >>> from rest_framework_tricks.models.fields import NestedProxyField >>> from .constants import BOOK_STATUS_CHOICES, BOOK_STATUS_DEFAULT >>> >>> >>> class Book(models.Model): >>> >>> 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_STATUS_CHOICES, >>> default=BOOK_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) >>> >>> # This does not cause a model change >>> publishing_information = NestedProxyField( >>> 'publication_date', >>> 'isbn', >>> 'pages', >>> ) >>> >>> # This does not cause a model change >>> stock_information = NestedProxyField( >>> 'stock_count', >>> 'price', >>> 'state', >>> ) >>> >>> class Meta: >>> >>> ordering = ["isbn"] >>> >>> def __str__(self): >>> return self.title
Nesting depth is unlimited, so the following would be possible as well.
Example:
>>> class Author(models.Model): >>> >>> salutation = models.CharField(max_length=10) >>> name = models.CharField(max_length=200) >>> email = models.EmailField() >>> birth_date = models.DateField(null=True, blank=True) >>> biography = models.TextField(null=True, blank=True) >>> phone_number = models.CharField(max_length=200, >>> null=True, >>> blank=True) >>> website = models.URLField(null=True, blank=True) >>> company = models.CharField(max_length=200, >>> null=True, >>> blank=True) >>> company_phone_number = models.CharField(max_length=200, >>> null=True, >>> blank=True) >>> company_email = models.EmailField(null=True, blank=True) >>> company_website = models.URLField(null=True, blank=True) >>> >>> # This does not cause a model change >>> personal_contact_information = NestedProxyField( >>> 'email', >>> 'phone_number', >>> 'website', >>> ) >>> >>> # This does not cause a model change >>> business_contact_information = NestedProxyField( >>> 'company', >>> 'company_email', >>> 'company_phone_number', >>> 'company_website', >>> ) >>> >>> # This does not cause a model change >>> contact_information = NestedProxyField( >>> 'personal_contact_information', >>> 'business_contact_information', >>> )
You could even do this (although the way it’s written above is at the moment the preferred/recommended way of dealing with unlimited nesting depth.
>>> # This does not cause a model change >>> contact_information = NestedProxyField( >>> { >>> 'personal_contact_information': ( >>> 'email', >>> 'phone_number', >>> 'website', >>> ) >>> }, >>> { >>> 'business_contact_information': ( >>> 'company', >>> 'company_email', >>> 'company_phone_number', >>> 'company_website', >>> ) >>> }, >>> )
Module contents¶
Fields.
- rest_framework_tricks.models.fields.NestedProxyField(*fields, **options)[source]¶
NestedProxyField field.
Example:
>>> from django.db import models >>> from rest_framework_tricks.models.fields import NestedProxyField >>> from .constants import BOOK_STATUS_CHOICES, BOOK_STATUS_DEFAULT >>> >>> >>> class Book(models.Model): >>> >>> 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_STATUS_CHOICES, >>> default=BOOK_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) >>> >>> # This does not cause a model change >>> publishing_information = NestedProxyField( >>> 'publication_date', >>> 'isbn', >>> 'pages', >>> ) >>> >>> # This does not cause a model change >>> stock_information = NestedProxyField( >>> 'stock_count', >>> 'price', >>> 'state', >>> ) >>> >>> class Meta: >>> >>> ordering = ["isbn"] >>> >>> def __str__(self): >>> return self.title
Nesting depth is unlimited, so the following would be possible as well.
Example:
>>> class Author(models.Model): >>> >>> salutation = models.CharField(max_length=10) >>> name = models.CharField(max_length=200) >>> email = models.EmailField() >>> birth_date = models.DateField(null=True, blank=True) >>> biography = models.TextField(null=True, blank=True) >>> phone_number = models.CharField(max_length=200, >>> null=True, >>> blank=True) >>> website = models.URLField(null=True, blank=True) >>> company = models.CharField(max_length=200, >>> null=True, >>> blank=True) >>> company_phone_number = models.CharField(max_length=200, >>> null=True, >>> blank=True) >>> company_email = models.EmailField(null=True, blank=True) >>> company_website = models.URLField(null=True, blank=True) >>> >>> # This does not cause a model change >>> personal_contact_information = NestedProxyField( >>> 'email', >>> 'phone_number', >>> 'website', >>> ) >>> >>> # This does not cause a model change >>> business_contact_information = NestedProxyField( >>> 'company', >>> 'company_email', >>> 'company_phone_number', >>> 'company_website', >>> ) >>> >>> # This does not cause a model change >>> contact_information = NestedProxyField( >>> 'personal_contact_information', >>> 'business_contact_information', >>> )
You could even do this (although the way it’s written above is at the moment the preferred/recommended way of dealing with unlimited nesting depth.
>>> # This does not cause a model change >>> contact_information = NestedProxyField( >>> { >>> 'personal_contact_information': ( >>> 'email', >>> 'phone_number', >>> 'website', >>> ) >>> }, >>> { >>> 'business_contact_information': ( >>> 'company', >>> 'company_email', >>> 'company_phone_number', >>> 'company_website', >>> ) >>> }, >>> )
Module contents¶
rest_framework_tricks.serializers package¶
Submodules¶
rest_framework_tricks.serializers.nested_proxy module¶
Serializers.
The following code is used in the usage examples of the ModelSerializer
and HyperlinkedModelSerializer
classes.
>>> from rest_framework import serializers
>>>
>>> from .models import Book
>>>
>>>
>>> class PublishingInformationSerializer(serializers.ModelSerializer):
>>>
>>> publication_date = serializers.DateField(required=False)
>>> isbn = serializers.CharField(required=False)
>>> pages = serializers.IntegerField(required=False)
>>>
>>> class Meta:
>>>
>>> model = Book
>>> fields = (
>>> 'publication_date',
>>> 'isbn',
>>> 'pages',
>>> )
>>> nested_proxy_field = True
>>>
>>>
>>> class StockInformationSerializer(serializers.ModelSerializer):
>>>
>>> class Meta:
>>>
>>> model = Book
>>> fields = (
>>> 'stock_count',
>>> 'price',
>>> 'state',
>>> )
>>> nested_proxy_field = True
- class rest_framework_tricks.serializers.nested_proxy.HyperlinkedModelSerializer(*args, **kwargs)[source]¶
Bases:
HyperlinkedModelSerializer
HyperlinkedModelSerializer for models with NestedProxyField fields.
Example:
>>> from rest_framework_tricks.serializers import ( >>> HyperlinkedModelSerializer, >>> ) >>> >>> >>> class BookSerializer(HyperlinkedModelSerializer): >>> >>> publishing_information = PublishingInformationSerializer( >>> required=False >>> ) >>> stock_information = StockInformationSerializer(required=False) >>> >>> class Meta: >>> >>> model = Book >>> fields = ( >>> 'url', >>> 'id', >>> 'title', >>> 'description', >>> 'summary', >>> 'publishing_information', >>> 'stock_information', >>> )
- class rest_framework_tricks.serializers.nested_proxy.ModelSerializer(*args, **kwargs)[source]¶
Bases:
ModelSerializer
ModelSerializer for models with NestedProxyField fields.
Example:
>>> from rest_framework_tricks.serializers import ModelSerializer >>> >>> >>> class BookSerializer(ModelSerializer): >>> >>> publishing_information = PublishingInformationSerializer( >>> required=False >>> ) >>> stock_information = StockInformationSerializer(required=False) >>> >>> class Meta: >>> >>> model = Book >>> fields = ( >>> 'url', >>> 'id', >>> 'title', >>> 'description', >>> 'summary', >>> 'publishing_information', >>> 'stock_information', >>> )
- class rest_framework_tricks.serializers.nested_proxy.NestedProxyFieldIdentifier[source]¶
Bases:
object
NestedProxyField identifier.
- rest_framework_tricks.serializers.nested_proxy.extract_nested_serializers(serializer, validated_data, nested_serializers=None, nested_serializers_data=None)[source]¶
Extract nested serializers.
- Parameters:
serializer (rest_framework.serializers.Serializer) – Serializer instance.
validated_data (dict) – Validated data.
nested_serializers (dict) –
nested_serializers_data –
- Returns:
- Return type:
tuple
- rest_framework_tricks.serializers.nested_proxy.is_nested_proxy_field(field)[source]¶
Check if field is nested proxy field.
- Parameters:
field –
- Returns:
True or False
- Return type:
bool
- rest_framework_tricks.serializers.nested_proxy.set_instance_values(nested_serializers, nested_serializers_data, instance)[source]¶
Set values on instance.
Does not perform any save actions.
- Parameters:
nested_serializers – Nested serializers.
nested_serializers_data – Nested serializers data.
instance – Instance (not yet saved)
- Returns:
Same instance with values set.
- Return type:
Module contents¶
Serializers.
- class rest_framework_tricks.serializers.HyperlinkedModelSerializer(*args, **kwargs)[source]¶
Bases:
HyperlinkedModelSerializer
HyperlinkedModelSerializer for models with NestedProxyField fields.
Example:
>>> from rest_framework_tricks.serializers import ( >>> HyperlinkedModelSerializer, >>> ) >>> >>> >>> class BookSerializer(HyperlinkedModelSerializer): >>> >>> publishing_information = PublishingInformationSerializer( >>> required=False >>> ) >>> stock_information = StockInformationSerializer(required=False) >>> >>> class Meta: >>> >>> model = Book >>> fields = ( >>> 'url', >>> 'id', >>> 'title', >>> 'description', >>> 'summary', >>> 'publishing_information', >>> 'stock_information', >>> )
- class rest_framework_tricks.serializers.ModelSerializer(*args, **kwargs)[source]¶
Bases:
ModelSerializer
ModelSerializer for models with NestedProxyField fields.
Example:
>>> from rest_framework_tricks.serializers import ModelSerializer >>> >>> >>> class BookSerializer(ModelSerializer): >>> >>> publishing_information = PublishingInformationSerializer( >>> required=False >>> ) >>> stock_information = StockInformationSerializer(required=False) >>> >>> class Meta: >>> >>> model = Book >>> fields = ( >>> 'url', >>> 'id', >>> 'title', >>> 'description', >>> 'summary', >>> 'publishing_information', >>> 'stock_information', >>> )
- class rest_framework_tricks.serializers.NestedProxyFieldIdentifier[source]¶
Bases:
object
NestedProxyField identifier.
- rest_framework_tricks.serializers.extract_nested_serializers(serializer, validated_data, nested_serializers=None, nested_serializers_data=None)[source]¶
Extract nested serializers.
- Parameters:
serializer (rest_framework.serializers.Serializer) – Serializer instance.
validated_data (dict) – Validated data.
nested_serializers (dict) –
nested_serializers_data –
- Returns:
- Return type:
tuple
- rest_framework_tricks.serializers.is_nested_proxy_field(field)[source]¶
Check if field is nested proxy field.
- Parameters:
field –
- Returns:
True or False
- Return type:
bool
- rest_framework_tricks.serializers.set_instance_values(nested_serializers, nested_serializers_data, instance)[source]¶
Set values on instance.
Does not perform any save actions.
- Parameters:
nested_serializers – Nested serializers.
nested_serializers_data – Nested serializers data.
instance – Instance (not yet saved)
- Returns:
Same instance with values set.
- Return type:
rest_framework_tricks.tests package¶
Submodules¶
rest_framework_tricks.tests.base module¶
Base tests.
rest_framework_tricks.tests.test_nested_proxy_field module¶
Test NestedProxyField.
- class rest_framework_tricks.tests.test_nested_proxy_field.TestNestedProxyFieldCreateAction(methodName='runTest')[source]¶
Bases:
TestNestedProxyFieldActionBase
Test NestedProxyField - create action.
- get_client_action() Callable [source]¶
Get client action.
- Returns:
Client action.
- Return type:
callable
- get_status_code() int [source]¶
Get status code.
- Returns:
Status code expected as result of the action.
- Return type:
str
- test_another_nested_proxy_field_model_serializer_depth()[source]¶
Test NestedProxyField and ModelSerializer with more depth.
- test_another_nested_proxy_field_model_serializer_more_depth()[source]¶
Test NestedProxyField and ModelSerializer with more depth.
- test_nested_proxy_field_hyperlinked_model_serializer()[source]¶
Test NestedProxyField and HyperlinkedModelSerializer.
- test_nested_proxy_field_model_serializer_depth()[source]¶
Test NestedProxyField and ModelSerializer with more depth.
- test_nested_proxy_field_model_serializer_depth_missing_fields()[source]¶
Test NestedProxyField and ModelSerializer with more depth.
Several non-required fields are missing.
- test_nested_proxy_field_model_serializer_depth_more_missing_fields()[source]¶
Test NestedProxyField and ModelSerializer with more depth.
All of the non-required fields are missing.
- class rest_framework_tricks.tests.test_nested_proxy_field.TestNestedProxyFieldUpdateAction(methodName='runTest')[source]¶
Bases:
TestNestedProxyFieldActionBase
Test NestedProxyField - update action.
- get_client_action() Callable [source]¶
Get client action.
- Returns:
Client action.
- Return type:
callable
- get_status_code() int [source]¶
Get status code.
- Returns:
Status code expected as result of the action.
- Return type:
str
- pytestmark = [Mark(name='django_db', args=(), kwargs={}), Mark(name='django_db', args=(), kwargs={})]¶
- test_another_nested_proxy_field_model_serializer_depth()[source]¶
Test NestedProxyField and ModelSerializer with more depth.
- test_another_nested_proxy_field_model_serializer_more_depth()[source]¶
Test NestedProxyField and ModelSerializer with more depth.
- test_nested_proxy_field_hyperlinked_model_serializer()[source]¶
Test NestedProxyField and HyperlinkedModelSerializer.
- test_nested_proxy_field_model_serializer_depth()[source]¶
Test NestedProxyField and ModelSerializer with more depth.
- test_nested_proxy_field_model_serializer_depth_missing_fields()[source]¶
Test NestedProxyField and ModelSerializer with more depth.
Several non-required fields are missing.
- test_nested_proxy_field_model_serializer_depth_more_missing_fields()[source]¶
Test NestedProxyField and ModelSerializer with more depth.
All of the non-required fields are missing.
rest_framework_tricks.tests.test_ordering_filter module¶
Test OrderingFilter.
- class rest_framework_tricks.tests.test_ordering_filter.TestOrderingFilter(methodName='runTest')[source]¶
Bases:
BaseRestFrameworkTestCase
Test OrderingFilter.
- pytestmark = [Mark(name='django_db', args=(), kwargs={}), Mark(name='django_db', args=(), kwargs={})]¶
rest_framework_tricks.tests.test_utils module¶
Test utils.
Module contents¶
Submodules¶
rest_framework_tricks.apps module¶
Apps.
rest_framework_tricks.utils module¶
Utils.
Module contents¶
Collection of various tricks for Django REST framework.