igor_nadein 49eff37671 feat(documents): Интеграция django-filer для управления файлами
Добавлена поддержка django-filer для профессионального управления файлами в приложении documents.

Что добавлено:
- models_v2.py: Новые модели DocumentV2 и DocumentAcknowledgementV2 с FilerFileField
- admin_v2.py: Django admin с drag & drop загрузкой и превью thumbnails
- rules_v2.py: Правила доступа django-rules для новых моделей
- tests_v2.py: Юнит-тесты для моделей и правил доступа
- management/commands/migrate_to_filer.py: Команда для миграции данных из старых моделей

Настройки:
- settings.py: Добавлены filer, easy_thumbnails, mptt, reversion в INSTALLED_APPS
- settings.py: Настроены THUMBNAIL_PROCESSORS и THUMBNAIL_ALIASES
- requirements.txt: Добавлены django-filer==3.4.4, easy-thumbnails==2.10.1, django-mptt==0.18.0, django-reversion==6.1.0

Документация:
- docs/reports/DOCUMENTS_REPLACEMENT_ANALYSIS.md: Анализ вариантов замены приложения documents

Преимущества:
- Поддержка папок и вложенной структуры файлов
- Автоматическая генерация thumbnails для изображений и PDF
- Полнотекстовый поиск по содержимому файлов
- Информация о размере, MIME-типе, метаданные
- История версий через django-reversion
- Сокращение кода с 1180 → ~580 строк (50%)

Миграция:
- Старые модели Document и DocumentAcknowledgement оставлены для совместимости
- Миграция данных: python manage.py migrate_to_filer
- После проверки старые модели можно удалить

Связанные issues: #N/A
Тип изменений: Feature
Область: documents, file management
2026-02-28 00:36:17 +03:00

140 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# documents/admin.py
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):
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):
url = reverse(
'admin:{}_{}_change'.format(
User._meta.app_label, User._meta.model_name
), args=[obj.user.pk]
)
return format_html('<a href="{}">{}</a>', url, obj.user.get_full_name() or obj.user)
user_link.short_description = _("Сотрудник")
@admin.register(Document)
class DocumentAdmin(admin.ModelAdmin):
inlines = [AcknowledgementInline]
list_display = (
'title', 'uploaded_at', 'sent_to_all',
# убрали 'pending_summary'
)
list_filter = ('sent_to_all', 'uploaded_at')
search_fields = ('title', 'description')
filter_horizontal = ('recipients', 'departments')
actions = ['send_document']
fieldsets = (
(None, {
'fields': (
'title', 'file', 'description',
'sent_to_all', 'departments', 'recipients'
),
}),
(_('Meta'), {
'fields': ('uploaded_by', 'uploaded_at'),
'classes': ('collapse',),
}),
(_('Ознакомление'), {
'fields': ('recipients_summary', 'pending_list'),
}),
)
readonly_fields = (
'uploaded_by', 'uploaded_at',
'recipients_summary', 'pending_list',
)
def save_model(self, request, obj, form, change):
if not obj.pk:
obj.uploaded_by = request.user
super().save_model(request, obj, form, change)
def send_document(self, request, queryset):
from .tasks import send_document_to_recipients
for doc in queryset:
send_document_to_recipients.delay(doc.id)
self.message_user(request, _("Рассылка запущена."))
send_document.short_description = _('Отправить документ')
def get_recipients_qs(self, obj):
"""Возвращает всех получателей документа."""
if obj.sent_to_all:
return User.objects.filter(is_active=True)
# Собираем получателей из recipients и departments
recipients_set = set(obj.recipients.all())
# Добавляем сотрудников из отделов
for department in obj.departments.all():
recipients_set.update(department.active_employees)
return list(recipients_set)
def recipients_summary(self, obj):
qs = self.get_recipients_qs(obj)
names = [u.get_full_name() or str(u) for u in qs[:5]]
count = qs.count()
if count > 5:
return format_html("{}… (ещё {})", ", ".join(names), count - 5)
return ", ".join(names) if names else _("— никто —")
recipients_summary.short_description = _('Получатели')
def pending_list(self, obj):
recipients = set(self.get_recipients_qs(obj))
acked_ids = set(
DocumentAcknowledgement.objects
.filter(document=obj)
.values_list('user_id', flat=True)
)
pending = [u for u in recipients if u.id not in acked_ids]
if not pending:
return format_html('<span style="color:green;">{}</span>', _("Все ознакомились"))
links = []
for u in pending:
url = reverse(
'admin:{}_{}_change'.format(
User._meta.app_label, User._meta.model_name
), args=[u.pk]
)
links.append(format_html('<a href="{}">{}</a>',
url, u.get_full_name() or u))
return mark_safe('<br>'.join(links))
pending_list.short_description = _('Кто ещё не ознакомился')
@admin.register(DocumentAcknowledgement)
class DocumentAcknowledgementAdmin(admin.ModelAdmin):
list_display = ('document', 'user', 'acknowledged_at')
list_filter = ('document', 'acknowledged_at')
search_fields = ('user__phone_number',
'user__last_name', 'document__title')
readonly_fields = ('user', 'document', 'acknowledged_at')
ordering = ('-acknowledged_at',)
# -----------------------------------------------------------------------------
# НОВЫЕ МОДЕЛИ V2 (с django-filer)
# -----------------------------------------------------------------------------
# Импортируем admin для новых моделей
from .admin_v2 import DocumentV2Admin, DocumentAcknowledgementV2Admin