Package: django-rest
django-RESTOverviewdjango-REST is atiny,lightweight,easy-to-useandincredibly fastlibrary to implement
REST views with django. The whole library's focused inonedecorator that transforms the
simple views into REST ones, allowing easy customizations (such as permissions, serializers, etc.)The library itself was highly inspired from the greatdjango-rest-frameworkandSerPyTable of contentsOverviewTable of contentsRequirementsInstallationExampleDocumentationThe @api_view decoratorDecorator argmentsDecorated view's argumentsHow to decorate a viewView PermissionsIntroductionAvailable PermissionsPermissions OperatorsImplement your own permissionDeserializersIntroductionImpmement a new DeserializerAvailable Deserializer FieldsNested DeserializersPost-clean methodsAll-pass DeserializerSerializersIntroductionImpmement a new SerializerAvailable Serializer FieldsPrimitive typesMethodFieldConstantFieldListFieldNested SerializersDictSerializerExceptions@api_view exceptions catchingExisting API ExceptionsDefine your own API ExceptionHTTPHTTP Status codesHTTP MethodsRequirementsdjango-REST library requires:Python version 2.7+ or 3.3+django version 1.10+InstallationYou can get the package usingpip, as the following:pipinstalldjango-restExampleLet's implement a quick public API endpoint that lists existing regular (i.e.not staff) users:First, start a new django project:pipinstalldjango-rest# Will install django if not already installeddjango-adminstartprojectfirst_project.
./manage.pymigrate
./manage.pycreatesuperuser# Follow instructionsLet's get started by implementing the views in./first_project/urls.py:fromtypingimportDictfromdjango.contribimportadminfromdjango.contrib.auth.modelsimportUserfromdjango.httpimportJsonResponsefromdjango.urlsimportpathfromdjango_rest.decoratorsimportapi_viewfromdjango_rest.httpimportstatusfromdjango_rest.serializersimportfieldsasfields,Serializer# The serializer defines the output format of our endpointsclassUserSerializer(Serializer):id=fields.IntegerField()username=fields.CharField()email=fields.CharField()is_staff=fields.BooleanField()@api_view(allowed_methods=["GET"])deflist_users_view(request,url_params:Dict,query_params:Dict,**kwargs):regular_users=User.objects.exclude(is_staff=True)returnJsonResponse(UserSerializer(regular_users,many=True).data,status=status.HTTP_200_OK,safe=False,)urlpatterns=[path("admin/",admin.site.urls),path("api/users/",list_users_view),]That's all! Now run the server:./manage.pyrunserverIn order to test your endpoints, you can usePostMan,httpieorcurl.
I'll be usinghttpiein the example:httpGEThttp://127.0.0.1:8000/api/users/

HTTP/1.1200OK
Content-Length:84Content-Type:application/json
Date:Sat,30May202002:13:49GMT
Server:WSGIServer/0.2CPython/3.6.9
X-Content-Type-Options:nosniff
X-Frame-Options:DENY[]# After creating a new user from django-admin section (visit: http://127.0.0.1:8000/admin/ using your browser)httpGEThttp://127.0.0.1:8000/api/users/

HTTP/1.1200OK
Content-Length:84Content-Type:application/json
Date:Sat,30May202002:17:23GMT
Server:WSGIServer/0.2CPython/3.6.9
X-Content-Type-Options:nosniff
X-Frame-Options:DENY[{"email":"myfirst@user.com","id":1,"is_staff":true,"username":"firstuser"}]Documentation1. The@api_viewdecorator1.1 Decorator argumentsAs shown in the example section, the@api_viewcould be used with multiple
(optional) arguments:api_view(permission_class:BasePermission=AllowAny,allowed_methods:Iterable[str]=ALL_HTTP_METHODS,deserializer_class:Union[Deserializer,Dict[str,Deserializer]]=AllPassDeserializer,allow_forms:bool=False,)permission_classA class that defines who is allowed to access the
decorated view. If nopermission_classgiven, the decorator's default permission isAllowAny(your view is public).In case the user isn't allowed to access the view,
a403 Forbidden accessresponse will be returned before even executing the
view's code. More details inpermissions section.allowed_methods:Alist/tupleof HTTP allowed methods. Allowed methods should
be in uppercase strings (ex.GET,POST, etc.). You can also use some
predefined sets indjango_rest.http.methods. If noallowed_methodsgiven, all HTTP methods will be allowed.If the user requests the decorated view
with a non-allowed method, a405 Method not allowedresponse will be
returned before executing your view's code.deserializer_class:Could be either a sub-class ofDeserializer(as shown in the
previous example), or adictthat maps HTTP methods that use payload (i.e.POST,PUTandPATCH) toDeserializersub-classes, as the following:@api_view(deserializer_class=MyDeserializerClass)deffirst_case_view(request,**kwargs):# [...]@api_view(deserializer_class={"POST":MyCustomPOSTDeserializer,"PUT":MyCustomPUTDeserializer,},)defsecond_case_view(request,**kwargs):# [...]In the first case above,MyDeserializerClasswill be applied to:POST,PUTandPATCHmethods. Also, note that in second case, thedeserializer_classmapping doesn't
define a deserializer for thePATCHHTTP method. In this case,
the "all-pass" deserializer (i.e.passes payload data to the view without any
validation) will be used. The same deserializer will be applied if nodeserializer_classis given.If the payload data doesn't respect the format defined in the deserializer,
a400 Bad Requestresponse will be returned.allow_forms:Aboolthat allows/forbids payloads coming from forms (application/x-www-form-urlencodedandmultipart/form-datacontent-types).A415 Unsupported Media Typeresponse will be returned in case the user sends form
data to a view decorated withallow_forms=False. The argument's default value isFalse.1.2 Decorated view's argumentsAs illustrated in the examples above, the@api_viewdecorator alters the
decorated view's arguments. The decorator gathers, extracts and standardizes
different arguments, then passes them to your view, in order to facilitate their
use. Let's explain each argument:@api_viewdefdecorated_view(request:HttpRequest,url_params:Dict[str,Any],query_params:Dict[str,str],deserialized_data:Optional[Dict[str,Any]],)->JsonResponse:# For class methods, the first argument is `self` (the class instance)# [...]request:django's native request object (django.http.request.HttpRequest). Similar
to every django view's first argument.
More details ondjango's documentationurl_params:Adictcontaining the parameter defined in your view's
route (django router). For example, let's take a look tourl_paramswhen requesting the URL/api/hello/foo/bar/25/in the following example:# urls.pyfromdjango.urlsimportpathfromdjango_rest.decoratorsimportapi_view@api_viewdefhello_view(request,url_params,query_params,**kwargs):#  url_params = {"first_name": "foo", "last_name": "bar", "age": 25}urlpatterns=[path("api/hello/<str:first_name>/<str:last_name>/<int:age>/",hello_view),]Important note:The parameters are already casted into their target types (in
the example above,url_params['age']isint, whileurl_params['first_name']isstr)query_params:Adictcontaining all the query parameters encoded in the request's URL.
Let's request the previous example's view with the following URL:/api/hello/foo/bar/25/?lang=fr&display=true:# views.py@api_viewdefhello_view(request,url_params,query_params,**kwargs):#  url_params = {"first_name": "foo", "last_name": "bar", "age": 25}#  query_params = {"lang": "fr", "display": "true"}Important note:Unlikeurl_params, for query parameters, the values areALWAYSstrings (str), and
they should be casted manually.deserialized_data:Adictwith the data validated by the deserializer. For HTTP
methods without payload (GET,DELETE,etc.), this argument's value isNone.As explained in the section before, for HTTP methods requiring
data, if nodeserializer_class's been given to the decorator,deserialized_datawill contain the raw payload's data (without any validation).Note:In case you want to ignore a argument (let's saydeserialized_datafor aGETview), add**kwargsargument to your view. Otherwise, you'll have
a arguments error.1.3 How to decorate a viewThe@api_viewdecorator could be applied differently on the views, depending on your use-case. You can:Decorate a function-based view. For example:@api_view(allowed_methods=['GET'])defhello_view(request,**kwargs):returnJsonResponse({'message':'Hello world'},status=200)Decorate a whole class-based view (should be a sub-class ofdjango.view.View). For example:@api_view(permission_class=IsStaffUser)classHelloView(View):defget(self,request,**kwargs):returnJsonResponse({"message":"Hello world"},status=200)defpost(self,request,**kwargs):returnJsonResponse({},status=201)defother_method(self,arg_1,arg_2):# [...]For class-based views, the decorator decorates all view's http methods
(get(),post(),put(),etc.) andONLYthem. In the example above, all http methods are restricted for staff-users only, butother_methodmethod hasn't been altered.Note:Both@api_view()and@api_viewsyntaxes are correct in case the decorator is used without arguments.2. View Permissions2.1 IntroductionPermissions is what determines whether a request should be granted or denied
access, for a particular view. The inspection process is done before executing
the decorated view's code, then, if the request satisfies the
permission's constraints, the access is granted. If not, a403 Forbidden accessresponse is returned.In django-REST, all permissions inherit fromPermission, and passed as argument to the@api_viewdecorator, as seen in
the previous examples.In this section, we will start by introducing the django-REST's provided
permissions, then how to build more complex permissions by combining the
existing ones, and finally, how to implement your own custom permission.2.2 Available PermissionsAll the permissions listed below could be imported fromdjango_rest.permissionsAllowAny:
By choosing this permission, your view will be public (all requests will have granted access). It's the default permission for@api_viewdecorator.IsAuthenticated:
Allows only authenticated users to access your view. Anonymous users (i.e.not authenticated) receive a403 Forbidden accessresponse.IsStaffUser:
The view can be accessed by staff users only. A staff user is aUserobject havingis_staffattribute set toTrue.IsAdminUser:
Admins are the only users who can access the decorated view. An admin is aUserobject havingis_superuserattribute set toTrue.IsReadOnly:
Only HTTP safe methods (GET,HEADandOPTIONS) are allowed. For aPOSTrequest for example, the user receives a403 Forbidden access.This permission is not meant to be used in standalone, because, remember, the@api_viewdecorator has already theallowed_methodsargument for this purpose, that returns a405 Method not allowed.
It has been implemented only to be combined with other permissions in order to build a more complex ones (the next permission on the list is a good example).IsAuthenticatedOrReadOnly:
This permission allows Authenticated users to use all HTTP methods (GET,POST,DELETE,etc.), and anonymous users to use safe methods only (GET,HEADandOPTIONS).2.3 Permissions OperatorsUsing logical operators allows you to combine differentPermissionsub-classes, in a simple and powerful way, to obtain more complex and complete permissions.django-REST provides you 4 logical operators:AND(&),OR(|),XOR(^) andNOT(~).Let's demonstrate those operators then their combinations in concrete examples:AND operator:Let's create a newIsStaffAndReadOnlypermission that grants access to:Staff users,andonly with reading http methods (GET,HEADandOPTIONS).It will be implemented as the following:fromdjango_rest.decoratorsimportapi_viewfromdjango_rest.permissionsimportIsReadOnly,IsStaffUserIsStaffAndReadOnly=IsStaffUser&IsReadOnly@api_view(permission_class=IsStaffAndReadOnly)deftarget_view(request,**kwargs):# [...]OR operator:Let's create a newIsAdminOrReadOnlypermission granting access to:Admin users with all HTTP methodsNon-admin users with reading http methods (GET,HEADandOPTIONS) only.fromdjango_rest.decoratorsimportapi_viewfromdjango_rest.permissionsimportIsAdminUser,IsReadOnlyIsAdminOrReadOnly=IsAdminUser|IsReadOnly@api_view(permission_class=IsAdminOrReadOnly)deftarget_view(request,**kwargs):# [...]XOR (eXclusive OR) operator:For this example, let's implement a permission that grants access to:Users that are staffUsers that arenotadminsNote that this permission could be implemented differently (and in a more
correct and readable way). The use of XOR operator here is for demonstration purpose only.
The correct implementation is shown below in "5. Combining Operators" example.fromdjango_rest.decoratorsimportapi_viewfromdjango_rest.permissionsimportIsAdminUser,IsStaffUserIsStaffAndNotAdminUser=IsAdminUser^IsStaffUser@api_view(permission_class=IsStaffAndNotAdminUser)deftarget_view(request,**kwargs):# [...]NOT operator:Let's consider a view that should be exposed to anonymous (i.e.not
authenticated) users only. This view's permission will be defined as the following:fromdjango_rest.decoratorsimportapi_viewfromdjango_rest.permissionsimportIsAuthenticatedAnonymousUserOnly=~IsAuthenticated@api_view(permission_class=AnonymousUserOnly)deftarget_view(request,**kwargs):# [...]Combining Operators:Let's re-implement theIsStaffAndNotAdminUserused in the XOR example above, by using
multiple operators. Then, we'll re-use it (IsStaffAndNotAdminUser) to implement a newIsStaffAndNotAdminUserOrReadOnly:fromdjango_rest.decoratorsimportapi_viewfromdjango_rest.permissionsimportIsAdminUser,IsReadOnly,IsStaffUserIsStaffAndNotAdminUser=IsStaffUser&(~IsAdminUser)IsStaffAndNotAdminUserOrReadOnly=IsStaffAndNotAdminUser|IsReadOnly# Or: IsStaffAndNotAdminUserOrReadOnly = (IsStaffUser & (~ IsAdminUser)) | IsReadOnly@api_view(permission_class=IsStaffAndNotAdminUserOrReadOnly)deftarget_view(request,**kwargs):# [...]2.4 Implement your own permissionEven if combining standard permissions covers the most usual use-cases, you may have some unusual constrains that cannot be tackled using existing operators only.django-REST provides you a way to implement a custom permission that fits your needs.
All you have to do is inherit fromdjango_rest.permissions.BasePermission, then implement thehas_permission()method.Thehas_permission()takes therequestobject, and the target view object as
arguments, and should return aboolthat represents if the access should be
granted (True) or not (False).defhas_permission(self,request:HttpRequest,view:Union[Callable,View])->bool:Let's implement a custom permission that grants access to authenticated users
havinggmailaddress only. The "authenticated users" part will be taken care
of using the existingIsAuthenticatedpermission.fromdjango_rest.decoratorsimportapi_viewfromdjango_rest.permissionsimportBasePermission,IsAuthenticatedclassHasGmailAddress(BasePermission):defhas_permission(self,request,view):user_email=request.user.emaildomain_name=user_email.split('@')[1]has_gmail_address=(domain_name=='gmail.com')returnhas_gmail_address@api_view(permission_class=IsAuthenticated&HasGmailAddress)deftarget_view(request,**kwargs):# [...]Important Note:While using operators, operands ordermatters.In the example above, inHasGmailAddresscode, we assumed that the user is
already authenticated, instead of manually checking it. That's because if the permissionIsAuthenticatedisn't satisfied,
django-REST returns a403 Forbidden accessbefore even evaluatingHasGmailAddresspermission.
That's why inHasGmailAddresscode, we assumed the user is authenticated.If we switched permissions order as the following:@api_view(permission_class=HasGmailAddress&IsAuthenticated)deftarget_view(request,**kwargs):# [...]We should have added a condition inHasGmailAddressto verify if the user is
authenticated (and therefore,IsAuthenticatedpermission will be useless).
Otherwise, if an anonymous user requests the view, aAttributeError: 'NoneType' object has no attribute 'email'exception will be raised.3. Deserializers3.1. IntroductionIn django-REST, a deserializer validates input data (request payload and/or form data)
based on custom fields ans constrains defined in the deserializer class,
then "translates" data into the target format (Python primitive types), and finally
executes some post-validation methods (if defined).
In this chapter, we'll cover how to implement a simple deserializer, what are the
fields available for use, how to nest deserializers for more complex validation and to post-clean your data,
and finally, what theAllPassDeserializeris.3.2. Implement a new DeserializerDefining a newDeserializeris quite simple. All you need to do is to inherit
fromDeserializerclass:fromdjango_rest.deserializersimportDeserializerclassMyCustomDeserializer(Deserializer):passBut, a deserializer class has no purpose without its fields. Let's define
a simpleDeserializerwith 2 fields: a positive integer primary key (pk),
and ausername(string).fromdjango_rest.deserializersimportfields,DeserializerclassMyCustomDeserializer(Deserializer):pk=fields.IntegerField(min_value=0)# Implicit requiredusername=fields.CharField(required=True)# Explicit requiredThat's how aDeserializeris defined. Now, if you want to use the deserializer
outside the@api_view'sdeserializer_classargument, you have two approaches to proceed:The first approachInstantiate your deserializer class, passingdataargument to the
constructor.Check your data validity, by calling.is_valid()method.Retrieve the validated data with.data(or errors with.errors)
attribute.Here is a simple example:fromdjango_rest.deserializersimportfields,DeserializerclassMyCustomDeserializer(Deserializer):pk=fields.IntegerField(min_value=0)username=fields.CharField(required=True)valid_input={'pk':'3','username':'foobar'}invalid_input={'pk':-3,'username':'foobar'}valid_instance=MyCustomDeserializer(data=valid_input)valid_instance.is_valid()# Truevalid_instance.data# {'pk': 3, 'username': 'foobar'}invalid_instance=MyCustomDeserializer(data=invalid_input)invalid_instance.is_valid()# Falseinvalid_instance.errors# {"pk": ["Ensure this value is greater than or equal to 0."]}The second approachInstantiate the deserializer class without arguments.Call the.clean()method with the data to validate, it should return the
valid data, or raise aValidationErrorin case the input data is invalid.Put the clean call inside atry/exceptclause to catch the validation errors.Here is a simple example:fromdjango_rest.deserializersimportfields,Deserializer,ValidationErrorclassMyCustomDeserializer(Deserializer):pk=fields.IntegerField(min_value=0)username=fields.CharField(required=True)valid_input={'pk':'3','username':'foobar'}invalid_input={'pk':-3,'username':'foobar'}deserializer_instance=MyCustomDeserializer()deserializer.clean(valid_input)# {'pk': 3, 'username': 'foobar'}try:data=deserializer.clean(invalid_input)exceptValidationErroraserrors:errors=dict(errors)# errors = {"pk": ["Ensure this value is greater than or equal to 0."]}do_something_with_errors(errors)else:do_something(data)3.3. Available Deserializer Fieldsdjango-REST deserializers use native django forms fields.Depending on the
django version you are using, you may have access (or not) to some fields, and some of
their attributes. More details ondjango's official doc.Important Note:You can enjoyeveryfeature available in django forms fields, such ascustom validatorsandcustom error messages3.4 Nested Deserializersdjango-REST offers support for nesting deserializers, in order to build more complex ones, in a flexible way and without losing readability.By nesting deserializers, errors are nested, and output data is a nesteddicttoo. The following example illustrates how to nest deserializers:fromdjango.core.validatorsimportMinValueValidator,MaxValueValidatorfromdjango_rest.deserializersimportfields,DeserializerclassRaceDriverDeserializer(Deserializer):first_name=fields.CharField(required=True)last_name=fields.CharField(required=True)birth_day=fields.DateField()classRaceCarDeserializer(Deserializer):brand=fields.CharField()model=fields.CharField()production_year=fields.IntegerField(required=False,validators=[MinValueValidator(1900),MaxValueValidator(2020)])driver=RaceDriverDeserializer(required=True)valid_data={"brand":"Mercedes","model":"C11","production_year":"1990","driver":{"first_name":"Michael","last_name":"Schumacher","birth_day":"1969-01-03",},}deserializer=RaceCarDeserializer(data=valid_data)deserializer.is_valid()# Truedeserializer.data"""{'brand': 'Mercedes','model': 'C11','production_year': 1880,'driver': {'first_name': 'Michael','last_name': 'Schumacher','birth_day': datetime.date(1969, 1, 3),},}"""invalid_data={"brand":"Mercedes","production_year":"1990","driver":{"birth_day":"1969-01-03",},}deserializer=RaceCarDeserializer(data=invalid_data)deserializer.is_valid()# Falsedeserializer.errors"""{"model": ["This field is required."],"driver": {"first_name": ["This field is required."],"last_name": ["This field is required."],}}"""Note that aDeserializeris a field too, it can be used the exact same way you use a field (with thesame arguments).3.5. Post-clean methodsA post-clean method is a deserializer's method, specific to a singleFieldand
that will be called once the "standard" validation is done by the deserializer,
allowing you to handle this validated value more easily, then return the value
that will appear in the output data (that will be given to your view).
By convention, their name follows the pattern:post_clean_<FIELD NAME>.For example, if your deserializer defines afoofield as aCharField(), and
you want that your view receives a custom transformation of thatfoofield (for example, let's
say: striping border spaces), the post-clean method for that field should be
namedpost_clean_foo():fromdjango_rest.deserializersimportfields,DeserializerclassFooDeserializer(Deserializer):foo=fields.CharField(required=True)defpost_clean_foo(self,cleaned_value):returncleaned_value.strip()Important Note:The post-clean methods are calledonlyif the field's standard
validation succeeds. If aValidationErroroccurs, the post-clean won't be
done.3.6 All-pass DeserializerTheAllPassDeserializer, is a particular deserializer that allows all payloads to pass to the view, without
any validation: No type-casts, no post-clean methods, and more importantly,
never raises aValidationErroror returns a400 BadRequest.TheAllPassDeserializeris the default deserializer used by@api_viewdecorator. (You probably won't need it unless you're dealing with a very unusual
use-case)4. Serializers4.1. IntroductionSerializers allow complex data such as querysets and model instances to be
converted into native Python data-types, so that they could be easily rendered
into JSON. Serializers do the opposite of Deserializers, and intervene at the
"return" statement of your view.4.2. Implement a new SerializerSimilar to how we've implemented aDeserializer, in order to implement your
own serializer, you have to inherit fromSerializerclass, then define the
fields that you want to include into your serialized data (probably your view's response). Here is a simple
example:fromdjango.httpimportJsonResponsefromdjango_rest.decoratorsimportapi_viewfromdjango_rest.httpimportstatusfromdjango_rest.serializersimportfields,Serializerfrom.modelsimportSubscriptionclassSubscriptionSerializer(Serializer):id=fields.IntegerField(required=True)user_id=fields.IntegerField()started_at=fields.CharField(attr_name="created")invoices_urls=fields.ListField(fields.CharField(required=True))@api_viewdefsubscription_details_view(request,url_params,**kwargs):subscription=Subscription.objects.get(id=url_params["subscription_pk"])returnJsonResponse(SubscriptionSerializer(subscription,many=False).data,status=status.HTTP_200_OK,)Serializerclass accepts 2 arguments:instance: The object (or iterable of objects) to be serialized.many: Boolean that tells theSerializerif the object is iterable or not. Ifmany=True, the serialized data will be alistof serialized elements of
theinstanceiterable. Its set by default toFalse.4.3. Available Serializer FieldsSerializers fields are very limited, because, remember that the data will be converted
into native Python data-types (that are limited too). Besides primitive fields (CharField,IntegerField,FloatField,BooleanField), django-REST provides 3 additional
fields to use withinSerializers:ListField,ConstantFieldandMethodField(and the nested
serializers). Let's dive into existing fields details.NoteIn order to simplify the wording in this section, "field" word refers
to the serializer's field, and "attribute" word to an attribute of the object
to serialize.1. Primitive typesThe primitive types are serializer's fields that cast your data into Python's
native data-types:str,int,floatandbool. TheCharField,IntegerField,FloatFieldandBooleanFieldaccept thesamearguments:BooleanField(attr_name:str=None,label:str=None,call:bool=False,required:bool=True)CharField(attr_name:str=None,label:str=None,call:bool=False,required:bool=True)FloatField(attr_name:str=None,label:str=None,call:bool=False,required:bool=True)IntegerField(attr_name:str=None,label:str=None,call:bool=False,required:bool=True)attr_name: It refers to the object's attribute that should be binded to the
current field. The default value is the field name. For example:classExample:def__init__(self,foo,bar):self.foo=fooself.bar=barclassExampleSerializer(Serializer):foo=fields.IntegerField()# if `attr_name` is omitted, this field will lookup for your object's `.foo` attribute valuewhatever=fields.CharField(attr_name="bar")# this field will store your object's `.bar` attribute's valueExampleSerializer(Example(foo=3,bar="test")).data# {'foo': 3, 'whatever': 'test'}label: It's the name you want to give to your field in the serialized object.
If omitted, it preserves the field's name. For the sameExampleclass defined above, let's uselabelattribute:classExampleSerializer(Serializer):foo=fields.IntegerField(label='integerFoo')whatever=fields.CharField(attr_name='bar',label='textBar')ExampleSerializer(Example(foo=3,bar="test")).data# {'integerFoo': 3, 'textBar': 'test'}call: If set toTrue, the serializer will try to execute (call)
your attribute. This is useful when the attribute referred-to is a method. Here is a quick example:classExample:def__init__(self,foo):self.foo=foodef_get_text(self):return"Hello"defbar(self):returnself._get_text()+" World!"classExampleSerializer(Serializer):foo=fields.IntegerField()whatever=fields.CharField(attr_name="bar",call=True)# 'bar' is callableExampleSerializer(Example(foo=3)).data# {'foo': 3, 'whatever': 'Hello World!'}required: When set toTrue, if the serializer fails to
retrieve the attribute's value, or to convert it into the target type, aSerializationErrorwill be raised.
If the fields isn't required (required=False), in case the serializer fails to render the attribute's value,
the field won't be added to the final result. If we take the sameExampleclass from the previous examples:classExample:def__init__(self,foo,bar):self.foo=fooself.bar=barclassExampleSerializer(Serializer):foo=fields.IntegerField()bar=fields.IntegerField(required=True)# Trying to fit a string into `IntegerField`ExampleSerializer(Example(foo=3,bar="test")).data# raises a `SerializationError`classExampleSerializer(Serializer):foo=fields.IntegerField()bar=fields.IntegerField(required=False)# Trying to fit a string into `IntegerField`ExampleSerializer(Example(foo=3,bar="test")).data# {'foo': 3}2. MethodFieldThere are some situations in which you'd need a calculated value (from one or multiple attributes),
without polluting your view, nor your model with a new method.
In that case,MethodFieldcould be very useful. By defining aMethodField,
you have to define a method in yourSerializer, that receives your object as
input, and has to return the value to be renderedNoteMethodFieldis very similar to a deserializer's post-clean method,
the only difference is that the post-clean receives the attribute's value,
while theMethodFieldreceives the whole object.Here is a simple example that illustrates howMethodFieldworks:fromdjango_rest.serializersimportfields,SerializerTAX_RATE=20classPricingExample:def__init__(self,initial_price):self.initial_price=initial_priceclassPricingSerializer(Serializer):initial_price=fields.FloatField()final_price=fields.MethodField(method_name="calculate_final_price",required=True)defcalculate_final_price(self,obj:PricingExample):tax_price=obj.initial_price*(TAX_RATE/100)returnobj.initial_price+tax_pricePricingSerializer(PricingExample(initial_price=200)).data# {'initial_price': 200.0, 'final_price': 240.0}MethodFieldaccepts 3 arguments:MethodField(label:str=None,required:bool=True,method_name:str=None)label: The same asprimitive fieldslabel.required: The same asprimitive fieldslabel.method_name: The name of the serializer's method that should be
called. The default value isget_<Serializer's field name>(in the previous
example, ifmethod_namewas not given, the method should have been renamedget_final_price(self, obj))Important note:TheMethodField's method should return native Python
data-types (str,bool,int,float,None) or (nested)list/dictof native types.3. ConstantFieldConstantFieldallows you to include constant data in your response, without
having to include that constant in your model. In the previous example,TAX_RATEwas a constant. In case we wanted to include it in the serialized
data, we should had defined it asPricingExampleclass/instance attribute, or
created aMethodFieldthat returns a constant. Both solutions are quite
"painful". UsingConstantField, the code will look like:fromdjango_rest.serializersimportfields,SerializerTAX_RATE=20classPricingExample:def__init__(self,initial_price):self.initial_price=initial_priceclassPricingSerializer(Serializer):initial_price=fields.FloatField()final_price=fields.MethodField(method_name="calculate_final_price",required=True)tax_rate=fields.ConstantField(constant=TAX_RATE)defcalculate_final_price(self,obj:PricingExample):tax_price=obj.initial_price*(TAX_RATE/100)returnobj.initial_price+tax_pricePricingSerializer(PricingExample(initial_price=200)).data# {'initial_price': 200.0, 'final_price': 240.0, 'tax_rate': 20}ConstantFieldaccepts 3 arguments:ConstantField(label:str=None,required:bool=True,constant:Any=None)label: The same asprimitive fieldslabel.required: The same asprimitive fieldslabel.constant: The constant to be included in the serialized object. The
constantshould beprimitive (i.e.str,bool,int,float,Noneor combinations -list/dict- of them), otherwiseSerializationErrorwill
be raised (unlessrequiredis set toFalse, in that case, the field won't figure
in the rendered object).4. ListFieldListFieldallows you to serialize iterables of primitives. Let's say your
object's attribute is a list of integers. With a simpleIntegerField, you
won't be able to serialize that field. It could be achieved withMethodField,
but it will be too much written code for a trivial thing.ListFielddoes the
same thing as themany=TrueforSerializerclass, but themanyargument
isn't implemented forIntegerField,BooleanField,FloatFieldandCharFieldfor performance purpose.
TheListFieldaccepts a single argument which is the field to be rendered as list.ListField(Union[BooleanField,CharField,FloatField,IntegerField])Here is a simple example:fromdjango_rest.serializersimportfields,SerializerclassPath:def__init__(self):self.x_coordinates=[1.0,1.2,1.5,1.8,2.3,8.6]self.y_coordinates=[19.0,20.9,30.1,15.0,22.3,5.0]classPathSerializer(Serializer):xs=fields.ListField(fields.FloatField(label='path_xs',attr_name='x_coordinates',required=True))ys=fields.ListField(fields.FloatField(label='path_ys',attr_name='y_coordinates',required=True))PathSerializer(Path()).data# {'path_xs': [1.0, 1.2, 1.5, 1.8, 2.3, 8.6], 'path_ys': [19.0, 20.9, 30.1, 15.0, 22.3, 5.0]}4.4. Nested SerializersSimilarly toDeserializer,Serializersub-classes could be nested (i.e.
usingSerializersub-class as a serializer's field). Here is a simple example
that shows how to nest serializers:fromdatetimeimportdatetimefromdjango_rest.serializersimportfields,SerializerclassInvoice:def__init__(self,id,date):self.id=idself.created_at=dateclassSubscription:def__init__(self):self.name="foo bar subscription"self.invoices=[Invoice(id=i,date=datetime.now())foriinrange(10)]classInvoiceSerializer(Serializer):id=fields.IntegerField()created=fields.CharField(attr_name="created_at")classSubscriptionSerializer(Serializer):name=fields.CharField()invoices=InvoiceSerializer(many=True)SubscriptionSerializer(instance=Subscription()).data# {'name': 'foo bar subscription', 'invoices': [{'id': 0, 'created': '2020-06-08 15:26:15.414524'}, ..., {'id': 9, '2020-06-08 15:26:15.93843'}]}4.5. DictSerializerADictSerializeris a sub-class ofSerializer(it means that it's
a particular serializer), that, instead of taking an object (class
instance) as input, it takes adict. TheDictSerializertransforms adictinto anotherdict. It accepts the same fields as the classic serializer.
Here is the previous example, rewritten usingDictSerializer(to show the
difference):fromdatetimeimportdatetimefromdjango_rest.serializersimportfields,DictSerializersubscription={"name":"foo bar subscription","invoices":[{"id":0,"created_at":"2020-06-08 15:26:15.414524"},{"id":9,"created_at":"2020-06-08 15:26:15.93843"},],}classInvoiceSerializer(DictSerializer):id=fields.IntegerField()created=fields.CharField(attr_name="created_at")classSubscriptionSerializer(DictSerializer):name=fields.CharField()invoices=InvoiceSerializer(many=True)SubscriptionSerializer(instance=subscription).data# {'name': 'foo bar subscription', 'invoices': [{'id': 0, 'created': '2020-06-08 15:26:15.414524'}, ..., {'id': 9, '2020-06-08 15:26:15.93843'}]}5. Exceptions5.1.@api_viewexceptions catchingThe@api_viewdecorator catches exceptions for you in case you did not, and returns a JSON response with the correct status code.
If the raised exception is a sub-class ofdjango_rest.http.exceptions.BaseAPIException, a custom message and status code will be returned.
If it's not the case, the returned JSON response will have"An unknown server error occured."as message, and500as status code.By raising one of theexisting API exceptions(ordefining your own), the decorator will
return the response with the correct message (and status code). This approach
ensures that:Your responses are standardized in all your decorated views: always the same message and status code for the same situations.Your view's code is lighter (dropping all the uselesstry/exceptclauses).Here is a simple example of a view that receivesurl_params, calls afind_results()function, and returns a404in case there is no result:fromdjango_rest.decoratorsimportapi_viewfromdjango_rest.http.exceptionsimportNotFound@api_viewdefuser_custom_view(request,url_params,**kwargs):results=find_results(**url_params)ifresultsisNoneorlen(results)==0:raiseNotFound# In case the `find_results()` returned non-empty results:# [....]5.2. Existing API ExceptionsAs seen in the previous chapter, django-REST provides you some custom exceptions that you can use (i.e.raise) so that your view returns an error response,
without having to do it manually everytime. Here is the list of the available API exceptions , each with its returned object and status code:BadRequest:Response message:"Bad request."- status code:400NotAuthenticated:Response message:"Unauthorized operation. Maybe forgot the authentication step ?"- status code:401PermissionDenied:Response message:"Forbidden operation. Make sure you have the right permissions."- status code:403NotFound:Response message:"The requested resource is not found."- status code:404MethodNotAllowed:Response message:"HTTP Method not allowed."- status code:405UnsupportedMediaType:Response message:"Unsupported Media Type. Check your request's Content-Type."- status code:415InternalServerError:Response message:"An unknown server error occured."- status code:500ServiceUnavailable:Response message:"The requested service is unavailable."- status code:5025.3. Define your own API ExceptionIn order to define your own API Exception, all you have to do is inheriting fromdjango_rest.http.exceptions.BaseAPIException(or one of its sub-classes), then override itsSTATUS_CODEandRESPONSE_MESSAGEattributes.Here is a simple example that shows how to define aconflictexception:fromdjango_rest.decoratorsimportapi_viewfromdjango_rest.httpimportexceptions,statusclassConflict(exceptions.BaseAPIException):STATUS_CODE=status.HTTP_409_CONFLICTRESPONSE_MESSAGE="Requests conflict."@api_viewdefmy_conflicting_view(request,**kwargs):# [...]ifsome_condition_is_satisfied:raiseConflict# returns JsonResponse({'error_msg': 'Requests conflict.'}, status=409)# [....]6. HTTPdjango-REST provides some constants/enumerations that allow you to avoid using
hard-coded values (strfor HTTP methods, andintfor status codes), and
improve your code readability.6.1. HTTP Status codesThe HTTP status codes can be imported fromdjango_rest.http.status:fromdjango_rest.http.statusimportHTTP_200_OKresponse_status=HTTP_200_OK# ORfromdjango_rest.httpimportstatusresponse_status=status.HTTP_200_OKHere is the exhaustive list of http status constants provided by django-REST
(more details about status codeshere):HTTP_100_CONTINUEHTTP_101_SWITCHING_PROTOCOLSHTTP_200_OKHTTP_201_CREATEDHTTP_202_ACCEPTEDHTTP_203_NON_AUTHORITATIVE_INFORMATIONHTTP_204_NO_CONTENTHTTP_205_RESET_CONTENTHTTP_206_PARTIAL_CONTENTHTTP_207_MULTI_STATUSHTTP_208_ALREADY_REPORTEDHTTP_226_IM_USEDHTTP_300_MULTIPLE_CHOICESHTTP_301_MOVED_PERMANENTLYHTTP_302_FOUNDHTTP_303_SEE_OTHERHTTP_304_NOT_MODIFIEDHTTP_305_USE_PROXYHTTP_306_RESERVEDHTTP_307_TEMPORARY_REDIRECTHTTP_308_PERMANENT_REDIRECTHTTP_400_BAD_REQUESTHTTP_401_UNAUTHORIZEDHTTP_402_PAYMENT_REQUIREDHTTP_403_FORBIDDENHTTP_404_NOT_FOUNDHTTP_405_METHOD_NOT_ALLOWEDHTTP_406_NOT_ACCEPTABLEHTTP_407_PROXY_AUTHENTICATION_REQUIREDHTTP_408_REQUEST_TIMEOUTHTTP_409_CONFLICTHTTP_410_GONEHTTP_411_LENGTH_REQUIREDHTTP_412_PRECONDITION_FAILEDHTTP_413_REQUEST_ENTITY_TOO_LARGEHTTP_414_REQUEST_URI_TOO_LONGHTTP_415_UNSUPPORTED_MEDIA_TYPEHTTP_416_REQUESTED_RANGE_NOT_SATISFIABLEHTTP_417_EXPECTATION_FAILEDHTTP_418_IM_A_TEAPOTHTTP_422_UNPROCESSABLE_ENTITYHTTP_423_LOCKEDHTTP_424_FAILED_DEPENDENCYHTTP_426_UPGRADE_REQUIREDHTTP_428_PRECONDITION_REQUIREDHTTP_429_TOO_MANY_REQUESTSHTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGEHTTP_451_UNAVAILABLE_FOR_LEGAL_REASONSHTTP_500_INTERNAL_SERVER_ERRORHTTP_501_NOT_IMPLEMENTEDHTTP_502_BAD_GATEWAYHTTP_503_SERVICE_UNAVAILABLEHTTP_504_GATEWAY_TIMEOUTHTTP_505_HTTP_VERSION_NOT_SUPPORTEDHTTP_506_VARIANT_ALSO_NEGOTIATESHTTP_507_INSUFFICIENT_STORAGEHTTP_508_LOOP_DETECTEDHTTP_509_BANDWIDTH_LIMIT_EXCEEDEDHTTP_510_NOT_EXTENDEDHTTP_511_NETWORK_AUTHENTICATION_REQUIREDBesides, you also have (in the same moduledjango_rest.http.status) 5 functions that you can use to verify
a status code category easily:is_informational(code: int) -> boolis_success(code: int) -> boolis_redirect(code: int) -> boolis_client_error(code: int) -> boolis_server_error(code: int) -> bool6.2. HTTP MethodsAll the following HTTP method's related constants can be found indjango_rest.http.methods:String constants:HEADGETPOSTPUTPATCHDELETEOPTIONSTRACECONNECTTuple constants:SAFE_METHODS= (GET,HEAD,OPTIONS)SUPPORTING_PAYLOAD_METHODS= (POST,PUT,PATCH)ALL_METHODS= (HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS,TRACE,CONNECT)— Made with :hearts: —