- Notifications
You must be signed in to change notification settings - Fork5
Collection of various tricks for Django REST framework.
barseghyanartur/django-rest-framework-tricks
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Collection of various tricks forDjango REST framework.
- 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.
- djangorestframework: Initially written with 3.6.3, but nowadays testedwith >=3.10,<3.14. May (still) work on earlier- or (even) supportlater- versions, although not guaranteed.
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_frameworkandrest_framework_trickstoINSTALLED_APPS:INSTALLED_APPS= (# ...# REST framework'rest_framework',# REST framework tricks (this package)'rest_framework_tricks',# ...)
Documentation is available onRead the Docs.
- Nested serializers: Nested (writable) serializers for non-relational fields.
- Ordering filter: Developer friendly names for ordering options (forinstance, for related field names).
- File field with restrictions: Restrict the file field (in size).
Nested serializers for non-relational fields.
Our imaginaryBook model consists of the following (non-relational) Djangomodel fields:
title:CharFielddescription:TextFieldsummary:TextFieldpublication_date:DateTimeFieldstate:CharField(with choices)isbn:CharFieldprice:DecimalFieldpages:IntegerFieldstock_count:IntegerField
In our REST API, we want to split the Book serializer into parts using nestedserializers to have the following structure:
{"id":"","title":"","description":"","summary":"","publishing_information":{"publication_date":"","isbn":"","pages":""},"stock_information":{"stock_count":"","price":"","state":""}}
The only variation from standard implementation here is that we declare twoNestedProxyField fields on theBook model level for to be used inBookSerializer serializer.
Note, that the change does not cause model change (no migrations orwhatsoever).
fromdjango.dbimportmodelsfromrest_framework_tricks.models.fieldsimportNestedProxyField
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_PUBLISHEDclassBook(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', )classMeta:"""Meta options."""ordering= ["isbn"]def__str__(self):returnself.title
At first, we addnested_proxy_field property to theMeta classdefinitions ofPublishingInformationSerializer andStockInformationSerializer nested serializers.
Then we define our (main)BookSerializer class, which is going to beused as aserializer_class of theBookViewSet. We inherit theBookSerializer fromrest_framework_tricks.serializers.HyperlinkedModelSerializerinstead of the one of the Django REST framework. There's also arest_framework_tricks.serializers.ModelSerializer available.
fromrest_frameworkimportserializersfromrest_framework_tricks.serializersimport (HyperlinkedModelSerializer,)from .modelsimportBook
Note
If you get validation errors about null-values, addallow_null=Truenext to therequired=False for serializer field definitions.
Nested serializer
classPublishingInformationSerializer(serializers.ModelSerializer):"""Publishing information serializer."""publication_date=serializers.DateField(required=False)isbn=serializers.CharField(required=False)pages=serializers.IntegerField(required=False)classMeta:"""Meta options."""model=Bookfields= ('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
classStockInformationSerializer(serializers.ModelSerializer):"""Stock information serializer."""classMeta:"""Meta options."""model=Bookfields= ('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.classBookSerializer(HyperlinkedModelSerializer):"""Book serializer."""publishing_information=PublishingInformationSerializer(required=False)stock_information=StockInformationSerializer(required=False)classMeta:"""Meta options."""model=Bookfields= ('url','id','title','description','summary','publishing_information','stock_information', )
Absolutely no variations from standard implementation here.
fromrest_framework.viewsetsimportModelViewSetfromrest_framework.permissionsimportAllowAnyfrom .modelsimportBookfrom .serializersimportBookSerializer
classBookViewSet(ModelViewSet):"""Book ViewSet."""queryset=Book.objects.all()serializer_class=BookSerializerpermission_classes= [AllowAny]
OPTIONS /books/api/books/HTTP 200 OKAllow: GET, POST, HEAD, OPTIONSContent-Type: application/jsonVary: 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 is supported.
Our imaginaryAuthor model could consist of the following (non-relational)Django model fields:
salutation:CharFieldname:CharFieldemail:EmailFieldbirth_date:DateFieldbiography:TextFieldphone_number:CharFieldwebsite:URLFieldcompany:CharFieldcompany_phone_number:CharFieldcompany_email:EmailFieldcompany_website:URLField
In our REST API, we could split the Author serializer into parts usingnested 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 (seeAdvanced usage examplesfor complete model definition):
classAuthor(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 theAdvanced usage examplesfor complete example.
Developer friendly names for ordering options (for instance, for related fieldnames) for making better APIs.
Absolutely no variations from standard implementation here.
fromdjango.dbimportmodels
classProfile(models.Model):"""Profile."""user=models.ForeignKey('auth.User')biography=models.TextField()hobbies=models.TextField()
Absolutely no variations from standard implementation here.
fromrest_frameworkimportserializersfrom .modelsimportProfile
classProfileSerializer(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)classMeta(object):model=Profilefields= ('id','username','full_name','email','biography','hobbies', )defget_full_name(self,obj):returnobj.user.get_full_name()
The only variation from standard implementation here is that weuserest_frameworks_tricks.filters.OrderingFilter insteadofrest_framework.filters.OrderingFilter.
fromrest_framework.viewsetsimportModelViewSetfromrest_framework.permissionsimportAllowAnyfromrest_framework_tricks.filtersimportOrderingFilterfrom .modelsimportProfilefrom .serializersimportProfileSerializer
classProfileViewSet(ModelViewSet):"""Profile ViewSet."""queryset=Profile.objects.all()serializer_class=ProfileSerializerpermission_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',)
Note, that our ordering options are now equal to the field names in theserializer (JSON response). API becomes easier to use/understand that way.
GET /api/profile/?ordering=emailGET /api/profile/?ordering=-usernameGET /api/profile/?ordering=full_nameGET /api/profile/?ordering=-full_nameAbsolutely no variations from standard implementation here.
fromdjango.dbimportmodels
classProfile(models.Model):"""Upload."""username=models.CharField(max_length=255)resume=models.FileField()
fromrest_frameworkimportserializersfromrest_framework_tricks.fieldsimportConstrainedFileFieldfrom .modelsimportUpload
classProfileSerializer(serializers.ModelSerializer):"""Profile serializer."""username=serializers.CharField()# Restrict resume to 5Mbresume=ConstrainedFileField(max_upload_size=5_242_880)classMeta(object):model=Profilefields= ('id','username','resume', )
In order to be able to quickly evaluate thedjango-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 theinstructions below to have the demo running within a minute.
Grab and run the latestrest_framework_tricks_demo_installer.sh demoinstaller:
wget -O - https://raw.github.com/barseghyanartur/django-rest-framework-tricks/master/examples/rest_framework_tricks_demo_installer.sh| bashOpen your browser and test the app.
http://127.0.0.1:8001/books/api/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
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**************************GPL-2.0-only OR LGPL-2.1-or-later
For any security issues contact me at the e-mail given in theAuthor section.
For overall issues, go toGitHub.
Artur Barseghyan <artur.barseghyan@gmail.com>
About
Collection of various tricks for Django REST framework.
Topics
Resources
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.