198 lines
7.3 KiB
Python
198 lines
7.3 KiB
Python
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']
|
||
|
||
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': 'Основной текст главной страницы.',
|
||
}),
|
||
)
|
||
|
||
|
||
@admin.register(Solution)
|
||
class SolutionAdmin(BaseContentAdmin):
|
||
# Поля: title, description, implementation, closing, time_create, time_update, is_published
|
||
list_display = ('id', 'title', 'time_create', 'is_published')
|
||
search_fields = ('title', 'description', 'implementation')
|
||
|
||
fieldsets = (
|
||
(None, {
|
||
'fields': ('title', 'is_published'),
|
||
}),
|
||
('Содержание', {
|
||
'fields': ('description', 'implementation', 'closing'),
|
||
'description': 'Описание, реализация и заключение по проекту.',
|
||
}),
|
||
)
|
||
|
||
|
||
@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': 'Описание компетенции и фото.',
|
||
}),
|
||
)
|
||
|
||
|
||
@admin.register(Recall)
|
||
class RecallAdmin(BaseContentAdmin):
|
||
# Поля: title, content, scan, time_create, time_update, is_published
|
||
list_display = ('id', 'title', 'time_create', 'scan', 'is_published')
|
||
search_fields = ('title', 'content')
|
||
|
||
fieldsets = (
|
||
(None, {
|
||
'fields': ('title', 'is_published'),
|
||
}),
|
||
('Содержание', {
|
||
'fields': ('content', 'scan'),
|
||
'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('<span style="color:red;font-weight:bold;">🆕 НОВАЯ</span>')
|
||
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 |