micro-users 1.5.0__py3-none-any.whl → 1.6.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of micro-users might be problematic. Click here for more details.
- {micro_users-1.5.0.dist-info → micro_users-1.6.2.dist-info}/METADATA +54 -134
- micro_users-1.6.2.dist-info/RECORD +38 -0
- users/admin.py +21 -2
- users/apps.py +2 -1
- users/filters.py +5 -4
- users/forms.py +13 -13
- users/middleware.py +32 -0
- users/migrations/{0003_department_alter_useractivitylog_options_and_more.py → 0003_scope_alter_customuser_options_and_more.py} +16 -7
- users/models.py +10 -5
- users/signals.py +107 -9
- users/static/img/login_logo.webp +0 -0
- users/static/{css → users/css}/login.css +50 -43
- users/static/users/css/style.css +201 -0
- users/static/users/js/anime.min.js +8 -0
- users/static/users/js/login.js +60 -0
- users/tables.py +19 -14
- users/templates/registration/login.html +29 -69
- users/templates/users/manage_users.html +20 -18
- users/templates/users/partials/{department_actions.html → scope_actions.html} +1 -1
- users/templates/users/partials/{department_form.html → scope_form.html} +3 -3
- users/templates/users/partials/{department_manager.html → scope_manager.html} +3 -3
- users/templates/users/user_activity_log.html +2 -0
- users/templates/users/widgets/grouped_permissions.html +1 -1
- users/urls.py +8 -8
- users/views.py +72 -50
- micro_users-1.5.0.dist-info/RECORD +0 -33
- {micro_users-1.5.0.dist-info → micro_users-1.6.2.dist-info}/LICENSE +0 -0
- {micro_users-1.5.0.dist-info → micro_users-1.6.2.dist-info}/WHEEL +0 -0
- {micro_users-1.5.0.dist-info → micro_users-1.6.2.dist-info}/top_level.txt +0 -0
users/tables.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import django_tables2 as tables
|
|
2
2
|
from django.contrib.auth import get_user_model
|
|
3
|
-
from .
|
|
3
|
+
from django.apps import apps
|
|
4
4
|
|
|
5
5
|
User = get_user_model() # Use custom user model
|
|
6
6
|
|
|
7
7
|
class UserTable(tables.Table):
|
|
8
8
|
username = tables.Column(verbose_name="اسم المستخدم")
|
|
9
9
|
email = tables.Column(verbose_name="البريد الالكتروني")
|
|
10
|
-
|
|
10
|
+
scope = tables.Column(verbose_name="النطاق", accessor='scope.name', default='-')
|
|
11
11
|
full_name = tables.Column(
|
|
12
12
|
verbose_name="الاسم الكامل",
|
|
13
13
|
accessor='user.full_name',
|
|
@@ -23,12 +23,13 @@ class UserTable(tables.Table):
|
|
|
23
23
|
actions = tables.TemplateColumn(
|
|
24
24
|
template_name='users/partials/user_actions.html',
|
|
25
25
|
orderable=False,
|
|
26
|
-
verbose_name=''
|
|
26
|
+
verbose_name='',
|
|
27
|
+
title='خيارات'
|
|
27
28
|
)
|
|
28
29
|
class Meta:
|
|
29
30
|
model = User
|
|
30
31
|
template_name = "django_tables2/bootstrap5.html"
|
|
31
|
-
fields = ("username", "email", "full_name", "phone", "
|
|
32
|
+
fields = ("username", "email", "full_name", "phone", "scope", "is_staff", "is_active","last_login", "actions")
|
|
32
33
|
attrs = {'class': 'table table-hover align-middle'}
|
|
33
34
|
|
|
34
35
|
class UserActivityLogTable(tables.Table):
|
|
@@ -41,30 +42,34 @@ class UserActivityLogTable(tables.Table):
|
|
|
41
42
|
accessor='user.full_name',
|
|
42
43
|
order_by='user__first_name'
|
|
43
44
|
)
|
|
44
|
-
|
|
45
|
-
verbose_name="
|
|
46
|
-
accessor='user.
|
|
45
|
+
scope = tables.Column(
|
|
46
|
+
verbose_name="النطاق",
|
|
47
|
+
accessor='user.scope.name',
|
|
47
48
|
default='عام'
|
|
48
49
|
)
|
|
49
50
|
class Meta:
|
|
50
|
-
model = UserActivityLog
|
|
51
|
+
model = apps.get_model('users', 'UserActivityLog')
|
|
51
52
|
template_name = "django_tables2/bootstrap5.html"
|
|
52
|
-
fields = ("timestamp", "user", "full_name", "
|
|
53
|
+
fields = ("timestamp", "user", "full_name", "model_name", "action", "object_id", "number", "scope")
|
|
54
|
+
exclude = ("id", "ip_address", "user_agent")
|
|
53
55
|
attrs = {'class': 'table table-hover align-middle'}
|
|
56
|
+
row_attrs = {
|
|
57
|
+
"class": lambda record: "row-deleted" if record.user and getattr(record.user, "deleted_at", None) else ""
|
|
58
|
+
}
|
|
54
59
|
|
|
55
60
|
class UserActivityLogTableNoUser(UserActivityLogTable):
|
|
56
61
|
class Meta(UserActivityLogTable.Meta):
|
|
57
|
-
# Remove the 'user', 'user.full_name' and '
|
|
58
|
-
exclude = ("user", "user.full_name", "
|
|
62
|
+
# Remove the 'user', 'user.full_name' and 'scope' columns
|
|
63
|
+
exclude = ("user", "user.full_name", "scope")
|
|
59
64
|
|
|
60
|
-
class
|
|
65
|
+
class ScopeTable(tables.Table):
|
|
61
66
|
actions = tables.TemplateColumn(
|
|
62
|
-
template_name='users/partials/
|
|
67
|
+
template_name='users/partials/scope_actions.html',
|
|
63
68
|
orderable=False,
|
|
64
69
|
verbose_name=''
|
|
65
70
|
)
|
|
66
71
|
class Meta:
|
|
67
|
-
model =
|
|
72
|
+
model = apps.get_model('users', 'Scope')
|
|
68
73
|
template_name = "django_tables2/bootstrap5.html"
|
|
69
74
|
fields = ("name", "actions")
|
|
70
75
|
attrs = {'class': 'table table-hover align-middle'}
|
|
@@ -3,13 +3,33 @@
|
|
|
3
3
|
{% block title %}تسجيل الدخول{% endblock %}
|
|
4
4
|
|
|
5
5
|
{% block content %}
|
|
6
|
-
<link rel="stylesheet" href="{% static 'css/login.css' %}">
|
|
6
|
+
<link rel="stylesheet" href="{% static 'users/css/login.css' %}">
|
|
7
|
+
{% if theme %}
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
{% if theme.selection_bg %}--selection-bg: {{ theme.selection_bg }} !important;{% endif %}
|
|
11
|
+
{% if theme.selection_moz_bg %}--selection-moz-bg: {{ theme.selection_moz_bg }} !important;{% endif %}
|
|
12
|
+
{% if theme.left_bg %}--left-bg: {{ theme.left_bg }} !important;{% endif %}
|
|
13
|
+
{% if theme.left_shadow %}--left-shadow: {{ theme.left_shadow }} !important;{% endif %}
|
|
14
|
+
{% if theme.right_bg %}--right-bg: {{ theme.right_bg }} !important;{% endif %}
|
|
15
|
+
{% if theme.right_shadow %}--right-shadow: {{ theme.right_shadow }} !important;{% endif %}
|
|
16
|
+
{% if theme.right_text %}--right-text: {{ theme.right_text }} !important;{% endif %}
|
|
17
|
+
{% if theme.label_color %}--label-color: {{ theme.label_color }} !important;{% endif %}
|
|
18
|
+
{% if theme.input_text %}--input-text: {{ theme.input_text }} !important;{% endif %}
|
|
19
|
+
{% if theme.submit_color %}--submit-color: {{ theme.submit_color }} !important;{% endif %}
|
|
20
|
+
{% if theme.submit_focus %}--submit-focus: {{ theme.submit_focus }} !important;{% endif %}
|
|
21
|
+
{% if theme.submit_active %}--submit-active: {{ theme.submit_active }} !important;{% endif %}
|
|
22
|
+
{% if theme.gradient_start %}--gradient-start: {{ theme.gradient_start }} !important;{% endif %}
|
|
23
|
+
{% if theme.gradient_end %}--gradient-end: {{ theme.gradient_end }} !important;{% endif %}
|
|
24
|
+
}
|
|
25
|
+
</style>
|
|
26
|
+
{% endif %}
|
|
7
27
|
|
|
8
28
|
<div class="page">
|
|
9
29
|
<div class="container d-flex">
|
|
10
|
-
<div class="right p-
|
|
30
|
+
<div class="right p-3 align-content-center">
|
|
11
31
|
<div class="">
|
|
12
|
-
<img src="{% static 'img/
|
|
32
|
+
<img src="{% static 'img/login_logo.webp' %}" alt="Login Logo" class="img-fluid mx-auto d-block logo-img">
|
|
13
33
|
</div>
|
|
14
34
|
{% comment %} <div class="login mb-3 text-secondary">تسجيل الدخــول</div> {% endcomment %}
|
|
15
35
|
</div>
|
|
@@ -25,11 +45,11 @@
|
|
|
25
45
|
y2="193.49992"
|
|
26
46
|
gradientUnits="userSpaceOnUse">
|
|
27
47
|
<stop
|
|
28
|
-
style="stop-color
|
|
48
|
+
style="stop-color:var(--gradient-end);"
|
|
29
49
|
offset="0"
|
|
30
50
|
id="stop876" />
|
|
31
51
|
<stop
|
|
32
|
-
style="stop-color
|
|
52
|
+
style="stop-color:var(--gradient-start);"
|
|
33
53
|
offset="1"
|
|
34
54
|
id="stop878" />
|
|
35
55
|
</linearGradient>
|
|
@@ -39,77 +59,17 @@
|
|
|
39
59
|
<form action="{% url 'login' %}" method="POST">
|
|
40
60
|
{% csrf_token %}
|
|
41
61
|
<div class="form">
|
|
42
|
-
<input type="text"
|
|
62
|
+
<input type="text" class="login-input" id="username" name="username" autofocus placeholder="اسم المستخدم" required>
|
|
43
63
|
|
|
44
|
-
<input type="password"
|
|
64
|
+
<input type="password" class="login-input" id="password" name="password" placeholder="كلمة المرور" required>
|
|
45
65
|
|
|
46
|
-
<input type="submit"
|
|
66
|
+
<input type="submit" class="login-submit mt-5 pe-3" value="دخول" id="submit">
|
|
47
67
|
</div>
|
|
48
68
|
</form>
|
|
49
69
|
</div>
|
|
50
70
|
</div>
|
|
51
71
|
</div>
|
|
52
72
|
|
|
53
|
-
<script nonce="{{ request.csp_nonce }}">
|
|
54
|
-
var current = null;
|
|
55
|
-
document.querySelector('#username').addEventListener('focus', function(e) {
|
|
56
|
-
if (current) current.pause();
|
|
57
|
-
current = anime({
|
|
58
|
-
targets: 'path',
|
|
59
|
-
strokeDashoffset: {
|
|
60
|
-
value: 0,
|
|
61
|
-
duration: 700,
|
|
62
|
-
easing: 'easeOutQuart'
|
|
63
|
-
},
|
|
64
|
-
strokeDasharray: {
|
|
65
|
-
value: '240 1386',
|
|
66
|
-
duration: 700,
|
|
67
|
-
easing: 'easeOutQuart'
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
document.querySelector('#password').addEventListener('focus', function(e) {
|
|
72
|
-
if (current) current.pause();
|
|
73
|
-
current = anime({
|
|
74
|
-
targets: 'path',
|
|
75
|
-
strokeDashoffset: {
|
|
76
|
-
value: -336,
|
|
77
|
-
duration: 700,
|
|
78
|
-
easing: 'easeOutQuart'
|
|
79
|
-
},
|
|
80
|
-
strokeDasharray: {
|
|
81
|
-
value: '240 1386',
|
|
82
|
-
duration: 700,
|
|
83
|
-
easing: 'easeOutQuart'
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
document.querySelector('#submit').addEventListener('focus', function(e) {
|
|
88
|
-
if (current) current.pause();
|
|
89
|
-
current = anime({
|
|
90
|
-
targets: 'path',
|
|
91
|
-
strokeDashoffset: {
|
|
92
|
-
value: -730,
|
|
93
|
-
duration: 700,
|
|
94
|
-
easing: 'easeOutQuart'
|
|
95
|
-
},
|
|
96
|
-
strokeDasharray: {
|
|
97
|
-
value: '530 1386',
|
|
98
|
-
duration: 700,
|
|
99
|
-
easing: 'easeOutQuart'
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// New script: Hide the login button in the title bar if present.
|
|
105
|
-
document.addEventListener("DOMContentLoaded", function() {
|
|
106
|
-
// Adjust the selector to match the login button in your title bar.
|
|
107
|
-
var loginTitleButton = document.querySelector(".login-title-btn");
|
|
108
|
-
if (loginTitleButton) {
|
|
109
|
-
loginTitleButton.style.display = "none";
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
</script>
|
|
113
|
-
|
|
114
73
|
<script src="{% static 'js/anime.min.js' %}"></script>
|
|
74
|
+
<script src="{% static 'users/js/login.js' %}"></script>
|
|
115
75
|
{% endblock %}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{% extends "base.html" %}
|
|
2
2
|
{% load django_tables2 %}
|
|
3
|
+
{% load static %}
|
|
3
4
|
{% load crispy_forms_tags %}
|
|
4
5
|
|
|
5
6
|
{% block title %}الاعدادت - إدارة المستخدمين{% endblock %}
|
|
6
7
|
|
|
7
8
|
{% block content %}
|
|
9
|
+
<link rel="stylesheet" href="{% static 'users/css/style.css' %}">
|
|
8
10
|
|
|
9
11
|
<form method="get" class="py-3 g-2 no-print">
|
|
10
12
|
{% crispy filter.form %}
|
|
@@ -18,9 +20,9 @@
|
|
|
18
20
|
</div>
|
|
19
21
|
|
|
20
22
|
<div class="mt-3">
|
|
21
|
-
{% if not request.user.
|
|
22
|
-
<button type="button" class="btn btn-info
|
|
23
|
-
<i class="bi bi-list me-1"></i> إدارة
|
|
23
|
+
{% if not request.user.scope %}
|
|
24
|
+
<button type="button" class="btn btn-info no-print" title="إدارة النطاقات" onclick="loadScopeManager()">
|
|
25
|
+
<i class="bi bi-list me-1 h4"></i> إدارة النطاقات
|
|
24
26
|
</button>
|
|
25
27
|
{% endif %}
|
|
26
28
|
<a href="{% url 'create_user' %}" class="btn btn-secondary no-print" title="إضافة مستخدم جديد">
|
|
@@ -52,14 +54,14 @@
|
|
|
52
54
|
</form>
|
|
53
55
|
{% endif %}
|
|
54
56
|
|
|
55
|
-
<!--
|
|
56
|
-
<div class="modal fade" id="
|
|
57
|
+
<!-- Scope Management Modal -->
|
|
58
|
+
<div class="modal fade" id="scopeModal" tabindex="-1" aria-hidden="true">
|
|
57
59
|
<div class="modal-dialog modal-lg">
|
|
58
60
|
<div class="modal-content">
|
|
59
61
|
<div class="modal-header">
|
|
60
62
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
61
63
|
</div>
|
|
62
|
-
<div class="modal-body" id="
|
|
64
|
+
<div class="modal-body" id="scopeModalBody">
|
|
63
65
|
<!-- Content loaded via AJAX -->
|
|
64
66
|
<div class="text-center py-5">
|
|
65
67
|
<div class="spinner-border text-primary" role="status">
|
|
@@ -71,17 +73,17 @@
|
|
|
71
73
|
</div>
|
|
72
74
|
</div>
|
|
73
75
|
|
|
74
|
-
<!-- Script for
|
|
76
|
+
<!-- Script for Scope Modal -->
|
|
75
77
|
<script>
|
|
76
78
|
// Defined globally so they can be called from injected HTML
|
|
77
|
-
function
|
|
78
|
-
const modal = new bootstrap.Modal(document.getElementById('
|
|
79
|
+
function loadScopeManager() {
|
|
80
|
+
const modal = new bootstrap.Modal(document.getElementById('scopeModal'));
|
|
79
81
|
modal.show();
|
|
80
|
-
|
|
82
|
+
loadScopeForm("{% url 'manage_scopes' %}");
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
function
|
|
84
|
-
// Default to
|
|
85
|
+
function loadScopeForm(url) {
|
|
86
|
+
// Default to manage_scopes if no URL (e.g. back button cases)
|
|
85
87
|
// But actually we want distinct behavior:
|
|
86
88
|
// 1. Initial Load (Table)
|
|
87
89
|
// 2. Load Form (Add/Edit)
|
|
@@ -94,12 +96,12 @@
|
|
|
94
96
|
})
|
|
95
97
|
.then(response => response.json())
|
|
96
98
|
.then(data => {
|
|
97
|
-
document.getElementById('
|
|
99
|
+
document.getElementById('scopeModalBody').innerHTML = data.html;
|
|
98
100
|
})
|
|
99
101
|
.catch(err => console.error('Error loading content:', err));
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
function
|
|
104
|
+
function submitScopeForm(e, url) {
|
|
103
105
|
e.preventDefault();
|
|
104
106
|
const form = e.target;
|
|
105
107
|
const formData = new FormData(form);
|
|
@@ -115,22 +117,22 @@
|
|
|
115
117
|
.then(data => {
|
|
116
118
|
// Whether success or error, we replace the body with the returned HTML
|
|
117
119
|
// (Updated table or Form with errors)
|
|
118
|
-
document.getElementById('
|
|
120
|
+
document.getElementById('scopeModalBody').innerHTML = data.html;
|
|
119
121
|
})
|
|
120
122
|
.catch(err => console.error('Error submitting form:', err));
|
|
121
123
|
}
|
|
122
124
|
|
|
123
|
-
function
|
|
125
|
+
function deleteScope(url) {
|
|
124
126
|
fetch(url, {
|
|
125
127
|
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
|
126
128
|
})
|
|
127
129
|
.then(response => response.json())
|
|
128
130
|
.then(data => {
|
|
129
131
|
if (data.success) {
|
|
130
|
-
document.getElementById('
|
|
132
|
+
document.getElementById('scopeModalBody').innerHTML = data.html;
|
|
131
133
|
}
|
|
132
134
|
})
|
|
133
|
-
.catch(err => console.error('Error deleting
|
|
135
|
+
.catch(err => console.error('Error deleting scope:', err));
|
|
134
136
|
}
|
|
135
137
|
</script>
|
|
136
138
|
{% endblock %}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="d-flex gap-2 justify-content-center">
|
|
2
2
|
<button class="btn btn-sm btn-primary"
|
|
3
|
-
onclick="
|
|
3
|
+
onclick="loadScopeForm('{% url 'get_scope_form' record.id %}')"
|
|
4
4
|
title="تعديل">
|
|
5
5
|
<i class="bi bi-pencil-square"></i>
|
|
6
6
|
</button>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{% load crispy_forms_tags %}
|
|
2
2
|
|
|
3
3
|
<div class="d-flex justify-content-between mb-3">
|
|
4
|
-
<h5 class="modal-title">{% if
|
|
5
|
-
<button class="btn btn-secondary" onclick="
|
|
4
|
+
<h5 class="modal-title">{% if scope_id %}تعديل نطاق{% else %}إضافة نطاق جديد{% endif %}</h5>
|
|
5
|
+
<button class="btn btn-secondary" onclick="loadScopeForm('{% url 'manage_scopes' %}')">
|
|
6
6
|
<i class="bi bi-arrow-right me-1"></i> عودة للقائمة
|
|
7
7
|
</button>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
|
-
<form id="
|
|
10
|
+
<form id="scopeForm" onsubmit="submitScopeForm(event, '{% url 'save_scope' %}{% if scope_id %}/{{ scope_id }}{% endif %}')">
|
|
11
11
|
{% csrf_token %}
|
|
12
12
|
{% crispy form %}
|
|
13
13
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{% load django_tables2 %}
|
|
2
2
|
<div class="d-flex justify-content-between mb-3">
|
|
3
|
-
<h5 class="modal-title">إدارة
|
|
3
|
+
<h5 class="modal-title">إدارة النطاقات</h5>
|
|
4
4
|
<button class="btn btn-success"
|
|
5
|
-
onclick="
|
|
6
|
-
<i class="bi bi-plus-lg me-1"></i> إضافة
|
|
5
|
+
onclick="loadScopeForm('{% url 'get_scope_form' %}')">
|
|
6
|
+
<i class="bi bi-plus-lg me-1"></i> إضافة نطاق
|
|
7
7
|
</button>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{% extends "base.html" %}
|
|
2
2
|
{% load django_tables2 %}
|
|
3
|
+
{% load static %}
|
|
3
4
|
{% load crispy_forms_tags %}
|
|
4
5
|
|
|
5
6
|
{% block title %}الاعدادت - السجل{% endblock %}
|
|
6
7
|
|
|
7
8
|
{% block content %}
|
|
9
|
+
<link rel="stylesheet" href="{% static 'users/css/style.css' %}">
|
|
8
10
|
|
|
9
11
|
<form method="get" class="py-3 g-2 no-print m-0">
|
|
10
12
|
{% crispy filter.form %}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
<button type="button" class="btn btn-outline-primary ms-auto" data-bs-toggle="collapse"
|
|
25
25
|
data-bs-target="#{{ id }}_detailed_list" aria-expanded="false" aria-controls="{{ id }}_detailed_list">
|
|
26
|
-
<i class="bi bi-list-check"></i> إظهار كل الصلاحيات
|
|
26
|
+
<i class="bi bi-list-check" title="إظهار قائمة تحتوي على كل الصلاحيات"></i> إظهار كل الصلاحيات
|
|
27
27
|
</button>
|
|
28
28
|
</div>
|
|
29
29
|
</div>
|
users/urls.py
CHANGED
|
@@ -5,7 +5,7 @@ from . import views
|
|
|
5
5
|
from django.contrib.auth import views as auth_views
|
|
6
6
|
|
|
7
7
|
urlpatterns = [
|
|
8
|
-
path('login/',
|
|
8
|
+
path('login/', views.CustomLoginView.as_view(), name='login'),
|
|
9
9
|
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
|
|
10
10
|
path("users/", views.UserListView.as_view(), name="manage_users"),
|
|
11
11
|
path('users/create/', views.create_user, name='create_user'),
|
|
@@ -17,11 +17,11 @@ urlpatterns = [
|
|
|
17
17
|
path('reset_password/<int:pk>/', views.reset_password, name="reset_password"),
|
|
18
18
|
path("users/<int:pk>/", views.UserDetailView.as_view(), name="user_detail"),
|
|
19
19
|
|
|
20
|
-
#
|
|
21
|
-
path("
|
|
22
|
-
path("
|
|
23
|
-
path("
|
|
24
|
-
path("
|
|
25
|
-
path("
|
|
26
|
-
path("
|
|
20
|
+
# Scope Management URLs
|
|
21
|
+
path("scopes/manage/", views.manage_scopes, name="manage_scopes"),
|
|
22
|
+
path("scopes/form/", views.get_scope_form, name="get_scope_form"),
|
|
23
|
+
path("scopes/form/<int:pk>/", views.get_scope_form, name="get_scope_form"),
|
|
24
|
+
path("scopes/save/", views.save_scope, name="save_scope"),
|
|
25
|
+
path("scopes/save/<int:pk>/", views.save_scope, name="save_scope"),
|
|
26
|
+
path("scopes/delete/<int:pk>/", views.delete_scope, name="delete_scope"),
|
|
27
27
|
]
|