Package: drf-util
Django Rest Framework - UtilsA set of util functions used in EBS ProjectsInstall:pipinstalldrf_utilFunctionsGet value from an object by pathDefinition:gt(obj,path,default=None,sep='.')Usage:>>>data={"a":{"b":1}}>>>print(gt(data,'a.b'))1Get recursive values from a dict by keysDefinition:get_object_labels(obj,path,default=None)Usage:>>>data={"a":{"b":'title'},"c":'test'}>>>print(get_object_labels(data))['title','test']>>>data={"a":{"b":'title'},"c":'test'}>>>print(get_object_labels(data,['c']))['test']map() alternative with chunk selectDefinition:fetch_objects(instance,function,select=50)Usage:>>>defprint_name(obj):printobj.name>>>>>>fetch_objects(UserBigList.objects.order_by('id'),print_name,500)Select a first true valueDefinition:any_value(items:list)Usage:>>>print(any_value('',None,0,"Some text",50000))SometextRecursive merge two dictDefinition:dict_merge(a,b,path=None)Usage:>>>a={'a':{'c':1,'d':{'x':1}}}>>>b={'a':{'e':1,'d':{'y':1}}}>>>print(dict_merge(a,b)){'a':{'c':1,'e':1,'d':{'x':1,'y':1}}}Iterate big queryDefinition:iterate_query(queryset,offset_field='pk',offset_start=0,limit=100)Usage:queryset=Thing.objects.all()for_inutils.iterate_query(queryset,'id',0):...Get applications from folderDefinition:get_applications(base_folder='apps',inside_file='',only_directory=True)Usage:# settings.pyAPPS_PATH='path_to_aps'# default is apps...# any fileget_applications()# ['path_to_aps.app1', 'path_to_aps.app2']get_applications(inside_file='models.py',only_directory=False)# ['path_to_aps.app1.models', 'path_to_aps.app2.models']Tricks:# settings.pyINSTALLED_APPS=get_applications()...# urls.pyurlpatterns=[path("",include(application_urls))forapplication_urlsinget_applications(inside_file='urls.py',only_directory=False))]Prefetch and select related by serializerDefinition:add_related(queryset,serializer)Usage:queryset=add_related(Thing.objects.all(),ThingSerializer)Compare dictsDefinition:dict_diff(a,b)Usage:>>>dict_diff({'a':2,'b':{'c':2,'d':1}},{'b':{'c':3,'d':1}})({'a':2,'b':{'c':2}},{'a':None,'b':{'c':3}})Tighten dictsDefinition:dict_normalise(data,separator='__')Usage:>>>dict_normalise({'a':1,'b':{'c':2,'d':{'e':3,'f':3}}}){'a':1,'b__c':2,'b__d__e':3,'b__d__f':3}Decoratorsserialize_decoratorDefinition:serialize_decorator(serializer_method,preview_function=None,read_params=False)Usage:classRestoreUserPassword(GenericAPIView):@serialize_decorator(RestoreUserSerializer)defpost(self,request,*args,**kwargs):returnResponse({"valid":True})await_process_decoratorDecorator for creating a queue for using a function, it is needed to adjust the call of a function from different
processes (Сelery, Threads). For example, this decorator can be used to limit the number of requests in the parser.Definition:# rate : count of usage some function, by default it's 20 times# period : period of usage some function,  by default it's 1 minuteawait_process_decorator(rate=20,period=60)Usage:@await_process_decorator(rate=10,period=5)# 10 times per 5 secondsdefsimple_print(text):print(text)ManagersNoDeleteManagerBaseManagerBaseManagerFeatures:fetch_only_annotation - make annotation only on iterationSimple annotation:queryset=User.objects.annotate(contacts_count=Count('contacts'))print(queryset.count())# SELECT COUNT(*) from users join contacts on contacts.owner = user.id;print(queryset.first())# SELECT *, COUNT(contacts) as contacts_count from users join contacts on contacts.owner = user.id;queryset=User.objects.fetch_only_annotation(contacts_count=Count('contacts'))print(queryset.count())# SELECT COUNT(*) from users;print(queryset.first())# SELECT *, COUNT(contacts) as contacts_count from users join contacts on contacts.owner = user.id;ModelsBaseModel - with created_at and updated_atclassThing(BaseModel):title=models.CharField(max_length=20)classMeta:db_table='another_things'CommonModel - with date_created and date_updatedNoDeleteModel - with date_deletedAbstractJsonModel - with languagesValidatorsObjectExistValidator - check if object existsObjectUniqueValidator - check if object not existsPhoneValidator - check phoneSerializersBaseModelSerializer - simple serializer for BaseModel classclassThingSerializer(BaseModelSerializer):classMeta(BaseModelSerializer.Meta):model=ThingElasticFilterSerializer - make easy conversion between serializer data and elastic filtersclassTenderFilterSerializer(PaginatorSerializer,ElasticFilterSerializer):sort_criteria=[{"date_updated":{"order":"desc"}},"_score"]status=StringListField(required=False)date_start=serializers.DateField(required=False)date_end=serializers.DateField(required=False)deffilter_status(self,value):return{'terms':{'search_status.keyword':value}}deffilter_date_start(self,value):return{"range":{"tenderPeriod.startDate":{'gte':value}}}deffilter_date_end(self,value):return{"range":{"tenderPeriod.startDate":{'lte':value}}}classTenderListView(GenericAPIView):@serialize_decorator(TenderFilterSerializer)defget(self,request,*args,**kwargs):returnResponse(es_app.search_response(request.serializer,'tenders_index'))FilterSerializer - filter queryset by serializer fieldsclassServiceListQuerySerializer(FilterSerializer):name=CharField(required=False)tag_id=CharField(required=False)type=CharField(required=False)status=CharField(required=False)deffilter_name(self,value,queryset):returnqueryset.filter(name__icontains=value)deffilter_tag_id(self,value,queryset):returnqueryset.filter(tags__contains=value)deffilter_type(self,value,queryset):returnqueryset.filter(type=value)deffilter_status(self,value,queryset):returnqueryset.filter(status=value)classServiceListView(ListAPIView):serializer_class=ServiceListQuerySerializer@swagger_auto_schema(query_serializer=ServiceListQuerySerializer)@serialize_decorator(ServiceListQuerySerializer)defget(self,request):services=request.serializer.get_filter(request.valid,Service.objects.all())returnResponse(ServiceSerializer(instance=services,many=True).data)ChangebleSerializer - metamorphic serializerclassContractNoticeCancelView(GenericAPIView):defput(self,request):serializer_meta={'id':PrimaryKeyRelatedField(queryset=Tender.objects.all(),required=True),'info':{'rationale':CharField(required=True),'description':CharField(required=True),},'documents':DocumentFileSerializer(required=True,many=True)}serializer=ChangebleSerializer(data=request.data)serializer.update_properties(serializer_meta)serializer.is_valid(raise_exception=True)returnResponse({"valid":True})PaginatorSerializer - serializer for paginatingclassListUserNotification(GenericAPIView):@serialize_decorator(PaginatorSerializer)defget(self,request):notifications=NotificationEvent.objects.filter(user=request.user)returnrequest.serializer.response(notifications,serializer=ListNotificationSerializer)Another serializersStringListField - simple string list of charsEmptySerializer - simple empty serializerIdSerializer - simple id serializerReturnSuccessSerializer - simple success, message serializerSerializers functionsbuild_model_serializer - build serializer with InheritanceDefinition:build_model_serializer(base=BaseModelSerializer,add_bases=True,**kwargs)Usage:ThingSerializer=build_model_serializer(meta_model=Thing,)CreateThingSerializer=build_model_serializer(ThingSerializer,meta_fields=('name','desctiption'))CreateThingSerializer=build_model_serializer(ThingSerializer,meta_fields=('name','desctiption')# 'id', 'created_at' and 'updated_at' is added automatically)ShortThingSerializer=build_model_serializer(ThingSerializer,meta_fields=('name','desctiption'),add_bases=False# so as not to add 'id', 'created_at' and 'updated_at')AnotherThingSerializer=build_model_serializer(things=ThingSerializer(many=True),meta_model=AnotherThing,)Note:Parameters with prefix 'meta_' is added to the meta class, the rest are added in the serializer classViewsNote: for them to work, set in swagger settings
DEFAULT_AUTO_SCHEMA_CLASS=drf_util.mixins.CustomAutoSchemaBaseModelViewSetUsage:classThingViewSet(BaseModelViewSet):queryset=Thing.objects.all()serializer_class=ThingSerializerAttributes:queryset=None# QuerySetquery_serializer=None# Serializer for queryserializer_class=None# Default and response serializerserializer_create_class=None# Body serializerserializer_by_action={}# Serializer by action {[action]: [serializer]}pagination_class=CustomPagination# Paginationfilter_backends=(filters.OrderingFilter,CustomFilterBackend,filters.SearchFilter,)# Filter backendsfilter_class=None# FilterSetsearch_fields=()# Fields for search query_paramordering_fields='__all__'# Fields for ordering query_paramordering=['-id']# Default ordering fieldspermission_classes_by_action={"default":[IsAuthenticated]}# Permission class by action {[action]: [permissions]}Another viewsBaseViewSetBaseCreateModelMixinBaseUpdateModelMixinBaseListModelMixinBaseReadOnlyViewSetBaseModelItemViewSetBaseModelViewSetPaginationCustomPaginationDeclaration:classCustomPagination(PageNumberPagination):page=DEFAULT_PAGEpage_size=10page_size_query_param='per_page'defget_paginated_response(self,data):custom_paginator=dict(count=self.page.paginator.count,# noqatotal_pages=self.page.paginator.num_pages,# noqaper_page=int(self.request.GET.get('per_page',self.page_size)),current_page=int(self.request.GET.get('page',DEFAULT_PAGE)),results=data)returnResponse(custom_paginator)TestsCustomClient - client which check response for status codeUsage:classBaseTestCase(TestCase):client_class=CustomClientbase_view='things'deftest_list(self)->None:self.client.get(reverse(f'{self.base_view}-list'))deftest_duplicate(self):self.client.post(reverse(f'{self.base_view}-duplicate',args=(test_instance.pk,)),assert_status_code=status.HTTP_200_OK).json()BaseTestCase - test case with custom clientUsage:classViewsTestCase(BaseTestCase,TestCase):deftest_swagger(self):response=self.client.get('/swagger/?format=openapi').json()self.assertEqual(len(response['schemes']),2)Note:Default setUp function authenticates the userCRUDTestCase - test case with crudUsage:classThingCRUDTestCase(CRUDTestCase,TestCase):fixtures=['tests/fixtures.json']base_view='things'queryset=Thing.objects.all()fake_data={'title':'Thing name'}MiddlewaresPrintSQlMiddleware - middleware to print sql request and their statisticsUsage:MIDDLEWARE=['drf_util.middlewares.PrintSQlMiddleware',...]Swagger utilsCustomAutoSchema - render schema with custom serializers methodsUsage:SWAGGER_SETTINGS={'DEFAULT_AUTO_SCHEMA_CLASS':'drf_util.mixins.CustomAutoSchema'...}get_custom_schema_view - function to get swagger with HTTP and HTTPSDeclaration:get_custom_schema_view(title,default_version='v1',description='',*args,**kwargs)Usage:schema_view=get_custom_schema_view(title="API Documentation",description="This is API Documentation")urlpatterns=[path("",schema_view.with_ui('swagger',cache_timeout=0),name='schema-swagger-ui'),path("redoc",schema_view.with_ui('redoc',cache_timeout=0),name='schema-redoc'),]LookupsLikeUsage:queryset=User.objects.filter(first_name__like='%ia').values('first_name')# SQL: SELECT name FROM users WHERE name LIKE '%ia';# queryset: <QuerySet [{'first_name': 'Olivia'}, {'first_name': 'Amelia'}, '...(remaining elements truncated)...']>NotUsage:queryset=queryset.objects.filter(first_name__not='Doe').values_list('first_name',flat=True)# SQL: SELECT name FROM users WHERE NOT name = 'Doe';# queryset: <QuerySet [{'first_name': 'Not Doe'}, {'first_name': 'Realy not Doe'}, '...(remaining elements truncated)...']>DistinctUsage:queryset=queryset.objects.filter(first_name__distinct='Doe').values_list('first_name',flat=True)# SQL: SELECT name FROM users WHERE NOT name IS DISTINCT FROM 'Doe';# queryset: <QuerySet [{'first_name': 'Not Doe'}, {'first_name': 'Realy not Doe'}, '...(remaining elements truncated)...']>FunctionsStringToArrUsage:queryset=User.objects.annotate(split_name=StringToArr('name',Value(' ')).values('name','split_name')# SQL: SELECT name FROM users WHERE name LIKE '%ia';# queryset: <QuerySet [{'name': 'Olivia Marchel', 'split_name': ['Olivia', 'Marchel']}, {'name': 'Amelia Hust', 'split_name': ['Amelia', 'Hust']}, '...(remaining elements truncated)...']>ArrIntersectionUsage:queryset=User.objects.annotate(teams_intersection=ArrIntersection('featured_teams','teams')).values('featured_teams','teams','teams_intersection')# SQL: SELECT name FROM users WHERE name LIKE '%ia';# queryset: <QuerySet [{'featured_teams': ['Team1', 'Team2'], 'teams': ['Team1', 'Team3'], 'teams_intersection': ['Team1']}, {'featured_teams': ['Team1', 'Team2'], 'teams': ['Team3', 'Team4'], 'teams_intersection': []}, '...(remaining elements truncated)...']>ArrayLengthUsage:queryset=User.objects.annotate(tokens_count=ArrayLength('tokens')).values('tokens','tokens_count')# SQL: SELECT name FROM users WHERE name LIKE '%ia';# queryset: <QuerySet [{'tokens': ['tok1', 'tok2'], 'tokens_count': 2}, {'tokens': [], 'tokens_count': 0}, '...(remaining elements truncated)...']>ExceptionsValidationExceptionUsage:value=-3ifvalue<0:raiseValidationException("Invalid value.")# Status code: 400# Body: {"detail": "Invalid value."}FailedDependencyUsage:response=request.get(url)ifnotresponse.ok:raiseFailedDependency(response.content)# Status code: 424IMATeapotUsage:try:number=13/0exceptException:raiseIMATeapot()# Status code: 418