Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/barseghyanartur/django-rest-framework-tricks
Collection of various tricks for Django REST framework.
https://github.com/barseghyanartur/django-rest-framework-tricks
django django-rest-framework django-rest-framework-addon django-rest-framework-file-field django-rest-framework-filters djangorestframework nested-serializers
Last synced: 17 days ago
JSON representation
Collection of various tricks for Django REST framework.
- Host: GitHub
- URL: https://github.com/barseghyanartur/django-rest-framework-tricks
- Owner: barseghyanartur
- Created: 2017-06-30T22:07:34.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2022-12-12T23:11:59.000Z (almost 2 years ago)
- Last Synced: 2024-10-13T07:45:56.945Z (about 1 month ago)
- Topics: django, django-rest-framework, django-rest-framework-addon, django-rest-framework-file-field, django-rest-framework-filters, djangorestframework, nested-serializers
- Language: Python
- Homepage: https://pypi.python.org/pypi/django-rest-framework-tricks
- Size: 441 KB
- Stars: 59
- Watchers: 7
- Forks: 5
- Open Issues: 2
-
Metadata Files:
- Readme: README.rst
- Changelog: CHANGELOG.rst
- License: LICENSE_GPL2.0.txt
Awesome Lists containing this project
README
============================
django-rest-framework-tricks
============================
Collection of various tricks for
`Django REST framework `_... image:: https://img.shields.io/pypi/v/django-rest-framework-tricks.svg
:target: https://pypi.python.org/pypi/django-rest-framework-tricks
:alt: PyPI Version.. image:: https://img.shields.io/pypi/pyversions/django-rest-framework-tricks.svg
:target: https://pypi.python.org/pypi/django-rest-framework-tricks/
:alt: Supported Python versions.. image:: https://img.shields.io/pypi/djversions/django-rest-framework-tricks.svg
:target: https://pypi.python.org/pypi/django-rest-framework-tricks/
:alt: Supported Django versions.. image:: https://github.com/barseghyanartur/django-rest-framework-tricks/workflows/test/badge.svg
:target: https://github.com/barseghyanartur/django-rest-framework-tricks/actions
:alt: Build Status.. image:: https://readthedocs.org/projects/django-rest-framework-tricks/badge/?version=latest
:target: http://django-rest-framework-tricks.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status.. image:: https://img.shields.io/badge/license-GPL--2.0--only%20OR%20LGPL--2.1--or--later-blue.svg
:target: https://github.com/barseghyanartur/django-rest-framework-tricks/#License
:alt: GPL-2.0-only OR LGPL-2.1-or-later.. image:: https://coveralls.io/repos/github/barseghyanartur/django-rest-framework-tricks/badge.svg?branch=master
:target: https://coveralls.io/github/barseghyanartur/django-rest-framework-tricks?branch=master
:alt: CoveragePrerequisites
=============- 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
============(1) Install latest stable version from PyPI:
.. code-block:: sh
pip install django-rest-framework-tricks
or latest development version from GitHub:
.. code-block:: sh
pip install https://github.com/barseghyanartur/django-rest-framework-tricks/archive/master.tar.gz
(2) Add ``rest_framework`` and ``rest_framework_tricks`` to ``INSTALLED_APPS``:
.. code-block:: python
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:.. code-block:: javascript
{
"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
^^^^^^^^^^^^^^^^.. code-block:: python
from django.db import models
from rest_framework_tricks.models.fields import NestedProxyField
Model definition
^^^^^^^^^^^^^^^^.. code-block:: python
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_PUBLISHEDclass 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.titleSample 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
^^^^^^^^^^^^^^^^.. code-block:: python
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**
.. code-block:: python
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**
.. code-block:: python
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**
.. code-block:: python
# 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
^^^^^^^^^^^^^^^^.. code-block:: python
from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import AllowAnyfrom .models import Book
from .serializers import BookSerializerViewSet definition
^^^^^^^^^^^^^^^^^^.. code-block:: python
class BookViewSet(ModelViewSet):
"""Book ViewSet."""queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [AllowAny]Sample OPTIONS call
^^^^^^^^^^^^^^^^^^^.. code-block:: text
OPTIONS /books/api/books/
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept.. code-block:: javascript
{
"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:.. code-block:: javascript
{
"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):.. code-block:: python
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
^^^^^^^^^^^^^^^^.. code-block:: python
from django.db import models
Model definition
^^^^^^^^^^^^^^^^.. code-block:: python
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
^^^^^^^^^^^^^^^^.. code-block:: python
from rest_framework import serializers
from .models import Profile
Defining the serializers
^^^^^^^^^^^^^^^^^^^^^^^^.. code-block:: python
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
^^^^^^^^^^^^^^^^.. code-block:: python
from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import AllowAny
from rest_framework_tricks.filters import OrderingFilterfrom .models import Profile
from .serializers import ProfileSerializerViewSet definition
^^^^^^^^^^^^^^^^^^.. code-block:: python
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... code-block:: text
GET /api/profile/?ordering=email
GET /api/profile/?ordering=-username
GET /api/profile/?ordering=full_name
GET /api/profile/?ordering=-full_nameFile field with restrictions
----------------------------Sample model
~~~~~~~~~~~~Absolutely no variations from standard implementation here.
Required imports
^^^^^^^^^^^^^^^^.. code-block:: python
from django.db import models
Model definition
^^^^^^^^^^^^^^^^.. code-block:: python
class Profile(models.Model):
"""Upload."""username = models.CharField(max_length=255)
resume = models.FileField()Sample serializer
~~~~~~~~~~~~~~~~~Required imports
^^^^^^^^^^^^^^^^.. code-block:: python
from rest_framework import serializers
from rest_framework_tricks.fields import ConstrainedFileFieldfrom .models import Upload
Defining the serializers
^^^^^^^^^^^^^^^^^^^^^^^^.. code-block:: python
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:.. code-block:: sh
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.
.. code-block:: text
http://127.0.0.1:8001/books/api/
Testing
=======Project is covered with tests.
To test with all supported Python/Django versions type:
.. code-block:: sh
tox
To test against specific environment, type:
.. code-block:: sh
tox -e py39-django32
To test just your working environment type:
.. code-block:: sh
pytest -vvv
To run a single test in your working environment type:
.. code-block:: sh
pytest -vvv src/rest_framework_tricks/tests/test_nested_proxy_field.py
.. code-block:: sh
pip install -r examples/requirements/test.txt
Writing documentation
=====================Keep the following hierarchy.
.. code-block:: text
=====
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 `_.
Author
======Artur Barseghyan