Merge multiple views with same functionality in one view

Question:

I’m trying to define 4 GET endpoints (max,min,sum,avg) to access by REST. I did this but making 4 views, and I want to know if I can define the 4 methods in a single view.
I can’t specify through the url which GET to hit. It’s possible?

my urls.py set

urlpatterns =[
path('medidores/', MedidorView.as_view()),
path('mediciones/', MedicionView.as_view()),
path('medicion_max/<str:id>', MedicionMaxView.as_view()),
path('medicion_min/<str:id>', MedicionMinView.as_view()),
path('medicion_total/<str:id>', MedicionTotalView.as_view()),
path('medicion_prom/<str:id>', MedicionPromedioView.as_view())
]

my view.py set

class MedicionMaxView(View):
    def get(self, request, id):
        return JsonResponse(Medicion.objects.filter(medidor=id).aggregate(Max('consumo_kwh')))

class MedicionMinView(View):
    def get(self, request, id):
        return JsonResponse(Medicion.objects.filter(medidor=id).aggregate(Min('consumo_kwh')))

class MedicionTotalView(View):
    def get(self, request, id):
        return JsonResponse(Medicion.objects.filter(medidor=id).aggregate(Sum('consumo_kwh')))

class MedicionPromedioView(View):
    def get(self, request, id):
        return JsonResponse(Medicion.objects.filter(medidor=id).aggregate(Avg('consumo_kwh')))

Separate by name in urls but not have effect

Asked By: Juan Manuel Amato

||

Answers:

Yes, you can!

as_view() accepts parameters, so you can have one view like:

class MedicionView(View):
    aggregation=""
    def get(self, request, id):
        if self.aggregation == 'max':
            return JsonResponse(Medicion.objects.filter(medidor=id).aggregate(Max('consumo_kwh')))
        elif self.aggregation == 'min':
            return JsonResponse(Medicion.objects.filter(medidor=id).aggregate(Min('consumo_kwh')))
        elif self.aggregation == 'sum':
            return JsonResponse(Medicion.objects.filter(medidor=id).aggregate(Sum('consumo_kwh')))
        elif self.aggregation == 'avg':
            return JsonResponse(Medicion.objects.filter(medidor=id).aggregate(Avg('consumo_kwh')))
        else:
            # you can handle it

Side note: you can make this get() method more clean. For example, move the common calls one level up like Medicion.objects.filter(medidor=id)

Now, can pass the needed aggregation way as parameter to as_view() with your URLs like:

urlpatterns =[
    path('medidores/', MedidorView.as_view()),
    path('mediciones/', MedicionView.as_view()),
    path('medicion_max/<str:id>', MedicionView.as_view(aggregation='max')),
    path('medicion_min/<str:id>', MedicionView.as_view(aggregation='min')),
    path('medicion_total/<str:id>', MedicionView.as_view(aggregation='sum')),
    path('medicion_prom/<str:id>', MedicionView.as_view(aggregation='avg'))
]

Better way:

You can even make it more clean by having one URL pattern for all of them by having the aggregation type as a URL parameter like:

class MedicionView(View):
    def get(self, request, aggregation, id):
        if aggregation == 'max':
            return JsonResponse(Medicion.objects.filter(medidor=id).aggregate(Max('consumo_kwh')))
        elif aggregation == 'min':
            return JsonResponse(Medicion.objects.filter(medidor=id).aggregate(Min('consumo_kwh')))
        elif aggregation == 'sum':
            return JsonResponse(Medicion.objects.filter(medidor=id).aggregate(Sum('consumo_kwh')))
        elif aggregation == 'avg':
            return JsonResponse(Medicion.objects.filter(medidor=id).aggregate(Avg('consumo_kwh')))
        else:
            # you can handle it

Now, you can have one URL pattern for them using regular expression:

urlpatterns =[
    path('medidores/', MedidorView.as_view()),
    path('mediciones/', MedicionView.as_view()),
    path(r'medicion/(?P<aggregation>(max|min|sum|avg))/<str:id>', MedicionView.as_view())
]

Update:

If you are following the second solution, don’t forget to add the character r at the beginning of the URL patterns that follow a regex pattern as I did above.

Answered By: Yasser Mohsen