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/views.py
CHANGED
|
@@ -11,20 +11,23 @@ from django_tables2 import RequestConfig, SingleTableView, SingleTableMixin
|
|
|
11
11
|
from django_filters.views import FilterView
|
|
12
12
|
from django.views.generic.detail import DetailView
|
|
13
13
|
from django.apps import apps
|
|
14
|
+
from django.utils.module_loading import import_string
|
|
15
|
+
from django.contrib.auth.views import LoginView
|
|
16
|
+
from django.conf import settings
|
|
14
17
|
|
|
15
18
|
# Project imports
|
|
16
19
|
#################
|
|
17
20
|
|
|
18
21
|
from .signals import get_client_ip
|
|
19
|
-
from .tables import UserTable
|
|
22
|
+
from .tables import UserTable
|
|
20
23
|
from .forms import CustomUserCreationForm, CustomUserChangeForm, ArabicPasswordChangeForm, ResetPasswordForm, UserProfileEditForm
|
|
21
|
-
from .filters import UserFilter
|
|
22
|
-
from .models import UserActivityLog
|
|
24
|
+
from .filters import UserFilter
|
|
23
25
|
|
|
24
26
|
User = get_user_model() # Use custom user model
|
|
25
27
|
|
|
26
28
|
# Helper Function to log actions
|
|
27
29
|
def log_user_action(request, instance, action, model_name):
|
|
30
|
+
UserActivityLog = apps.get_model('users', 'UserActivityLog')
|
|
28
31
|
UserActivityLog.objects.create(
|
|
29
32
|
user=request.user,
|
|
30
33
|
action=action,
|
|
@@ -38,6 +41,15 @@ def log_user_action(request, instance, action, model_name):
|
|
|
38
41
|
|
|
39
42
|
#####################################################################
|
|
40
43
|
|
|
44
|
+
# Custom Login View with Theme Injection
|
|
45
|
+
class CustomLoginView(LoginView):
|
|
46
|
+
def get_context_data(self, **kwargs):
|
|
47
|
+
context = super().get_context_data(**kwargs)
|
|
48
|
+
# Inject theme configuration from settings
|
|
49
|
+
context['theme'] = getattr(settings, 'MICRO_USERS_THEME', {})
|
|
50
|
+
return context
|
|
51
|
+
|
|
52
|
+
|
|
41
53
|
# Function to recognize staff
|
|
42
54
|
def is_staff(user):
|
|
43
55
|
return user.is_staff
|
|
@@ -64,12 +76,14 @@ class UserListView(LoginRequiredMixin, UserPassesTestMixin, FilterView, SingleTa
|
|
|
64
76
|
def get_queryset(self):
|
|
65
77
|
# Apply the filter and order by any logic you need
|
|
66
78
|
qs = super().get_queryset().order_by('date_joined')
|
|
79
|
+
# Exclude soft-deleted users
|
|
80
|
+
qs = qs.filter(deleted_at__isnull=True)
|
|
67
81
|
# Hide superuser entries from non-superusers
|
|
68
82
|
if not self.request.user.is_superuser:
|
|
69
83
|
qs = qs.exclude(is_superuser=True)
|
|
70
|
-
# Restrict to same
|
|
71
|
-
if self.request.user.
|
|
72
|
-
qs = qs.filter(
|
|
84
|
+
# Restrict to same scope
|
|
85
|
+
if self.request.user.scope:
|
|
86
|
+
qs = qs.filter(scope=self.request.user.scope)
|
|
73
87
|
return qs
|
|
74
88
|
|
|
75
89
|
def get_context_data(self, **kwargs):
|
|
@@ -91,12 +105,11 @@ def create_user(request):
|
|
|
91
105
|
form = CustomUserCreationForm(request.POST or None, user=request.user)
|
|
92
106
|
if form.is_valid():
|
|
93
107
|
user = form.save(commit=False)
|
|
94
|
-
# Auto-assign
|
|
95
|
-
if not request.user.is_superuser and request.user.
|
|
96
|
-
user.
|
|
108
|
+
# Auto-assign scope for non-superusers
|
|
109
|
+
if not request.user.is_superuser and request.user.scope:
|
|
110
|
+
user.scope = request.user.scope
|
|
97
111
|
user.save()
|
|
98
112
|
user.user_permissions.set(form.cleaned_data["permissions"])
|
|
99
|
-
log_user_action(request, user, "CREATE", "مستخدم")
|
|
100
113
|
return redirect("manage_users")
|
|
101
114
|
else:
|
|
102
115
|
return render(request, "users/user_form.html", {"form": form})
|
|
@@ -116,9 +129,9 @@ def edit_user(request, pk):
|
|
|
116
129
|
messages.error(request, "لا يمكن تعديل هذا الحساب!")
|
|
117
130
|
|
|
118
131
|
|
|
119
|
-
# Restrict to same
|
|
132
|
+
# Restrict to same scope
|
|
120
133
|
if not request.user.is_superuser:
|
|
121
|
-
if request.user.
|
|
134
|
+
if request.user.scope and user.scope != request.user.scope:
|
|
122
135
|
messages.error(request, "ليس لديك صلاحية لتعديل هذا المستخدم!")
|
|
123
136
|
return redirect('manage_users')
|
|
124
137
|
|
|
@@ -130,7 +143,6 @@ def edit_user(request, pk):
|
|
|
130
143
|
user = form.save(commit=False)
|
|
131
144
|
user.save()
|
|
132
145
|
user.user_permissions.set(form.cleaned_data["permissions"])
|
|
133
|
-
log_user_action(request, user, "UPDATE", "مستخدم")
|
|
134
146
|
return redirect("manage_users")
|
|
135
147
|
else:
|
|
136
148
|
# Validation errors will be automatically handled by the form object
|
|
@@ -147,24 +159,26 @@ def edit_user(request, pk):
|
|
|
147
159
|
def delete_user(request, pk):
|
|
148
160
|
user = get_object_or_404(User, pk=pk)
|
|
149
161
|
|
|
150
|
-
# Restrict to same
|
|
162
|
+
# Restrict to same scope
|
|
151
163
|
if not request.user.is_superuser:
|
|
152
|
-
if request.user.
|
|
164
|
+
if request.user.scope and user.scope != request.user.scope:
|
|
153
165
|
messages.error(request, "ليس لديك صلاحية لحذف هذا المستخدم!")
|
|
154
166
|
return redirect('manage_users')
|
|
155
167
|
|
|
156
168
|
if request.method == "POST":
|
|
157
|
-
|
|
158
|
-
user.
|
|
169
|
+
# Soft delete the user
|
|
170
|
+
user.is_active = False
|
|
171
|
+
user.deleted_at = timezone.now()
|
|
172
|
+
user.save()
|
|
159
173
|
return redirect("manage_users")
|
|
160
174
|
return redirect("manage_users") # Redirect instead of rendering a separate page
|
|
161
175
|
|
|
162
176
|
|
|
163
177
|
# Class Function for the Log
|
|
164
178
|
class UserActivityLogView(LoginRequiredMixin, UserPassesTestMixin, SingleTableMixin, FilterView):
|
|
165
|
-
model = UserActivityLog
|
|
166
|
-
table_class = UserActivityLogTable
|
|
167
|
-
filterset_class = UserActivityLogFilter
|
|
179
|
+
model = apps.get_model('users', 'UserActivityLog')
|
|
180
|
+
table_class = import_string('users.tables.UserActivityLogTable')
|
|
181
|
+
filterset_class = import_string('users.filters.UserActivityLogFilter')
|
|
168
182
|
template_name = "users/user_activity_log.html"
|
|
169
183
|
|
|
170
184
|
def test_func(self):
|
|
@@ -175,14 +189,14 @@ class UserActivityLogView(LoginRequiredMixin, UserPassesTestMixin, SingleTableMi
|
|
|
175
189
|
qs = super().get_queryset().order_by('-timestamp')
|
|
176
190
|
if not self.request.user.is_superuser:
|
|
177
191
|
qs = qs.exclude(user__is_superuser=True)
|
|
178
|
-
if self.request.user.
|
|
179
|
-
qs = qs.filter(
|
|
192
|
+
if self.request.user.scope:
|
|
193
|
+
qs = qs.filter(user__scope=self.request.user.scope)
|
|
180
194
|
return qs
|
|
181
195
|
|
|
182
196
|
def get_table(self, **kwargs):
|
|
183
197
|
table = super().get_table(**kwargs)
|
|
184
|
-
if self.request.user.
|
|
185
|
-
table.exclude = ('
|
|
198
|
+
if self.request.user.scope:
|
|
199
|
+
table.exclude = ('scope',)
|
|
186
200
|
return table
|
|
187
201
|
|
|
188
202
|
def get_context_data(self, **kwargs):
|
|
@@ -204,9 +218,11 @@ class UserDetailView(LoginRequiredMixin, UserPassesTestMixin, DetailView):
|
|
|
204
218
|
context = super().get_context_data(**kwargs)
|
|
205
219
|
|
|
206
220
|
# self.object is the User instance
|
|
221
|
+
UserActivityLog = apps.get_model('users', 'UserActivityLog')
|
|
207
222
|
logs_qs = UserActivityLog.objects.filter(user=self.object).order_by('-timestamp')
|
|
208
223
|
|
|
209
224
|
# Create table manually
|
|
225
|
+
UserActivityLogTableNoUser = import_string('users.tables.UserActivityLogTableNoUser')
|
|
210
226
|
table = UserActivityLogTableNoUser(logs_qs)
|
|
211
227
|
RequestConfig(self.request, paginate={'per_page': 10}).configure(table)
|
|
212
228
|
|
|
@@ -219,9 +235,9 @@ class UserDetailView(LoginRequiredMixin, UserPassesTestMixin, DetailView):
|
|
|
219
235
|
def reset_password(request, pk):
|
|
220
236
|
user = get_object_or_404(User, id=pk)
|
|
221
237
|
|
|
222
|
-
# Restrict to same
|
|
238
|
+
# Restrict to same scope
|
|
223
239
|
if not request.user.is_superuser:
|
|
224
|
-
if request.user.
|
|
240
|
+
if request.user.scope and user.scope != request.user.scope:
|
|
225
241
|
messages.error(request, "ليس لديك صلاحية لتعديل هذا المستخدم!")
|
|
226
242
|
return redirect('manage_users')
|
|
227
243
|
|
|
@@ -278,78 +294,84 @@ def edit_profile(request):
|
|
|
278
294
|
form = UserProfileEditForm(instance=request.user)
|
|
279
295
|
return render(request, 'users/profile/profile_edit.html', {'form': form})
|
|
280
296
|
|
|
281
|
-
#
|
|
297
|
+
# Scope Management Views
|
|
282
298
|
# ###########################
|
|
283
299
|
from django.template.loader import render_to_string
|
|
284
|
-
from .forms import DepartmentForm
|
|
285
|
-
from .models import Department
|
|
286
|
-
from .tables import DepartmentTable
|
|
287
300
|
|
|
288
301
|
@login_required # staff check handled in template or can be added here
|
|
289
302
|
@user_passes_test(is_staff)
|
|
290
|
-
def
|
|
303
|
+
def manage_scopes(request):
|
|
291
304
|
"""
|
|
292
305
|
Returns the initial modal content with the table.
|
|
293
306
|
"""
|
|
294
|
-
if request.user.
|
|
307
|
+
if request.user.scope:
|
|
295
308
|
return JsonResponse({'error': 'Permission denied.'}, status=403)
|
|
296
309
|
|
|
297
|
-
|
|
310
|
+
Scope = apps.get_model('users', 'Scope')
|
|
311
|
+
ScopeTable = import_string('users.tables.ScopeTable')
|
|
312
|
+
table = ScopeTable(Scope.objects.all())
|
|
298
313
|
RequestConfig(request, paginate={'per_page': 5}).configure(table)
|
|
299
314
|
|
|
300
315
|
context = {'table': table}
|
|
301
|
-
html = render_to_string('users/partials/
|
|
316
|
+
html = render_to_string('users/partials/scope_manager.html', context, request=request)
|
|
302
317
|
return JsonResponse({'html': html})
|
|
303
318
|
|
|
304
319
|
@login_required
|
|
305
320
|
@user_passes_test(is_staff)
|
|
306
|
-
def
|
|
321
|
+
def get_scope_form(request, pk=None):
|
|
307
322
|
"""
|
|
308
323
|
Returns the Add/Edit form partial.
|
|
309
324
|
"""
|
|
310
|
-
if request.user.
|
|
325
|
+
if request.user.scope:
|
|
311
326
|
return JsonResponse({'error': 'Permission denied.'}, status=403)
|
|
312
327
|
|
|
328
|
+
ScopeForm = import_string('users.forms.ScopeForm')
|
|
329
|
+
Scope = apps.get_model('users', 'Scope')
|
|
330
|
+
|
|
313
331
|
if pk:
|
|
314
|
-
|
|
315
|
-
form =
|
|
332
|
+
scope = get_object_or_404(Scope, pk=pk)
|
|
333
|
+
form = ScopeForm(instance=scope)
|
|
316
334
|
else:
|
|
317
|
-
form =
|
|
335
|
+
form = ScopeForm()
|
|
318
336
|
|
|
319
|
-
html = render_to_string('users/partials/
|
|
337
|
+
html = render_to_string('users/partials/scope_form.html', {'form': form, 'scope_id': pk}, request=request)
|
|
320
338
|
return JsonResponse({'html': html})
|
|
321
339
|
|
|
322
340
|
@login_required
|
|
323
341
|
@user_passes_test(is_staff)
|
|
324
|
-
def
|
|
342
|
+
def save_scope(request, pk=None):
|
|
325
343
|
"""
|
|
326
344
|
Handles form submission. Returns updated table on success, or form with errors on failure.
|
|
327
345
|
"""
|
|
328
|
-
if request.user.
|
|
346
|
+
if request.user.scope:
|
|
329
347
|
return JsonResponse({'error': 'Permission denied.'}, status=403)
|
|
348
|
+
|
|
349
|
+
ScopeForm = import_string('users.forms.ScopeForm')
|
|
350
|
+
Scope = apps.get_model('users', 'Scope')
|
|
351
|
+
ScopeTable = import_string('users.tables.ScopeTable')
|
|
330
352
|
|
|
331
353
|
if request.method == "POST":
|
|
332
354
|
if pk:
|
|
333
|
-
|
|
334
|
-
form =
|
|
355
|
+
scope = get_object_or_404(Scope, pk=pk)
|
|
356
|
+
form = ScopeForm(request.POST, instance=scope)
|
|
335
357
|
else:
|
|
336
|
-
form =
|
|
358
|
+
form = ScopeForm(request.POST)
|
|
337
359
|
|
|
338
360
|
if form.is_valid():
|
|
339
361
|
form.save()
|
|
340
362
|
# Return updated table
|
|
341
|
-
table =
|
|
363
|
+
table = ScopeTable(Scope.objects.all())
|
|
342
364
|
RequestConfig(request, paginate={'per_page': 5}).configure(table)
|
|
343
|
-
html = render_to_string('users/partials/
|
|
365
|
+
html = render_to_string('users/partials/scope_manager.html', {'table': table}, request=request)
|
|
344
366
|
return JsonResponse({'success': True, 'html': html})
|
|
345
367
|
else:
|
|
346
368
|
# Return form with errors
|
|
347
|
-
html = render_to_string('users/partials/
|
|
369
|
+
html = render_to_string('users/partials/scope_form.html', {'form': form, 'scope_id': pk}, request=request)
|
|
348
370
|
return JsonResponse({'success': False, 'html': html})
|
|
349
371
|
|
|
350
372
|
return JsonResponse({'success': False, 'error': 'Invalid method'})
|
|
351
373
|
|
|
352
374
|
@login_required
|
|
353
375
|
@user_passes_test(is_staff)
|
|
354
|
-
def
|
|
355
|
-
return JsonResponse({'success': False, 'error': 'تم تعطيل حذف
|
|
376
|
+
def delete_scope(request, pk):
|
|
377
|
+
return JsonResponse({'success': False, 'error': 'تم تعطيل حذف النطاقات لأسباب أمنية.'})
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
users/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
users/admin.py,sha256=tjaAXU5Un6P6sVF9uGXQP6aqyTguMf_jLquidvaD04Y,622
|
|
3
|
-
users/apps.py,sha256=Xb1nGvCl08KaUVqcUG82-jYdG6-KTVjaw_lgr5GIuYY,1133
|
|
4
|
-
users/filters.py,sha256=tXEW7W8V_DsttD0h2Dsc5KUjCQJG18hNqY-w02NqZCw,4722
|
|
5
|
-
users/forms.py,sha256=DQb2GCk9z6NyMtwoL7oCWD2sbTKQ23dMD3opKHCQkhI,16549
|
|
6
|
-
users/models.py,sha256=JZTu3IcsJfE0hkJe8ycFd5ITu-X2KD90Gno6iv0znuY,2479
|
|
7
|
-
users/signals.py,sha256=5Kd3KyfPT6740rvwZj4vy1yXsmjVhmaQ__RB8p5R5aE,1336
|
|
8
|
-
users/tables.py,sha256=m78Ano8k0CCINQrCn9lk08cag6RXjQqTbYBRyfJP64g,2780
|
|
9
|
-
users/urls.py,sha256=4pzhGcwWuLx8rDq_AnkhOhWeRigpOuDgnZCDwTBYi_E,1585
|
|
10
|
-
users/views.py,sha256=aN3qnkkdxWRjvmBAJ8Ay6bWrzZcNMvJpQ8wIQXbk5JQ,13602
|
|
11
|
-
users/migrations/0001_initial.py,sha256=lx9sSKS-lxHhI6gelVH52NOkwqEMJ32TvOJUn9zaOXM,4709
|
|
12
|
-
users/migrations/0002_alter_useractivitylog_action.py,sha256=I7NLxgcPTslCMuADcr1srXS_C_0y_LcZiAFFHBG5NsE,715
|
|
13
|
-
users/migrations/0003_department_alter_useractivitylog_options_and_more.py,sha256=RGTawFewYHxCUVIU8nCKWTTZ5OH22iIcfVv1jk_37K4,1289
|
|
14
|
-
users/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
users/static/css/login.css,sha256=SiJ6jBbWQAP2Nxt7DOTZbTcFYP9JEp557AuQZ9Eirb0,2120
|
|
16
|
-
users/static/img/default_profile.webp,sha256=BKUoQHo4z_fZnmc6z6I-KFvLEHahDr98U9LnDQKHLAM,3018
|
|
17
|
-
users/templates/registration/login.html,sha256=owbzO_XjqMeSncwWxkTzsvbkhjEZd7LdbblC3HBnld0,4091
|
|
18
|
-
users/templates/users/manage_users.html,sha256=rxXZPNXu_cmdnfxFU4K79n4FvjJe4hOZqoT-ybG_JTg,6329
|
|
19
|
-
users/templates/users/user_activity_log.html,sha256=41G7Wjv8ehBTSALwLLVzzoIBIo5hSM3FOw36olDINF8,481
|
|
20
|
-
users/templates/users/user_detail.html,sha256=yPiuOGF96rV8t2H1Fl2hhIq78N1588ZFbh5gbAezaxw,2053
|
|
21
|
-
users/templates/users/user_form.html,sha256=jcyI7OQZOY4ue4DajPtfjAt2SmAYO5ZgHNOqTp2-FO0,1352
|
|
22
|
-
users/templates/users/partials/department_actions.html,sha256=sdZr-awxOeXgPuYHYcrJx6msDcG3KcQzQlCpgseszQ4,283
|
|
23
|
-
users/templates/users/partials/department_form.html,sha256=WvjX8hZjqbLMmFCoACkcHZFXTlAwEdy0GyrMRaobwV8,777
|
|
24
|
-
users/templates/users/partials/department_manager.html,sha256=dB9_sgv2cRr0x3MKM62vDZzPDWZtkpf4ORFYnbYTIYM,399
|
|
25
|
-
users/templates/users/partials/user_actions.html,sha256=J44-sn0fMbLUWjdtlcf5YhgT5OYRykr1mFkeVXoI1ew,1543
|
|
26
|
-
users/templates/users/profile/profile.html,sha256=Ir8zvYUgDm89BlwVuuCsPJIVvTPa_2wH3HAaitPc4s8,2911
|
|
27
|
-
users/templates/users/profile/profile_edit.html,sha256=L9DUHlQHG-PmxwxBbSjgPk1dEmy0spPi6wXzT4hQe-U,4218
|
|
28
|
-
users/templates/users/widgets/grouped_permissions.html,sha256=q51WO-xMvg0aAqn6Ey8pMINDbFOHap_BgHcMxOvfLBw,9878
|
|
29
|
-
micro_users-1.5.0.dist-info/LICENSE,sha256=Fco89ULLSSxKkC2KKnx57SaT0R7WOkZfuk8IYcGiN50,1063
|
|
30
|
-
micro_users-1.5.0.dist-info/METADATA,sha256=V3Nwbu4x03gIRBkO1UIsLLVbPKD3xW-8K2CPqW4aKqs,10721
|
|
31
|
-
micro_users-1.5.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
32
|
-
micro_users-1.5.0.dist-info/top_level.txt,sha256=tWT24ZcWau2wrlbpU_h3mP2jRukyLaVYiyHBuOezpLQ,6
|
|
33
|
-
micro_users-1.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|