from django.contrib import admin from django.utils import timezone from django.utils.html import format_html from django.urls import path from django.shortcuts import render from django.db import models from django_ckeditor_5.widgets import CKEditor5Widget from .models import Home, Solution, Competence, Recall, CallbackRequest, PageView # ===== BASE ADMIN CLASSES ===== class BaseContentAdmin(admin.ModelAdmin): """ Базовый класс для контентных моделей. CKEditor, actions публикации, date_hierarchy. """ list_display_links = ('id', 'title') list_editable = ('is_published',) list_filter = ('time_create', 'is_published') search_fields = ('title',) date_hierarchy = 'time_create' actions = ['make_published', 'make_unpublished'] readonly_fields = ('time_create', 'time_update') formfield_overrides = { models.TextField: {'widget': CKEditor5Widget(config_name='default')}, } def make_published(self, request, queryset): updated = queryset.update(is_published=True) self.message_user(request, f'{updated} объектов опубликовано.') make_published.short_description = 'Опубликовать выбранные' def make_unpublished(self, request, queryset): updated = queryset.update(is_published=False) self.message_user(request, f'{updated} объектов снято с публикации.') make_unpublished.short_description = 'Снять с публикации' # ===== MODEL ADMINS ===== @admin.register(Home) class HomeAdmin(BaseContentAdmin): # Поля: title, content, home_image, time_create, time_update, is_published list_display = ('id', 'title', 'time_create', 'is_published') search_fields = ('title', 'content') fieldsets = ( (None, { 'fields': ('title', 'is_published'), }), ('Содержание', { 'fields': ('content', 'home_image'), 'description': 'Основной текст главной страницы.', }), ('Служебное', { 'fields': ('time_create', 'time_update'), 'classes': ('collapse',), }), ) @admin.register(Solution) class SolutionAdmin(BaseContentAdmin): # Поля: title, description, implementation, closing, time_create, time_update, is_published list_display = ('id', 'title', 'time_create', 'is_published') list_display_links = ('id', 'title') search_fields = ('title', 'description', 'implementation') prepopulated_fields = {'slug': ('title',)} fieldsets = ( (None, { 'fields': ('title', 'slug', 'is_published'), }), ('Содержание', { 'fields': ('description', 'implementation', 'closing'), 'description': 'Описание, реализация и заключение по проекту.', }), ('Служебное', { 'fields': ('time_create', 'time_update'), 'classes': ('collapse',), }), ) @admin.register(Competence) class CompetenceAdmin(BaseContentAdmin): # Поля: title, content, photo, time_create, time_update, is_published list_display = ('id', 'title', 'time_create', 'is_published') search_fields = ('title', 'content') fieldsets = ( (None, { 'fields': ('title', 'is_published'), }), ('Содержание', { 'fields': ('content', 'photo'), 'description': 'Описание компетенции и фото.', }), ('Служебное', { 'fields': ('time_create', 'time_update'), 'classes': ('collapse',), }), ) @admin.register(Recall) class RecallAdmin(BaseContentAdmin): # Поля: title, content, scan, time_create, time_update, is_published list_display = ('id', 'title', 'time_create', 'scan_preview', 'is_published') search_fields = ('title', 'content') fieldsets = ( (None, { 'fields': ('title', 'is_published'), }), ('Содержание', { 'fields': ('content', 'scan', 'scan_preview'), 'description': 'Текст отзыва и скан документа.', }), ('Служебное', { 'fields': ('time_create', 'time_update'), 'classes': ('collapse',), }), ) readonly_fields = ('time_create', 'time_update', 'scan_preview') def scan_preview(self, obj): if obj.scan: return format_html( '', obj.scan.url ) return '—' scan_preview.short_description = 'Превью скана' # ===== CALLBACK ===== @admin.register(CallbackRequest) class CallbackAdmin(admin.ModelAdmin): list_display = ('name', 'phone', 'email', 'time_create', 'is_processed', 'is_read', 'new_badge') list_display_links = ('name', 'phone') list_editable = ('is_processed', 'is_read') list_filter = ('time_create', 'is_processed', 'is_read') search_fields = ('name', 'phone', 'email') readonly_fields = ('time_create',) actions = ['mark_as_read', 'mark_as_unread', 'mark_as_processed', 'resend_notification'] def new_badge(self, obj): if not obj.is_read: return format_html('🆕 НОВАЯ') return '' new_badge.short_description = 'Статус' def mark_as_read(self, request, queryset): updated = queryset.update(is_read=True) self.message_user(request, f'{updated} заявок отмечены как прочитанные') mark_as_read.short_description = 'Отметить как прочитанные' def mark_as_unread(self, request, queryset): updated = queryset.update(is_read=False) self.message_user(request, f'{updated} заявок отмечены как непрочитанные') mark_as_unread.short_description = 'Отметить как непрочитанные' def mark_as_processed(self, request, queryset): updated = queryset.update(is_processed=True) self.message_user(request, f'{updated} заявок отмечены как обработанные') mark_as_processed.short_description = 'Отметить как обработанные' def resend_notification(self, request, queryset): from .utils.email_notifications import send_multiple_callback_notifications count = send_multiple_callback_notifications(queryset) self.message_user(request, f'Уведомления отправлены для {count} заявок') resend_notification.short_description = 'Переотправить email уведомления' def get_urls(self): urls = super().get_urls() custom_urls = [ path( 'callback-stats/', self.admin_site.admin_view(self.callback_stats_view), name='callback_stats', ), ] return custom_urls + urls def callback_stats_view(self, request): today = timezone.now().date() week_ago = today - timezone.timedelta(days=7) stats = { 'total': CallbackRequest.objects.count(), 'today': CallbackRequest.objects.filter(time_create__date=today).count(), 'week': CallbackRequest.objects.filter(time_create__date__gte=week_ago).count(), 'unread': CallbackRequest.objects.filter(is_read=False).count(), 'unprocessed': CallbackRequest.objects.filter(is_processed=False).count(), } context = { **self.admin_site.each_context(request), 'title': 'Статистика заявок', 'stats': stats, } return render(request, 'admin/callback_stats.html', context) # ===== PAGE VIEWS ===== @admin.register(PageView) class PageViewAdmin(admin.ModelAdmin): list_display = ('url', 'timestamp', 'ip_address') list_filter = ('timestamp', 'url') search_fields = ('url', 'ip_address') date_hierarchy = 'timestamp' readonly_fields = ('url', 'timestamp', 'ip_address') def get_queryset(self, request): return super().get_queryset(request).order_by('-timestamp') def has_add_permission(self, request): return False def has_change_permission(self, request, obj=None): return False