micro-users 1.4.1__py3-none-any.whl → 1.6.1__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.4.1.dist-info → micro_users-1.6.1.dist-info}/METADATA +57 -133
- micro_users-1.6.1.dist-info/RECORD +38 -0
- users/admin.py +21 -2
- users/apps.py +2 -1
- users/filters.py +6 -6
- users/forms.py +37 -14
- users/middleware.py +32 -0
- users/migrations/0003_scope_alter_customuser_options_and_more.py +47 -0
- users/models.py +20 -1
- 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 +29 -7
- users/templates/registration/login.html +29 -69
- users/templates/users/manage_users.html +88 -0
- users/templates/users/partials/scope_actions.html +9 -0
- users/templates/users/partials/scope_form.html +19 -0
- users/templates/users/partials/scope_manager.html +12 -0
- users/templates/{user_activity_log.html → users/user_activity_log.html} +2 -0
- users/urls.py +9 -1
- users/views.py +165 -24
- micro_users-1.4.1.dist-info/RECORD +0 -29
- {micro_users-1.4.1.dist-info → micro_users-1.6.1.dist-info}/LICENSE +0 -0
- {micro_users-1.4.1.dist-info → micro_users-1.6.1.dist-info}/WHEEL +0 -0
- {micro_users-1.4.1.dist-info → micro_users-1.6.1.dist-info}/top_level.txt +0 -0
- /users/templates/users/{user_actions.html → partials/user_actions.html} +0 -0
- /users/templates/users/{profile.html → profile/profile.html} +0 -0
- /users/templates/users/{profile_edit.html → profile/profile_edit.html} +0 -0
users/tables.py
CHANGED
|
@@ -1,12 +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
|
+
scope = tables.Column(verbose_name="النطاق", accessor='scope.name', default='-')
|
|
10
11
|
full_name = tables.Column(
|
|
11
12
|
verbose_name="الاسم الكامل",
|
|
12
13
|
accessor='user.full_name',
|
|
@@ -20,14 +21,14 @@ class UserTable(tables.Table):
|
|
|
20
21
|
)
|
|
21
22
|
# Action buttons for edit and delete (summoned column)
|
|
22
23
|
actions = tables.TemplateColumn(
|
|
23
|
-
template_name='users/user_actions.html',
|
|
24
|
+
template_name='users/partials/user_actions.html',
|
|
24
25
|
orderable=False,
|
|
25
26
|
verbose_name=''
|
|
26
27
|
)
|
|
27
28
|
class Meta:
|
|
28
29
|
model = User
|
|
29
30
|
template_name = "django_tables2/bootstrap5.html"
|
|
30
|
-
fields = ("username", "email", "full_name", "phone", "
|
|
31
|
+
fields = ("username", "email", "full_name", "phone", "scope", "is_staff", "is_active","last_login", "actions")
|
|
31
32
|
attrs = {'class': 'table table-hover align-middle'}
|
|
32
33
|
|
|
33
34
|
class UserActivityLogTable(tables.Table):
|
|
@@ -40,14 +41,35 @@ class UserActivityLogTable(tables.Table):
|
|
|
40
41
|
accessor='user.full_name',
|
|
41
42
|
order_by='user__first_name'
|
|
42
43
|
)
|
|
44
|
+
scope = tables.Column(
|
|
45
|
+
verbose_name="النطاق",
|
|
46
|
+
accessor='user.scope.name',
|
|
47
|
+
default='عام'
|
|
48
|
+
)
|
|
43
49
|
class Meta:
|
|
44
|
-
model = UserActivityLog
|
|
50
|
+
model = apps.get_model('users', 'UserActivityLog')
|
|
45
51
|
template_name = "django_tables2/bootstrap5.html"
|
|
46
|
-
fields = ("timestamp", "user", "full_name", "
|
|
52
|
+
fields = ("timestamp", "user", "full_name", "model_name", "action", "object_id", "number", "scope")
|
|
53
|
+
exclude = ("id", "ip_address", "user_agent")
|
|
47
54
|
attrs = {'class': 'table table-hover align-middle'}
|
|
55
|
+
row_attrs = {
|
|
56
|
+
"class": lambda record: "row-deleted" if record.user and getattr(record.user, "deleted_at", None) else ""
|
|
57
|
+
}
|
|
48
58
|
|
|
49
59
|
class UserActivityLogTableNoUser(UserActivityLogTable):
|
|
50
60
|
class Meta(UserActivityLogTable.Meta):
|
|
51
|
-
# Remove the 'user'
|
|
52
|
-
exclude = ("user", "user.full_name")
|
|
61
|
+
# Remove the 'user', 'user.full_name' and 'scope' columns
|
|
62
|
+
exclude = ("user", "user.full_name", "scope")
|
|
63
|
+
|
|
64
|
+
class ScopeTable(tables.Table):
|
|
65
|
+
actions = tables.TemplateColumn(
|
|
66
|
+
template_name='users/partials/scope_actions.html',
|
|
67
|
+
orderable=False,
|
|
68
|
+
verbose_name=''
|
|
69
|
+
)
|
|
70
|
+
class Meta:
|
|
71
|
+
model = apps.get_model('users', 'Scope')
|
|
72
|
+
template_name = "django_tables2/bootstrap5.html"
|
|
73
|
+
fields = ("name", "actions")
|
|
74
|
+
attrs = {'class': 'table table-hover align-middle'}
|
|
53
75
|
|
|
@@ -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,6 +20,11 @@
|
|
|
18
20
|
</div>
|
|
19
21
|
|
|
20
22
|
<div class="mt-3">
|
|
23
|
+
{% if not request.user.scope %}
|
|
24
|
+
<button type="button" class="btn btn-info no-print" onclick="loadScopeManager()">
|
|
25
|
+
<i class="bi bi-list me-1 h4"></i> إدارة النطاقات
|
|
26
|
+
</button>
|
|
27
|
+
{% endif %}
|
|
21
28
|
<a href="{% url 'create_user' %}" class="btn btn-secondary no-print" title="إضافة مستخدم جديد">
|
|
22
29
|
<i class="bi bi-person-plus-fill text-light me-1 h4"></i> إضافة مستخدم جديد
|
|
23
30
|
</a>
|
|
@@ -47,6 +54,87 @@
|
|
|
47
54
|
</form>
|
|
48
55
|
{% endif %}
|
|
49
56
|
|
|
57
|
+
<!-- Scope Management Modal -->
|
|
58
|
+
<div class="modal fade" id="scopeModal" tabindex="-1" aria-hidden="true">
|
|
59
|
+
<div class="modal-dialog modal-lg">
|
|
60
|
+
<div class="modal-content">
|
|
61
|
+
<div class="modal-header">
|
|
62
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="modal-body" id="scopeModalBody">
|
|
65
|
+
<!-- Content loaded via AJAX -->
|
|
66
|
+
<div class="text-center py-5">
|
|
67
|
+
<div class="spinner-border text-primary" role="status">
|
|
68
|
+
<span class="visually-hidden">Loading...</span>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<!-- Script for Scope Modal -->
|
|
77
|
+
<script>
|
|
78
|
+
// Defined globally so they can be called from injected HTML
|
|
79
|
+
function loadScopeManager() {
|
|
80
|
+
const modal = new bootstrap.Modal(document.getElementById('scopeModal'));
|
|
81
|
+
modal.show();
|
|
82
|
+
loadScopeForm("{% url 'manage_scopes' %}");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function loadScopeForm(url) {
|
|
86
|
+
// Default to manage_scopes if no URL (e.g. back button cases)
|
|
87
|
+
// But actually we want distinct behavior:
|
|
88
|
+
// 1. Initial Load (Table)
|
|
89
|
+
// 2. Load Form (Add/Edit)
|
|
90
|
+
|
|
91
|
+
// If url is for the form, we fetch it. If it's for the manager (table), we fetch that.
|
|
92
|
+
// The partials allow us to just dump html into the body.
|
|
93
|
+
|
|
94
|
+
fetch(url, {
|
|
95
|
+
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
|
96
|
+
})
|
|
97
|
+
.then(response => response.json())
|
|
98
|
+
.then(data => {
|
|
99
|
+
document.getElementById('scopeModalBody').innerHTML = data.html;
|
|
100
|
+
})
|
|
101
|
+
.catch(err => console.error('Error loading content:', err));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function submitScopeForm(e, url) {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
const form = e.target;
|
|
107
|
+
const formData = new FormData(form);
|
|
108
|
+
|
|
109
|
+
fetch(url, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
body: formData,
|
|
112
|
+
headers: {
|
|
113
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
.then(response => response.json())
|
|
117
|
+
.then(data => {
|
|
118
|
+
// Whether success or error, we replace the body with the returned HTML
|
|
119
|
+
// (Updated table or Form with errors)
|
|
120
|
+
document.getElementById('scopeModalBody').innerHTML = data.html;
|
|
121
|
+
})
|
|
122
|
+
.catch(err => console.error('Error submitting form:', err));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function deleteScope(url) {
|
|
126
|
+
fetch(url, {
|
|
127
|
+
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
|
128
|
+
})
|
|
129
|
+
.then(response => response.json())
|
|
130
|
+
.then(data => {
|
|
131
|
+
if (data.success) {
|
|
132
|
+
document.getElementById('scopeModalBody').innerHTML = data.html;
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
.catch(err => console.error('Error deleting scope:', err));
|
|
136
|
+
}
|
|
137
|
+
</script>
|
|
50
138
|
{% endblock %}
|
|
51
139
|
|
|
52
140
|
{% block scripts %}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{% load crispy_forms_tags %}
|
|
2
|
+
|
|
3
|
+
<div class="d-flex justify-content-between mb-3">
|
|
4
|
+
<h5 class="modal-title">{% if scope_id %}تعديل نطاق{% else %}إضافة نطاق جديد{% endif %}</h5>
|
|
5
|
+
<button class="btn btn-secondary" onclick="loadScopeForm('{% url 'manage_scopes' %}')">
|
|
6
|
+
<i class="bi bi-arrow-right me-1"></i> عودة للقائمة
|
|
7
|
+
</button>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<form id="scopeForm" onsubmit="submitScopeForm(event, '{% url 'save_scope' %}{% if scope_id %}/{{ scope_id }}{% endif %}')">
|
|
11
|
+
{% csrf_token %}
|
|
12
|
+
{% crispy form %}
|
|
13
|
+
|
|
14
|
+
<div class="mt-3 text-center">
|
|
15
|
+
<button type="submit" class="btn btn-primary w-100">
|
|
16
|
+
<i class="bi bi-save me-1"></i> حفظ
|
|
17
|
+
</button>
|
|
18
|
+
</div>
|
|
19
|
+
</form>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{% load django_tables2 %}
|
|
2
|
+
<div class="d-flex justify-content-between mb-3">
|
|
3
|
+
<h5 class="modal-title">إدارة النطاقات</h5>
|
|
4
|
+
<button class="btn btn-success"
|
|
5
|
+
onclick="loadScopeForm('{% url 'get_scope_form' %}')">
|
|
6
|
+
<i class="bi bi-plus-lg me-1"></i> إضافة نطاق
|
|
7
|
+
</button>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="table-responsive">
|
|
11
|
+
{% render_table table %}
|
|
12
|
+
</div>
|
|
@@ -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 %}
|
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'),
|
|
@@ -16,4 +16,12 @@ urlpatterns = [
|
|
|
16
16
|
path("logs/", views.UserActivityLogView.as_view(), name="user_activity_log"),
|
|
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
|
+
|
|
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"),
|
|
19
27
|
]
|