Package: drf-gears
DRF gears ⚙ ⚙ ⚙Some gears collection for getting life a little bit better.Installationpip install drf-gearsHow to useConditionalQuerysetMixinIt gives you an ability to have multipleget_querysetmethods for every needs.Querysets mappingquerysets- is a dictionary of named querysets. You can provide different querysets for any action.fromrest_frameworkimportviewsetsfromrest_framework.decoratorsimportactionfromgearsimportConditionalQuerysetMixinfrom.modelsimportMyModelclassMyModelViewSet(ConditionalQuerysetMixin,viewsets.ModelViewSet,):querysets={'list':MyModel.objects.all(),'custom':MyModel.objects.filter(),}@action(methods=['get'])defsome_action(self,request,*args,**kwargs):qs=self.get_queryset(name='custom')...Named queryset methodUse named methods likeget_name_queryset, wherenameis an any name.fromrest_frameworkimportviewsetsfromrest_framework.decoratorsimportactionfromgearsimportConditionalQuerysetMixinfrom.modelsimportMyModelclassMyModelViewSet(ConditionalQuerysetMixin,viewsets.ModelViewSet,):defget_custom_queryset(self,qs):# do somethingreturnqs@action(methods=['get'])defsome_action(self,request,*args,**kwargs):qs=self.get_queryset(name='custom')...ViewSet actionsIf ConditionalQuerysetMixin can't find a mapped or named queryset, it will try to find a method with a ViewSet action instead of name.E.g:get_list_queryset,get_update_queryset,get_custom_action_queryset.fromrest_frameworkimportviewsetsfromgearsimportConditionalQuerysetMixinfrom.modelsimportMyModelclassMyModelViewSet(ConditionalQuerysetMixin,viewsets.ModelViewSet,):defget_list_queryset(self,qs):# do somethingreturnqsdefget_retrieve_queryset(self,qs):# do somethingreturnqsdefget_some_action_queryset(self,qs):# do somethingreturnqsSerializersMixinUse this mixin if you need different serializers for any action.
By default, will be used aserializer_classor adefaultitem ofserializersdictionary.By the way, you can change the default key providingdefault_nameattribute.fromrest_frameworkimportviewsetsfromrest_cogs.mixinsimportSerializersMixinclassSomeViewSet(SerializersMixin,viewsets.ModelViewSet,):serializers={'default':DefaultModelSerializer,'list':AdditionalModelSerializer,}Also, you can use aget_serializermethod providing a specific name when you need to use a particular serializer.fromrest_frameworkimportviewsetsfromrest_framework.decoratorsimportactionfromrest_cogs.mixinsimportSerializersMixinclassSomeViewSet(SerializersMixin,viewsets.ModelViewSet,):serializers={'default':DefaultModelSerializer,'some_action':SomeActionModelSerializer,'list':ListModelSerializer,}@action(methods=['get'])defsome_action(self,request,*args,**kwargs):serializer=self.get_serializer()# SomeActionModelSerializerserializer=self.get_serializer(serializer_name='list')# ListModelSerializer...PermissionsMixinThis part of specification is in progress.RenderersThere are a pair of things, which makes a charm when you work with API responses.Imagine, every API response has an expected structure. I mean every response!For example, take a look at this paginated objects list API response:{"success":true,"status_code":200,"pagination":{"count":2,"next":null,"previous":null},"errors":[],"data":[{"id":"c65e9bf7-1724-4593-bf57-394cea491887","phone_number":"10001001001"},{"id":"d7c9342d-0bd8-44e7-8e3c-44a18ecbfb8f","phone_number":"10001001002"}]}... and this validation error response.{"success":false,"status_code":400,"pagination":null,"errors":[[{"code":"value_required","location":"phone_number","description":"This field is required.","detail":null}],[{"code":"value_required","location":"password","description":"This field is required.","detail":null}]],"data":null}... and this authorization error too.{"success":false,"status_code":403,"pagination":null,"errors":[{"code":"not_authenticated","location":null,"description":"Authentication credentials were not provided.","detail":null}],"data":null}API rendererAll of these and any possible response will have the same structure:
booleansuccess, integerstatus_code,paginationobject, a list oferrorsobjects and the requesteddataof course.You should just use a single gear for it:REST_FRAMEWORK={...'DEFAULT_RENDERER_CLASSES':('gears.ApiRenderer',# <-- that's it),...}Exception handlerAlso, you could use the exception handler, if you want to see the same structure errors:REST_FRAMEWORK={...'EXCEPTION_HANDLER':'gears.exception_handler',# <-- that's it...}Error codes mappingYou have an ability to remap the standard error codes if you want.
Yeah, sometimes the standard ones are not verbose enough.
But you could handle this codes and remap them on your taste.
Just add the following settings variable and describe the errors' behaviour.
FYI, you're able to set not only the codes, butdescription,detailsandlocationare available as well.# setting.pyfromgearsimportErrorRESPONSE_ERROR_MAPPING={'required':Error(code='value_required'),...}JWT helpersBefore we start. We assumed you use Django Rest Framework and djangorestframework-simplejwt packages for handling the JWT authorisation process.There are some gears which want to help you fill tokens with an additional payload.Firstly, add theJWTUserModelMixinto your user model. It delivers a set of methods for extending the tokens.fromgearsimportJWTUserModelMixinclassUser(JWTUserModelMixin,BaseUserModel):...defget_public_jwt_data(self):return{'name':self.name,}defget_private_jwt_data(self):return{'address':self.address,}You should override these two method. The first one must return a dictionary with an open data.
The second one must return a dictionary with a sensitive data, which must be closed for anyone.Keep on mind, JWT tokens could be unpacked by any person, so the information inside the token available for anyone who has this token.
If you need to fill JWT token with a sensitive data, please put it to theget_private_jwt_data. The entire data in this method will be encrypted with a secret symmetric key you will generate on the next step.Now we should generate a secret key and put it into the settings:fromgearsimportTokenEncryptionsecret_key=TokenEncryption.generate_key()The value of thesecret_keyyou must put to the project's settings.
It would be better to keep it in the virtual environment variable.importosJWT_PAYLOAD_ENCRYPTION_KEY=os.environ.get('JWT_PAYLOAD_ENCRYPTION_KEY')DetailsThere was used the symmetric Fernet algorithm. Anyone, who has a secret key could decrypt data.An example how to do it on python:fromgearsimportTokenEncryptiondecrypted_data=TokenEncryption.decrypt_data(encrypted_data,secret_key)Google help you if you need similar functionality for another programming language.