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

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

@@ -22,6 +22,7 @@ from ..forms import (
VchLinkForm,
)
from ..mixins import FormMessageMixin
from ..permissions import PermissionRequiredMixin
from ..utils import (
add_satellite_list,
compare_and_link_vch_load,
@@ -41,9 +42,10 @@ class AddSatellitesView(LoginRequiredMixin, View):
return redirect("mainapp:source_list")
class AddTranspondersView(LoginRequiredMixin, FormMessageMixin, FormView):
class AddTranspondersView(LoginRequiredMixin, PermissionRequiredMixin, FormMessageMixin, FormView):
"""View for uploading and parsing transponder data from XML."""
permission_required = 'transponder_import_xml'
template_name = "mainapp/transponders_upload.html"
form_class = UploadFileForm
success_message = "Файл успешно обработан"
@@ -85,8 +87,9 @@ class AddTranspondersView(LoginRequiredMixin, FormMessageMixin, FormView):
return reverse_lazy("mainapp:add_trans")
class LoadExcelDataView(LoginRequiredMixin, FormMessageMixin, FormView):
class LoadExcelDataView(LoginRequiredMixin, PermissionRequiredMixin, FormMessageMixin, FormView):
"""View for loading data from Excel files."""
permission_required = 'source_import_excel'
template_name = "mainapp/add_data_from_excel.html"
form_class = LoadExcelData
@@ -134,8 +137,9 @@ class LoadExcelDataView(LoginRequiredMixin, FormMessageMixin, FormView):
return reverse_lazy("mainapp:load_excel_data")
class LoadCsvDataView(LoginRequiredMixin, FormMessageMixin, FormView):
class LoadCsvDataView(LoginRequiredMixin, PermissionRequiredMixin, FormMessageMixin, FormView):
"""View for loading data from CSV files."""
permission_required = 'source_import_csv'
template_name = "mainapp/add_data_from_csv.html"
form_class = LoadCsvData

View File

@@ -14,11 +14,13 @@ from openpyxl.styles import Font, Alignment
from mainapp.forms import KubsatFilterForm
from mainapp.models import Source, ObjItem
from mainapp.permissions import PermissionRequiredMixin
from mainapp.utils import calculate_mean_coords
class KubsatView(LoginRequiredMixin, FormView):
class KubsatView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
"""Страница Кубсат с фильтрами и таблицей источников"""
permission_required = 'kubsat_view'
template_name = 'mainapp/kubsat_tabs.html'
form_class = KubsatFilterForm
@@ -349,8 +351,9 @@ class KubsatView(LoginRequiredMixin, FormView):
return queryset.distinct()
class KubsatExportView(LoginRequiredMixin, FormView):
class KubsatExportView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
"""Экспорт отфильтрованных данных в Excel"""
permission_required = 'kubsat_view'
form_class = KubsatFilterForm
def post(self, request, *args, **kwargs):
@@ -595,8 +598,9 @@ class KubsatExportView(LoginRequiredMixin, FormView):
return response
class KubsatCreateRequestsView(LoginRequiredMixin, FormView):
class KubsatCreateRequestsView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
"""Массовое создание заявок из отфильтрованных данных"""
permission_required = 'request_create'
form_class = KubsatFilterForm
def post(self, request, *args, **kwargs):
@@ -688,8 +692,9 @@ class KubsatCreateRequestsView(LoginRequiredMixin, FormView):
})
class KubsatRecalculateCoordsView(LoginRequiredMixin, FormView):
class KubsatRecalculateCoordsView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
"""API для пересчёта усреднённых координат по списку ObjItem ID"""
permission_required = 'kubsat_view'
form_class = KubsatFilterForm
def post(self, request, *args, **kwargs):

View File

@@ -23,6 +23,7 @@ from mainapp.models import (
Modulation,
Standard,
)
from mainapp.permissions import PermissionRequiredMixin, has_permission
class SignalMarksView(LoginRequiredMixin, View):
@@ -324,11 +325,12 @@ class SignalMarksEntryAPIView(LoginRequiredMixin, View):
})
class SaveSignalMarksView(LoginRequiredMixin, View):
class SaveSignalMarksView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""
API для сохранения отметок сигналов.
Принимает массив отметок и сохраняет их в базу.
"""
permission_required = 'mark_create'
def post(self, request):
try:
@@ -404,10 +406,11 @@ class SaveSignalMarksView(LoginRequiredMixin, View):
}, status=500)
class CreateTechAnalyzeView(LoginRequiredMixin, View):
class CreateTechAnalyzeView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""
API для создания нового теханализа из модального окна.
"""
permission_required = 'tech_analyze_create'
def post(self, request):
try:

View File

@@ -15,6 +15,7 @@ from django.views.generic import CreateView, DeleteView, UpdateView
from ..forms import GeoForm, ObjItemForm, ParameterForm
from ..mixins import CoordinateProcessingMixin, FormMessageMixin, RoleRequiredMixin
from ..models import Geo, Modulation, ObjItem, Polarization, Satellite
from ..permissions import PermissionRequiredMixin
from ..utils import (
format_coordinate,
format_coords_display,
@@ -24,10 +25,9 @@ from ..utils import (
)
class DeleteSelectedObjectsView(RoleRequiredMixin, View):
class DeleteSelectedObjectsView(PermissionRequiredMixin, View):
"""View for deleting multiple selected objects."""
required_roles = ["admin", "moderator"]
permission_required = 'objitem_delete'
def post(self, request):
ids = request.POST.get("ids", "")
@@ -503,7 +503,7 @@ class ObjItemListView(LoginRequiredMixin, View):
class ObjItemFormView(
RoleRequiredMixin, CoordinateProcessingMixin, FormMessageMixin, UpdateView
PermissionRequiredMixin, CoordinateProcessingMixin, FormMessageMixin, UpdateView
):
"""
Base class for creating and editing ObjItem.
@@ -515,7 +515,7 @@ class ObjItemFormView(
form_class = ObjItemForm
template_name = "mainapp/objitem_form.html"
success_url = reverse_lazy("mainapp:source_list")
required_roles = ["admin", "moderator"]
permission_required = 'objitem_edit'
def get_success_url(self):
"""Returns URL with saved filter parameters."""
@@ -651,7 +651,7 @@ class ObjItemFormView(
class ObjItemUpdateView(ObjItemFormView):
"""View for editing ObjItem."""
permission_required = 'objitem_edit'
success_message = "Объект успешно сохранён!"
def set_user_fields(self):
@@ -660,7 +660,7 @@ class ObjItemUpdateView(ObjItemFormView):
class ObjItemCreateView(ObjItemFormView, CreateView):
"""View for creating ObjItem."""
permission_required = 'objitem_create'
success_message = "Объект успешно создан!"
def get_object(self, queryset=None):
@@ -672,14 +672,13 @@ class ObjItemCreateView(ObjItemFormView, CreateView):
self.object.updated_by = self.request.user.customuser
class ObjItemDeleteView(RoleRequiredMixin, FormMessageMixin, DeleteView):
class ObjItemDeleteView(PermissionRequiredMixin, FormMessageMixin, DeleteView):
"""View for deleting ObjItem."""
permission_required = 'objitem_delete'
model = ObjItem
template_name = "mainapp/objitem_confirm_delete.html"
success_url = reverse_lazy("mainapp:objitem_list")
success_message = "Объект успешно удалён!"
required_roles = ["admin", "moderator"]
def get_success_url(self):
"""Returns URL with saved filter parameters."""

View File

@@ -10,6 +10,7 @@ from django.views import View
from django.utils import timezone
from ..models import ObjItem, Satellite, Source
from ..permissions import PermissionRequiredMixin
from ..utils import (
calculate_mean_coords,
calculate_distance_wgs84,
@@ -24,10 +25,11 @@ from ..utils import (
)
class PointsAveragingView(LoginRequiredMixin, View):
class PointsAveragingView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""
View for points averaging form with date range selection and grouping.
"""
permission_required = 'source_averaging'
def get(self, request):
# Get satellites that have sources with points with geo data

View File

@@ -16,6 +16,7 @@ from django.views.generic import CreateView, UpdateView
from ..forms import SatelliteForm
from ..mixins import RoleRequiredMixin, FormMessageMixin
from ..models import Satellite, Band
from ..permissions import PermissionRequiredMixin
from ..utils import parse_pagination_params
@@ -252,15 +253,14 @@ class SatelliteListView(LoginRequiredMixin, View):
return render(request, "mainapp/satellite_list.html", context)
class SatelliteCreateView(RoleRequiredMixin, FormMessageMixin, CreateView):
class SatelliteCreateView(PermissionRequiredMixin, FormMessageMixin, CreateView):
"""View for creating a new satellite."""
permission_required = 'satellite_create'
model = Satellite
form_class = SatelliteForm
template_name = "mainapp/satellite_form.html"
success_url = reverse_lazy("mainapp:satellite_list")
success_message = "Спутник успешно создан!"
required_roles = ["admin", "moderator"]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -274,15 +274,14 @@ class SatelliteCreateView(RoleRequiredMixin, FormMessageMixin, CreateView):
return super().form_valid(form)
class SatelliteUpdateView(RoleRequiredMixin, FormMessageMixin, UpdateView):
class SatelliteUpdateView(PermissionRequiredMixin, FormMessageMixin, UpdateView):
"""View for updating an existing satellite."""
permission_required = 'satellite_edit'
model = Satellite
form_class = SatelliteForm
template_name = "mainapp/satellite_form.html"
success_url = reverse_lazy("mainapp:satellite_list")
success_message = "Спутник успешно обновлен!"
required_roles = ["admin", "moderator"]
def get_context_data(self, **kwargs):
import json
@@ -320,10 +319,9 @@ class SatelliteUpdateView(RoleRequiredMixin, FormMessageMixin, UpdateView):
return super().form_valid(form)
class DeleteSelectedSatellitesView(RoleRequiredMixin, View):
class DeleteSelectedSatellitesView(PermissionRequiredMixin, View):
"""View for deleting multiple selected satellites with confirmation."""
required_roles = ["admin", "moderator"]
permission_required = 'satellite_delete'
def get(self, request):
"""Show confirmation page with details about satellites to be deleted."""

View File

@@ -17,6 +17,7 @@ from django.views import View
from ..forms import SourceForm
from ..models import Source, Satellite
from ..utils import format_coords_display, parse_pagination_params
from ..permissions import PermissionRequiredMixin, permission_required
class SourceListView(LoginRequiredMixin, View):
@@ -818,7 +819,10 @@ class SourceListView(LoginRequiredMixin, View):
class AdminModeratorMixin(UserPassesTestMixin):
"""Mixin to restrict access to admin and moderator roles only."""
"""Mixin to restrict access to admin and moderator roles only.
DEPRECATED: Use PermissionRequiredMixin instead for granular permissions.
"""
def test_func(self):
return (
@@ -832,8 +836,9 @@ class AdminModeratorMixin(UserPassesTestMixin):
return redirect('mainapp:source_list')
class SourceCreateView(LoginRequiredMixin, AdminModeratorMixin, View):
class SourceCreateView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""View for creating new Source."""
permission_required = 'source_create'
def get(self, request):
form = SourceForm()
@@ -874,8 +879,9 @@ class SourceCreateView(LoginRequiredMixin, AdminModeratorMixin, View):
return render(request, 'mainapp/source_form.html', context)
class SourceUpdateView(LoginRequiredMixin, AdminModeratorMixin, View):
class SourceUpdateView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""View for editing Source with 4 coordinate fields and related ObjItems."""
permission_required = 'source_edit'
def get(self, request, pk):
source = get_object_or_404(Source, pk=pk)
@@ -945,8 +951,9 @@ class SourceUpdateView(LoginRequiredMixin, AdminModeratorMixin, View):
return render(request, 'mainapp/source_form.html', context)
class SourceDeleteView(LoginRequiredMixin, AdminModeratorMixin, View):
class SourceDeleteView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""View for deleting Source."""
permission_required = 'source_delete'
def get(self, request, pk):
source = get_object_or_404(Source, pk=pk)
@@ -975,8 +982,9 @@ class SourceDeleteView(LoginRequiredMixin, AdminModeratorMixin, View):
return redirect('mainapp:source_list')
class DeleteSelectedSourcesView(LoginRequiredMixin, AdminModeratorMixin, View):
class DeleteSelectedSourcesView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""View for deleting multiple selected sources with confirmation."""
permission_required = 'source_delete'
def get(self, request):
"""Show confirmation page with details about sources to be deleted."""
@@ -1062,8 +1070,9 @@ class DeleteSelectedSourcesView(LoginRequiredMixin, AdminModeratorMixin, View):
class MergeSourcesView(LoginRequiredMixin, AdminModeratorMixin, View):
class MergeSourcesView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""View for merging multiple sources into one."""
permission_required = 'source_merge'
def post(self, request):
"""Merge selected sources into the first one."""

View File

@@ -12,6 +12,7 @@ from django.utils import timezone
from mainapp.models import SourceRequest, SourceRequestStatusHistory, Source, Satellite
from mainapp.forms import SourceRequestForm
from mainapp.permissions import PermissionRequiredMixin
import re
import pandas as pd
@@ -83,8 +84,9 @@ class SourceRequestListView(LoginRequiredMixin, ListView):
return context
class SourceRequestCreateView(LoginRequiredMixin, CreateView):
class SourceRequestCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""Создание заявки на источник."""
permission_required = 'request_create'
model = SourceRequest
form_class = SourceRequestForm
template_name = 'mainapp/source_request_form.html'
@@ -132,8 +134,9 @@ class SourceRequestCreateView(LoginRequiredMixin, CreateView):
return super().form_invalid(form)
class SourceRequestUpdateView(LoginRequiredMixin, UpdateView):
class SourceRequestUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
"""Редактирование заявки на источник."""
permission_required = 'request_edit'
model = SourceRequest
form_class = SourceRequestForm
template_name = 'mainapp/source_request_form.html'
@@ -164,8 +167,9 @@ class SourceRequestUpdateView(LoginRequiredMixin, UpdateView):
return super().form_invalid(form)
class SourceRequestDeleteView(LoginRequiredMixin, View):
class SourceRequestDeleteView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Удаление заявки на источник."""
permission_required = 'request_delete'
def post(self, request, pk):
try:
@@ -182,8 +186,9 @@ class SourceRequestDeleteView(LoginRequiredMixin, View):
}, status=404)
class SourceRequestBulkDeleteView(LoginRequiredMixin, View):
class SourceRequestBulkDeleteView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Массовое удаление заявок."""
permission_required = 'request_delete'
def post(self, request):
import json
@@ -688,8 +693,9 @@ class SourceDataAPIView(LoginRequiredMixin, View):
return JsonResponse(data)
class SourceRequestImportView(LoginRequiredMixin, View):
class SourceRequestImportView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Импорт заявок из Excel файла."""
permission_required = 'request_import'
def get(self, request):
"""Отображает форму загрузки файла."""

View File

@@ -10,11 +10,13 @@ from django.views.generic import TemplateView
from django.http import JsonResponse
from ..models import ObjItem, Source, Satellite, Geo, SourceRequest, SourceRequestStatusHistory
from ..permissions import PermissionRequiredMixin
from mapsapp.models import Transponders
class StatisticsView(TemplateView):
class StatisticsView(PermissionRequiredMixin, TemplateView):
"""Страница статистики по данным геолокации."""
permission_required = 'statistics_view'
template_name = 'mainapp/statistics.html'

View File

@@ -19,13 +19,15 @@ from ..models import (
Parameter,
)
from ..mixins import RoleRequiredMixin
from ..permissions import PermissionRequiredMixin
from ..utils import parse_pagination_params, find_matching_transponder, find_matching_lyngsat
class TechAnalyzeEntryView(LoginRequiredMixin, View):
class TechAnalyzeEntryView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""
Представление для ввода данных технического анализа.
"""
permission_required = 'source_tech_analyze'
def get(self, request):
satellites = Satellite.objects.all().order_by('name')
@@ -37,10 +39,11 @@ class TechAnalyzeEntryView(LoginRequiredMixin, View):
return render(request, 'mainapp/tech_analyze_entry.html', context)
class TechAnalyzeSaveView(LoginRequiredMixin, View):
class TechAnalyzeSaveView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""
API endpoint для сохранения данных технического анализа.
"""
permission_required = 'tech_analyze_create'
def post(self, request):
try:
@@ -177,7 +180,7 @@ class TechAnalyzeSaveView(LoginRequiredMixin, View):
class LinkExistingPointsView(LoginRequiredMixin, View):
class LinkExistingPointsView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""
API endpoint для привязки существующих точек к данным теханализа.
@@ -194,6 +197,7 @@ class LinkExistingPointsView(LoginRequiredMixin, View):
* Обновить полосу частот (если 0 или None)
* Подобрать подходящий транспондер
"""
permission_required = 'tech_analyze_edit'
def post(self, request):
try:
@@ -388,11 +392,11 @@ class TechAnalyzeListView(LoginRequiredMixin, View):
return render(request, 'mainapp/tech_analyze_list.html', context)
class TechAnalyzeDeleteView(LoginRequiredMixin, RoleRequiredMixin, View):
class TechAnalyzeDeleteView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""
API endpoint для удаления выбранных записей теханализа.
"""
allowed_roles = ['admin', 'moderator']
permission_required = 'tech_analyze_delete'
def post(self, request):
try:

View File

@@ -15,7 +15,8 @@ from django.views.generic import CreateView, UpdateView
from mapsapp.models import Transponders
from ..forms import TransponderForm
from ..mixins import RoleRequiredMixin, FormMessageMixin
from ..mixins import FormMessageMixin
from ..permissions import PermissionRequiredMixin
from ..models import Satellite, Polarization
from ..utils import parse_pagination_params
@@ -246,15 +247,15 @@ class TransponderListView(LoginRequiredMixin, View):
return render(request, "mainapp/transponder_list.html", context)
class TransponderCreateView(RoleRequiredMixin, FormMessageMixin, CreateView):
class TransponderCreateView(PermissionRequiredMixin, FormMessageMixin, CreateView):
"""View for creating a new transponder."""
permission_required = 'transponder_create'
model = Transponders
form_class = TransponderForm
template_name = "mainapp/transponder_form.html"
success_url = reverse_lazy("mainapp:transponder_list")
success_message = "Транспондер успешно создан!"
required_roles = ["admin", "moderator"]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -268,15 +269,15 @@ class TransponderCreateView(RoleRequiredMixin, FormMessageMixin, CreateView):
return super().form_valid(form)
class TransponderUpdateView(RoleRequiredMixin, FormMessageMixin, UpdateView):
class TransponderUpdateView(PermissionRequiredMixin, FormMessageMixin, UpdateView):
"""View for updating an existing transponder."""
permission_required = 'transponder_edit'
model = Transponders
form_class = TransponderForm
template_name = "mainapp/transponder_form.html"
success_url = reverse_lazy("mainapp:transponder_list")
success_message = "Транспондер успешно обновлен!"
required_roles = ["admin", "moderator"]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -293,10 +294,10 @@ class TransponderUpdateView(RoleRequiredMixin, FormMessageMixin, UpdateView):
return super().form_valid(form)
class DeleteSelectedTranspondersView(RoleRequiredMixin, View):
class DeleteSelectedTranspondersView(PermissionRequiredMixin, View):
"""View for deleting multiple selected transponders with confirmation."""
required_roles = ["admin", "moderator"]
permission_required = 'transponder_delete'
def get(self, request):
"""Show confirmation page with details about transponders to be deleted."""

View File

@@ -0,0 +1,180 @@
"""
Views для управления правами пользователей.
"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.contrib import messages
from django.http import JsonResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.views import View
from ..models import CustomUser, UserPermission
from ..permissions import (
PERMISSIONS,
DEFAULT_ROLE_PERMISSIONS,
PermissionRequiredMixin,
has_permission
)
class UserPermissionsListView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Список пользователей с их правами."""
permission_required = 'admin_access'
def get(self, request):
users = CustomUser.objects.select_related('user').prefetch_related(
'user_permissions'
).order_by('user__username')
context = {
'users': users,
'permissions': PERMISSIONS,
'default_permissions': DEFAULT_ROLE_PERMISSIONS,
}
return render(request, 'mainapp/user_permissions_list.html', context)
class UserPermissionsEditView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Редактирование прав конкретного пользователя."""
permission_required = 'admin_access'
def get(self, request, pk):
custom_user = get_object_or_404(CustomUser.objects.select_related('user'), pk=pk)
# Получаем все разрешения
all_permissions = UserPermission.objects.all()
# Текущие разрешения пользователя
user_perm_codes = set(custom_user.user_permissions.values_list('code', flat=True))
# Права по умолчанию для роли
default_perms = set(DEFAULT_ROLE_PERMISSIONS.get(custom_user.role, []))
# Группируем разрешения по категориям
permission_groups = {
'Источники': [],
'Заявки': [],
'Точки ГЛ': [],
'Спутники': [],
'Транспондеры': [],
'Тех. анализ': [],
'Отметки': [],
'Прочее': [],
}
for code, name, desc in PERMISSIONS:
perm_data = {
'code': code,
'name': name,
'description': desc,
'has_permission': code in user_perm_codes if custom_user.use_custom_permissions else code in default_perms,
'is_default': code in default_perms,
}
if code.startswith('source_'):
permission_groups['Источники'].append(perm_data)
elif code.startswith('request_'):
permission_groups['Заявки'].append(perm_data)
elif code.startswith('objitem_'):
permission_groups['Точки ГЛ'].append(perm_data)
elif code.startswith('satellite_'):
permission_groups['Спутники'].append(perm_data)
elif code.startswith('transponder_'):
permission_groups['Транспондеры'].append(perm_data)
elif code.startswith('tech_analyze_'):
permission_groups['Тех. анализ'].append(perm_data)
elif code.startswith('mark_'):
permission_groups['Отметки'].append(perm_data)
else:
permission_groups['Прочее'].append(perm_data)
context = {
'custom_user': custom_user,
'permission_groups': permission_groups,
'default_perms': default_perms,
}
return render(request, 'mainapp/user_permissions_edit.html', context)
def post(self, request, pk):
custom_user = get_object_or_404(CustomUser, pk=pk)
# Получаем выбранные разрешения
selected_permissions = request.POST.getlist('permissions')
use_custom = request.POST.get('use_custom_permissions') == 'on'
# Обновляем флаг использования индивидуальных разрешений
custom_user.use_custom_permissions = use_custom
if use_custom:
# Очищаем текущие разрешения и добавляем новые
custom_user.user_permissions.clear()
for perm_code in selected_permissions:
perm, created = UserPermission.objects.get_or_create(code=perm_code)
custom_user.user_permissions.add(perm)
custom_user.save()
messages.success(request, f'Права пользователя {custom_user.user.username} обновлены.')
return redirect('mainapp:user_permissions_list')
class UserPermissionsApiView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""API для управления правами пользователей."""
permission_required = 'admin_access'
def post(self, request, pk):
"""Обновление прав пользователя через AJAX."""
import json
try:
data = json.loads(request.body)
custom_user = get_object_or_404(CustomUser, pk=pk)
use_custom = data.get('use_custom_permissions', False)
permissions = data.get('permissions', [])
custom_user.use_custom_permissions = use_custom
if use_custom:
custom_user.user_permissions.clear()
for perm_code in permissions:
perm, _ = UserPermission.objects.get_or_create(code=perm_code)
custom_user.user_permissions.add(perm)
custom_user.save()
return JsonResponse({
'success': True,
'message': f'Права пользователя {custom_user.user.username} обновлены'
})
except Exception as e:
return JsonResponse({
'success': False,
'error': str(e)
}, status=400)
class InitPermissionsView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Инициализация всех разрешений в базе данных."""
permission_required = 'admin_access'
def get(self, request):
from ..permissions import PERMISSIONS
created_count = 0
existing_count = 0
for code, name, description in PERMISSIONS:
perm, created = UserPermission.objects.get_or_create(code=code)
if created:
created_count += 1
else:
existing_count += 1
messages.success(
request,
f'Разрешения инициализированы. Создано: {created_count}, уже существовало: {existing_count}'
)
return redirect('mainapp:user_permissions_list')