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.
- First you find
min_salary
,max_salary
, andavg_salary
by using the aggregate method. - Use found
min_salary
andmax_salary
to find employees. - Implement a serializer class that serializes
highest
,lowest
, andaverage
employee salary report. - Serialize the
lowest
,highest
, andavarage
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"), ]