# documents/admin.py
"""
Django admin для моделей Document и DocumentAcknowledgement.
Основные возможности:
- Использование django-filer для управления файлами
- Поддержка drag & drop загрузки файлов
- Превью thumbnails в списке и форме редактирования
- Полнотекстовый поиск по содержимому файлов
- Версионирование через django-reversion
- Workflow управление через django-fsm
"""
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from django.utils.html import format_html, mark_safe
from django.urls import reverse
from .models import Document, DocumentAcknowledgement
from django.contrib.auth import get_user_model
User = get_user_model()
class AcknowledgementInline(admin.TabularInline):
"""Inline для отображения ознакомлений с документом."""
model = DocumentAcknowledgement
extra = 0
can_delete = False
verbose_name = _("Ознакомление")
verbose_name_plural = _("Ознакомления")
fields = ('user_link', 'acknowledged_at')
readonly_fields = ('user_link', 'acknowledged_at')
def user_link(self, obj):
"""Ссылка на профиль сотрудника."""
if not obj.user:
return '—'
url = reverse(
'admin:{}_{}_change'.format(
User._meta.app_label, User._meta.model_name
), args=[obj.user.pk]
)
return format_html('{}', url, obj.user.get_full_name() or obj.user)
user_link.short_description = _("Сотрудник")
@admin.register(Document)
class DocumentAdmin(admin.ModelAdmin):
"""
Admin для Document с поддержкой django-filer и django-fsm.
Возможности:
- Drag & drop загрузка файлов через filer
- Превью thumbnails для изображений и PDF
- Поиск по содержимому файлов
- Версионирование через django-reversion
- Workflow управление через django-fsm
"""
inlines = [AcknowledgementInline]
list_display = (
'title',
'file_thumbnail',
'file_info',
'uploaded_at',
'sent_to_all',
'acknowledgement_status',
)
list_filter = ('sent_to_all', 'uploaded_at', 'departments')
search_fields = ('title', 'description', 'file__name', 'file__description')
filter_horizontal = ('recipients', 'departments')
actions = ['send_document']
# Для django-reversion
history_latest_first = True
fieldsets = (
(None, {
'fields': (
'title',
'file', # FilerFileField с drag & drop
'file_preview', # Превью файла
'description',
'sent_to_all',
'departments',
'recipients'
),
}),
(_('Meta'), {
'fields': ('uploaded_by', 'uploaded_at', 'file_size', 'file_extension'),
'classes': ('collapse',),
}),
(_('Ознакомление'), {
'fields': ('recipients_summary', 'pending_list'),
}),
)
readonly_fields = (
'uploaded_by',
'uploaded_at',
'file_preview',
'file_size',
'file_extension',
'recipients_summary',
'pending_list',
)
def save_model(self, request, obj, form, change):
"""Автоматически устанавливаем uploaded_by при создании."""
if not obj.pk:
obj.uploaded_by = request.user
super().save_model(request, obj, form, change)
def file_thumbnail(self, obj):
"""Превью файла в списке документов."""
if not obj.file:
return '—'
# Для изображений показываем thumbnail
if obj.file.file_type == 'Image':
thumbnail_url = obj.get_thumbnail('small')
if thumbnail_url:
return format_html(
'',
obj.file.url,
thumbnail_url
)
# Для других файлов показываем иконку
icon_url = obj.file.icons.get('48', '')
if icon_url:
return format_html(
'
',
obj.file.url,
icon_url
)
return format_html('📎', obj.file.url)
file_thumbnail.short_description = _('Превью')
def file_preview(self, obj):
"""Большое превью файла в форме редактирования."""
if not obj.file:
return '—'
# Для изображений показываем большой thumbnail
if obj.file.file_type == 'Image':
thumbnail_url = obj.get_thumbnail('medium')
if thumbnail_url:
return format_html(
'