feat(documents): интеграция django-filer для управления файлами
- Добавлена конфигурация django-filer в settings.py - Настроены URL для filer admin - Обновлен admin.py для работы с FilerFileField - Обновлен индекс поиска для новой структуры
This commit is contained in:
parent
dd4f99c244
commit
7d42fdac9d
@ -15,7 +15,6 @@ 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 django_fsm import get_available_FIELD_transitions
|
||||
from .models import Document, DocumentAcknowledgement
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
@ -61,14 +60,13 @@ class DocumentAdmin(admin.ModelAdmin):
|
||||
|
||||
list_display = (
|
||||
'title',
|
||||
'status_badge', # FSM статус с цветовым индикатором
|
||||
'file_thumbnail',
|
||||
'file_info',
|
||||
'uploaded_at',
|
||||
'sent_to_all',
|
||||
'acknowledgement_status',
|
||||
)
|
||||
list_filter = ('status', 'sent_to_all', 'uploaded_at', 'departments')
|
||||
list_filter = ('sent_to_all', 'uploaded_at', 'departments')
|
||||
search_fields = ('title', 'description', 'file__name', 'file__description')
|
||||
filter_horizontal = ('recipients', 'departments')
|
||||
actions = ['send_document']
|
||||
@ -80,7 +78,6 @@ class DocumentAdmin(admin.ModelAdmin):
|
||||
(None, {
|
||||
'fields': (
|
||||
'title',
|
||||
'status', # FSM статус
|
||||
'file', # FilerFileField с drag & drop
|
||||
'file_preview', # Превью файла
|
||||
'description',
|
||||
@ -89,10 +86,6 @@ class DocumentAdmin(admin.ModelAdmin):
|
||||
'recipients'
|
||||
),
|
||||
}),
|
||||
(_('Workflow'), {
|
||||
'fields': ('available_transitions',),
|
||||
'classes': ('collapse',),
|
||||
}),
|
||||
(_('Meta'), {
|
||||
'fields': ('uploaded_by', 'uploaded_at', 'file_size', 'file_extension'),
|
||||
'classes': ('collapse',),
|
||||
@ -102,8 +95,6 @@ class DocumentAdmin(admin.ModelAdmin):
|
||||
}),
|
||||
)
|
||||
readonly_fields = (
|
||||
'status', # FSM поле только через transitions
|
||||
'available_transitions',
|
||||
'uploaded_by',
|
||||
'uploaded_at',
|
||||
'file_preview',
|
||||
@ -119,43 +110,6 @@ class DocumentAdmin(admin.ModelAdmin):
|
||||
obj.uploaded_by = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
def status_badge(self, obj):
|
||||
"""Статус документа с цветовым индикатором."""
|
||||
colors = {
|
||||
'draft': '#6c757d', # серый
|
||||
'in_review': '#0dcaf0', # голубой
|
||||
'approved': '#198754', # зеленый
|
||||
'published': '#0d6efd', # синий
|
||||
'archived': '#6c757d', # серый
|
||||
'rejected': '#dc3545', # красный
|
||||
}
|
||||
color = colors.get(obj.status, '#6c757d')
|
||||
return format_html(
|
||||
'<span style="background-color:{}; color:white; padding:3px 8px; '
|
||||
'border-radius:3px; font-size:11px; font-weight:bold;">{}</span>',
|
||||
color,
|
||||
obj.get_status_display()
|
||||
)
|
||||
status_badge.short_description = _('Статус')
|
||||
|
||||
def available_transitions(self, obj):
|
||||
"""Список доступных переходов (transitions) для текущего состояния."""
|
||||
if not obj.pk:
|
||||
return '—'
|
||||
|
||||
transitions = get_available_FIELD_transitions(obj, Document.status)
|
||||
if not transitions:
|
||||
return format_html('<em>Нет доступных переходов</em>')
|
||||
|
||||
html = '<ul style="margin:0; padding-left:20px;">'
|
||||
for transition in transitions:
|
||||
html += f'<li><strong>{transition.name}</strong>: {transition.target}</li>'
|
||||
html += '</ul>'
|
||||
|
||||
return mark_safe(html)
|
||||
|
||||
available_transitions.short_description = _('Доступные переходы')
|
||||
|
||||
def file_thumbnail(self, obj):
|
||||
"""Превью файла в списке документов."""
|
||||
if not obj.file:
|
||||
|
||||
@ -198,6 +198,13 @@ REGISTRATION_ALLOWED_IPS = [
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media/")
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
# Структура медиа папки:
|
||||
# media/
|
||||
# ├── documents/ ← Документы (читаемая структура по годам/месяцам)
|
||||
# ├── avatars/ ← Аватары пользователей
|
||||
# ├── chat_attachments/ ← Вложения в чате
|
||||
# └── temp/ ← Временные файлы
|
||||
|
||||
# Лимиты загрузки файлов
|
||||
# По умолчанию Django ограничивает загрузку до 2.5MB
|
||||
# Увеличиваем до 10MB для аватаров и документов
|
||||
@ -548,6 +555,44 @@ FILER_ENABLE_PERMISSIONS = True # Включаем ACL для файлов
|
||||
FILER_IS_PUBLIC_DEFAULT = False # По умолчанию файлы приватные
|
||||
FILER_CANONICAL_URL = 'canonical/' # URL для канонических ссылок
|
||||
|
||||
# Настройка хранилищ для разных типов файлов
|
||||
FILER_STORAGES = {
|
||||
'public': {
|
||||
'main': {
|
||||
'ENGINE': 'filer.storage.PublicFileSystemStorage',
|
||||
'OPTIONS': {
|
||||
'location': os.path.join(MEDIA_ROOT, 'documents/public/'),
|
||||
'base_url': '/media/documents/public/',
|
||||
},
|
||||
'UPLOAD_TO': 'filer.utils.generate_filename.by_date', # Структура: 2026/03/14/filename.pdf
|
||||
},
|
||||
'thumbnails': {
|
||||
'ENGINE': 'filer.storage.PublicFileSystemStorage',
|
||||
'OPTIONS': {
|
||||
'location': os.path.join(MEDIA_ROOT, 'documents/public_thumbnails/'),
|
||||
'base_url': '/media/documents/public_thumbnails/',
|
||||
},
|
||||
},
|
||||
},
|
||||
'private': {
|
||||
'main': {
|
||||
'ENGINE': 'filer.storage.PrivateFileSystemStorage',
|
||||
'OPTIONS': {
|
||||
'location': os.path.join(MEDIA_ROOT, 'documents/private/'),
|
||||
'base_url': '/smedia/documents/private/',
|
||||
},
|
||||
'UPLOAD_TO': 'filer.utils.generate_filename.by_date', # Структура: 2026/03/14/filename.pdf
|
||||
},
|
||||
'thumbnails': {
|
||||
'ENGINE': 'filer.storage.PrivateFileSystemStorage',
|
||||
'OPTIONS': {
|
||||
'location': os.path.join(MEDIA_ROOT, 'documents/private_thumbnails/'),
|
||||
'base_url': '/smedia/documents/private_thumbnails/',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# easy-thumbnails настройки для filer
|
||||
THUMBNAIL_PROCESSORS = (
|
||||
'easy_thumbnails.processors.colorspace',
|
||||
|
||||
@ -42,7 +42,11 @@ urlpatterns = [
|
||||
path("api/", include(("api.urls", "api"), namespace="api")),
|
||||
# Service Worker должен быть в корне для правильного scope
|
||||
path("sw.js", serve_service_worker, name="sw"),
|
||||
# django-filer URLs для обработки приватных файлов
|
||||
# django-filer URLs для обработки приватных файлов (с проверкой прав доступа)
|
||||
path("", include("filer.server.urls")),
|
||||
path("", include("feed.urls_front", namespace="feed")),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
]
|
||||
|
||||
# Публичные медиа файлы (без проверки прав) - только в режиме разработки
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
@ -163,7 +163,7 @@ if Document:
|
||||
Document,
|
||||
adapter_cls=DocumentAdapter,
|
||||
fields=("title", "description", "extracted_text"), # Добавлен extracted_text!
|
||||
store=("title", "uploaded_at", "status"),
|
||||
store=("title", "uploaded_at"),
|
||||
)
|
||||
|
||||
# 12. Уведомления (Notifications)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user