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

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

@@ -3,6 +3,7 @@
Использование:
{% include 'mainapp/components/_navbar.html' %}
{% endcomment %}
{% load permission_tags %}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
@@ -37,18 +38,27 @@
<li class="nav-item">
<a class="nav-link" href="{% url 'mainapp:signal_marks' %}">Отметки сигналов</a>
</li>
{% if user|has_perm:'kubsat_view' %}
<li class="nav-item">
<a class="nav-link" href="{% url 'mainapp:kubsat' %}">Кубсат</a>
</li>
{% endif %}
<!-- <li class="nav-item">
<a class="nav-link" href="{% url 'mapsapp:3dmap' %}">3D карта</a>
</li> -->
<li class="nav-item">
<a class="nav-link" href="{% url 'mapsapp:2dmap' %}">Карта</a>
</li>
{% if user.customuser.role == 'admin' %}
<li class="nav-item">
<a class="nav-link" href="{% url 'mainapp:user_permissions_list' %}">Разрешения</a>
</li>
{% endif %}
{% if user.customuser.role == 'admin' %}
<li class="nav-item">
<a class="nav-link" href="{% url 'admin:index' %}">Админ панель</a>
</li>
{% endif %}
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">

View File

@@ -0,0 +1,18 @@
{% comment %}
Компонент кнопки с проверкой разрешений.
Используется через template tag {% permission_button %}
{% endcomment %}
{% if has_permission %}
{% if url %}
<a href="{% url url %}" class="{{ btn_class }}" {% if title %}title="{{ title }}"{% endif %}>
{% if icon %}<i class="{{ icon }}"></i>{% endif %}
{% if text %} {{ text }}{% endif %}
</a>
{% elif onclick %}
<button type="button" class="{{ btn_class }}" onclick="{{ onclick }}" {% if title %}title="{{ title }}"{% endif %}>
{% if icon %}<i class="{{ icon }}"></i>{% endif %}
{% if text %} {{ text }}{% endif %}
</button>
{% endif %}
{% endif %}

View File

@@ -1,4 +1,5 @@
{% load static %}
{% load permission_tags %}
<!-- Вкладка заявок на источники -->
<link href="{% static 'tabulator/css/tabulator_bootstrap5.min.css' %}" rel="stylesheet">
<style>
@@ -22,15 +23,19 @@
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-list-task"></i> Заявки на источники</h5>
<div>
{% if user|has_perm:'request_delete' %}
<button type="button" class="btn btn-outline-danger btn-sm me-2" id="bulkDeleteBtn" onclick="bulkDeleteRequests()">
<i class="bi bi-trash"></i> Удалить
</button>
{% endif %}
<button type="button" class="btn btn-outline-success btn-sm me-2" onclick="exportRequests()">
<i class="bi bi-file-earmark-excel"></i> Экспорт
</button>
{% if user|has_perm:'request_create' %}
<button type="button" class="btn btn-success btn-sm" onclick="openCreateRequestModal()">
<i class="bi bi-plus-circle"></i> Создать
</button>
{% endif %}
</div>
</div>
<div class="card-body">
@@ -193,22 +198,37 @@ function commentFormatter(cell) {
return val;
}
// Права пользователя (передаются из Django)
const userPermissions = {
canEditRequest: {% if user|has_perm:'request_edit' %}true{% else %}false{% endif %},
canDeleteRequest: {% if user|has_perm:'request_delete' %}true{% else %}false{% endif %}
};
// Форматтер для действий
function actionsFormatter(cell) {
const id = cell.getData().id;
return `
let buttons = `
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-outline-info btn-sm" onclick="showHistory(${id})" title="История">
<i class="bi bi-clock-history"></i>
</button>
</button>`;
if (userPermissions.canEditRequest) {
buttons += `
<button type="button" class="btn btn-outline-warning btn-sm" onclick="openEditRequestModal(${id})" title="Редактировать">
<i class="bi bi-pencil"></i>
</button>
</button>`;
}
if (userPermissions.canDeleteRequest) {
buttons += `
<button type="button" class="btn btn-outline-danger btn-sm" onclick="deleteRequest(${id})" title="Удалить">
<i class="bi bi-trash"></i>
</button>
</div>
`;
</button>`;
}
buttons += `</div>`;
return buttons;
}
// Инициализация Tabulator

View File

@@ -2,6 +2,7 @@
{% load static %}
{% load static leaflet_tags %}
{% load l10n %}
{% load permission_tags %}
{% block title %}{% if object %}Редактировать объект: {{ object.name }}{% else %}Создать новый объект{% endif %}{% endblock %}
@@ -146,12 +147,18 @@
<div class="col-12 d-flex justify-content-between align-items-center">
<h2>{% if object %}Редактировать объект: {{ object.name }}{% else %}Создать новый объект{% endif %}</h2>
<div>
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
<button type="submit" form="objitem-form" class="btn btn-primary btn-action">Сохранить</button>
{% if object %}
<a href="{% url 'mainapp:objitem_delete' object.id %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}"
class="btn btn-danger btn-action">Удалить</a>
{% endif %}
{% if user|has_perm:'objitem_edit' %}
<button type="submit" form="objitem-form" class="btn btn-primary btn-action">Сохранить</button>
{% endif %}
{% if user|has_perm:'objitem_delete' %}
<a href="{% url 'mainapp:objitem_delete' object.id %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}"
class="btn btn-danger btn-action">Удалить</a>
{% endif %}
{% else %}
{% if user|has_perm:'objitem_create' %}
<button type="submit" form="objitem-form" class="btn btn-primary btn-action">Сохранить</button>
{% endif %}
{% endif %}
<a href="{% url 'mainapp:objitem_list' %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}"
class="btn btn-secondary btn-action">Назад</a>

View File

@@ -1,5 +1,6 @@
{% extends 'mainapp/base.html' %}
{% load static %}
{% load permission_tags %}
{% block title %}Список объектов{% endblock %}
{% block extra_css %}
@@ -47,10 +48,12 @@
<!-- Action buttons bar -->
<div class="d-flex gap-2">
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
{% if user|has_perm:'objitem_create' %}
<a href="{% url 'mainapp:objitem_create' %}" class="btn btn-success btn-sm" title="Создать новый объект">
<i class="bi bi-plus-circle"></i> Создать
</a>
{% endif %}
{% if user|has_perm:'objitem_delete' %}
<button type="button" class="btn btn-danger btn-sm" title="Удалить"
onclick="deleteSelectedObjects()">
<i class="bi bi-trash"></i> Удалить
@@ -390,7 +393,7 @@
<input type="checkbox" class="form-check-input item-checkbox" value="{{ item.id }}">
</td>
<td>
<a href="{% if item.obj.id %}{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}{% url 'mainapp:objitem_update' item.obj.id %}?{{ request.GET.urlencode }}{% else %}{% url 'mainapp:objitem_detail' item.obj.id %}?{{ request.GET.urlencode }}{% endif %}{% endif %}">{{ item.name }}</a></td>
<a href="{% if item.obj.id %}{% if user|has_perm:'objitem_edit' %}{% url 'mainapp:objitem_update' item.obj.id %}?{{ request.GET.urlencode }}{% else %}{% url 'mainapp:objitem_detail' item.obj.id %}?{{ request.GET.urlencode }}{% endif %}{% endif %}">{{ item.name }}</a></td>
<td>
{% if item.satellite_id %}
<a href="#" class="text-decoration-underline"

View File

@@ -1,4 +1,5 @@
{% extends 'mainapp/base.html' %}
{% load permission_tags %}
{% block title %}Список спутников{% endblock %}
@@ -60,10 +61,12 @@
<!-- Action buttons -->
<div class="d-flex gap-2">
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
{% if user|has_perm:'satellite_create' %}
<a href="{% url 'mainapp:satellite_create' %}" class="btn btn-success btn-sm" title="Создать">
<i class="bi bi-plus-circle"></i> Создать
</a>
{% endif %}
{% if user|has_perm:'satellite_delete' %}
<button type="button" class="btn btn-danger btn-sm" title="Удалить"
onclick="deleteSelectedSatellites()">
<i class="bi bi-trash"></i> Удалить
@@ -355,7 +358,7 @@
<td>{{ satellite.updated_at|date:"d.m.Y H:i" }}</td>
<td class="text-center">
<div class="d-flex gap-1 justify-content-center">
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
{% if user|has_perm:'satellite_edit' %}
<a href="{% url 'mainapp:satellite_update' satellite.id %}"
class="btn btn-sm btn-outline-warning"
title="Редактировать спутник">

View File

@@ -1,5 +1,6 @@
{% extends "mainapp/base.html" %}
{% load static %}
{% load permission_tags %}
{% block title %}Отметки сигналов{% endblock %}
@@ -192,13 +193,17 @@
placeholder="Поиск по имени..." style="width: 200px;">
</div>
<div class="d-flex gap-2">
{% if user|has_perm:'tech_analyze_create' %}
<button class="btn btn-outline-primary btn-sm" onclick="openCreateModal()">
<i class="bi bi-plus-lg"></i> Создать теханализ
</button>
{% endif %}
{% if user|has_perm:'mark_create' %}
<button class="btn btn-success" id="save-marks-btn" onclick="saveMarks()" disabled>
<i class="bi bi-check-lg"></i> Сохранить
<span class="badge bg-light text-dark" id="marks-count">0</span>
</button>
{% endif %}
</div>
</div>
<div class="card-body p-0">
@@ -262,6 +267,7 @@
{% endif %}
</div>
{% if user|has_perm:'tech_analyze_create' %}
<!-- Modal for creating TechAnalyze -->
<div class="modal fade" id="createTechAnalyzeModal" tabindex="-1">
<div class="modal-dialog modal-lg">
@@ -321,12 +327,14 @@
</select>
</div>
</div>
{% if user|has_perm:'mark_create' %}
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="ta-add-mark" checked>
<label class="form-check-label" for="ta-add-mark">
Сразу добавить отметку "Есть сигнал"
</label>
</div>
{% endif %}
</form>
</div>
<div class="modal-footer">
@@ -336,6 +344,7 @@
</div>
</div>
</div>
{% endif %}
{% endblock %}
{% block extra_js %}
@@ -343,6 +352,8 @@
<script>
const SATELLITE_ID = {% if selected_satellite_id %}{{ selected_satellite_id }}{% else %}null{% endif %};
const CSRF_TOKEN = '{{ csrf_token }}';
const CAN_CREATE_MARK = {% if user|has_perm:'mark_create' %}true{% else %}false{% endif %};
const CAN_CREATE_TECH_ANALYZE = {% if user|has_perm:'tech_analyze_create' %}true{% else %}false{% endif %};
let entryTable = null;
let pendingMarks = {};
@@ -360,6 +371,48 @@ function selectSatellite() {
function initEntryTable() {
if (!SATELLITE_ID) return;
// Базовые колонки
const columns = [
{title: "ID", field: "id", width: 60},
{title: "Имя", field: "name", width: 500},
{title: "Частота", field: "frequency", width: 120, hozAlign: "right",
formatter: c => c.getValue() ? c.getValue().toFixed(3) : '-'},
{title: "Полоса", field: "freq_range", width: 120, hozAlign: "right",
formatter: c => c.getValue() ? c.getValue().toFixed(3) : '-'},
{title: "Сим.v", field: "bod_velocity", width: 120, hozAlign: "right",
formatter: c => c.getValue() ? Math.round(c.getValue()) : '-'},
{title: "Пол.", field: "polarization", width: 105, hozAlign: "center"},
{title: "Мод.", field: "modulation", width: 95, hozAlign: "center"},
{title: "Станд.", field: "standard", width: 125},
{title: "Посл. отметка", field: "last_mark", width: 190,
formatter: function(c) {
const d = c.getValue();
if (!d) return '<span class="text-muted">—</span>';
const icon = d.mark ? '✓' : '✗';
const cls = d.mark ? 'text-success' : 'text-danger';
return `<span class="${cls}">${icon}</span> ${d.timestamp}`;
}
},
];
// Добавляем колонку отметок только если есть право
if (CAN_CREATE_MARK) {
columns.push({
title: "Отметка", field: "id", width: 100, hozAlign: "center", headerSort: false,
formatter: function(c) {
const row = c.getRow().getData();
const id = row.id;
if (!row.can_add_mark) return '<span class="text-muted small">5 мин</span>';
const yesS = pendingMarks[id] === true ? 'selected' : '';
const noS = pendingMarks[id] === false ? 'selected' : '';
return `<div class="mark-btn-group">
<button type="button" class="mark-btn mark-btn-yes ${yesS}" data-id="${id}" data-val="true">✓</button>
<button type="button" class="mark-btn mark-btn-no ${noS}" data-id="${id}" data-val="false">✗</button>
</div>`;
}
});
}
entryTable = new Tabulator("#entry-table", {
ajaxURL: "{% url 'mainapp:signal_marks_entry_api' %}",
ajaxParams: { satellite_id: SATELLITE_ID },
@@ -370,41 +423,7 @@ function initEntryTable() {
layout: "fitColumns",
height: "65vh",
placeholder: "Нет данных",
columns: [
{title: "ID", field: "id", width: 60},
{title: "Имя", field: "name", width: 500},
{title: "Частота", field: "frequency", width: 120, hozAlign: "right",
formatter: c => c.getValue() ? c.getValue().toFixed(3) : '-'},
{title: "Полоса", field: "freq_range", width: 120, hozAlign: "right",
formatter: c => c.getValue() ? c.getValue().toFixed(3) : '-'},
{title: "Сим.v", field: "bod_velocity", width: 120, hozAlign: "right",
formatter: c => c.getValue() ? Math.round(c.getValue()) : '-'},
{title: "Пол.", field: "polarization", width: 105, hozAlign: "center"},
{title: "Мод.", field: "modulation", width: 95, hozAlign: "center"},
{title: "Станд.", field: "standard", width: 125},
{title: "Посл. отметка", field: "last_mark", width: 190,
formatter: function(c) {
const d = c.getValue();
if (!d) return '<span class="text-muted">—</span>';
const icon = d.mark ? '✓' : '✗';
const cls = d.mark ? 'text-success' : 'text-danger';
return `<span class="${cls}">${icon}</span> ${d.timestamp}`;
}
},
{title: "Отметка", field: "id", width: 100, hozAlign: "center", headerSort: false,
formatter: function(c) {
const row = c.getRow().getData();
const id = row.id;
if (!row.can_add_mark) return '<span class="text-muted small">5 мин</span>';
const yesS = pendingMarks[id] === true ? 'selected' : '';
const noS = pendingMarks[id] === false ? 'selected' : '';
return `<div class="mark-btn-group">
<button type="button" class="mark-btn mark-btn-yes ${yesS}" data-id="${id}" data-val="true">✓</button>
<button type="button" class="mark-btn mark-btn-no ${noS}" data-id="${id}" data-val="false">✗</button>
</div>`;
}
},
],
columns: columns,
});
// Делегирование событий для кнопок отметок - без перерисовки таблицы
@@ -600,12 +619,21 @@ document.addEventListener('DOMContentLoaded', function() {
// Modal
function openCreateModal() {
if (!CAN_CREATE_TECH_ANALYZE) {
alert('У вас нет прав для создания теханализа');
return;
}
document.getElementById('create-tech-analyze-form').reset();
document.getElementById('ta-add-mark').checked = true;
const addMarkCheckbox = document.getElementById('ta-add-mark');
if (addMarkCheckbox) addMarkCheckbox.checked = true;
new bootstrap.Modal(document.getElementById('createTechAnalyzeModal')).show();
}
function createTechAnalyze() {
if (!CAN_CREATE_TECH_ANALYZE) {
alert('У вас нет прав для создания теханализа');
return;
}
const name = document.getElementById('ta-name').value.trim();
if (!name) { alert('Укажите имя'); return; }
@@ -627,7 +655,8 @@ function createTechAnalyze() {
.then(result => {
if (result.success) {
bootstrap.Modal.getInstance(document.getElementById('createTechAnalyzeModal')).hide();
if (document.getElementById('ta-add-mark').checked) {
const addMarkCheckbox = document.getElementById('ta-add-mark');
if (CAN_CREATE_MARK && addMarkCheckbox && addMarkCheckbox.checked) {
pendingMarks[result.tech_analyze.id] = true;
updateMarksCount();
}

View File

@@ -1,6 +1,7 @@
{% extends 'mainapp/base.html' %}
{% load static %}
{% load static leaflet_tags %}
{% load permission_tags %}
{% block title %}Список объектов{% endblock %}
@@ -74,21 +75,22 @@
<!-- Action buttons -->
<div class="d-flex gap-2">
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
{% if user|has_perm:'source_create' %}
<a href="{% url 'mainapp:source_create' %}" class="btn btn-success btn-sm" title="Создать новый источник">
<i class="bi bi-plus-circle"></i> Создать
</a>
{% endif %}
<!-- <a href="{% url 'mainapp:data_entry' %}" class="btn btn-info btn-sm" title="Ввод данных точек спутников">
Передача точек
</a> -->
{% if user|has_perm:'source_import_excel' %}
<a href="{% url 'mainapp:load_excel_data' %}" class="btn btn-primary btn-sm" title="Загрузка данных из Excel">
<i class="bi bi-file-earmark-excel"></i> Excel
</a>
{% endif %}
{% if user|has_perm:'source_import_csv' %}
<a href="{% url 'mainapp:load_csv_data' %}" class="btn btn-success btn-sm" title="Загрузка данных из CSV">
<i class="bi bi-file-earmark-text"></i> CSV
</a>
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
{% endif %}
{% if user|has_perm:'source_delete' %}
<button type="button" class="btn btn-danger btn-sm" title="Удалить"
onclick="deleteSelectedSources()">
<i class="bi bi-trash"></i> Удалить
@@ -98,15 +100,21 @@
onclick="showSelectedOnMap()">
<i class="bi bi-map"></i> Карта
</button>
{% if user|has_perm:'source_averaging' %}
<a href="{% url 'mainapp:points_averaging' %}" class="btn btn-warning btn-sm" title="Усреднение точек">
<i class="bi bi-calculator"></i> Усреднение
</a>
{% endif %}
{% if user|has_perm:'source_tech_analyze' %}
<a href="{% url 'mainapp:tech_analyze_list' %}" class="btn btn-info btn-sm" title="Технический анализ">
<i class="bi bi-gear-wide-connected"></i> Тех. анализ
</a>
{% endif %}
{% if user|has_perm:'statistics_view' %}
<a href="{% url 'mainapp:statistics' %}" class="btn btn-secondary btn-sm" title="Статистика">
<i class="bi bi-bar-chart-line"></i> Статистика
</a>
{% endif %}
</div>
<!-- Add to List Button -->
@@ -729,7 +737,7 @@
<i class="bi bi-list-task"></i>
</button>
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
{% if user|has_perm:'source_edit' %}
<a href="{% url 'mainapp:source_update' source.id %}"
class="btn btn-sm btn-outline-warning"
title="Редактировать объект">
@@ -2224,7 +2232,7 @@ function showTransponderModal(transponderId) {
<button type="button" class="btn btn-outline-info btn-sm" onclick="showPlaybackAnimation()" title="Анимация движения объектов">
<i class="bi bi-play-circle"></i> Анимация
</button>
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
{% if user|has_perm:'source_merge' %}
<button type="button" class="btn btn-outline-success btn-sm" onclick="mergeSelectedSources()">
<i class="bi bi-union"></i> Объединить
</button>
@@ -2384,11 +2392,13 @@ function showTransponderModal(transponderId) {
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body">
{% if user|has_perm:'request_create' %}
<div class="mb-3">
<button type="button" class="btn btn-success btn-sm" onclick="openCreateRequestModalForSource()">
<i class="bi bi-plus-circle"></i> Создать заявку
</button>
</div>
{% endif %}
<div id="requestsLoadingSpinner" class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Загрузка...</span>
@@ -2648,12 +2658,16 @@ function showSourceRequests(sourceId) {
<button type="button" class="btn btn-outline-info" onclick="showRequestHistory(${req.id})" title="История">
<i class="bi bi-clock-history"></i>
</button>
{% if user|has_perm:'request_edit' %}
<button type="button" class="btn btn-outline-warning" onclick="editSourceRequest(${req.id})" title="Редактировать">
<i class="bi bi-pencil"></i>
</button>
{% endif %}
{% if user|has_perm:'request_delete' %}
<button type="button" class="btn btn-outline-danger" onclick="deleteSourceRequest(${req.id})" title="Удалить">
<i class="bi bi-trash"></i>
</button>
{% endif %}
</div>
</td>
`;

View File

@@ -1,5 +1,6 @@
{% extends 'mainapp/base.html' %}
{% load static %}
{% load permission_tags %}
{% block title %}Тех. анализ - Список{% endblock %}
@@ -53,17 +54,21 @@
<!-- Action buttons -->
<div class="d-flex gap-2">
{% if user|has_perm:'tech_analyze_create' %}
<a href="{% url 'mainapp:tech_analyze_entry' %}" class="btn btn-success btn-sm" title="Ввод данных">
<i class="bi bi-plus-circle"></i> Ввод данных
</a>
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
{% endif %}
{% if user|has_perm:'tech_analyze_delete' %}
<button type="button" class="btn btn-danger btn-sm" title="Удалить выбранные" onclick="deleteSelected()">
<i class="bi bi-trash"></i> Удалить
</button>
{% endif %}
{% if user|has_perm:'tech_analyze_edit' %}
<button type="button" class="btn btn-info btn-sm" title="Привязать к существующим точкам" onclick="showLinkModal()">
<i class="bi bi-link-45deg"></i> Привязать к точкам
</button>
{% endif %}
</div>
<!-- Filter Toggle Button -->

View File

@@ -1,4 +1,5 @@
{% extends 'mainapp/base.html' %}
{% load permission_tags %}
{% block title %}Список транспондеров{% endblock %}
@@ -57,13 +58,17 @@
<!-- Action buttons -->
<div class="d-flex gap-2">
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
{% if user|has_perm:'transponder_create' %}
<a href="{% url 'mainapp:transponder_create' %}" class="btn btn-success btn-sm" title="Создать">
<i class="bi bi-plus-circle"></i> Создать
</a>
{% endif %}
{% if user|has_perm:'transponder_import_xml' %}
<a href="{% url 'mainapp:add_trans' %}" class="btn btn-warning btn-sm" title="Загрузить из XML">
<i class="bi bi-upload"></i> Загрузить XML
</a>
{% endif %}
{% if user|has_perm:'transponder_delete' %}
<button type="button" class="btn btn-danger btn-sm" title="Удалить"
onclick="deleteSelectedTransponders()">
<i class="bi bi-trash"></i> Удалить
@@ -354,7 +359,7 @@
<td>{{ transponder.created_at|date:"d.m.Y H:i" }}</td>
<td>{{ transponder.updated_at|date:"d.m.Y H:i" }}</td>
<td class="text-center">
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
{% if user|has_perm:'transponder_edit' %}
<a href="{% url 'mainapp:transponder_update' transponder.id %}"
class="btn btn-sm btn-outline-warning"
title="Редактировать транспондер">

View File

@@ -0,0 +1,153 @@
{% extends 'mainapp/base.html' %}
{% load permission_tags %}
{% block title %}Права пользователя {{ custom_user.user.username }}{% endblock %}
{% block content %}
<div class="container-fluid px-3">
<div class="row mb-3">
<div class="col-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'mainapp:user_permissions_list' %}">Управление правами</a></li>
<li class="breadcrumb-item active">{{ custom_user.user.username }}</li>
</ol>
</nav>
<h2>
<i class="bi bi-person-gear"></i>
Права пользователя: {{ custom_user.user.username }}
{% if custom_user.user.first_name or custom_user.user.last_name %}
<small class="text-muted">({{ custom_user.user.first_name }} {{ custom_user.user.last_name }})</small>
{% endif %}
</h2>
</div>
</div>
<form method="post">
{% csrf_token %}
<div class="card mb-3">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<span>Настройки прав</span>
<span class="badge {% if custom_user.role == 'admin' %}bg-danger{% elif custom_user.role == 'moderator' %}bg-warning text-dark{% else %}bg-secondary{% endif %}">
{{ custom_user.get_role_display }}
</span>
</div>
</div>
<div class="card-body">
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="use_custom_permissions"
name="use_custom_permissions" {% if custom_user.use_custom_permissions %}checked{% endif %}
onchange="toggleCustomPermissions()">
<label class="form-check-label" for="use_custom_permissions">
<strong>Использовать индивидуальные разрешения</strong>
</label>
<div class="form-text">
Если выключено, будут использоваться права по умолчанию для роли "{{ custom_user.get_role_display }}"
</div>
</div>
</div>
</div>
<div id="permissions-container" {% if not custom_user.use_custom_permissions %}style="opacity: 0.5; pointer-events: none;"{% endif %}>
{% for group_name, perms in permission_groups.items %}
{% if perms %}
<div class="card mb-3">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<span><i class="bi bi-folder"></i> {{ group_name }}</span>
<div>
<button type="button" class="btn btn-outline-success btn-sm"
onclick="selectAllInGroup('{{ group_name }}')">
Выбрать все
</button>
<button type="button" class="btn btn-outline-secondary btn-sm"
onclick="deselectAllInGroup('{{ group_name }}')">
Снять все
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="row">
{% for perm in perms %}
<div class="col-md-6 col-lg-4 mb-2">
<div class="form-check">
<input class="form-check-input perm-checkbox" type="checkbox"
name="permissions" value="{{ perm.code }}"
id="perm_{{ perm.code }}"
data-group="{{ group_name }}"
{% if perm.has_permission %}checked{% endif %}>
<label class="form-check-label" for="perm_{{ perm.code }}">
<strong>{{ perm.name }}</strong>
<br>
<small class="text-muted">{{ perm.description }}</small>
</label>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% endfor %}
</div>
<div class="d-flex gap-2 mb-4">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-lg"></i> Сохранить
</button>
<a href="{% url 'mainapp:user_permissions_list' %}" class="btn btn-secondary">
<i class="bi bi-x-lg"></i> Отмена
</a>
<button type="button" class="btn btn-outline-warning" onclick="resetToDefaults()">
<i class="bi bi-arrow-counterclockwise"></i> Сбросить к правам роли
</button>
</div>
</form>
</div>
<script>
function toggleCustomPermissions() {
const checkbox = document.getElementById('use_custom_permissions');
const container = document.getElementById('permissions-container');
if (checkbox.checked) {
container.style.opacity = '1';
container.style.pointerEvents = 'auto';
} else {
container.style.opacity = '0.5';
container.style.pointerEvents = 'none';
}
}
function selectAllInGroup(groupName) {
document.querySelectorAll(`.perm-checkbox[data-group="${groupName}"]`).forEach(cb => {
cb.checked = true;
});
}
function deselectAllInGroup(groupName) {
document.querySelectorAll(`.perm-checkbox[data-group="${groupName}"]`).forEach(cb => {
cb.checked = false;
});
}
function resetToDefaults() {
// Снимаем все галочки
document.querySelectorAll('.perm-checkbox').forEach(cb => {
cb.checked = false;
});
// Устанавливаем галочки для прав по умолчанию
const defaultPerms = [{% for perm in default_perms %}'{{ perm }}'{% if not forloop.last %}, {% endif %}{% endfor %}];
defaultPerms.forEach(perm => {
const checkbox = document.getElementById('perm_' + perm);
if (checkbox) {
checkbox.checked = true;
}
});
}
</script>
{% endblock %}

View File

@@ -0,0 +1,117 @@
{% extends 'mainapp/base.html' %}
{% load permission_tags %}
{% block title %}Управление правами пользователей{% endblock %}
{% block content %}
<div class="container-fluid px-3">
<div class="row mb-3">
<div class="col-12">
<h2><i class="bi bi-shield-lock"></i> Управление правами пользователей</h2>
<p class="text-muted">Настройка индивидуальных разрешений для пользователей системы</p>
</div>
</div>
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<span>Пользователи</span>
<a href="{% url 'mainapp:init_permissions' %}" class="btn btn-outline-primary btn-sm">
<i class="bi bi-arrow-repeat"></i> Инициализировать разрешения
</a>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0">
<thead class="table-dark">
<tr>
<th>Пользователь</th>
<th>Роль</th>
<th>Тип прав</th>
<th>Кол-во разрешений</th>
<th class="text-center">Действия</th>
</tr>
</thead>
<tbody>
{% for custom_user in users %}
<tr>
<td>
<strong>{{ custom_user.user.username }}</strong>
{% if custom_user.user.first_name or custom_user.user.last_name %}
<br><small class="text-muted">{{ custom_user.user.first_name }} {{ custom_user.user.last_name }}</small>
{% endif %}
</td>
<td>
{% if custom_user.role == 'admin' %}
<span class="badge bg-danger">Администратор</span>
{% elif custom_user.role == 'moderator' %}
<span class="badge bg-warning text-dark">Модератор</span>
{% else %}
<span class="badge bg-secondary">Пользователь</span>
{% endif %}
</td>
<td>
{% if custom_user.use_custom_permissions %}
<span class="badge bg-info">Индивидуальные</span>
{% else %}
<span class="badge bg-secondary">По роли</span>
{% endif %}
</td>
<td>
{% if custom_user.use_custom_permissions %}
{{ custom_user.user_permissions.count }}
{% else %}
{{ default_permissions|get_item:custom_user.role|length|default:0 }}
{% endif %}
</td>
<td class="text-center">
<a href="{% url 'mainapp:user_permissions_edit' custom_user.pk %}"
class="btn btn-outline-primary btn-sm">
<i class="bi bi-pencil"></i> Настроить
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center text-muted">Нет пользователей</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- <div class="card mt-4">
<div class="card-header">
<i class="bi bi-info-circle"></i> Права по умолчанию для ролей
</div>
<div class="card-body">
<div class="row">
{% for role, perms in default_permissions.items %}
<div class="col-md-4">
<h6>
{% if role == 'admin' %}
<span class="badge bg-danger">Администратор</span>
{% elif role == 'moderator' %}
<span class="badge bg-warning text-dark">Модератор</span>
{% else %}
<span class="badge bg-secondary">Пользователь</span>
{% endif %}
</h6>
<ul class="list-unstyled small">
{% for perm in perms|slice:":10" %}
<li><i class="bi bi-check text-success"></i> {{ perm }}</li>
{% endfor %}
{% if perms|length > 10 %}
<li class="text-muted">... и ещё {{ perms|length|add:"-10" }}</li>
{% endif %}
</ul>
</div>
{% endfor %}
</div>
</div>
</div> -->
</div>
{% endblock %}