Реализовал систему разрешений
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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="Редактировать спутник">
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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="Редактировать транспондер">
|
||||
|
||||
153
dbapp/mainapp/templates/mainapp/user_permissions_edit.html
Normal file
153
dbapp/mainapp/templates/mainapp/user_permissions_edit.html
Normal 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 %}
|
||||
117
dbapp/mainapp/templates/mainapp/user_permissions_list.html
Normal file
117
dbapp/mainapp/templates/mainapp/user_permissions_list.html
Normal 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 %}
|
||||
Reference in New Issue
Block a user