- Создан модуль calendarEventDragDrop.js с классами DragDropState и CalendarUtils - Реализовано перетаскивание событий между календарями через модальное окно - Добавлена кнопка 'Переместить все' для массового перемещения - Модальное окно отображает источник и цель перемещения - Поддержка legacy-календарей (company/department/personal) - Добавлены стили для подсветки drop-target (папки и календари) - Backend: исправлена обработка null значений в serializer - Backend: добавлен endpoint list_raw для получения немодерируемых событий - Использованы стандартные Bootstrap стили для модалов - Исправлено всплытие событий dragover/drop (stopPropagation) - Эмодзи иконки для типов календарей (����) - Toast уведомления об успехе/ошибках перемещения
7.8 KiB
Исправление снятия выбора календарей компании
Дата: 12 февраля 2026 г. Тип: Исправление бага Компонент: Менеджер календарей (calendarManager.js)
Проблема
При попытке снять выбор (скрыть) календарь компании возникала ошибка 403 Forbidden:
PATCH http://localhost:9000/api/v1/calendar/subscriptions/8/ 403 (Forbidden)
Error: Только владелец календаря может изменять права подписки.
Причина
Система пыталась обновить подписку (subscription) через API для всех новых календарей, включая календари компании, к которым пользователь не имеет прав редактирования.
Логика была:
- Legacy календари → localStorage ✅
- Новые календари → API (updateSubscription) ❌
Проблема: Календари компании (scope: 'company') имеют подписки, но пользователь не владелец и не может изменять их через API.
Решение
Изменена логика определения способа сохранения видимости календаря. Теперь проверяется не только is_legacy, но и права редактирования (user_can_edit):
До исправления:
if (calendar?.is_legacy) {
// localStorage
} else {
// API (updateSubscription) - ОШИБКА для календарей компании!
}
После исправления:
const canUseAPI = calendar && !calendar.is_legacy && calendar.user_can_edit;
if (!canUseAPI) {
// localStorage - для legacy ИЛИ без прав редактирования
localStorage.setItem(`calendar_visible_${calendarId}`, String(isVisible));
} else {
// API - только для календарей с правами редактирования
await updateSubscription(subscription.id, { is_visible: isVisible });
}
Изменения в коде
1. Функция toggleCalendarVisibility()
Файл: backend/static/js/components/calendarManager.js
Было:
if (calendar?.is_legacy) {
// localStorage
} else {
// API
}
Стало:
const canUseAPI = calendar && !calendar.is_legacy && calendar.user_can_edit;
if (!canUseAPI) {
// localStorage для legacy ИЛИ календарей без прав редактирования
localStorage.setItem(`calendar_visible_${calendarId}`, String(isVisible));
} else {
// API только для календарей с правами редактирования
await updateSubscription(subscription.id, { is_visible: isVisible });
}
2. Функция loadCalendars() - загрузка видимости
Было:
if (cal.is_legacy) {
// Читать из localStorage
} else {
// Читать из подписок
}
Стало:
if (cal.is_legacy || !cal.user_can_edit) {
// Читать из localStorage для legacy ИЛИ без прав редактирования
const stored = localStorage.getItem(`calendar_visible_${cal.id}`);
isVisible = stored === null ? true : stored === "true";
} else {
// Читать из подписок для календарей с правами редактирования
const subscription = subscriptions.find((s) => s.calendar === cal.id);
isVisible = subscription ? subscription.is_visible : true;
}
Логика работы
Типы календарей и методы сохранения видимости:
| Тип календаря | is_legacy |
user_can_edit |
Метод сохранения |
|---|---|---|---|
| Legacy компания | ✅ true | ❌ false | localStorage |
| Legacy личный | ✅ true | ✅ true | localStorage |
| Legacy отдел | ✅ true | ❌ false | localStorage |
| Новый компании (не владелец) | ❌ false | ❌ false | localStorage ✅ |
| Новый компании (владелец) | ❌ false | ✅ true | API |
| Новый личный (владелец) | ❌ false | ✅ true | API |
| Новый отдела (не владелец) | ❌ false | ❌ false | localStorage ✅ |
| Новый отдела (владелец/админ) | ❌ false | ✅ true | API |
Ключевое изменение:
Раньше: Все новые календари пытались использовать API
Теперь: Только календари с user_can_edit: true используют API
Результат
✅ Календари компании можно скрывать/показывать без ошибок ✅ Видимость сохраняется в localStorage для календарей без прав редактирования ✅ API используется только для календарей, где пользователь имеет права ✅ Fallback на localStorage работает при ошибках API
Технические детали
Почему нельзя изменять подписки через API?
Backend API /api/v1/calendar/subscriptions/{id}/ имеет ограничение:
# Только владелец календаря может изменять права подписки
if subscription.calendar.owner_user_id != request.user.id:
return Response(
{"detail": "Только владелец календаря может изменять права подписки."},
status=status.HTTP_403_FORBIDDEN
)
Это правильная логика безопасности - обычные пользователи не должны изменять подписки других пользователей.
Почему localStorage - правильное решение?
Видимость календаря (is_visible) - это локальная настройка UI конкретного пользователя:
- Не влияет на других пользователей
- Не требует синхронизации между устройствами
- Не нуждается в серверном хранении для календарей без прав редактирования
Для календарей, где пользователь владелец, имеет смысл использовать API, чтобы:
- Синхронизировать настройки между устройствами
- Сохранять как часть профиля пользователя
Тестирование
После исправления проверьте:
- ✅ Создайте календарь компании (как админ)
- ✅ Зайдите как обычный пользователь
- ✅ Попробуйте скрыть календарь компании
- ✅ Убедитесь, что нет ошибки 403
- ✅ Обновите страницу - видимость должна сохраниться
- ✅ Проверьте, что ваш личный календарь по-прежнему использует API
Связанные файлы
backend/static/js/components/calendarManager.js- основная логикаbackend/static/js/api/calendarsApi.js- API функции- Backend:
backend/calendar_app/views.py- API endpoint с проверкой прав