micro-users 1.2.3__py3-none-any.whl → 1.3.0__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.2.3.dist-info → micro_users-1.3.0.dist-info}/METADATA +16 -7
- {micro_users-1.2.3.dist-info → micro_users-1.3.0.dist-info}/RECORD +14 -13
- users/filters.py +0 -12
- users/migrations/0002_alter_useractivitylog_action.py +18 -0
- users/models.py +1 -0
- users/tables.py +8 -1
- users/templates/users/manage_users.html +1 -1
- users/templates/users/profile.html +1 -1
- users/templates/users/profile_edit.html +1 -1
- users/templates/users/user_actions.html +7 -8
- users/views.py +35 -18
- {micro_users-1.2.3.dist-info → micro_users-1.3.0.dist-info}/LICENSE +0 -0
- {micro_users-1.2.3.dist-info → micro_users-1.3.0.dist-info}/WHEEL +0 -0
- {micro_users-1.2.3.dist-info → micro_users-1.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: micro-users
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Arabic Django user management app with abstract user, permissions, and activity logging
|
|
5
5
|
Home-page: https://github.com/debeski/micro-users
|
|
6
6
|
Author: DeBeski
|
|
@@ -99,12 +99,19 @@ python manage.py migrate users
|
|
|
99
99
|
## Structure
|
|
100
100
|
```
|
|
101
101
|
users/
|
|
102
|
-
├──
|
|
103
|
-
├──
|
|
104
|
-
├──
|
|
105
|
-
├──
|
|
106
|
-
├──
|
|
107
|
-
|
|
102
|
+
├── views.py # CRUD operations
|
|
103
|
+
├── urls.py # URL routing
|
|
104
|
+
├── tables.py # User and Activity Log tables
|
|
105
|
+
├── signals.py # Logging signals
|
|
106
|
+
├── models.py # User model, permissions, activity logs
|
|
107
|
+
├── forms.py # Creation, edit,. etc.
|
|
108
|
+
├── filter.py # Search filters
|
|
109
|
+
├── apps.py # Permissions Localization
|
|
110
|
+
├── admin.py # Admin UI integration
|
|
111
|
+
├── __init__.py # Python init
|
|
112
|
+
├── templates/ # HTML templates
|
|
113
|
+
├── static/ # CSS classes
|
|
114
|
+
└── migrations/ # Database migrations
|
|
108
115
|
```
|
|
109
116
|
|
|
110
117
|
## Version History
|
|
@@ -121,3 +128,5 @@ users/
|
|
|
121
128
|
| v1.2.0 | • Added User Details view with specific user activity log |
|
|
122
129
|
| v1.2.1 | • Fixed a minor import bug |
|
|
123
130
|
| v1.2.3 | • Separated user detail view from table for consistency<br> • Optimized the new detail + log view for optimal compatibiliyy with users |
|
|
131
|
+
| v1.2.4 | • Fixed a couple of visual inconsistencies |
|
|
132
|
+
| v1.3.0 | • Patched a critical security permission issue<br> • Disabled ADMIN from being viewed/edited from other staff members<br> • Fixed an issue when sorting with full_name<br> • Enabled Logging for all actions |
|
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
users/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
users/admin.py,sha256=VF0V6hQ9Obcdinnjb8nBHaknas2p3O5w-6yAJ-DeARQ,636
|
|
3
3
|
users/apps.py,sha256=Xb1nGvCl08KaUVqcUG82-jYdG6-KTVjaw_lgr5GIuYY,1133
|
|
4
|
-
users/filters.py,sha256=
|
|
4
|
+
users/filters.py,sha256=neOdbyOSYVQXAQ2vKAW-0bcj7KIh9xc8UboHTlaZU4Q,4785
|
|
5
5
|
users/forms.py,sha256=GHC8pFm2i9PD3MVaakrgMXEszsBrXieHq7DYiAfo8Fw,14977
|
|
6
|
-
users/models.py,sha256=
|
|
6
|
+
users/models.py,sha256=V_SIyGGq2w_bww7YufMjqXMSKN1u9CkSMPuOLiwPjtc,2100
|
|
7
7
|
users/signals.py,sha256=5Kd3KyfPT6740rvwZj4vy1yXsmjVhmaQ__RB8p5R5aE,1336
|
|
8
|
-
users/tables.py,sha256=
|
|
8
|
+
users/tables.py,sha256=WwC7BMpzNrcfEatJj6gHMP8k_FGqer-Zfn9vZRB7kZo,2196
|
|
9
9
|
users/urls.py,sha256=FwQ9GVOBRQ4iXQ9UyLFI0aEAga0d5qL_miPNpmFPA-Q,1022
|
|
10
|
-
users/views.py,sha256=
|
|
10
|
+
users/views.py,sha256=_O6xRoej-JW4u-9vTK55Jr4SVRpgL2qG1BSx1o6xYRs,8741
|
|
11
11
|
users/migrations/0001_initial.py,sha256=lx9sSKS-lxHhI6gelVH52NOkwqEMJ32TvOJUn9zaOXM,4709
|
|
12
|
+
users/migrations/0002_alter_useractivitylog_action.py,sha256=I7NLxgcPTslCMuADcr1srXS_C_0y_LcZiAFFHBG5NsE,715
|
|
12
13
|
users/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
14
|
users/static/css/login.css,sha256=SiJ6jBbWQAP2Nxt7DOTZbTcFYP9JEp557AuQZ9Eirb0,2120
|
|
14
15
|
users/static/img/default_profile.webp,sha256=BKUoQHo4z_fZnmc6z6I-KFvLEHahDr98U9LnDQKHLAM,3018
|
|
15
16
|
users/templates/user_activity_log.html,sha256=S_FDN6vVLz_mB826yjeU9vtVGtzk7E_LKBmQIeYtdkQ,611
|
|
16
17
|
users/templates/registration/login.html,sha256=owbzO_XjqMeSncwWxkTzsvbkhjEZd7LdbblC3HBnld0,4091
|
|
17
|
-
users/templates/users/manage_users.html,sha256=
|
|
18
|
-
users/templates/users/profile.html,sha256=
|
|
19
|
-
users/templates/users/profile_edit.html,sha256=
|
|
20
|
-
users/templates/users/user_actions.html,sha256=
|
|
18
|
+
users/templates/users/manage_users.html,sha256=71SIAF6xyyKa863yLmqCHaqbGwATmpVmXRVtpy_330M,2942
|
|
19
|
+
users/templates/users/profile.html,sha256=Ir8zvYUgDm89BlwVuuCsPJIVvTPa_2wH3HAaitPc4s8,2911
|
|
20
|
+
users/templates/users/profile_edit.html,sha256=L9DUHlQHG-PmxwxBbSjgPk1dEmy0spPi6wXzT4hQe-U,4218
|
|
21
|
+
users/templates/users/user_actions.html,sha256=J44-sn0fMbLUWjdtlcf5YhgT5OYRykr1mFkeVXoI1ew,1543
|
|
21
22
|
users/templates/users/user_detail.html,sha256=QkJ-6jdtUdi8mM-V_MzqYcdoEkzXEsIeFMliNjgIOsc,2053
|
|
22
23
|
users/templates/users/user_form.html,sha256=jcyI7OQZOY4ue4DajPtfjAt2SmAYO5ZgHNOqTp2-FO0,1352
|
|
23
|
-
micro_users-1.
|
|
24
|
-
micro_users-1.
|
|
25
|
-
micro_users-1.
|
|
26
|
-
micro_users-1.
|
|
27
|
-
micro_users-1.
|
|
24
|
+
micro_users-1.3.0.dist-info/LICENSE,sha256=Fco89ULLSSxKkC2KKnx57SaT0R7WOkZfuk8IYcGiN50,1063
|
|
25
|
+
micro_users-1.3.0.dist-info/METADATA,sha256=tI_SdrsRx0JA1ppDV95GZUzYeOVvoUYzt7cPvEDUelQ,4277
|
|
26
|
+
micro_users-1.3.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
27
|
+
micro_users-1.3.0.dist-info/top_level.txt,sha256=tWT24ZcWau2wrlbpU_h3mP2jRukyLaVYiyHBuOezpLQ,6
|
|
28
|
+
micro_users-1.3.0.dist-info/RECORD,,
|
users/filters.py
CHANGED
|
@@ -14,11 +14,9 @@ class UserFilter(django_filters.FilterSet):
|
|
|
14
14
|
method='filter_keyword',
|
|
15
15
|
label='',
|
|
16
16
|
)
|
|
17
|
-
|
|
18
17
|
class Meta:
|
|
19
18
|
model = User
|
|
20
19
|
fields = []
|
|
21
|
-
|
|
22
20
|
def __init__(self, *args, **kwargs):
|
|
23
21
|
super().__init__(*args, **kwargs)
|
|
24
22
|
self.form.helper = FormHelper()
|
|
@@ -33,7 +31,6 @@ class UserFilter(django_filters.FilterSet):
|
|
|
33
31
|
css_class='form-row'
|
|
34
32
|
),
|
|
35
33
|
)
|
|
36
|
-
|
|
37
34
|
def filter_keyword(self, queryset, name, value):
|
|
38
35
|
"""
|
|
39
36
|
Filter the queryset by matching the keyword in username, email, phone, and occupation.
|
|
@@ -48,37 +45,31 @@ class UserFilter(django_filters.FilterSet):
|
|
|
48
45
|
)
|
|
49
46
|
|
|
50
47
|
|
|
51
|
-
|
|
52
48
|
class UserActivityLogFilter(django_filters.FilterSet):
|
|
53
49
|
keyword = django_filters.CharFilter(
|
|
54
50
|
method='filter_keyword',
|
|
55
51
|
label='',
|
|
56
52
|
)
|
|
57
|
-
|
|
58
53
|
year = django_filters.ChoiceFilter(
|
|
59
54
|
field_name="timestamp__year",
|
|
60
55
|
lookup_expr="exact",
|
|
61
56
|
choices=[],
|
|
62
57
|
empty_label="السنة",
|
|
63
58
|
)
|
|
64
|
-
|
|
65
59
|
class Meta:
|
|
66
60
|
model = UserActivityLog
|
|
67
61
|
fields = {
|
|
68
62
|
'timestamp': ['gte', 'lte'],
|
|
69
63
|
}
|
|
70
|
-
|
|
71
64
|
def __init__(self, *args, **kwargs):
|
|
72
65
|
super().__init__(*args, **kwargs)
|
|
73
66
|
|
|
74
67
|
# Fetch distinct years dynamically
|
|
75
68
|
years = UserActivityLog.objects.dates('timestamp', 'year').distinct()
|
|
76
69
|
self.filters['year'].extra['choices'] = [(year.year, year.year) for year in years]
|
|
77
|
-
|
|
78
70
|
self.filters['year'].field.widget.attrs.update({
|
|
79
71
|
'onchange': 'this.form.submit();'
|
|
80
72
|
})
|
|
81
|
-
|
|
82
73
|
self.form.helper = FormHelper()
|
|
83
74
|
self.form.helper.form_method = 'GET'
|
|
84
75
|
self.form.helper.form_class = 'form-inline'
|
|
@@ -100,7 +91,6 @@ class UserActivityLogFilter(django_filters.FilterSet):
|
|
|
100
91
|
css_class='form-row'
|
|
101
92
|
),
|
|
102
93
|
)
|
|
103
|
-
|
|
104
94
|
def filter_keyword(self, queryset, name, value):
|
|
105
95
|
"""
|
|
106
96
|
Filter the queryset by matching the keyword in username, email, phone, and occupation.
|
|
@@ -116,5 +106,3 @@ class UserActivityLogFilter(django_filters.FilterSet):
|
|
|
116
106
|
Q(ip_address__icontains=value)
|
|
117
107
|
)
|
|
118
108
|
|
|
119
|
-
|
|
120
|
-
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django 5.2.8 on 2025-12-08 14:58
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('users', '0001_initial'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name='useractivitylog',
|
|
15
|
+
name='action',
|
|
16
|
+
field=models.CharField(choices=[('LOGIN', 'تسجيل دخـول'), ('LOGOUT', 'تسجيل خـروج'), ('CREATE', 'انشـاء'), ('UPDATE', 'تعديـل'), ('DELETE', 'حــذف'), ('VIEW', 'عـرض'), ('DOWNLOAD', 'تحميل'), ('CONFIRM', 'تأكيـد'), ('REJECT', 'رفــض'), ('RESET', 'اعادة ضبط')], max_length=10, verbose_name='العملية'),
|
|
17
|
+
),
|
|
18
|
+
]
|
users/models.py
CHANGED
|
@@ -25,6 +25,7 @@ class UserActivityLog(models.Model):
|
|
|
25
25
|
('DOWNLOAD', 'تحميل'),
|
|
26
26
|
('CONFIRM', 'تأكيـد'),
|
|
27
27
|
('REJECT', 'رفــض'),
|
|
28
|
+
('RESET', 'اعادة ضبط'),
|
|
28
29
|
]
|
|
29
30
|
|
|
30
31
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, verbose_name="اسم المستخدم", null=True, blank=True)
|
users/tables.py
CHANGED
|
@@ -10,6 +10,8 @@ class UserTable(tables.Table):
|
|
|
10
10
|
username = tables.Column(verbose_name="اسم المستخدم")
|
|
11
11
|
email = tables.Column(verbose_name="البريد الالكتروني")
|
|
12
12
|
full_name = tables.Column(verbose_name="الاسم بالكامل", orderable=False,)
|
|
13
|
+
is_staff = tables.BooleanColumn(verbose_name="مسؤول")
|
|
14
|
+
is_active = tables.BooleanColumn(verbose_name="نشط")
|
|
13
15
|
last_login = tables.DateColumn(
|
|
14
16
|
format="H:i Y-m-d ", # This is the format you want for the timestamp
|
|
15
17
|
verbose_name="اخر دخول"
|
|
@@ -33,10 +35,15 @@ class UserActivityLogTable(tables.Table):
|
|
|
33
35
|
format="H:i Y-m-d ", # This is the format you want for the timestamp
|
|
34
36
|
verbose_name="وقت العملية"
|
|
35
37
|
)
|
|
38
|
+
full_name = tables.Column(
|
|
39
|
+
verbose_name="الاسم بالكامل",
|
|
40
|
+
accessor='user.full_name',
|
|
41
|
+
order_by='user__first_name'
|
|
42
|
+
)
|
|
36
43
|
class Meta:
|
|
37
44
|
model = UserActivityLog
|
|
38
45
|
template_name = "django_tables2/bootstrap5.html"
|
|
39
|
-
fields = ("timestamp", "user", "
|
|
46
|
+
fields = ("timestamp", "user", "full_name", "action", "model_name", "object_id", "number")
|
|
40
47
|
attrs = {'class': 'table table-hover align-middle'}
|
|
41
48
|
|
|
42
49
|
class UserActivityLogTableNoUser(UserActivityLogTable):
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
</div>
|
|
22
22
|
|
|
23
23
|
<div class="mt-3">
|
|
24
|
-
<a href="{% url 'create_user' %}" class="btn btn-
|
|
24
|
+
<a href="{% url 'create_user' %}" class="btn btn-secondary" title="إضافة مستخدم جديد">
|
|
25
25
|
<i class="bi bi-person-plus-fill text-light me-1 h4"></i> إضافة مستخدم جديد
|
|
26
26
|
</a>
|
|
27
27
|
</div>
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<div class="row mt-4">
|
|
28
28
|
<form method="post">
|
|
29
29
|
{% csrf_token %}
|
|
30
|
-
<a class="btn btn-
|
|
30
|
+
<a class="btn btn-secondary" href="{% url 'edit_profile' %}" role="button" title="تحديث البيانات">
|
|
31
31
|
<i class="bi bi-pencil-square text-light me-1 h4"></i> تحديث البيانات
|
|
32
32
|
</a>
|
|
33
33
|
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
<button type="submit" class="btn btn-success" title="حفظ التغييرات">
|
|
50
50
|
<i class="bi bi-check2-square text-light me-1 h4"></i> حفظ التغييرات
|
|
51
51
|
</button>
|
|
52
|
-
<a href="{% url 'user_profile' %}" class="btn btn-
|
|
52
|
+
<a href="{% url 'user_profile' %}" class="btn btn-secondary" title="إلغـــاء">
|
|
53
53
|
<i class="bi bi-x-circle text-light me-1 h4"></i> إلغـــاء
|
|
54
54
|
</a>
|
|
55
55
|
</div>
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
<div class="d-flex gap-2 align-items-center justify-content-end no-print">
|
|
2
|
+
{% if user.is_superuser or user.is_staff and not record.is_superuser %}
|
|
2
3
|
<div class="dropdown-center">
|
|
3
4
|
<a href="#" class="action-icon" id="actionDropdown{{ record.id }}" data-bs-toggle="dropdown" aria-expanded="false">
|
|
4
5
|
<i class="bi bi-three-dots-vertical text-dark"></i>
|
|
5
6
|
</a>
|
|
6
7
|
<ul class="dropdown-menu" aria-labelledby="actionDropdown{{ record.id }}">
|
|
7
|
-
|
|
8
|
-
<a class="dropdown-item" href="#" title="عرض">
|
|
9
|
-
<i class="bi bi-person-lines-fill text-dark me-1 h5"> </i> عرض
|
|
10
|
-
</a>
|
|
11
|
-
</li> {% endcomment %}
|
|
8
|
+
|
|
12
9
|
<li>
|
|
13
10
|
<a class="dropdown-item" href="{% url 'user_detail' record.pk %}" title="عرض">
|
|
14
|
-
<i class="bi bi-person-
|
|
11
|
+
<i class="bi bi-person-lines-fill text-dark me-1 h5"> </i> عرض
|
|
15
12
|
</a>
|
|
16
13
|
</li>
|
|
17
|
-
|
|
14
|
+
|
|
15
|
+
{% if user.is_superuser or user.is_staff and not record.is_superuser %}
|
|
18
16
|
<li>
|
|
19
17
|
<a class="dropdown-item" href="{% url 'edit_user' record.pk %}" title="تعديل">
|
|
20
|
-
<i class="bi bi-person-dash-fill text-dark me-1 h5"
|
|
18
|
+
<i class="bi bi-person-dash-fill text-dark me-1 h5"></i> تعديل
|
|
21
19
|
</a>
|
|
22
20
|
</li>
|
|
23
21
|
{% endif %}
|
|
@@ -31,4 +29,5 @@
|
|
|
31
29
|
{% endif %}
|
|
32
30
|
</ul>
|
|
33
31
|
</div>
|
|
32
|
+
{% endif %}
|
|
34
33
|
</div>
|
users/views.py
CHANGED
|
@@ -10,6 +10,7 @@ from django.shortcuts import render, redirect, get_object_or_404
|
|
|
10
10
|
from django_tables2 import RequestConfig, SingleTableView
|
|
11
11
|
from django_filters.views import FilterView
|
|
12
12
|
from django.views.generic.detail import DetailView
|
|
13
|
+
from django.apps import apps
|
|
13
14
|
|
|
14
15
|
# Project imports
|
|
15
16
|
#################
|
|
@@ -22,6 +23,19 @@ from .models import UserActivityLog
|
|
|
22
23
|
|
|
23
24
|
User = get_user_model() # Use custom user model
|
|
24
25
|
|
|
26
|
+
# Helper Function to log actions
|
|
27
|
+
def log_user_action(request, instance, action, model_name):
|
|
28
|
+
UserActivityLog.objects.create(
|
|
29
|
+
user=request.user,
|
|
30
|
+
action=action,
|
|
31
|
+
model_name=model_name,
|
|
32
|
+
object_id=instance.pk,
|
|
33
|
+
number=instance.number if hasattr(instance, 'number') else '',
|
|
34
|
+
timestamp=timezone.now(),
|
|
35
|
+
ip_address=get_client_ip(request),
|
|
36
|
+
user_agent=request.META.get("HTTP_USER_AGENT", ""),
|
|
37
|
+
)
|
|
38
|
+
|
|
25
39
|
#####################################################################
|
|
26
40
|
|
|
27
41
|
# Function to recognize staff
|
|
@@ -48,7 +62,9 @@ class UserListView(LoginRequiredMixin, UserPassesTestMixin, FilterView, SingleTa
|
|
|
48
62
|
def get_queryset(self):
|
|
49
63
|
# Apply the filter and order by any logic you need
|
|
50
64
|
qs = super().get_queryset().order_by('date_joined')
|
|
51
|
-
#
|
|
65
|
+
# Hide superuser entries from non-superusers
|
|
66
|
+
if not self.request.user.is_superuser:
|
|
67
|
+
qs = qs.exclude(is_superuser=True)
|
|
52
68
|
return qs
|
|
53
69
|
|
|
54
70
|
def get_context_data(self, **kwargs):
|
|
@@ -66,11 +82,11 @@ class UserListView(LoginRequiredMixin, UserPassesTestMixin, FilterView, SingleTa
|
|
|
66
82
|
# Function for creating a new User
|
|
67
83
|
@user_passes_test(is_staff)
|
|
68
84
|
def create_user(request):
|
|
69
|
-
|
|
70
85
|
if request.method == "POST":
|
|
71
86
|
form = CustomUserCreationForm(request.POST or None)
|
|
72
87
|
if form.is_valid():
|
|
73
|
-
form.save()
|
|
88
|
+
user = form.save()
|
|
89
|
+
log_user_action(request, user, "CREATE", "مستخدم")
|
|
74
90
|
return redirect("manage_users")
|
|
75
91
|
else:
|
|
76
92
|
return render(request, "users/user_form.html", {"form": form})
|
|
@@ -89,7 +105,8 @@ def edit_user(request, pk):
|
|
|
89
105
|
if request.method == "POST":
|
|
90
106
|
form = CustomUserChangeForm(request.POST, instance=user)
|
|
91
107
|
if form.is_valid():
|
|
92
|
-
form.save()
|
|
108
|
+
user = form.save()
|
|
109
|
+
log_user_action(request, user, "UPDATE", "مستخدم")
|
|
93
110
|
return redirect("manage_users")
|
|
94
111
|
else:
|
|
95
112
|
# Validation errors will be automatically handled by the form object
|
|
@@ -106,17 +123,8 @@ def edit_user(request, pk):
|
|
|
106
123
|
def delete_user(request, pk):
|
|
107
124
|
user = get_object_or_404(User, pk=pk)
|
|
108
125
|
if request.method == "POST":
|
|
126
|
+
log_user_action(request, user, "DELETE", "مستخدم")
|
|
109
127
|
user.delete()
|
|
110
|
-
UserActivityLog.objects.create(
|
|
111
|
-
user=request.user,
|
|
112
|
-
action="DELETE",
|
|
113
|
-
model_name='مستخدم',
|
|
114
|
-
object_id=user.pk,
|
|
115
|
-
number=user.username, # Save the relevant number
|
|
116
|
-
timestamp=timezone.now(),
|
|
117
|
-
ip_address=get_client_ip(request), # Assuming you have this function
|
|
118
|
-
user_agent=request.META.get("HTTP_USER_AGENT", ""),
|
|
119
|
-
)
|
|
120
128
|
return redirect("manage_users")
|
|
121
129
|
return redirect("manage_users") # Redirect instead of rendering a separate page
|
|
122
130
|
|
|
@@ -172,10 +180,11 @@ def reset_password(request, pk):
|
|
|
172
180
|
form = ResetPasswordForm(user=user, data=request.POST) # ✅ Correct usage with SetPasswordForm
|
|
173
181
|
if form.is_valid():
|
|
174
182
|
form.save()
|
|
175
|
-
|
|
183
|
+
log_user_action(request, user, "RESET", "رمز سري")
|
|
184
|
+
return redirect("manage_users")
|
|
176
185
|
else:
|
|
177
|
-
print("Form errors:", form.errors)
|
|
178
|
-
return redirect("edit_user", pk=pk)
|
|
186
|
+
print("Form errors:", form.errors)
|
|
187
|
+
return redirect("edit_user", pk=pk)
|
|
179
188
|
|
|
180
189
|
return redirect("manage_users") # Fallback redirect
|
|
181
190
|
|
|
@@ -189,6 +198,7 @@ def user_profile(request):
|
|
|
189
198
|
password_form = ArabicPasswordChangeForm(user, request.POST)
|
|
190
199
|
if password_form.is_valid():
|
|
191
200
|
password_form.save()
|
|
201
|
+
log_user_action(request, user, "UPDATE", "رمز سري")
|
|
192
202
|
update_session_auth_hash(request, password_form.user) # Prevent user from being logged out
|
|
193
203
|
messages.success(request, 'تم تغيير كلمة المرور بنجاح!')
|
|
194
204
|
return redirect('user_profile')
|
|
@@ -206,10 +216,17 @@ def user_profile(request):
|
|
|
206
216
|
# Function for editing the user profile
|
|
207
217
|
@login_required
|
|
208
218
|
def edit_profile(request):
|
|
219
|
+
|
|
220
|
+
# 🚫 Block staff users from editing superuser accounts
|
|
221
|
+
if request.user.is_superuser and not request.user.is_superuser:
|
|
222
|
+
messages.error(request, "لا يمكنك تعديل حساب المدير!")
|
|
223
|
+
return redirect('user_profile')
|
|
224
|
+
|
|
209
225
|
if request.method == 'POST':
|
|
210
226
|
form = UserProfileEditForm(request.POST, request.FILES, instance=request.user)
|
|
211
227
|
if form.is_valid():
|
|
212
|
-
form.save()
|
|
228
|
+
user = form.save()
|
|
229
|
+
log_user_action(request, user, "UPDATE", "بيانات شخصية")
|
|
213
230
|
messages.success(request, 'تم حفظ التغييرات بنجاح')
|
|
214
231
|
return redirect('user_profile')
|
|
215
232
|
else:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|