Corp/backend/calendar_app/admin_calendar.py
Игорь Надеин 281f0b2bbc feat: современное отображение событий календаря с множественным выбором
Реализовано:

1. Множественный выбор календарей через чекбоксы
   - Заменил <select> на чекбоксы для выбора нескольких календарей
   - События создаются одновременно во всех выбранных календарях
   - При редактировании - обновляется только первый выбранный
   - Цветовые индикаторы для каждого календаря

2. Современный дизайн событий
   - Двухцветная схема: левая граница = цвет календаря, фон = цвет события
   - Полупрозрачный фон события (80%) для лучшей видимости
   - Тени и hover эффекты для объёма
   - Закругленные углы (4px)
   - Плавные анимации (transform + transition)

3. Улучшенное отображение накладывающихся событий
   - dayMaxEvents: автоматически показывать '+N еще'
   - dayMaxEventRows: 4 - максимум 4 строки на день
   - moreLinkClick: 'popover' - красивый popover для остальных
   - eventMaxStack: 3 - до 3 событий в стеке
   - eventOrder: сортировка по началу, длительности, алфавиту

4. Новый CSS файл calendar-improvements.css
   - Современные стили для FullCalendar
   - Улучшенный popover с градиентом
   - Стили для выходных дней (розоватый фон)
   - Hover эффекты на события
   - Анимация fadeInEvent при загрузке
   - Улучшенная читаемость текущего дня
   - Адаптивность для мобильных устройств
   - Кастомные scrollbar для списков

5. Передача цвета календаря в события
   - calendar_color добавляется в extendedProps
   - Используется __calendar из интеграции
   - Отдельная обработка eventColor и calendarColor

6. Визуальные улучшения
   - Текст события: font-weight 500, line-height 1.3
   - Отступы: paddingLeft 6px (с цветом календаря)
   - Тень текста для читаемости на любом фоне
   - Паттерн для ежегодных событий (диагональные полосы)
   - Индикатор текущего дня: синий круг с белым текстом

Результат:
✅ События визуально различимы по календарям (левая граница)
✅ Сохранён индивидуальный цвет события (основной фон)
✅ Множество событий в день отображается компактно (+N еще)
✅ Современный Material Design стиль
✅ Плавные анимации и hover эффекты
✅ Улучшенная читаемость и UX
2026-02-11 12:52:25 +03:00

160 lines
5.0 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.

"""Админка для модели Calendar."""
from django.contrib import admin
from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
from .models import Calendar
@admin.register(Calendar)
class CalendarAdmin(admin.ModelAdmin):
"""Админка для управления календарями."""
# ===== Инлайны =====
inlines = [] # Будет заполнено после импорта CalendarSubscriptionInline
# ===== Список =====
list_display = (
"id",
"title",
"visibility_display",
"owner_display",
"color_swatch",
"is_active",
"event_count",
"subscriber_count",
"created_at",
)
list_select_related = ("owner_user", "owner_department")
list_per_page = 50
ordering = ("-created_at",)
# ===== Поиск/Фильтры =====
search_fields = (
"title",
"description",
"owner_user__username",
"owner_user__first_name",
"owner_user__last_name",
"owner_department__name",
"owner_department__title",
)
list_filter = (
"visibility",
"is_active",
"auto_subscribe_new_users",
"auto_subscribe_department_members",
("created_at", admin.DateFieldListFilter),
)
date_hierarchy = "created_at"
# ===== Поля формы =====
readonly_fields = ("created_at", "updated_at", "event_count", "subscriber_count")
raw_id_fields = ("owner_user", "owner_department", "created_by")
fieldsets = (
(
_("Основное"),
{
"fields": ("title", "description", "icon"),
},
),
(
_("Владелец"),
{
"fields": ("owner_user", "owner_department"),
"description": _("Указывается либо пользователь, либо отдел"),
},
),
(
_("Видимость"),
{
"fields": ("visibility",),
},
),
(
_("Отображение"),
{
"fields": ("color", "sort_order"),
},
),
(
_("Автоподписка"),
{
"fields": (
"auto_subscribe_new_users",
"auto_subscribe_department_members",
),
},
),
(
_("Статус"),
{
"fields": ("is_active",),
},
),
(
_("Статистика"),
{
"fields": (
"event_count",
"subscriber_count",
"created_at",
"updated_at",
),
"classes": ("collapse",),
},
),
)
@admin.display(description=_("Видимость"), ordering="visibility")
def visibility_display(self, obj: Calendar) -> str:
"""Возвращает человекочитаемое название видимости."""
return obj.get_visibility_display()
@admin.display(description=_("Владелец"))
def owner_display(self, obj: Calendar) -> str:
"""Возвращает владельца календаря."""
if obj.owner_user_id:
return f"👤 {obj.owner_user}"
if obj.owner_department_id:
return f"🏢 {obj.owner_department}"
return _("🌐 Система")
@admin.display(description=_("Цвет"))
def color_swatch(self, obj: Calendar) -> str:
"""Возвращает HTML со свотчем цвета."""
return (
f'<div style="width:20px;height:20px;background-color:{obj.color};'
f'border:1px solid #ccc;display:inline-block;"></div> {obj.color}'
)
color_swatch.allow_tags = True # type: ignore[attr-defined]
@admin.display(description=_("События"))
def event_count(self, obj: Calendar) -> int:
"""Возвращает количество событий в календаре."""
return obj.events.count()
@admin.display(description=_("Подписчики"))
def subscriber_count(self, obj: Calendar) -> int:
"""Возвращает количество подписчиков календаря."""
return obj.subscriptions.count()
def save_model(
self, request: HttpRequest, obj: Calendar, form, change: bool
) -> None:
"""Валидация перед сохранением.
Args:
request: Текущий запрос.
obj: Объект календаря.
form: Форма админки.
change: True, если редактирование.
"""
# Проставляем created_by при создании
if not change and obj.created_by_id is None:
obj.created_by = request.user
super().save_model(request, obj, form, change)