Реализовал систему разрешений
This commit is contained in:
251
dbapp/mainapp/permissions.py
Normal file
251
dbapp/mainapp/permissions.py
Normal 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')
|
||||
Reference in New Issue
Block a user