Реализовал систему разрешений

This commit is contained in:
2025-12-15 11:45:25 +03:00
parent ca7709ebff
commit 46dc79b93f
33 changed files with 1340 additions and 124 deletions

View File

@@ -0,0 +1,251 @@
"""
Система гранулярных прав доступа.
Определяет все доступные разрешения и функции для их проверки.
"""
from functools import wraps
from django.http import JsonResponse, HttpResponseForbidden
from django.shortcuts import redirect
from django.contrib import messages
# Определение всех разрешений в системе
# Формат: (код, название, описание)
PERMISSIONS = [
# Source List Page - Toolbar buttons
('source_create', 'Создание источника', 'Кнопка "Создать" на странице списка источников'),
('source_edit', 'Редактирование источника', 'Кнопка редактирования источника'),
('source_delete', 'Удаление источников', 'Кнопка "Удалить" на странице списка источников'),
('source_import_excel', 'Импорт из Excel', 'Кнопка "Excel" для загрузки данных'),
('source_import_csv', 'Импорт из CSV', 'Кнопка "CSV" для загрузки данных'),
('source_averaging', 'Усреднение точек', 'Кнопка "Усреднение" на странице списка источников'),
('source_tech_analyze', 'Технический анализ', 'Кнопка "Тех. анализ" на странице списка источников'),
('source_merge', 'Объединение источников', 'Кнопка "Объединить" в offcanvas списка'),
# Source Requests
('request_create', 'Создание заявки', 'Создание новой заявки на источник'),
('request_edit', 'Редактирование заявки', 'Редактирование существующей заявки'),
('request_delete', 'Удаление заявки', 'Удаление заявки на источник'),
('request_import', 'Импорт заявок', 'Импорт заявок из файла'),
# ObjItem (Points)
('objitem_create', 'Создание точки', 'Создание новой точки ГЛ'),
('objitem_edit', 'Редактирование точки', 'Редактирование точки ГЛ'),
('objitem_delete', 'Удаление точки', 'Удаление точки ГЛ'),
# Satellites
('satellite_create', 'Создание спутника', 'Создание нового спутника'),
('satellite_edit', 'Редактирование спутника', 'Редактирование спутника'),
('satellite_delete', 'Удаление спутника', 'Удаление спутника'),
# Tech Analyze
('tech_analyze_create', 'Создание тех. анализа', 'Создание записи технического анализа'),
('tech_analyze_edit', 'Редактирование тех. анализа', 'Редактирование записи технического анализа'),
('tech_analyze_delete', 'Удаление тех. анализа', 'Удаление записи технического анализа'),
# Signal Marks
('mark_create', 'Создание отметки', 'Создание отметки о сигнале'),
('mark_edit', 'Редактирование отметки', 'Редактирование отметки о сигнале'),
# Statistics
('statistics_view', 'Просмотр статистики', 'Доступ к странице статистики'),
# Kubsat
('kubsat_view', 'Просмотр Кубсат', 'Доступ к странице Кубсат'),
('kubsat_edit', 'Редактирование Кубсат', 'Редактирование данных Кубсат'),
# LyngSat
('lyngsat_parse', 'Парсинг LyngSat', 'Запуск парсинга LyngSat'),
# Transponders
('transponder_create', 'Создание транспондера', 'Создание нового транспондера'),
('transponder_edit', 'Редактирование транспондера', 'Редактирование транспондера'),
('transponder_delete', 'Удаление транспондера', 'Удаление транспондера'),
('transponder_import_xml', 'Импорт транспондеров из XML', 'Загрузка транспондеров из XML файла'),
# Admin access
# ('admin_access', 'Доступ к админ-панели', 'Доступ к административной панели Django'),
]
# Словарь для быстрого доступа к разрешениям
PERMISSION_CHOICES = [(code, name) for code, name, _ in PERMISSIONS]
PERMISSION_DESCRIPTIONS = {code: desc for code, _, desc in PERMISSIONS}
# Права по умолчанию для ролей
DEFAULT_ROLE_PERMISSIONS = {
'admin': [code for code, _, _ in PERMISSIONS], # Все права
'moderator': [
'source_create', 'source_edit', 'source_import_excel', 'source_import_csv',
'source_averaging', 'source_tech_analyze', 'source_merge',
'request_create', 'request_edit', 'request_import',
'objitem_create', 'objitem_edit',
'satellite_create', 'satellite_edit',
'transponder_create', 'transponder_edit', 'transponder_import_xml',
'tech_analyze_create', 'tech_analyze_edit',
'mark_create', 'mark_edit',
'statistics_view',
'kubsat_view', 'kubsat_edit',
],
'user': [
'statistics_view',
'kubsat_view',
],
}
def has_permission(user, permission_code):
"""
Проверяет, имеет ли пользователь указанное разрешение.
Args:
user: Объект User Django
permission_code: Код разрешения (строка)
Returns:
bool: True если пользователь имеет разрешение
"""
if not user or not user.is_authenticated:
return False
# Суперпользователь имеет все права
if user.is_superuser:
return True
# Получаем CustomUser
custom_user = getattr(user, 'customuser', None)
if not custom_user:
return False
# Проверяем, используются ли индивидуальные разрешения
if custom_user.use_custom_permissions:
# Используем индивидуальные разрешения
return custom_user.user_permissions.filter(code=permission_code).exists()
# Иначе используем права по умолчанию для роли
role = custom_user.role
default_perms = DEFAULT_ROLE_PERMISSIONS.get(role, [])
return permission_code in default_perms
def get_user_permissions(user):
"""
Возвращает список кодов разрешений пользователя.
Args:
user: Объект User Django
Returns:
list: Список кодов разрешений
"""
if not user or not user.is_authenticated:
return []
if user.is_superuser:
return [code for code, _, _ in PERMISSIONS]
custom_user = getattr(user, 'customuser', None)
if not custom_user:
return []
# Проверяем, используются ли индивидуальные разрешения
if custom_user.use_custom_permissions:
return list(custom_user.user_permissions.values_list('code', flat=True))
# Права по умолчанию для роли
return DEFAULT_ROLE_PERMISSIONS.get(custom_user.role, [])
def permission_required(permission_code, redirect_url=None, raise_exception=False):
"""
Декоратор для проверки разрешения на уровне view.
Args:
permission_code: Код разрешения
redirect_url: URL для редиректа при отсутствии прав (по умолчанию на предыдущую страницу)
raise_exception: Если True, возвращает 403 вместо редиректа
Usage:
@permission_required('source_create')
def my_view(request):
...
"""
def decorator(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
if has_permission(request.user, permission_code):
return view_func(request, *args, **kwargs)
# Для AJAX запросов возвращаем JSON
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return JsonResponse({
'success': False,
'error': 'У вас нет прав для выполнения этого действия'
}, status=403)
if raise_exception:
return HttpResponseForbidden('У вас нет прав для выполнения этого действия')
messages.error(request, 'У вас нет прав для выполнения этого действия')
if redirect_url:
return redirect(redirect_url)
# Редирект на предыдущую страницу или на главную
referer = request.META.get('HTTP_REFERER')
if referer:
return redirect(referer)
return redirect('mainapp:source_list')
return wrapper
return decorator
class PermissionRequiredMixin:
"""
Миксин для class-based views для проверки разрешений.
Usage:
class MyView(PermissionRequiredMixin, View):
permission_required = 'source_create'
# или для нескольких разрешений (любое из них):
permission_required = ['source_create', 'source_edit']
"""
permission_required = None
permission_denied_message = 'У вас нет прав для выполнения этого действия'
def has_permission(self):
"""Проверяет наличие разрешения."""
perms = self.get_permission_required()
if isinstance(perms, str):
return has_permission(self.request.user, perms)
# Для списка - проверяем наличие хотя бы одного разрешения
return any(has_permission(self.request.user, perm) for perm in perms)
def get_permission_required(self):
"""Возвращает требуемое разрешение."""
if self.permission_required is None:
raise ValueError(
f'{self.__class__.__name__} is missing the permission_required attribute.'
)
return self.permission_required
def dispatch(self, request, *args, **kwargs):
if not self.has_permission():
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
def handle_no_permission(self):
"""Обработка отсутствия разрешения."""
# Для AJAX запросов
if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return JsonResponse({
'success': False,
'error': self.permission_denied_message
}, status=403)
messages.error(self.request, self.permission_denied_message)
referer = self.request.META.get('HTTP_REFERER')
if referer:
return redirect(referer)
return redirect('mainapp:source_list')