diff --git a/programmer/admin.py b/programmer/admin.py index f5055c7..12e6313 100644 --- a/programmer/admin.py +++ b/programmer/admin.py @@ -3,7 +3,8 @@ from django.utils import timezone from django.utils.html import format_html from django.urls import path from django.shortcuts import render -from django.contrib import messages +from django.db import models +from django_ckeditor_5.widgets import CKEditor5Widget from .models import Home, Solution, Competence, Recall, CallbackRequest, PageView @@ -11,38 +12,103 @@ from .models import Home, Solution, Competence, Recall, CallbackRequest, PageVie # ===== 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): @@ -54,16 +120,12 @@ class CallbackAdmin(admin.ModelAdmin): readonly_fields = ('time_create',) actions = ['mark_as_read', 'mark_as_unread', 'mark_as_processed', 'resend_notification'] - # ── badge helper ────────────────────────────────────────────────────────── - def new_badge(self, obj): if not obj.is_read: return format_html('🆕 НОВАЯ') return '' new_badge.short_description = 'Статус' - # ── bulk actions ────────────────────────────────────────────────────────── - def mark_as_read(self, request, queryset): updated = queryset.update(is_read=True) self.message_user(request, f'{updated} заявок отмечены как прочитанные') @@ -85,8 +147,6 @@ class CallbackAdmin(admin.ModelAdmin): self.message_user(request, f'Уведомления отправлены для {count} заявок') resend_notification.short_description = 'Переотправить email уведомления' - # ── custom URL + view for stats ─────────────────────────────────────────── - def get_urls(self): urls = super().get_urls() custom_urls = [ @@ -117,11 +177,8 @@ class CallbackAdmin(admin.ModelAdmin): } return render(request, 'admin/callback_stats.html', context) - # ── FIX: removed get_queryset message_user spam ─────────────────────────── - # Previously, a warning banner was shown on EVERY request to the changelist, - # including background requests. Removed entirely — the new_badge column - # and base_site.html header notification already surface unread counts. +# ===== PAGE VIEWS ===== @admin.register(PageView) class PageViewAdmin(admin.ModelAdmin): @@ -134,9 +191,8 @@ class PageViewAdmin(admin.ModelAdmin): def get_queryset(self, request): return super().get_queryset(request).order_by('-timestamp') - # PageViews should never be created or edited manually def has_add_permission(self, request): return False def has_change_permission(self, request, obj=None): - return False + return False \ No newline at end of file