Skip to content
Advertisement

Python Django Rest – Return extra field with lowest, highest and average values of some other field

I’m new to Django and APIs in general and I want to create a Django based API using Django Rest Framework.

Here’s what I want to do:

Endpoint to age range report:

curl -H 'Content-Type: application/json' localhost:8000/reports/employees/age/

Response:

{
    "younger": {
        "id": "1",
        "name": "Anakin Skywalker",
        "email": "skywalker@ssys.com.br",
        "department": "Architecture",
        "salary": "4000.00",
        "birth_date": "01-01-1983"},
    "older": {
        "id": "2",
        "name": "Obi-Wan Kenobi",
        "email": "kenobi@ssys.com.br",
        "department": "Back-End",
        "salary": "3000.00",
        "birth_date": "01-01-1977"},
    "average": "40.00"
}

Endpoint to salary range report:

curl -H 'Content-Type: application/json' localhost:8000/reports/employees/salary/

Response:

{
    "lowest ": {
        "id": "2",
        "name": "Obi-Wan Kenobi",
        "email": "kenobi@ssys.com.br",
        "department": "Back-End",
        "salary": "3000.00",
        "birth_date": "01-01-1977"},
    "highest": {
        "id": "3",
        "name": "Leia Organa",
        "email": "organa@ssys.com.br",
        "department": "DevOps",
        "salary": "5000.00",
        "birth_date": "01-01-1980"},
    "average": "4000.00"
}

I have two apps, employees and reports.

Here’s employees/models.py:

class Employee(models.Model):
    name = models.CharField(max_length=250, default='FirstName LastName')
    email = models.EmailField(max_length=250, default='employee@email.com')
    departament = models.CharField(max_length=250, default='Full-Stack')
    salary = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    birth_date = models.DateField()

Here’s employees/serializers.py:

class EmployeeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Employee
        fields = ['id', 'name', 'email', 'departament', 'salary', 'birth_date']

I’m not sure how to create my views and serializers for my report app. How should I approach this?

How can I return an extra field with a calculation between values of another field? Should I create a custom field in my serializer?

By reading the docs I’ve figured that the query I should use should be something like:

Employee.objects.aggregate(Max("salary"), Min("salary"), Avg("salary"))

Or maybe, for salary for example:

Employee.objects.all().order_by('salary')

But how would I go about using it?

I don’t know where to use or how to go about it as my understanding of APIs and Django Rest Framework is still very lacking.

Should it go in my reports/view.py? Or in my employees/serializers.py?

Should I create a reports/serializers.py file?

Do I need a reports/model class with lowest, highest, average fields plus an employee object field?

Should I override the list() function of my ReportSalaryListAPIView class in my reports/views.py? (This class doesn’t exist yet)

I’m very lost and confused. Please help point me in the right direction.

Thank you in advance.


Edit:

My employees/model.py now looks like this:

class Employee(models.Model):
    name = models.CharField(max_length=250, default='FirstName LastName')
    email = models.EmailField(max_length=250, default='employee@email.com')
    departament = models.CharField(max_length=250, default='Full-Stack')
    salary = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    birth_date = models.DateField()

    @property
    def get_age(self):
        delta = relativedelta(self.birth_date.days, datetime.today()).years
        return delta

    def save(self, *args, **kwargs):
        self.age = self.get_age()
        super(Employee, self).save(*args, **kwargs)

My employees/serializers.py now:

class EmployeeSerializer(serializers.ModelSerializer):

    class Meta:
        model = Employee
        fields = ['id', 'name', 'email', 'departament', 'salary', 'birth_date', 'age']

I’ve run the makemigrations and migrate commands with manager.py, just in case.

But I’m now running into this error when trying to create a new employee:

'datetime.date' object has no attribute 'days'

What is happening?

Advertisement

Answer

Idea

No extra fields are needed to store the calculated values since they are calculated based on the rows in the database.

  1. First you find min_salary, max_salary, and avg_salary by using the aggregate method.
  2. Use found min_salary and max_salary to find employees.
  3. Implement a serializer class that serializes highest, lowest, and average employee salary report.
  4. Serialize the lowest, highest, and avarage by using the implemented serializer class.

Code

In my opinion, the code for performing the logic should be on another layer, usually called the service layer.

# employees/services.py
class EmployeeSalaryReport:
    def __init__(self, lowest, highest, average):
        self.lowest = lowest
        self.highest = highest
        self.average = average

def get_employee_salary_report():
    salary_report_dict = Employee.objects.aggregate(
        min_salary=Min("salary"),
        max_salary=Max("salary"),
        avg_salary=Avg("salary"),
    )
    lowest_salary_employee = Employee.objects.filter(
        salary=salary_report_dict.get("min_salary")
    ).first()
    highest_salary_employee = Employee.objects.filter(
        salary=salary_report_dict.get("max_salary")
    ).first()
    return EmployeeSalaryReport(
        lowest=lowest_salary_employee,
        highest=highest_salary_employee,
        average=salary_report_dict.get("avg_salary"),
    )
# employees/serializers.py
class EmployeeSalaryReportSerializer(serializers.Serializer):
    lowest = EmployeeSerializer()
    highest = EmployeeSerializer()
    average = serializers.FloatField()
# employees/views.py
from rest_framework import views
from rest_framework.response import Response

from employees.services import get_employee_salary_report
from employees.serializers import EmployeeSalaryReportSerializer


class ReportSalaryView(views.APIView):
    def get(self, request, *args, **kwargs):
        employee_salary_report = get_employee_salary_report()
        serializer = EmployeeSalaryReportSerializer(employee_salary_report)
        return Response(serializer.data)
# employees/urls.py
from django.urls import path

from employees.views import ReportSalaryView


urlpatterns = [
    ...  # other paths
    path("reports/employees/salary/", ReportSalaryView.as_view(), name="report-salary"),
]
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement