Package: django-rest-framework-tricks
Collection of various tricks forDjango REST framework.PrerequisitesDjango 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.Dependenciesdjangorestframework: 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.InstallationInstall latest stable version from PyPI:pipinstalldjango-rest-framework-tricksor latest development version from GitHub:pipinstallhttps://github.com/barseghyanartur/django-rest-framework-tricks/archive/master.tar.gzAddrest_frameworkandrest_framework_trickstoINSTALLED_APPS:INSTALLED_APPS=(# ...# REST framework'rest_framework',# REST framework tricks (this package)'rest_framework_tricks',# ...)DocumentationDocumentation is available onRead the Docs.Main features and highlightsNested 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 examplesNested serializersNested serializers for non-relational fields.Our imaginaryBookmodel consists of the following (non-relational) Django
model fields:title:CharFielddescription:TextFieldsummary:TextFieldpublication_date:DateTimeFieldstate:CharField(with choices)isbn:CharFieldprice:DecimalFieldpages:IntegerFieldstock_count:IntegerFieldIn 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 modelThe only variation from standard implementation here is that we declare twoNestedProxyFieldfields on theBookmodel level for to be used inBookSerializerserializer.Note, that the change does not cause model change (no migrations or
whatsoever).Required importsfromdjango.dbimportmodelsfromrest_framework_tricks.models.fieldsimportNestedProxyFieldModel definitionBOOK_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.titleSample serializersAt first, we addnested_proxy_fieldproperty to theMetaclass
definitions  ofPublishingInformationSerializerandStockInformationSerializernested serializers.Then we define our (main)BookSerializerclass, which is going to be
used as aserializer_classof theBookViewSet. We inherit theBookSerializerfromrest_framework_tricks.serializers.HyperlinkedModelSerializerinstead of the one of the Django REST framework. There’s also arest_framework_tricks.serializers.ModelSerializeravailable.Required importsfromrest_frameworkimportserializersfromrest_framework_tricks.serializersimport(HyperlinkedModelSerializer,)from.modelsimportBookDefining the serializersNoteIf you get validation errors about null-values, addallow_null=Truenext to therequired=Falsefor serializer field definitions.Nested serializerclassPublishingInformationSerializer(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=TrueNested serializerclassStockInformationSerializer(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=TrueMain 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',)Sample ViewSetAbsolutely no variations from standard implementation here.Required importsfromrest_framework.viewsetsimportModelViewSetfromrest_framework.permissionsimportAllowAnyfrom.modelsimportBookfrom.serializersimportBookSerializerViewSet definitionclassBookViewSet(ModelViewSet):"""Book ViewSet."""queryset=Book.objects.all()serializer_class=BookSerializerpermission_classes=[AllowAny]Sample OPTIONS callOPTIONS /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 depthUnlimited nesting depth is supported.Our imaginaryAuthormodel 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:URLFieldIn 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 (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.Ordering filterDeveloper friendly names for ordering options (for instance, for related field
names) for making better APIs.Sample modelAbsolutely no variations from standard implementation here.Required importsfromdjango.dbimportmodelsModel definitionclassProfile(models.Model):"""Profile."""user=models.ForeignKey('auth.User')biography=models.TextField()hobbies=models.TextField()Sample serializerAbsolutely no variations from standard implementation here.Required importsfromrest_frameworkimportserializersfrom.modelsimportProfileDefining the serializersclassProfileSerializer(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()Sample ViewSetThe only variation from standard implementation here is that we
userest_frameworks_tricks.filters.OrderingFilterinstead
ofrest_framework.filters.OrderingFilter.Required importsfromrest_framework.viewsetsimportModelViewSetfromrest_framework.permissionsimportAllowAnyfromrest_framework_tricks.filtersimportOrderingFilterfrom.modelsimportProfilefrom.serializersimportProfileSerializerViewSet definitionclassProfileViewSet(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',)Sample GET callsNote, 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_nameFile field with restrictionsSample modelAbsolutely no variations from standard implementation here.Required importsfromdjango.dbimportmodelsModel definitionclassProfile(models.Model):"""Upload."""username=models.CharField(max_length=255)resume=models.FileField()Sample serializerRequired importsfromrest_frameworkimportserializersfromrest_framework_tricks.fieldsimportConstrainedFileFieldfrom.modelsimportUploadDefining the serializersclassProfileSerializer(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',)DemoRun demo locallyIn 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 the
instructions below to have the demo running within a minute.Grab and run the latestrest_framework_tricks_demo_installer.shdemo
installer: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/TestingProject is covered with tests.To test with all supported Python/Django versions type:toxTo test against specific environment, type:tox-epy39-django32To test just your working environment type:pytest-vvvTo run a single test in your working environment type:pytest-vvvsrc/rest_framework_tricks/tests/test_nested_proxy_field.pypipinstall-rexamples/requirements/test.txtWriting documentationKeep 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
**************************LicenseGPL-2.0-only OR LGPL-2.1-or-laterSupportFor any issues contact me at the e-mail given in theAuthorsection.AuthorArtur Barseghyan <artur.barseghyan@gmail.com>