Package: django-api-decorators
django-api-decoratorsTiny decorator functions to make it easier to build an API using Django in ~100 LoC (shorter than this README!)Table of ContentsI.InstallationII.UsageIII.How it WorksIV.Related LibrariesV.BackstoryInstallationpipinstalldjango-api-decoratorsIt is expected that you already have Django installedCompatibilityThis was originally used in an older Django 1.5 codebase with Python 2.7.Should work with Django 1.x-2.x and with Python 2.7-3.xLikely works with Django 0.95-0.99 as well, didn't check any earlier versions' release notes2to3shows that there is nothing to change, so should be compatible with Python 3.xHave not confirmed if this works with earlier versions of Python.Please submit a PR or file an issue if you have a compatibility problem or have confirmed compatibility on versions.Usage@method_exclusive, per docstring:Checks if request.method is equal to method, if not, returns a 405 not allowed response. Example:fromdjango_api_decoratorsimportmethod_exlusive@method_exclusive('GET')defget_latest_public_posts(request):...@require_auth, per docstring:Checks if the request was made by an authenticated user, and if not, returns a 401 unauthorized response. Example:fromdjango_api_decoratorsimportmethod_exclusive,require_auth@method_exclusive('GET')@require_authdefget_favorites(request):favs=request.user.favorites.all()...One can add more authorization checks on the User, such as for specific user types,
by building on top of the@require_authdecorator. For instance:fromfunctoolsimportwrapsfromdjango.httpimportHttpResponsefromdjango_api_decoratorsimportrequire_authdeftenant_exclusive(func):"""Checks if the authenticated user is a tenant, and if not, returns a401 unauthorized response"""@wraps(func)@require_authdeffunc_wrapper(request,*args,**kwargs):ifnotrequest.user.is_tenant():returnHttpResponse(status=401)returnfunc(request,*args,**kwargs)returnfunc_wrapper@clean_form, per docstring:Cleans the data in the POST or GET params using the form_class specified. Responds with a 400 bad request if the form is invalid with the errors specified in the form as JSON. Adds the cleaned data as a kwarg (cd) to the decorated function. Example:fromdjango.shortcutsimportget_object_or_404fromdjango_api_decoratorsimportmethod_exclusive,clean_form,require_authfromposts.modelsimportPostfromposts.formsimportAddFavForm@method_exclusive('POST')@clean_form(AddFavForm)@require_authdefadd_fav(request,cd):post=get_object_or_404(Post,pk=cd['post_id'])request.user.favorites.add(post)...@clean_forms, per docstring:Cleans the data in the POST or GET params using the form_class specified. Responds with a 400 bad request if any of the forms are invalid with the errors specified in the form as JSON. Adds the cleaned data as a kwarg (cd_list) to the decorated function. Example:fromdjango_api_decoratorsimportmethod_exclusive,clean_forms,require_authfromposts.modelsimportPostfromposts.formsimportCreatePostForm@method_exclusive('POST')@clean_forms(CreatePostForm,'posts')@require_authdefbulk_create_posts(request,cd_list):post_list=[]fordataincd_list:post_list.append(Post(user=request.user,cotent=data['content']))Post.objects.bulk_create(post_list)...How it worksAll of the decorators currently just perform a check against therequestobject, have an early return if the request is invalid, and otherwise let the next function execute. Some of them add a keyword argument when calling the next function so that the interpreted data can be used within it (like with the cleaned dictionaries of forms, which are added as akwargof the keywordcd).I'd encourage you to read the source code, since it's shorter than this README :)Related Librariesdjango-serializable-modelDjango classes to make your models, managers, and querysets serializable, with built-in support for related objects in ~100 LoCBackstoryThis library was built while I was working onYorango's ad-hoc API and transitioning from an MPA to an SPA. Instead of repeating lots of authentication, authorization, and validation code for every request, I wanted to DRY it up more using decorators or middleware. Decorators would allow us to have early returns with proper HTTP Status Codes for invalid requests. Request code became easier to reason about as a result, guaranteeing it would only execute after authz/authn/etc, and much less prone to accidental bugs, e.g. security issues due to forgetting an authorization check.@method_exclusive,@require_auth, and a few more project-specific decorators were born out of some of those needs.Validation was a bit more difficult, as we had many existingDjangoFormsin the MPA, wanted to re-use the classes and validation code we already had in our API instead of re-writing, and wanted to keep things in the same idiomatic style.Django REST Framework has the concept of "Validators", but it is explicitly different from Django's standardForminterface and requires you to buy-in to other parts of DRF to use, like Serializers.@clean_formwas born to address those needs. Later@clean_formswas made to address the case of multiple of the same form in one API request (e.g. for bulk creation), somewhat similar to how aDjangoFormSetmight work, but much simpler and requiring a lot less coupling of front and back end code.These were all used in production with great results, some API methods having just 1 decorator and others having 3 or more decorators, such as:@method_exclusive('POST')@clean_form(CreateBillsForm)@clean_forms(BillForm,'bills')@landlord_saas_exclusive@authorize_action(Listing,'listing_id')defcreate_bills(request,cd,cd_list,listing):...# bulk_create the new list of Billsbill_list=[]fordataincd_list:bill_list.append(Bill(listing=listing,price=data['price'],due_date=data['due_date'],))Bill.objects.bulk_create(bill_list)...Had been meaning to extract and open source this as well as other various useful utility libraries I had made at Yorango and finally got the chance!