Игорь Надеин d7c025f120 feat: Add department-based document distribution with department picker in UI
- Added departments ManyToMany field to Document model
- Implemented department selection in document create/edit modals
- Added automatic notification creation for all department employees
- Fixed document filtering to include uploaded documents in 'My Documents'
- Fixed acknowledgements API to include department recipients
- Added bulk notification support with configurable threshold
- Removed acknowledge button, added visual highlighting for unread documents
- Implemented server-side acknowledgment flow with redirect to file
- Fixed mobile responsive layout for document actions
- Removed temporary test files and documentation
2025-11-21 12:46:43 +03:00

352 lines
11 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.

from django.contrib import admin
from django.utils.html import format_html
from .models import (
NotificationCategory,
NotificationType,
Notification,
UserNotificationSettings,
NotificationTemplate,
)
from .telegram_models import TelegramUser
@admin.register(NotificationCategory)
class NotificationCategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'code', 'icon_preview', 'color', 'order', 'is_active']
list_editable = ['order', 'is_active']
list_filter = ['is_active']
search_fields = ['name', 'code', 'description']
ordering = ['order', 'name']
def icon_preview(self, obj):
return format_html('<i class="{}" style="font-size: 1.2em;"></i>', obj.icon)
icon_preview.short_description = 'Иконка'
@admin.register(NotificationType)
class NotificationTypeAdmin(admin.ModelAdmin):
list_display = [
'name',
'code',
'category',
'priority',
'default_enabled',
'is_groupable',
'is_required',
'is_active',
]
list_filter = [
'category',
'priority',
'default_enabled',
'is_groupable',
'is_required',
'is_active',
]
search_fields = ['name', 'code', 'description']
list_editable = ['is_active']
ordering = ['category__order', 'name']
fieldsets = (
('Основная информация', {
'fields': ('category', 'code', 'name', 'description', 'is_active')
}),
('Настройки по умолчанию', {
'fields': ('default_enabled', 'default_channels', 'priority', 'is_required')
}),
('Группировка', {
'fields': ('is_groupable', 'grouping_window_minutes'),
'classes': ('collapse',)
}),
)
@admin.register(Notification)
class NotificationAdmin(admin.ModelAdmin):
list_display = [
'id',
'recipient_name',
'title_short',
'notification_type',
'is_read',
'created_at',
'channels_sent',
]
list_filter = [
'is_read',
'is_archived',
'notification_type__category',
'notification_type',
'created_at',
]
search_fields = [
'title',
'message',
'recipient__first_name',
'recipient__last_name',
'recipient__email',
]
readonly_fields = [
'created_at',
'updated_at',
'read_at',
'sent_at',
'archived_at',
]
date_hierarchy = 'created_at'
ordering = ['-created_at']
fieldsets = (
('Основная информация', {
'fields': ('recipient', 'notification_type', 'title', 'message', 'short_message')
}),
('Связанный объект', {
'fields': ('content_type', 'object_id'),
'classes': ('collapse',)
}),
('Действие', {
'fields': ('action_url', 'action_text')
}),
('Статус', {
'fields': ('is_read', 'read_at', 'is_archived', 'archived_at')
}),
('Доставка', {
'fields': (
'sent_web',
'sent_email',
'sent_telegram',
'sent_whatsapp',
'sent_wechat',
'sent_at',
),
'classes': ('collapse',)
}),
('Группировка', {
'fields': ('group_key', 'is_grouped', 'grouped_count'),
'classes': ('collapse',)
}),
('Метаданные', {
'fields': ('metadata', 'created_at', 'updated_at'),
'classes': ('collapse',)
}),
)
def recipient_name(self, obj):
return obj.recipient.get_full_name() or obj.recipient.username
recipient_name.short_description = 'Получатель'
recipient_name.admin_order_field = 'recipient__last_name'
def title_short(self, obj):
return obj.title[:50] + '...' if len(obj.title) > 50 else obj.title
title_short.short_description = 'Заголовок'
def channels_sent(self, obj):
channels = []
if obj.sent_web:
channels.append('🌐 Веб')
if obj.sent_email:
channels.append('📧 Email')
if obj.sent_telegram:
channels.append('✈️ Telegram')
if obj.sent_whatsapp:
channels.append('💬 WhatsApp')
if obj.sent_wechat:
channels.append('💚 WeChat')
return ', '.join(channels) if channels else '—'
channels_sent.short_description = 'Отправлено'
actions = ['mark_as_read', 'mark_as_unread', 'archive_notifications']
def mark_as_read(self, request, queryset):
count = 0
for notification in queryset.filter(is_read=False):
notification.mark_as_read()
count += 1
self.message_user(request, f'Отмечено как прочитанное: {count}')
mark_as_read.short_description = 'Отметить как прочитанное'
def mark_as_unread(self, request, queryset):
count = queryset.filter(is_read=True).update(is_read=False, read_at=None)
self.message_user(request, f'Отмечено как непрочитанное: {count}')
mark_as_unread.short_description = 'Отметить как непрочитанное'
def archive_notifications(self, request, queryset):
count = 0
for notification in queryset.filter(is_archived=False):
notification.archive()
count += 1
self.message_user(request, f'Заархивировано: {count}')
archive_notifications.short_description = 'Архивировать'
@admin.register(UserNotificationSettings)
class UserNotificationSettingsAdmin(admin.ModelAdmin):
list_display = [
'user_name',
'notification_type',
'is_enabled',
'channels_enabled',
'quiet_hours_status',
]
list_filter = [
'is_enabled',
'notification_type__category',
'send_web',
'send_email',
'send_telegram',
'quiet_hours_enabled',
]
search_fields = [
'user__first_name',
'user__last_name',
'user__email',
'notification_type__name',
]
ordering = ['user__last_name', 'notification_type__category__order']
fieldsets = (
('Основная информация', {
'fields': ('user', 'notification_type', 'is_enabled')
}),
('Каналы доставки', {
'fields': ('send_web', 'send_email', 'send_telegram', 'send_whatsapp', 'send_wechat')
}),
('Тихий режим', {
'fields': ('quiet_hours_enabled', 'quiet_start_time', 'quiet_end_time'),
'classes': ('collapse',)
}),
)
def user_name(self, obj):
return obj.user.get_full_name() or obj.user.username
user_name.short_description = 'Пользователь'
user_name.admin_order_field = 'user__last_name'
def channels_enabled(self, obj):
channels = []
if obj.send_web:
channels.append('🌐')
if obj.send_email:
channels.append('📧')
if obj.send_telegram:
channels.append('✈️')
if obj.send_whatsapp:
channels.append('💬')
if obj.send_wechat:
channels.append('💚')
return ' '.join(channels) if channels else '—'
channels_enabled.short_description = 'Каналы'
def quiet_hours_status(self, obj):
if obj.quiet_hours_enabled and obj.quiet_start_time and obj.quiet_end_time:
return f'🌙 {obj.quiet_start_time.strftime("%H:%M")} - {obj.quiet_end_time.strftime("%H:%M")}'
return '—'
quiet_hours_status.short_description = 'Тихий режим'
@admin.register(NotificationTemplate)
class NotificationTemplateAdmin(admin.ModelAdmin):
list_display = [
'notification_type',
'channel',
'is_active',
'updated_at',
]
list_filter = [
'channel',
'is_active',
'notification_type__category',
]
search_fields = [
'notification_type__name',
'title_template',
'message_template',
]
ordering = ['notification_type__category__order', 'channel']
fieldsets = (
('Основная информация', {
'fields': ('notification_type', 'channel', 'is_active')
}),
('Шаблоны', {
'fields': ('title_template', 'message_template', 'action_button_template')
}),
('Email (дополнительно)', {
'fields': ('html_template',),
'classes': ('collapse',)
}),
)
@admin.register(TelegramUser)
class TelegramUserAdmin(admin.ModelAdmin):
list_display = [
'user_name',
'telegram_username',
'telegram_id',
'is_active',
'is_blocked',
'linked_at',
]
list_filter = [
'is_active',
'is_blocked',
'linked_at',
]
search_fields = [
'user__first_name',
'user__last_name',
'user__email',
'telegram_username',
'telegram_id',
]
readonly_fields = [
'telegram_id',
'link_code',
'link_code_created_at',
'linked_at',
'last_interaction_at',
'updated_at',
]
ordering = ['-linked_at']
fieldsets = (
('Пользователь', {
'fields': ('user', 'is_active')
}),
('Telegram информация', {
'fields': (
'telegram_id',
'telegram_username',
'first_name',
'last_name',
'is_blocked',
)
}),
('Код привязки', {
'fields': ('link_code', 'link_code_created_at'),
'classes': ('collapse',)
}),
('Временные метки', {
'fields': ('linked_at', 'last_interaction_at', 'updated_at'),
'classes': ('collapse',)
}),
)
def user_name(self, obj):
return obj.user.get_full_name() or obj.user.username
user_name.short_description = 'Пользователь'
user_name.admin_order_field = 'user__last_name'
actions = ['unlink_accounts', 'reactivate_accounts']
def unlink_accounts(self, request, queryset):
count = queryset.filter(is_active=True).update(is_active=False)
self.message_user(request, f'Отвязано аккаунтов: {count}')
unlink_accounts.short_description = 'Отвязать аккаунты'
def reactivate_accounts(self, request, queryset):
count = queryset.update(is_active=True, is_blocked=False)
self.message_user(request, f'Реактивировано аккаунтов: {count}')
reactivate_accounts.short_description = 'Реактивировать'