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"),
]