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.

Files changed (31) hide show
  1. {micro_users-1.4.1.dist-info → micro_users-1.6.1.dist-info}/METADATA +57 -133
  2. micro_users-1.6.1.dist-info/RECORD +38 -0
  3. users/admin.py +21 -2
  4. users/apps.py +2 -1
  5. users/filters.py +6 -6
  6. users/forms.py +37 -14
  7. users/middleware.py +32 -0
  8. users/migrations/0003_scope_alter_customuser_options_and_more.py +47 -0
  9. users/models.py +20 -1
  10. users/signals.py +107 -9
  11. users/static/img/login_logo.webp +0 -0
  12. users/static/{css → users/css}/login.css +50 -43
  13. users/static/users/css/style.css +201 -0
  14. users/static/users/js/anime.min.js +8 -0
  15. users/static/users/js/login.js +60 -0
  16. users/tables.py +29 -7
  17. users/templates/registration/login.html +29 -69
  18. users/templates/users/manage_users.html +88 -0
  19. users/templates/users/partials/scope_actions.html +9 -0
  20. users/templates/users/partials/scope_form.html +19 -0
  21. users/templates/users/partials/scope_manager.html +12 -0
  22. users/templates/{user_activity_log.html → users/user_activity_log.html} +2 -0
  23. users/urls.py +9 -1
  24. users/views.py +165 -24
  25. micro_users-1.4.1.dist-info/RECORD +0 -29
  26. {micro_users-1.4.1.dist-info → micro_users-1.6.1.dist-info}/LICENSE +0 -0
  27. {micro_users-1.4.1.dist-info → micro_users-1.6.1.dist-info}/WHEEL +0 -0
  28. {micro_users-1.4.1.dist-info → micro_users-1.6.1.dist-info}/top_level.txt +0 -0
  29. /users/templates/users/{user_actions.html → partials/user_actions.html} +0 -0
  30. /users/templates/users/{profile.html → profile/profile.html} +0 -0
  31. /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 .models import UserActivityLog
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", "occupation", "is_staff", "is_active","last_login", "actions")
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", "action", "model_name", "object_id", "number")
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' and 'user.full_name' columns
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-2 d-felx align-content-center">
30
+ <div class="right p-3 align-content-center">
11
31
  <div class="">
12
- <img src="{% static 'img/pdf_logo.webp' %}" alt="Ministry Logo" class="img-fluid mx-auto d-block "style="width: 75%;">
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:#c9aa5e;"
48
+ style="stop-color:var(--gradient-end);"
29
49
  offset="0"
30
50
  id="stop876" />
31
51
  <stop
32
- style="stop-color:#cb9447;"
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" style="font-size: 28px;" id="username" name="username" autofocus placeholder="اسم المستخدم" required>
62
+ <input type="text" class="login-input" id="username" name="username" autofocus placeholder="اسم المستخدم" required>
43
63
 
44
- <input type="password" style="font-size: 28px;" id="password" name="password" placeholder="كلمة المرور" required>
64
+ <input type="password" class="login-input" id="password" name="password" placeholder="كلمة المرور" required>
45
65
 
46
- <input type="submit" style="font-size: 25px;" class="mt-5 pe-3" value="دخول" id="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,9 @@
1
+ <div class="d-flex gap-2 justify-content-center">
2
+ <button class="btn btn-sm btn-primary"
3
+ onclick="loadScopeForm('{% url 'get_scope_form' record.id %}')"
4
+ title="تعديل">
5
+ <i class="bi bi-pencil-square"></i>
6
+ </button>
7
+
8
+
9
+ </div>
@@ -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/', auth_views.LoginView.as_view(), name='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
  ]