feat: add user roles management UI with owner access control

- Added role management views (list, create, edit, delete)
- Created user_roles URL routing
- Added role management templates with Bootstrap styling
- Updated navbar with Roles link for owners and superusers
- Enhanced decorators and mixins with superuser bypass
- Added assign_owner_role.py utility script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-01 21:24:27 +03:00
parent 9f48ae0a35
commit 14cc73722f
11 changed files with 479 additions and 2 deletions

View File

@@ -0,0 +1,64 @@
{% extends "base.html" %}
{% block title %}Добавить пользователя{% endblock %}
{% block content %}
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h4 class="mb-0">Добавить пользователя</h4>
</div>
<div class="card-body">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="email" class="form-label">Email *</label>
<input type="email" class="form-control" id="email" name="email" required>
<div class="form-text">Email пользователя для входа</div>
</div>
<div class="mb-3">
<label for="name" class="form-label">Имя *</label>
<input type="text" class="form-control" id="name" name="name" required>
<div class="form-text">Полное имя пользователя</div>
</div>
<div class="mb-3">
<label for="role" class="form-label">Роль *</label>
<select class="form-select" id="role" name="role" required>
<option value="">Выберите роль...</option>
{% for role in roles %}
<option value="{{ role.code }}">{{ role.name }} - {{ role.description }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="password" class="form-label">Пароль</label>
<input type="text" class="form-control" id="password" name="password">
<div class="form-text">Оставьте пустым для автогенерации. Пароль будет показан после создания.</div>
</div>
<div class="d-flex justify-content-between">
<a href="{% url 'user_roles:list' %}" class="btn btn-secondary">Отмена</a>
<button type="submit" class="btn btn-primary">Создать пользователя</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,61 @@
{% extends "base.html" %}
{% block title %}Удалить пользователя{% endblock %}
{% block content %}
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card border-danger">
<div class="card-header bg-danger text-white">
<h4 class="mb-0">Удалить доступ пользователя</h4>
</div>
<div class="card-body">
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle"></i>
<strong>Внимание!</strong> Вы собираетесь удалить доступ пользователя к системе.
</div>
<dl class="row">
<dt class="col-sm-4">Email:</dt>
<dd class="col-sm-8">{{ user_role.user.email }}</dd>
<dt class="col-sm-4">Имя:</dt>
<dd class="col-sm-8">{{ user_role.user.name }}</dd>
<dt class="col-sm-4">Роль:</dt>
<dd class="col-sm-8">
<span class="badge
{% if user_role.role.code == 'owner' %}bg-danger
{% elif user_role.role.code == 'manager' %}bg-primary
{% elif user_role.role.code == 'florist' %}bg-success
{% elif user_role.role.code == 'courier' %}bg-info
{% endif %}">
{{ user_role.role.name }}
</span>
</dd>
<dt class="col-sm-4">Создан:</dt>
<dd class="col-sm-8">{{ user_role.created_at|date:"d.m.Y H:i" }}</dd>
</dl>
<p class="text-muted">
Пользователь больше не сможет войти в систему через этот магазин.
Учетная запись пользователя не будет удалена.
</p>
<form method="post">
{% csrf_token %}
<div class="d-flex justify-content-between">
<a href="{% url 'user_roles:list' %}" class="btn btn-secondary">Отмена</a>
<button type="submit" class="btn btn-danger">
<i class="bi bi-trash"></i> Да, удалить доступ
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,63 @@
{% extends "base.html" %}
{% block title %}Изменить роль пользователя{% endblock %}
{% block content %}
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h4 class="mb-0">Изменить роль пользователя</h4>
</div>
<div class="card-body">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
<div class="mb-3">
<label class="form-label">Пользователь:</label>
<p class="form-control-plaintext">{{ user_role.user.email }} ({{ user_role.user.name }})</p>
</div>
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="role" class="form-label">Роль *</label>
<select class="form-select" id="role" name="role" required>
{% for role in roles %}
<option value="{{ role.code }}" {% if role.code == user_role.role.code %}selected{% endif %}>
{{ role.name }} - {{ role.description }}
</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="is_active" name="is_active"
{% if user_role.is_active %}checked{% endif %}>
<label class="form-check-label" for="is_active">
Активен
</label>
</div>
<div class="form-text">Снимите галочку, чтобы временно отключить доступ</div>
</div>
<div class="d-flex justify-content-between">
<a href="{% url 'user_roles:list' %}" class="btn btn-secondary">Отмена</a>
<button type="submit" class="btn btn-primary">Сохранить изменения</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,93 @@
{% extends "base.html" %}
{% block title %}Управление ролями пользователей{% endblock %}
{% block content %}
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Управление ролями пользователей</h2>
<a href="{% url 'user_roles:create' %}" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Добавить пользователя
</a>
</div>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
<div class="card">
<div class="card-body">
{% if user_roles %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Email</th>
<th>Имя</th>
<th>Роль</th>
<th>Статус</th>
<th>Создан</th>
<th>Создал</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
{% for user_role in user_roles %}
<tr>
<td>{{ user_role.user.email }}</td>
<td>{{ user_role.user.name }}</td>
<td>
<span class="badge
{% if user_role.role.code == 'owner' %}bg-danger
{% elif user_role.role.code == 'manager' %}bg-primary
{% elif user_role.role.code == 'florist' %}bg-success
{% elif user_role.role.code == 'courier' %}bg-info
{% endif %}">
{{ user_role.role.name }}
</span>
</td>
<td>
{% if user_role.is_active %}
<span class="badge bg-success">Активен</span>
{% else %}
<span class="badge bg-secondary">Неактивен</span>
{% endif %}
</td>
<td>{{ user_role.created_at|date:"d.m.Y H:i" }}</td>
<td>
{% if user_role.created_by %}
{{ user_role.created_by.name }}
{% else %}
{% endif %}
</td>
<td>
<a href="{% url 'user_roles:edit' user_role.pk %}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-pencil"></i> Изменить
</a>
<a href="{% url 'user_roles:delete' user_role.pk %}" class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i> Удалить
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5">
<p class="text-muted">Нет пользователей с ролями</p>
<a href="{% url 'user_roles:create' %}" class="btn btn-primary">
Добавить первого пользователя
</a>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}