# backend/requests_app/admin.py from django import forms from django.contrib import admin, messages from django.db import models from django.utils.translation import gettext_lazy as _ from .enums import RequestStatus from .models import Request, RequestSettings, RequestVacationTerms @admin.register(RequestSettings) class RequestSettingsAdmin(admin.ModelAdmin): """Единственная запись настроек получателей заявлений.""" list_display = ("name", "allowed_recipients_count", "updated_at") fields = ("name", "allowed_recipients", "updated_at") readonly_fields = ("updated_at",) autocomplete_fields = ("allowed_recipients",) def has_add_permission(self, request): if RequestSettings.objects.exists(): return False return super().has_add_permission(request) def has_delete_permission(self, request, obj=None): return request.user.is_superuser @admin.display(description="Доступные получатели") def allowed_recipients_count(self, obj): count = obj.allowed_recipients.count() return count or "Все сотрудники" @admin.register(RequestVacationTerms) class RequestVacationTermsAdmin(admin.ModelAdmin): list_display = ( "request", "paid_days", "finance_status", "recorded_by", "updated_at", ) list_filter = ("finance_status",) search_fields = ( "request__employee__last_name", "request__employee__first_name", "request__employee__email", ) autocomplete_fields = ("request", "recorded_by") readonly_fields = ("recorded_at", "updated_at") # Админ-форма: без принуждения вводить approver (мы выставим его автоматически) class RequestAdminForm(forms.ModelForm): class Meta: model = Request fields = "__all__" def clean(self): cleaned = super().clean() status = cleaned.get("status") approver = cleaned.get("approver") # Если статус "Отменено" — согласующий должен быть пустым if status == RequestStatus.CANCELLED and approver: cleaned["approver"] = None # Для approved/rejected НЕ требуем approver — его поставим в save_model. return cleaned @admin.register(Request) class RequestAdmin(admin.ModelAdmin): form = RequestAdminForm list_display = ( "id", "display_title", "type", "status", "employee", "department", "date_from", "date_to", "decided_at", "created_at", ) list_filter = ( "type", "status", "department", ("created_at", admin.DateFieldListFilter), ("decided_at", admin.DateFieldListFilter), ) search_fields = ( "title", "comment", "employee__last_name", "employee__first_name", "employee__email", ) autocomplete_fields = ("employee", "department") # approver только для просмотра (покажем, кто согласовал) readonly_fields = ("approver", "created_at", "updated_at", "decided_at") fieldsets = ( ( _("Основное"), { "fields": ( "employee", "department", "type", "title", ("date_from", "date_to"), ("time_from", "time_to"), "comment", "attachment", ) }, ), ( _("Статус"), { "fields": ( "status", # показываем, но не редактируем "approver", "decided_at", ) }, ), ( _("Служебное"), { "classes": ("collapse",), "fields": ("created_at", "updated_at"), }, ), ) # HTML5-пикеры дат/времени в админке formfield_overrides = { models.DateField: {"widget": forms.DateInput(attrs={"type": "date"})}, models.DateTimeField: { "widget": forms.DateTimeInput(attrs={"type": "datetime-local"}) }, } def save_model(self, request, obj: Request, form, change): """ Приводим пару статус/согласующий к валидному виду ДО сохранения: - approved/rejected -> approver = текущий пользователь, если не задан - cancelled -> approver = None Это гарантирует отсутствие конфликтов с БД-constraint. """ if obj.status in {RequestStatus.APPROVED, RequestStatus.REJECTED}: if obj.approver_id is None: obj.approver = request.user elif obj.status == RequestStatus.CANCELLED: if obj.approver_id is not None: obj.approver = None super().save_model(request, obj, form, change) # Массовые действия работают через методы модели (ставят approver и # decided_at) @admin.action(description=_("Одобрить выбранные")) def action_approve(self, request, queryset): updated = 0 for obj in queryset: if obj.status != RequestStatus.APPROVED: obj.approve(request.user) updated += 1 self.message_user( request, _(f"Одобрено заявок: {updated}"), level=messages.SUCCESS ) @admin.action(description=_("Отклонить выбранные")) def action_reject(self, request, queryset): updated = 0 for obj in queryset: if obj.status != RequestStatus.REJECTED: obj.reject(request.user) updated += 1 self.message_user( request, _(f"Отклонено заявок: {updated}"), level=messages.SUCCESS ) @admin.action(description=_("Отменить выбранные")) def action_cancel(self, request, queryset): updated = 0 for obj in queryset: if obj.status != RequestStatus.CANCELLED: obj.cancel() updated += 1 self.message_user( request, _(f"Отменено заявок: {updated}"), level=messages.SUCCESS ) actions = ("action_approve", "action_reject", "action_cancel")