From 41679d82e9e740e57f32fe01de864fd8a67f765a Mon Sep 17 00:00:00 2001 From: kdeterme Date: Wed, 30 Sep 2026 19:04:39 +0200 Subject: [PATCH] perf(contracts): optimize ContractAdmin queries on add and change views - Avoid loading all database ContractPost instances when adding a new contract - Use select_related('contract') on ContractPost to prevent N+1 queries in __str__ - Only include ContractOperationTemplatePostInline on saved contracts - Add prefetch_related('teams') for CompanyMember in pause reason assignments - Optimize ContractAdmin changelist queryset with select_related and prefetch_related --- loko/contracts/admin.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/loko/contracts/admin.py b/loko/contracts/admin.py index 4ca4c1f..ae7f56e 100644 --- a/loko/contracts/admin.py +++ b/loko/contracts/admin.py @@ -75,6 +75,11 @@ def _get_pause_reason_inline(): verbose_name = "Réassignation automatique sur pause" verbose_name_plural = "Réassignations automatiques sur pause" + def formfield_for_foreignkey(self, db_field, request, **kwargs): + if db_field.name == "assigned_member": + kwargs["queryset"] = CompanyMember.objects.prefetch_related('teams').order_by('name') + return super().formfield_for_foreignkey(db_field, request, **kwargs) + return ContractPauseReasonAssignmentInline @@ -106,15 +111,21 @@ def _get_operation_template_posts_inline(): if db_field.name == "contract_post": contract_id = request.resolver_match.kwargs.get('object_id') if request.resolver_match else None if contract_id: - kwargs["queryset"] = ContractPost.objects.filter(contract_id=contract_id).order_by('order_number') + kwargs["queryset"] = ContractPost.objects.filter( + contract_id=contract_id + ).select_related('contract').order_by('order_number') + else: + kwargs["queryset"] = ContractPost.objects.none() elif db_field.name == "operation_template": contract_id = request.resolver_match.kwargs.get('object_id') if request.resolver_match else None if contract_id: - contract = Contract.objects.filter(pk=contract_id).first() + contract = Contract.objects.prefetch_related('thematics').filter(pk=contract_id).first() if contract and contract.thematics.exists(): kwargs["queryset"] = OperationTemplate.objects.filter( Q(thematic__in=contract.thematics.all()) | Q(thematic__isnull=True) ).distinct().order_by('name') + else: + kwargs["queryset"] = OperationTemplate.objects.none() return super().formfield_for_foreignkey(db_field, request, **kwargs) return ContractOperationTemplatePostInline @@ -131,8 +142,14 @@ class ContractAdmin(admin.ModelAdmin): list_filter = ('is_blocked', 'is_active', 'municipalities', 'thematics', 'uses_occupations', 'occupations_required', 'uses_intervention_manager', 'allow_team_members_edit', 'auto_assign_control_team', 'send_daily_summary') filter_horizontal = ('thematics', 'municipalities') + def get_queryset(self, request): + return super().get_queryset(request).select_related('company', 'executive_officer').prefetch_related('municipalities', 'thematics') + def get_inlines(self, request, obj=None): - return [_get_pause_reason_inline(), _get_symptom_autoclose_inline(), _get_operation_template_posts_inline()] + list(super().get_inlines(request, obj)) + inlines = [_get_pause_reason_inline(), _get_symptom_autoclose_inline()] + if obj: + inlines.append(_get_operation_template_posts_inline()) + return inlines + list(super().get_inlines(request, obj)) fieldsets = ( (None, {