Help, pliz, to optimize the monstrous code in the serializer, which is very slow ... As I understand it, the main problem is in several SerializerMethodFields, where get_tasks () are called. Tell me, please, how to do it right here - in init, load all the tasks that are in self.tasks. And in the to_representation method, all 4 SerializerMethodField call keys - equipment_types, recovery_days, completed_days, work_in_progress_days? And most importantly - how to properly optimize the call of nested serializers?
class MonthPlanViewSerializer(serializers.HyperlinkedModelSerializer):
year_plan_id = serializers.PrimaryKeyRelatedField(queryset=YearPlan.objects.all(), source='year_plan.id')
equipment_id = serializers.PrimaryKeyRelatedField(queryset=Equipment.objects.all(), source='equipment.id',
required=False)
equipment = EquipmentSerializer(many=False, read_only=True)
transport_id = serializers.PrimaryKeyRelatedField(queryset=Transport.objects.all(), source='transport.id',
default=None)
transport = TransportSerializer(many=False, read_only=True)
equipment_types = serializers.SerializerMethodField()
tasks = SimpleTaskSerializer(many=True, read_only=True)
recovery_days = serializers.SerializerMethodField()
completed_days = serializers.SerializerMethodField()
work_in_progress_days = serializers.SerializerMethodField()
@staticmethod
def get_tasks(instance: MonthPlan):
return instance.tasks.all()
@staticmethod
def get_work_in_progress_days(instance: MonthPlan) -> set:
tasks = MonthPlanViewSerializer.get_tasks(instance)
return set(int(task.planned_date.strftime("%d")) for task in tasks if task.work_in_progress)
@staticmethod
def get_completed_days(instance: MonthPlan) -> list:
return MonthPlanViewSerializer.get_common_days(instance, 'is_completed')
@staticmethod
def get_recovery_days(instance: MonthPlan) -> list:
return MonthPlanViewSerializer.get_common_days(instance, 'is_broken')
@staticmethod
def get_common_days(instance: MonthPlan, attribute: str) -> list:
tasks = MonthPlanViewSerializer.get_tasks(instance)
days = set()
for task in tasks:
for item in task.maintenance_executions:
if getattr(item, attribute):
if task.closing_date:
days.add(int(task.closing_date.strftime("%d")))
elif task.planned_date:
days.add(int(task.planned_date.strftime("%d")))
return list(days)
@staticmethod
def get_equipment_types(instance: MonthPlan):
tasks = MonthPlanViewSerializer.get_tasks(instance)
equipment_types = EquipmentType.objects.filter(
id__in=Equipment.objects.filter(transport_id=instance.transport_id).values_list('equipment_type_id'))
return EquipmentTypeSerializer(equipment_types, many=True).data
class Meta:
model = MonthPlan
fields = [
'id', 'year_plan_id', 'month', 'enabled',
'equipment_id', 'equipment',
'required_maintenance_quantity',
'scheduled_maintenance_quantity', 'scheduled_days', 'scheduled_maintenance_quantity_over_year',
'completed_maintenance_quantity', 'completed_days', 'completed_maintenance_quantity_over_year',
'recovery_days', 'transport', 'transport_id',
]
class MaintenanceCheckTemplateSerializer(serializers.ModelSerializer):
class Meta:
model = MaintenanceCheckTemplate
fields = ['id', 'title', 'description']
class MaintenanceExecutionCheckViewSerializer(serializers.ModelSerializer):
check = MaintenanceCheckTemplateSerializer(many=False, source='template', read_only=True)
class Meta:
model = MaintenanceExecutionCheck
fields = ['id', 'check', 'value']
class SimpleTaskSerializer(serializers.HyperlinkedModelSerializer):
team_id = serializers.PrimaryKeyRelatedField(
queryset=Team.objects.all(), source='team.id', allow_null=True
)
location = LocationSerializer(many=False, allow_null=True)
state_id = serializers.PrimaryKeyRelatedField(queryset=State.objects.all(), source='state.id')
equipment_type_id = serializers.PrimaryKeyRelatedField(
queryset=EquipmentType.objects.all(), source="equipment_type.id", allow_null=True
)
equipment_type = EquipmentTypeSerializer(many=False, allow_null=True)
maintenance_execution = MaintenanceExecutionSerializer(many=True, read_only=True)
maintenance_executor = EmployeeSerializer(many=False, read_only=True)
class Meta:
model = Task
fields = [
'id', 'number', 'description', 'type', 'urgency',
'location_id', 'location', 'service', 'state_id',
'equipment_type_id', 'equipment_type', 'team_id', 'maintenance_execution', 'maintenance_executor'
]
class MaintenanceExecutionSerializer(serializers.ModelSerializer):
equipment_type = EquipmentTypeSerializer(many=False, read_only=True)
checklist = MaintenanceExecutionCheckViewSerializer(many=True)
class Meta:
model = MaintenanceExecution
fields = ['id', 'equipment_type', 'is_completed', 'is_broken', 'comment', 'used_materials', 'checklist']
class EquipmentSerializer(serializers.ModelSerializer):
location_id = serializers.PrimaryKeyRelatedField(queryset=Location.objects.all(), source='location.id')
location = LocationSerializer(many=False)
transport = TransportSerializer(many=False)
class Meta:
model = Equipment
fields = ['id', 'name', 'location_id', 'location', 'transport']
class TransportSerializer(serializers.ModelSerializer):
location = LocationSerializer(many=False, read_only=True)
location_id = serializers.PrimaryKeyRelatedField(queryset=Location.objects.all(), source='location.id')
class Meta:
model = Transport
fields = ['id', 'name', 'description', 'manufacturer', 'model', 'status', 'not_working_since', 'location',
'location_id']
def create(self, validated_data):
for key in ['location']:
validated_data[key] = validated_data[key]['id']
instance = Transport.objects.create(**validated_data)
return instance
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = ['id', 'name']
class EquipmentType(models.Model):
name = models.CharField(max_length=200, verbose_name='Наименование')
maintenance_quantity = models.IntegerField(verbose_name='Норматив обслуживания на 1 шт/год', default=1)
is_mounted = models.BooleanField(verbose_name='Навесное оборудование', default=False)
system = models.ForeignKey(System, on_delete=models.RESTRICT, verbose_name='Система', blank=True, null=True)
class Meta:
verbose_name = 'тип оборудования'
verbose_name_plural = 'Типы оборудования'
ordering = ['name']
def __str__(self):
return self.name
Most importantly - how to properly optimize the call of nested serializers?
CodePudding user response:
Here are some suggestions for optimizing the code in your serializer:
Preload all tasks in the MonthPlan instance in the init method of the MonthPlanViewSerializer, rather than calling the get_tasks method for each SerializerMethodField. This will avoid making unnecessary database queries for each field.
In the to_representation method, call the appropriate method for each SerializerMethodField (e.g., get_recovery_days) and store the result in a local variable, rather than calling the method multiple times.
For the nested serializers (e.g., EquipmentTypeSerializer), you can use the prefetch_related method to preload related objects when querying the database. This can improve performance by reducing the number of database queries needed to serialize the data. Here is an example of how you could apply these suggestions to your code:
class MonthPlanViewSerializer(serializers.HyperlinkedModelSerializer):
year_plan_id = serializers.PrimaryKeyRelatedField(
queryset=YearPlan.objects.all(),
source='year_plan.id'
)
equipment_id = serializers.PrimaryKeyRelatedField(
queryset=Equipment.objects.all(),
source='equipment.id',
required=False
)
equipment = EquipmentSerializer(many=False, read_only=True)
transport_id = serializers.PrimaryKeyRelatedField(
queryset=Transport.objects.all(),
source='transport.id',
default=None
)
transport = TransportSerializer(many=False, read_only=True)
equipment_types = serializers.SerializerMethodField()
tasks = SimpleTaskSerializer(many=True, read_only=True)
recovery_days = serializers.SerializerMethodField()
completed_days = serializers.SerializerMethodField()
work_in_progress_days = serializers.SerializerMethodField()
# Preload all tasks for the MonthPlan instance in the __init__ method
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tasks = self.instance.tasks.all()
# Return the preloaded tasks
def get_tasks(self, instance: MonthPlan):
return self.tasks
def get_work_in_progress_days(self, instance: MonthPlan) -> set:
tasks = self.get_tasks(instance)
return set(int(task.planned_date.strftime("%d")) for task in tasks if task.work_in_progress)
def get_completed_days(self, instance: MonthPlan) -> list:
return self.get_common_days(instance, 'is_completed')
def get_recovery_days(self, instance: MonthPlan) -> list:
return self.get_common_days(instance, 'is_broken')
To use prefetch_related(), you can specify the related objects that you want to preload when querying the database. You can specify the names of the related objects as arguments to the prefetch_related() method. For example, if you want to preload the team object for each SimpleTask instance, you can use the following code:
class SimpleTaskSerializer(serializers.HyperlinkedModelSerializer):
team_id = serializers.PrimaryKeyRelatedField(
queryset=Team.objects.all(),
source='team.id',
allow_null=True
)
# Use prefetch_related() to preload the team object for each SimpleTask instance
@staticmethod
def setup_eager_loading(queryset):
queryset = queryset.prefetch_related('team')
return queryset
class Meta:
model = SimpleTask
fields = [
# Your existing list of fields here
]
The setup_eager_loading() method is called by Django Rest Framework when querying the database for the objects that are being serialized. This method allows you to specify how the queryset should be optimized for serialization. In this case, the setup_eager_loading() method uses prefetch_related() to preload the team object for each SimpleTask instance.