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
This commit is contained in:
kdeterme 2026-09-30 19:04:39 +02:00
parent 6782959253
commit 41679d82e9

View file

@ -75,6 +75,11 @@ def _get_pause_reason_inline():
verbose_name = "Réassignation automatique sur pause" verbose_name = "Réassignation automatique sur pause"
verbose_name_plural = "Réassignations automatiques 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 return ContractPauseReasonAssignmentInline
@ -106,15 +111,21 @@ def _get_operation_template_posts_inline():
if db_field.name == "contract_post": if db_field.name == "contract_post":
contract_id = request.resolver_match.kwargs.get('object_id') if request.resolver_match else None contract_id = request.resolver_match.kwargs.get('object_id') if request.resolver_match else None
if contract_id: 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": elif db_field.name == "operation_template":
contract_id = request.resolver_match.kwargs.get('object_id') if request.resolver_match else None contract_id = request.resolver_match.kwargs.get('object_id') if request.resolver_match else None
if contract_id: 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(): if contract and contract.thematics.exists():
kwargs["queryset"] = OperationTemplate.objects.filter( kwargs["queryset"] = OperationTemplate.objects.filter(
Q(thematic__in=contract.thematics.all()) | Q(thematic__isnull=True) Q(thematic__in=contract.thematics.all()) | Q(thematic__isnull=True)
).distinct().order_by('name') ).distinct().order_by('name')
else:
kwargs["queryset"] = OperationTemplate.objects.none()
return super().formfield_for_foreignkey(db_field, request, **kwargs) return super().formfield_for_foreignkey(db_field, request, **kwargs)
return ContractOperationTemplatePostInline 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') 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') 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): 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 = ( fieldsets = (
(None, { (None, {