В сериализаторе Django Rest Framework можно добавить больше данных в сериализованный объект, чем в исходной модели.
Это полезно для расчета статистической информации на стороне сервера и добавления этой дополнительной информации при ответе на вызов API.
Как я понимаю, добавление дополнительных данных выполняется с использованием SerializerMethodField
, где каждое поле реализуется с помощью функции get_...
.
Однако, если у вас есть несколько этих полей SerializerMethodFields, каждое из них может запрашивать модель / базу данных по отдельности, что может быть по существу одинаковыми данными.
Можно ли сделать запрос к базе данных один раз, сохранить список / результат как элемент данных объекта ModelSerializer и использовать результат набора запросов во многих функциях?
Вот очень простой пример, просто для иллюстрации:
############## Model
class Employee(Model):
SALARY_TYPE_CHOICES = (('HR', 'Hourly Rate'), ('YR', 'Annual Salary'))
salary_type = CharField(max_length=2, choices=SALARY_TYPE_CHOICES, blank=False)
salary = PositiveIntegerField(blank=True, null=True, default=0)
company = ForeignKey(Company, related_name='employees')
class Company(Model):
name = CharField(verbose_name='company name', max_length=100)
############## View
class CompanyView(RetrieveAPIView):
queryset = Company.objects.all()
lookup_field='id'
serializer_class = CompanySerialiser
class CompanyListView(ListAPIView):
queryset = Company.objects.all()
serializer_class = CompanySerialiser
############## Serializer
class CompanySerialiser(ModelSerializer):
number_employees = SerializerMethodField()
total_salaries_estimate = SerializerMethodField()
class Meta:
model = Company
fields = ['id', 'name',
'number_employees',
'total_salaries_estimate',
]
def get_number_employees(self, obj):
return obj.employees.count()
def get_total_salaries_estimate(self, obj):
employee_list = obj.employees.all()
salaries_estimate = 0
HOURS_PER_YEAR = 8*200 # 8hrs/day, 200days/year
for empl in employee_list:
if empl.salary_type == 'YR':
salaries_estimate += empl.salary
elif empl.salary_type == 'HR':
salaries_estimate += empl.salary * HOURS_PER_YEAR
return salaries_estimate
Сериализатор может быть оптимизирован для:
- использовать элемент данных объекта для сохранения результата из набора запросов,
- получить набор запросов только один раз,
- повторно использовать результат набора запросов для всей дополнительной информации, предоставленной в SerializerMethodFields.
Пример:
class CompanySerialiser(ModelSerializer):
def __init__(self, *args, **kwargs):
super(CompanySerialiser, self).__init__(*args, **kwargs)
self.employee_list = None
number_employees = SerializerMethodField()
total_salaries_estimate = SerializerMethodField()
class Meta:
model = Company
fields = ['id', 'name',
'number_employees',
'total_salaries_estimate',
]
def _populate_employee_list(self, obj):
if not self.employee_list: # Query the database only once.
self.employee_list = obj.employees.all()
def get_number_employees(self, obj):
self._populate_employee_list(obj)
return len(self.employee_list)
def get_total_salaries_estimate(self, obj):
self._populate_employee_list(obj)
salaries_estimate = 0
HOURS_PER_YEAR = 8*200 # 8hrs/day, 200days/year
for empl in self.employee_list:
if empl.salary_type == 'YR':
salaries_estimate += empl.salary
elif empl.salary_type == 'HR':
salaries_estimate += empl.salary * HOURS_PER_YEAR
return salaries_estimate
Это работает для одиночного извлечения CompanyView
. И, фактически, сохраняет один запрос / переключение контекста / туда-обратно в базу данных; Я удалил запрос "count".
Тем не менее, не работает для представления списка CompanyListView
, потому что кажется, что объект сериализатора создается один раз и повторно используется для каждой компании. Таким образом, в элементе данных «self.employee_list
» хранится только первый список сотрудников Компании, и, таким образом, все остальные компании ошибочно получают данные из первой компании.
Существует ли наилучшее практическое решение проблемы такого типа? Или я просто неправ в использовании ListAPIView, и если да, есть ли альтернатива?