micro-users 1.4.1__tar.gz → 1.5.0__tar.gz

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 (41) hide show
  1. {micro_users-1.4.1 → micro_users-1.5.0}/PKG-INFO +9 -4
  2. {micro_users-1.4.1 → micro_users-1.5.0}/README.md +8 -4
  3. {micro_users-1.4.1 → micro_users-1.5.0}/micro_users.egg-info/PKG-INFO +9 -4
  4. {micro_users-1.4.1 → micro_users-1.5.0}/micro_users.egg-info/SOURCES.txt +8 -4
  5. {micro_users-1.4.1 → micro_users-1.5.0}/pyproject.toml +4 -1
  6. {micro_users-1.4.1 → micro_users-1.5.0}/setup.py +1 -1
  7. {micro_users-1.4.1 → micro_users-1.5.0}/users/admin.py +1 -1
  8. {micro_users-1.4.1 → micro_users-1.5.0}/users/filters.py +2 -3
  9. {micro_users-1.4.1 → micro_users-1.5.0}/users/forms.py +37 -14
  10. micro_users-1.5.0/users/migrations/0003_department_alter_useractivitylog_options_and_more.py +38 -0
  11. {micro_users-1.4.1 → micro_users-1.5.0}/users/models.py +15 -1
  12. {micro_users-1.4.1 → micro_users-1.5.0}/users/tables.py +24 -6
  13. micro_users-1.5.0/users/templates/users/manage_users.html +156 -0
  14. micro_users-1.5.0/users/templates/users/partials/department_actions.html +9 -0
  15. micro_users-1.5.0/users/templates/users/partials/department_form.html +19 -0
  16. micro_users-1.5.0/users/templates/users/partials/department_manager.html +12 -0
  17. {micro_users-1.4.1 → micro_users-1.5.0}/users/urls.py +8 -0
  18. {micro_users-1.4.1 → micro_users-1.5.0}/users/views.py +133 -14
  19. micro_users-1.4.1/users/templates/users/manage_users.html +0 -70
  20. {micro_users-1.4.1 → micro_users-1.5.0}/LICENSE +0 -0
  21. {micro_users-1.4.1 → micro_users-1.5.0}/MANIFEST.in +0 -0
  22. {micro_users-1.4.1 → micro_users-1.5.0}/micro_users.egg-info/dependency_links.txt +0 -0
  23. {micro_users-1.4.1 → micro_users-1.5.0}/micro_users.egg-info/requires.txt +0 -0
  24. {micro_users-1.4.1 → micro_users-1.5.0}/micro_users.egg-info/top_level.txt +0 -0
  25. {micro_users-1.4.1 → micro_users-1.5.0}/setup.cfg +0 -0
  26. {micro_users-1.4.1 → micro_users-1.5.0}/users/__init__.py +0 -0
  27. {micro_users-1.4.1 → micro_users-1.5.0}/users/apps.py +0 -0
  28. {micro_users-1.4.1 → micro_users-1.5.0}/users/migrations/0001_initial.py +0 -0
  29. {micro_users-1.4.1 → micro_users-1.5.0}/users/migrations/0002_alter_useractivitylog_action.py +0 -0
  30. {micro_users-1.4.1 → micro_users-1.5.0}/users/migrations/__init__.py +0 -0
  31. {micro_users-1.4.1 → micro_users-1.5.0}/users/signals.py +0 -0
  32. {micro_users-1.4.1 → micro_users-1.5.0}/users/static/css/login.css +0 -0
  33. {micro_users-1.4.1 → micro_users-1.5.0}/users/static/img/default_profile.webp +0 -0
  34. {micro_users-1.4.1 → micro_users-1.5.0}/users/templates/registration/login.html +0 -0
  35. {micro_users-1.4.1/users/templates/users → micro_users-1.5.0/users/templates/users/partials}/user_actions.html +0 -0
  36. {micro_users-1.4.1/users/templates/users → micro_users-1.5.0/users/templates/users/profile}/profile.html +0 -0
  37. {micro_users-1.4.1/users/templates/users → micro_users-1.5.0/users/templates/users/profile}/profile_edit.html +0 -0
  38. {micro_users-1.4.1/users/templates → micro_users-1.5.0/users/templates/users}/user_activity_log.html +0 -0
  39. {micro_users-1.4.1 → micro_users-1.5.0}/users/templates/users/user_detail.html +0 -0
  40. {micro_users-1.4.1 → micro_users-1.5.0}/users/templates/users/user_form.html +0 -0
  41. {micro_users-1.4.1 → micro_users-1.5.0}/users/templates/users/widgets/grouped_permissions.html +0 -0
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: micro_users
3
- Version: 1.4.1
3
+ Version: 1.5.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
7
7
  Author-email: DeBeski <debeski1@gmail.com>
8
8
  License: MIT
9
+ Project-URL: Homepage, https://github.com/debeski/micro-users
9
10
  Keywords: django,users,permissions,authentication
10
11
  Classifier: Framework :: Django
11
12
  Classifier: Framework :: Django :: 5
@@ -47,9 +48,11 @@ Requires-Dist: babel>=2.1
47
48
 
48
49
  ## Features
49
50
  - Custom AbstractUser model
50
- - User permissions system
51
+ - Department Management System
52
+ - Custom Grouped User permissions system *NEW*
53
+ - Custom Grouped User permissions system *NEW*
51
54
  - Activity logging (login/logout, CRUD tracking)
52
- - Specific User detail and log view *new*
55
+ - Specific User detail and log view
53
56
  - Localization support
54
57
  - Admin interface integration
55
58
  - CRUD views and templates
@@ -244,6 +247,7 @@ All user management URLs are prefixed with `manage/` as configured. Below is the
244
247
  | `manage/profile/edit/` | `views.edit_profile` | Edit current profile |
245
248
  | `manage/logs/` | `views.UserActivityLogView.as_view()` | View activity logs |
246
249
  | `manage/reset_password/<int:pk>/` | `views.reset_password` | Reset user password |
250
+ | `manage/departments/manage/` | `views.manage_departments` | Department Manager (Modal) |
247
251
 
248
252
  ## Structure
249
253
  ```
@@ -258,7 +262,7 @@ users/
258
262
  ├── apps.py # Permissions Localization
259
263
  ├── admin.py # Admin UI integration
260
264
  ├── __init__.py # Python init
261
- ├── templates/ # HTML templates
265
+ ├── templates/ # HTML templates (includes partials)
262
266
  ├── static/ # CSS classes
263
267
  └── migrations/ # Database migrations
264
268
  ```
@@ -283,3 +287,4 @@ users/
283
287
  | v1.3.2 | • Minor table modifications |
284
288
  | v1.4.0 | • Redesigned Permissions UI (Grouped by App/Action) <br> • Added Global Bulk Permission Selectors <br> • Improved Arabic Localization for Permissions <br> • Optimized printing (hidden forms/buttons) <br> • Fixed various bugs and crashes |
285
289
  | v1.4.1 | • Changed "Administrative User" translation to "Responsible User" (مستخدم مسؤول) <br> • Enforced custom sorting order for Permissions (View -> Add -> Change -> Other) |
290
+ | v1.5.0 | • Department Management (Modal-based CRUD)<br> • Department field implementation<br> • Template refactoring (partials/, profile/, users/ for logs)<br> • Verbose names for models |
@@ -16,9 +16,11 @@
16
16
 
17
17
  ## Features
18
18
  - Custom AbstractUser model
19
- - User permissions system
19
+ - Department Management System
20
+ - Custom Grouped User permissions system *NEW*
21
+ - Custom Grouped User permissions system *NEW*
20
22
  - Activity logging (login/logout, CRUD tracking)
21
- - Specific User detail and log view *new*
23
+ - Specific User detail and log view
22
24
  - Localization support
23
25
  - Admin interface integration
24
26
  - CRUD views and templates
@@ -213,6 +215,7 @@ All user management URLs are prefixed with `manage/` as configured. Below is the
213
215
  | `manage/profile/edit/` | `views.edit_profile` | Edit current profile |
214
216
  | `manage/logs/` | `views.UserActivityLogView.as_view()` | View activity logs |
215
217
  | `manage/reset_password/<int:pk>/` | `views.reset_password` | Reset user password |
218
+ | `manage/departments/manage/` | `views.manage_departments` | Department Manager (Modal) |
216
219
 
217
220
  ## Structure
218
221
  ```
@@ -227,7 +230,7 @@ users/
227
230
  ├── apps.py # Permissions Localization
228
231
  ├── admin.py # Admin UI integration
229
232
  ├── __init__.py # Python init
230
- ├── templates/ # HTML templates
233
+ ├── templates/ # HTML templates (includes partials)
231
234
  ├── static/ # CSS classes
232
235
  └── migrations/ # Database migrations
233
236
  ```
@@ -251,4 +254,5 @@ users/
251
254
  | v1.3.1 | • Corrected a misplaced code that caused a crash when editing profile |
252
255
  | v1.3.2 | • Minor table modifications |
253
256
  | v1.4.0 | • Redesigned Permissions UI (Grouped by App/Action) <br> • Added Global Bulk Permission Selectors <br> • Improved Arabic Localization for Permissions <br> • Optimized printing (hidden forms/buttons) <br> • Fixed various bugs and crashes |
254
- | v1.4.1 | • Changed "Administrative User" translation to "Responsible User" (مستخدم مسؤول) <br> • Enforced custom sorting order for Permissions (View -> Add -> Change -> Other) |
257
+ | v1.4.1 | • Changed "Administrative User" translation to "Responsible User" (مستخدم مسؤول) <br> • Enforced custom sorting order for Permissions (View -> Add -> Change -> Other) |
258
+ | v1.5.0 | • Department Management (Modal-based CRUD)<br> • Department field implementation<br> • Template refactoring (partials/, profile/, users/ for logs)<br> • Verbose names for models |
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: micro-users
3
- Version: 1.4.1
3
+ Version: 1.5.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
7
7
  Author-email: DeBeski <debeski1@gmail.com>
8
8
  License: MIT
9
+ Project-URL: Homepage, https://github.com/debeski/micro-users
9
10
  Keywords: django,users,permissions,authentication
10
11
  Classifier: Framework :: Django
11
12
  Classifier: Framework :: Django :: 5
@@ -47,9 +48,11 @@ Requires-Dist: babel>=2.1
47
48
 
48
49
  ## Features
49
50
  - Custom AbstractUser model
50
- - User permissions system
51
+ - Department Management System
52
+ - Custom Grouped User permissions system *NEW*
53
+ - Custom Grouped User permissions system *NEW*
51
54
  - Activity logging (login/logout, CRUD tracking)
52
- - Specific User detail and log view *new*
55
+ - Specific User detail and log view
53
56
  - Localization support
54
57
  - Admin interface integration
55
58
  - CRUD views and templates
@@ -244,6 +247,7 @@ All user management URLs are prefixed with `manage/` as configured. Below is the
244
247
  | `manage/profile/edit/` | `views.edit_profile` | Edit current profile |
245
248
  | `manage/logs/` | `views.UserActivityLogView.as_view()` | View activity logs |
246
249
  | `manage/reset_password/<int:pk>/` | `views.reset_password` | Reset user password |
250
+ | `manage/departments/manage/` | `views.manage_departments` | Department Manager (Modal) |
247
251
 
248
252
  ## Structure
249
253
  ```
@@ -258,7 +262,7 @@ users/
258
262
  ├── apps.py # Permissions Localization
259
263
  ├── admin.py # Admin UI integration
260
264
  ├── __init__.py # Python init
261
- ├── templates/ # HTML templates
265
+ ├── templates/ # HTML templates (includes partials)
262
266
  ├── static/ # CSS classes
263
267
  └── migrations/ # Database migrations
264
268
  ```
@@ -283,3 +287,4 @@ users/
283
287
  | v1.3.2 | • Minor table modifications |
284
288
  | v1.4.0 | • Redesigned Permissions UI (Grouped by App/Action) <br> • Added Global Bulk Permission Selectors <br> • Improved Arabic Localization for Permissions <br> • Optimized printing (hidden forms/buttons) <br> • Fixed various bugs and crashes |
285
289
  | v1.4.1 | • Changed "Administrative User" translation to "Responsible User" (مستخدم مسؤول) <br> • Enforced custom sorting order for Permissions (View -> Add -> Change -> Other) |
290
+ | v1.5.0 | • Department Management (Modal-based CRUD)<br> • Department field implementation<br> • Template refactoring (partials/, profile/, users/ for logs)<br> • Verbose names for models |
@@ -20,15 +20,19 @@ users/urls.py
20
20
  users/views.py
21
21
  users/migrations/0001_initial.py
22
22
  users/migrations/0002_alter_useractivitylog_action.py
23
+ users/migrations/0003_department_alter_useractivitylog_options_and_more.py
23
24
  users/migrations/__init__.py
24
25
  users/static/css/login.css
25
26
  users/static/img/default_profile.webp
26
- users/templates/user_activity_log.html
27
27
  users/templates/registration/login.html
28
28
  users/templates/users/manage_users.html
29
- users/templates/users/profile.html
30
- users/templates/users/profile_edit.html
31
- users/templates/users/user_actions.html
29
+ users/templates/users/user_activity_log.html
32
30
  users/templates/users/user_detail.html
33
31
  users/templates/users/user_form.html
32
+ users/templates/users/partials/department_actions.html
33
+ users/templates/users/partials/department_form.html
34
+ users/templates/users/partials/department_manager.html
35
+ users/templates/users/partials/user_actions.html
36
+ users/templates/users/profile/profile.html
37
+ users/templates/users/profile/profile_edit.html
34
38
  users/templates/users/widgets/grouped_permissions.html
@@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
8
8
 
9
9
  [project]
10
10
  name = "micro_users"
11
- version = "1.4.1"
11
+ version = "1.5.0"
12
12
  description = "Arabic Django user management app with abstract user, permissions, and activity logging"
13
13
  readme = "README.md"
14
14
  requires-python = ">=3.11"
@@ -51,3 +51,6 @@ include = ["users"]
51
51
  # Include static/templates files (equivalent to include_package_data=True)
52
52
  [tool.setuptools]
53
53
  include-package-data = true
54
+
55
+ [project.urls]
56
+ Homepage = "https://github.com/debeski/micro-users"
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="micro_users",
8
- version="1.4.1",
8
+ version="1.5.0",
9
9
  author="DeBeski",
10
10
  author_email="debeski1@gmail.com",
11
11
  description="Arabic django user management app with abstract user, permissions, and activity logging",
@@ -9,7 +9,7 @@ User = get_user_model()
9
9
 
10
10
  class CustomUserAdmin(UserAdmin):
11
11
  model = User
12
- list_display = ['username', 'email', 'is_staff', 'is_active', 'phone', 'occupation']
12
+ list_display = ['username', 'email', 'is_staff', 'is_active', 'phone']
13
13
  list_filter = ['is_staff', 'is_active']
14
14
  search_fields = ['username', 'email']
15
15
  ordering = ['username']
@@ -39,7 +39,7 @@ class UserFilter(django_filters.FilterSet):
39
39
  Q(username__icontains=value) |
40
40
  Q(email__icontains=value) |
41
41
  Q(phone__icontains=value) |
42
- Q(occupation__icontains=value) |
42
+ Q(department__name__icontains=value) |
43
43
  Q(first_name__icontains=value) |
44
44
  Q(last_name__icontains=value)
45
45
  )
@@ -98,8 +98,7 @@ class UserActivityLogFilter(django_filters.FilterSet):
98
98
  return queryset.filter(
99
99
  Q(user__username__icontains=value) |
100
100
  Q(user__email__icontains=value) |
101
- Q(user__profile__phone__icontains=value) |
102
- Q(user__profile__occupation__icontains=value) |
101
+ Q(user__phone__icontains=value) |
103
102
  Q(action__icontains=value) |
104
103
  Q(model_name__icontains=value) |
105
104
  Q(number__icontains=value) |
@@ -14,6 +14,7 @@ from django.db.models import Q
14
14
 
15
15
 
16
16
  from django.forms.widgets import ChoiceWidget
17
+ from .models import Department
17
18
 
18
19
  User = get_user_model()
19
20
 
@@ -126,11 +127,16 @@ class CustomUserCreationForm(UserCreationForm):
126
127
 
127
128
  class Meta:
128
129
  model = User
129
- fields = ["username", "email", "password1", "password2", "first_name", "last_name", "phone", "occupation", "is_staff", "permissions", "is_active"]
130
+ fields = ["username", "email", "password1", "password2", "first_name", "last_name", "phone", "department", "is_staff", "permissions", "is_active"]
130
131
 
131
132
  def __init__(self, *args, **kwargs):
133
+ self.user = kwargs.pop('user', None)
132
134
  super().__init__(*args, **kwargs)
133
135
 
136
+ if self.user and not self.user.is_superuser and self.user.department:
137
+ self.fields['department'].initial = self.user.department
138
+ self.fields['department'].disabled = True
139
+
134
140
  self.fields["username"].label = "اسم المستخدم"
135
141
  self.fields["email"].label = "البريد الإلكتروني"
136
142
  self.fields["first_name"].label = "الاسم"
@@ -163,7 +169,7 @@ class CustomUserCreationForm(UserCreationForm):
163
169
  ),
164
170
  Div(
165
171
  Div(Field("phone", css_class="col-md-6"), css_class="col-md-6"),
166
- Div(Field("occupation", css_class="col-md-6"), css_class="col-md-6"),
172
+ Div(Field("department", css_class="col-md-6"), css_class="col-md-6"),
167
173
  css_class="row"
168
174
  ),
169
175
  HTML("<hr>"),
@@ -193,8 +199,8 @@ class CustomUserCreationForm(UserCreationForm):
193
199
  user = super().save(commit=False)
194
200
  if commit:
195
201
  user.save()
196
- # Manually set permissions
197
- user.user_permissions.set(self.cleaned_data["permissions"])
202
+ # Manually set permissions
203
+ user.user_permissions.set(self.cleaned_data["permissions"])
198
204
  return user
199
205
 
200
206
 
@@ -219,11 +225,15 @@ class CustomUserChangeForm(UserChangeForm):
219
225
 
220
226
  class Meta:
221
227
  model = User
222
- fields = ["username", "email", "first_name", "last_name", "phone", "occupation", "is_staff", "permissions", "is_active"]
228
+ fields = ["username", "email", "first_name", "last_name", "phone", "department", "is_staff", "permissions", "is_active"]
223
229
 
224
230
  def __init__(self, *args, **kwargs):
225
- user = kwargs.get('instance')
231
+ self.user = kwargs.pop('user', None)
232
+ user_instance = kwargs.get('instance')
226
233
  super().__init__(*args, **kwargs)
234
+
235
+ if self.user and not self.user.is_superuser and self.user.department:
236
+ self.fields['department'].disabled = True
227
237
 
228
238
  # Labels
229
239
  self.fields["username"].label = "اسم المستخدم"
@@ -239,8 +249,8 @@ class CustomUserChangeForm(UserChangeForm):
239
249
  self.fields["is_staff"].help_text = "يحدد ما إذا كان بإمكان المستخدم الوصول إلى قسم ادارة المستخدمين."
240
250
  self.fields["is_active"].help_text = "يحدد ما إذا كان يجب اعتبار هذا الحساب نشطًا. قم بإلغاء تحديد هذا الخيار بدلاً من الحذف."
241
251
 
242
- if user:
243
- self.fields["permissions"].initial = user.user_permissions.all()
252
+ if user_instance:
253
+ self.fields["permissions"].initial = user_instance.user_permissions.all()
244
254
 
245
255
  # Use Crispy Forms Layout helper
246
256
  self.helper = FormHelper()
@@ -256,7 +266,7 @@ class CustomUserChangeForm(UserChangeForm):
256
266
  ),
257
267
  Div(
258
268
  Div(Field("phone", css_class="col-md-6"), css_class="col-md-6"),
259
- Div(Field("occupation", css_class="col-md-6"), css_class="col-md-6"),
269
+ Div(Field("department", css_class="col-md-6"), css_class="col-md-6"),
260
270
  css_class="row"
261
271
  ),
262
272
  HTML("<hr>"),
@@ -294,8 +304,8 @@ class CustomUserChangeForm(UserChangeForm):
294
304
  user = super().save(commit=False)
295
305
  if commit:
296
306
  user.save()
297
- # Manually set permissions
298
- user.user_permissions.set(self.cleaned_data["permissions"])
307
+ # Manually set permissions
308
+ user.user_permissions.set(self.cleaned_data["permissions"])
299
309
  return user
300
310
 
301
311
 
@@ -329,7 +339,7 @@ class ResetPasswordForm(SetPasswordForm):
329
339
  class UserProfileEditForm(forms.ModelForm):
330
340
  class Meta:
331
341
  model = User
332
- fields = ['username', 'email', 'first_name', 'last_name', 'phone', 'occupation', 'profile_picture']
342
+ fields = ['username', 'email', 'first_name', 'last_name', 'phone', 'profile_picture']
333
343
 
334
344
  def __init__(self, *args, **kwargs):
335
345
  super().__init__(*args, **kwargs)
@@ -338,7 +348,6 @@ class UserProfileEditForm(forms.ModelForm):
338
348
  self.fields['first_name'].label = "الاسم الاول"
339
349
  self.fields['last_name'].label = "اللقب"
340
350
  self.fields['phone'].label = "رقم الهاتف"
341
- self.fields['occupation'].label = "جهة العمل"
342
351
  self.fields['profile_picture'].label = "الصورة الشخصية"
343
352
 
344
353
  def clean_profile_picture(self):
@@ -369,4 +378,18 @@ class ArabicPasswordChangeForm(PasswordChangeForm):
369
378
  new_password2 = forms.CharField(
370
379
  label=_('تأكيد كلمة المرور الجديدة'),
371
380
  widget=forms.PasswordInput(attrs={'autocomplete': 'new-password', 'dir': 'rtl'}),
372
- )
381
+ )
382
+
383
+ class DepartmentForm(forms.ModelForm):
384
+ class Meta:
385
+ model = Department
386
+ fields = ['name']
387
+
388
+ def __init__(self, *args, **kwargs):
389
+ super().__init__(*args, **kwargs)
390
+ self.fields['name'].label = "اسم القسم"
391
+ self.helper = FormHelper()
392
+ self.helper.form_tag = False
393
+ self.helper.layout = Layout(
394
+ Field('name', css_class='col-12'),
395
+ )
@@ -0,0 +1,38 @@
1
+ # Generated by Django 5.2.8 on 2026-01-26 02:15
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('users', '0002_alter_useractivitylog_action'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name='Department',
16
+ fields=[
17
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18
+ ('name', models.CharField(max_length=100, verbose_name='القسم')),
19
+ ],
20
+ options={
21
+ 'verbose_name': 'قسم',
22
+ 'verbose_name_plural': 'الأقسام',
23
+ },
24
+ ),
25
+ migrations.AlterModelOptions(
26
+ name='useractivitylog',
27
+ options={'verbose_name': 'حركة سجل', 'verbose_name_plural': 'حركات السجل'},
28
+ ),
29
+ migrations.RemoveField(
30
+ model_name='customuser',
31
+ name='occupation',
32
+ ),
33
+ migrations.AddField(
34
+ model_name='customuser',
35
+ name='department',
36
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.department', verbose_name='القسم'),
37
+ ),
38
+ ]
@@ -5,9 +5,19 @@ from django.contrib.auth.models import AbstractUser
5
5
  from django.conf import settings # Use this to reference the custom user model
6
6
  from django.contrib.postgres.fields import JSONField
7
7
 
8
+ class Department(models.Model):
9
+ name = models.CharField(max_length=100, verbose_name="القسم")
10
+
11
+ def __str__(self):
12
+ return self.name
13
+
14
+ class Meta:
15
+ verbose_name = "قسم"
16
+ verbose_name_plural = "الأقسام"
17
+
8
18
  class CustomUser(AbstractUser):
9
19
  phone = models.CharField(max_length=15, blank=True, null=True, verbose_name="رقم الهاتف")
10
- occupation = models.CharField(max_length=100, blank=True, null=True, verbose_name="جهة العمل")
20
+ department = models.ForeignKey('Department', on_delete=models.PROTECT, null=True, blank=True, verbose_name="القسم")
11
21
  profile_picture = models.ImageField(upload_to='profile_pictures/', null=True, blank=True)
12
22
 
13
23
  @property
@@ -39,3 +49,7 @@ class UserActivityLog(models.Model):
39
49
 
40
50
  def __str__(self):
41
51
  return f"{self.user} {self.action} {self.model_name or 'General'} at {self.timestamp}"
52
+
53
+ class Meta:
54
+ verbose_name = "حركة سجل"
55
+ verbose_name_plural = "حركات السجل"
@@ -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 .models import UserActivityLog, Department
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
+ department = tables.Column(verbose_name="القسم", accessor='department.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", "department", "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,31 @@ class UserActivityLogTable(tables.Table):
40
41
  accessor='user.full_name',
41
42
  order_by='user__first_name'
42
43
  )
44
+ department = tables.Column(
45
+ verbose_name="القسم",
46
+ accessor='user.department.name',
47
+ default='عام'
48
+ )
43
49
  class Meta:
44
50
  model = 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", "department", "action", "model_name", "object_id", "number")
47
53
  attrs = {'class': 'table table-hover align-middle'}
48
54
 
49
55
  class UserActivityLogTableNoUser(UserActivityLogTable):
50
56
  class Meta(UserActivityLogTable.Meta):
51
- # Remove the 'user' and 'user.full_name' columns
52
- exclude = ("user", "user.full_name")
57
+ # Remove the 'user', 'user.full_name' and 'department' columns
58
+ exclude = ("user", "user.full_name", "department")
59
+
60
+ class DepartmentTable(tables.Table):
61
+ actions = tables.TemplateColumn(
62
+ template_name='users/partials/department_actions.html',
63
+ orderable=False,
64
+ verbose_name=''
65
+ )
66
+ class Meta:
67
+ model = Department
68
+ template_name = "django_tables2/bootstrap5.html"
69
+ fields = ("name", "actions")
70
+ attrs = {'class': 'table table-hover align-middle'}
53
71
 
@@ -0,0 +1,156 @@
1
+ {% extends "base.html" %}
2
+ {% load django_tables2 %}
3
+ {% load crispy_forms_tags %}
4
+
5
+ {% block title %}الاعدادت - إدارة المستخدمين{% endblock %}
6
+
7
+ {% block content %}
8
+
9
+ <form method="get" class="py-3 g-2 no-print">
10
+ {% crispy filter.form %}
11
+ </form>
12
+
13
+ <div class="card border-light shadow">
14
+ <div class="card-body p-0 table-responsive-lg">
15
+ <!-- Render the table -->
16
+ {% render_table table %}
17
+ </div>
18
+ </div>
19
+
20
+ <div class="mt-3">
21
+ {% if not request.user.department %}
22
+ <button type="button" class="btn btn-info me-2 no-print" onclick="loadDepartmentManager()">
23
+ <i class="bi bi-list me-1"></i> إدارة الأقسام
24
+ </button>
25
+ {% endif %}
26
+ <a href="{% url 'create_user' %}" class="btn btn-secondary no-print" title="إضافة مستخدم جديد">
27
+ <i class="bi bi-person-plus-fill text-light me-1 h4"></i> إضافة مستخدم جديد
28
+ </a>
29
+ </div>
30
+
31
+ <!-- Delete Modal -->
32
+ {% if request.user.is_superuser %}
33
+ <form id="deleteForm" method="post">
34
+ {% csrf_token %}
35
+ <div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
36
+ <div class="modal-dialog">
37
+ <div class="modal-content">
38
+ <div class="modal-header">
39
+ <h5 class="modal-title" id="deleteModalLabel">تأكيد الحذف</h5>
40
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
41
+ </div>
42
+ <div class="modal-body">
43
+ <p>هل انت متأكد انك تريد حذف العضو <strong><span id="userName"></span></strong>؟</p>
44
+ </div>
45
+ <div class="modal-footer">
46
+ <button type="submit" class="btn btn-danger">نعم، احذف</button>
47
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إلغاء</button>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </form>
53
+ {% endif %}
54
+
55
+ <!-- Department Management Modal -->
56
+ <div class="modal fade" id="departmentModal" tabindex="-1" aria-hidden="true">
57
+ <div class="modal-dialog modal-lg">
58
+ <div class="modal-content">
59
+ <div class="modal-header">
60
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
61
+ </div>
62
+ <div class="modal-body" id="departmentModalBody">
63
+ <!-- Content loaded via AJAX -->
64
+ <div class="text-center py-5">
65
+ <div class="spinner-border text-primary" role="status">
66
+ <span class="visually-hidden">Loading...</span>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </div>
73
+
74
+ <!-- Script for Department Modal -->
75
+ <script>
76
+ // Defined globally so they can be called from injected HTML
77
+ function loadDepartmentManager() {
78
+ const modal = new bootstrap.Modal(document.getElementById('departmentModal'));
79
+ modal.show();
80
+ loadDepartmentForm("{% url 'manage_departments' %}");
81
+ }
82
+
83
+ function loadDepartmentForm(url) {
84
+ // Default to manage_departments if no URL (e.g. back button cases)
85
+ // But actually we want distinct behavior:
86
+ // 1. Initial Load (Table)
87
+ // 2. Load Form (Add/Edit)
88
+
89
+ // If url is for the form, we fetch it. If it's for the manager (table), we fetch that.
90
+ // The partials allow us to just dump html into the body.
91
+
92
+ fetch(url, {
93
+ headers: { 'X-Requested-With': 'XMLHttpRequest' }
94
+ })
95
+ .then(response => response.json())
96
+ .then(data => {
97
+ document.getElementById('departmentModalBody').innerHTML = data.html;
98
+ })
99
+ .catch(err => console.error('Error loading content:', err));
100
+ }
101
+
102
+ function submitDepartmentForm(e, url) {
103
+ e.preventDefault();
104
+ const form = e.target;
105
+ const formData = new FormData(form);
106
+
107
+ fetch(url, {
108
+ method: 'POST',
109
+ body: formData,
110
+ headers: {
111
+ 'X-Requested-With': 'XMLHttpRequest',
112
+ }
113
+ })
114
+ .then(response => response.json())
115
+ .then(data => {
116
+ // Whether success or error, we replace the body with the returned HTML
117
+ // (Updated table or Form with errors)
118
+ document.getElementById('departmentModalBody').innerHTML = data.html;
119
+ })
120
+ .catch(err => console.error('Error submitting form:', err));
121
+ }
122
+
123
+ function deleteDepartment(url) {
124
+ fetch(url, {
125
+ headers: { 'X-Requested-With': 'XMLHttpRequest' }
126
+ })
127
+ .then(response => response.json())
128
+ .then(data => {
129
+ if (data.success) {
130
+ document.getElementById('departmentModalBody').innerHTML = data.html;
131
+ }
132
+ })
133
+ .catch(err => console.error('Error deleting department:', err));
134
+ }
135
+ </script>
136
+ {% endblock %}
137
+
138
+ {% block scripts %}
139
+
140
+ <script nonce="{{ request.csp_nonce }}">
141
+ document.addEventListener("DOMContentLoaded", function () {
142
+ const deleteModal = document.getElementById("deleteModal");
143
+ deleteModal.addEventListener("show.bs.modal", function (event) {
144
+ let button = event.relatedTarget; // Button that triggered the modal
145
+ let userId = button.getAttribute("data-user-id");
146
+ let userName = button.getAttribute("data-user-name");
147
+ let form = document.getElementById("deleteForm");
148
+
149
+ // Update the modal content
150
+ document.getElementById("userName").textContent = userName;
151
+ form.action = "{% url 'delete_user' 0 %}".replace("/0/", `/${userId}/`);
152
+ });
153
+ });
154
+ </script>
155
+
156
+ {% endblock %}
@@ -0,0 +1,9 @@
1
+ <div class="d-flex gap-2 justify-content-center">
2
+ <button class="btn btn-sm btn-primary"
3
+ onclick="loadDepartmentForm('{% url 'get_department_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 department_id %}تعديل قسم{% else %}إضافة قسم جديد{% endif %}</h5>
5
+ <button class="btn btn-secondary" onclick="loadDepartmentForm('{% url 'manage_departments' %}')">
6
+ <i class="bi bi-arrow-right me-1"></i> عودة للقائمة
7
+ </button>
8
+ </div>
9
+
10
+ <form id="departmentForm" onsubmit="submitDepartmentForm(event, '{% url 'save_department' %}{% if department_id %}/{{ department_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="loadDepartmentForm('{% url 'get_department_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>
@@ -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
+ # Department Management URLs
21
+ path("departments/manage/", views.manage_departments, name="manage_departments"),
22
+ path("departments/form/", views.get_department_form, name="get_department_form"),
23
+ path("departments/form/<int:pk>/", views.get_department_form, name="get_department_form"),
24
+ path("departments/save/", views.save_department, name="save_department"),
25
+ path("departments/save/<int:pk>/", views.save_department, name="save_department"),
26
+ path("departments/delete/<int:pk>/", views.delete_department, name="delete_department"),
19
27
  ]
@@ -7,7 +7,7 @@ from django.contrib.auth.decorators import login_required, user_passes_test
7
7
  from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
8
8
  from django.http import JsonResponse
9
9
  from django.shortcuts import render, redirect, get_object_or_404
10
- from django_tables2 import RequestConfig, SingleTableView
10
+ 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
@@ -59,12 +59,17 @@ class UserListView(LoginRequiredMixin, UserPassesTestMixin, FilterView, SingleTa
59
59
  def test_func(self):
60
60
  return self.request.user.is_staff
61
61
 
62
+
63
+
62
64
  def get_queryset(self):
63
65
  # Apply the filter and order by any logic you need
64
66
  qs = super().get_queryset().order_by('date_joined')
65
67
  # Hide superuser entries from non-superusers
66
68
  if not self.request.user.is_superuser:
67
69
  qs = qs.exclude(is_superuser=True)
70
+ # Restrict to same department
71
+ if self.request.user.department:
72
+ qs = qs.filter(department=self.request.user.department)
68
73
  return qs
69
74
 
70
75
  def get_context_data(self, **kwargs):
@@ -83,15 +88,20 @@ class UserListView(LoginRequiredMixin, UserPassesTestMixin, FilterView, SingleTa
83
88
  @user_passes_test(is_staff)
84
89
  def create_user(request):
85
90
  if request.method == "POST":
86
- form = CustomUserCreationForm(request.POST or None)
91
+ form = CustomUserCreationForm(request.POST or None, user=request.user)
87
92
  if form.is_valid():
88
- user = form.save()
93
+ user = form.save(commit=False)
94
+ # Auto-assign department for non-superusers
95
+ if not request.user.is_superuser and request.user.department:
96
+ user.department = request.user.department
97
+ user.save()
98
+ user.user_permissions.set(form.cleaned_data["permissions"])
89
99
  log_user_action(request, user, "CREATE", "مستخدم")
90
100
  return redirect("manage_users")
91
101
  else:
92
102
  return render(request, "users/user_form.html", {"form": form})
93
103
  else:
94
- form = CustomUserCreationForm()
104
+ form = CustomUserCreationForm(user=request.user)
95
105
 
96
106
  return render(request, "users/user_form.html", {"form": form})
97
107
 
@@ -104,14 +114,22 @@ def edit_user(request, pk):
104
114
  # 🚫 Block staff users from editing superuser accounts
105
115
  if user.is_superuser and not request.user.is_superuser:
106
116
  messages.error(request, "لا يمكن تعديل هذا الحساب!")
107
- return redirect('manage_users')
117
+
118
+
119
+ # Restrict to same department
120
+ if not request.user.is_superuser:
121
+ if request.user.department and user.department != request.user.department:
122
+ messages.error(request, "ليس لديك صلاحية لتعديل هذا المستخدم!")
123
+ return redirect('manage_users')
108
124
 
109
125
  form_reset = ResetPasswordForm(user, data=request.POST or None)
110
126
 
111
127
  if request.method == "POST":
112
- form = CustomUserChangeForm(request.POST, instance=user)
128
+ form = CustomUserChangeForm(request.POST, instance=user, user=request.user)
113
129
  if form.is_valid():
114
- user = form.save()
130
+ user = form.save(commit=False)
131
+ user.save()
132
+ user.user_permissions.set(form.cleaned_data["permissions"])
115
133
  log_user_action(request, user, "UPDATE", "مستخدم")
116
134
  return redirect("manage_users")
117
135
  else:
@@ -119,7 +137,7 @@ def edit_user(request, pk):
119
137
  return render(request, "users/user_form.html", {"form": form, "edit_mode": True, "form_reset": form_reset})
120
138
 
121
139
  else:
122
- form = CustomUserChangeForm(instance=user)
140
+ form = CustomUserChangeForm(instance=user, user=request.user)
123
141
 
124
142
  return render(request, "users/user_form.html", {"form": form, "edit_mode": True, "form_reset": form_reset})
125
143
 
@@ -128,6 +146,13 @@ def edit_user(request, pk):
128
146
  @user_passes_test(is_superuser)
129
147
  def delete_user(request, pk):
130
148
  user = get_object_or_404(User, pk=pk)
149
+
150
+ # Restrict to same department
151
+ if not request.user.is_superuser:
152
+ if request.user.department and user.department != request.user.department:
153
+ messages.error(request, "ليس لديك صلاحية لحذف هذا المستخدم!")
154
+ return redirect('manage_users')
155
+
131
156
  if request.method == "POST":
132
157
  log_user_action(request, user, "DELETE", "مستخدم")
133
158
  user.delete()
@@ -136,22 +161,34 @@ def delete_user(request, pk):
136
161
 
137
162
 
138
163
  # Class Function for the Log
139
- class UserActivityLogView(LoginRequiredMixin, UserPassesTestMixin, SingleTableView):
164
+ class UserActivityLogView(LoginRequiredMixin, UserPassesTestMixin, SingleTableMixin, FilterView):
140
165
  model = UserActivityLog
141
166
  table_class = UserActivityLogTable
142
167
  filterset_class = UserActivityLogFilter
143
- template_name = "user_activity_log.html"
168
+ template_name = "users/user_activity_log.html"
144
169
 
145
170
  def test_func(self):
146
171
  return self.request.user.is_staff # Only staff can access logs
147
172
 
148
173
  def get_queryset(self):
149
174
  # Order by timestamp descending by default
150
- return super().get_queryset().order_by('-timestamp')
175
+ qs = super().get_queryset().order_by('-timestamp')
176
+ if not self.request.user.is_superuser:
177
+ qs = qs.exclude(user__is_superuser=True)
178
+ if self.request.user.department:
179
+ qs = qs.filter(user__department=self.request.user.department)
180
+ return qs
181
+
182
+ def get_table(self, **kwargs):
183
+ table = super().get_table(**kwargs)
184
+ if self.request.user.department:
185
+ table.exclude = ('department',)
186
+ return table
151
187
 
152
188
  def get_context_data(self, **kwargs):
153
189
  context = super().get_context_data(**kwargs)
154
- context["filter"] = self.filterset_class # Make sure 'filter' is added
190
+ # Handle the filter object
191
+ context['filter'] = self.filterset
155
192
  return context
156
193
 
157
194
 
@@ -182,6 +219,12 @@ class UserDetailView(LoginRequiredMixin, UserPassesTestMixin, DetailView):
182
219
  def reset_password(request, pk):
183
220
  user = get_object_or_404(User, id=pk)
184
221
 
222
+ # Restrict to same department
223
+ if not request.user.is_superuser:
224
+ if request.user.department and user.department != request.user.department:
225
+ messages.error(request, "ليس لديك صلاحية لتعديل هذا المستخدم!")
226
+ return redirect('manage_users')
227
+
185
228
  if request.method == "POST":
186
229
  form = ResetPasswordForm(user=user, data=request.POST) # ✅ Correct usage with SetPasswordForm
187
230
  if form.is_valid():
@@ -213,7 +256,7 @@ def user_profile(request):
213
256
  messages.error(request, "هناك خطأ في البيانات المدخلة")
214
257
  print(password_form.errors) # You can log or print errors here for debugging
215
258
 
216
- return render(request, 'users/profile.html', {
259
+ return render(request, 'users/profile/profile.html', {
217
260
  'user': user,
218
261
  'password_form': password_form
219
262
  })
@@ -233,4 +276,80 @@ def edit_profile(request):
233
276
  messages.error(request, 'حدث خطأ أثناء حفظ التغييرات')
234
277
  else:
235
278
  form = UserProfileEditForm(instance=request.user)
236
- return render(request, 'users/profile_edit.html', {'form': form})
279
+ return render(request, 'users/profile/profile_edit.html', {'form': form})
280
+
281
+ # Department Management Views
282
+ # ###########################
283
+ from django.template.loader import render_to_string
284
+ from .forms import DepartmentForm
285
+ from .models import Department
286
+ from .tables import DepartmentTable
287
+
288
+ @login_required # staff check handled in template or can be added here
289
+ @user_passes_test(is_staff)
290
+ def manage_departments(request):
291
+ """
292
+ Returns the initial modal content with the table.
293
+ """
294
+ if request.user.department:
295
+ return JsonResponse({'error': 'Permission denied.'}, status=403)
296
+
297
+ table = DepartmentTable(Department.objects.all())
298
+ RequestConfig(request, paginate={'per_page': 5}).configure(table)
299
+
300
+ context = {'table': table}
301
+ html = render_to_string('users/partials/department_manager.html', context, request=request)
302
+ return JsonResponse({'html': html})
303
+
304
+ @login_required
305
+ @user_passes_test(is_staff)
306
+ def get_department_form(request, pk=None):
307
+ """
308
+ Returns the Add/Edit form partial.
309
+ """
310
+ if request.user.department:
311
+ return JsonResponse({'error': 'Permission denied.'}, status=403)
312
+
313
+ if pk:
314
+ department = get_object_or_404(Department, pk=pk)
315
+ form = DepartmentForm(instance=department)
316
+ else:
317
+ form = DepartmentForm()
318
+
319
+ html = render_to_string('users/partials/department_form.html', {'form': form, 'department_id': pk}, request=request)
320
+ return JsonResponse({'html': html})
321
+
322
+ @login_required
323
+ @user_passes_test(is_staff)
324
+ def save_department(request, pk=None):
325
+ """
326
+ Handles form submission. Returns updated table on success, or form with errors on failure.
327
+ """
328
+ if request.user.department:
329
+ return JsonResponse({'error': 'Permission denied.'}, status=403)
330
+
331
+ if request.method == "POST":
332
+ if pk:
333
+ department = get_object_or_404(Department, pk=pk)
334
+ form = DepartmentForm(request.POST, instance=department)
335
+ else:
336
+ form = DepartmentForm(request.POST)
337
+
338
+ if form.is_valid():
339
+ form.save()
340
+ # Return updated table
341
+ table = DepartmentTable(Department.objects.all())
342
+ RequestConfig(request, paginate={'per_page': 5}).configure(table)
343
+ html = render_to_string('users/partials/department_manager.html', {'table': table}, request=request)
344
+ return JsonResponse({'success': True, 'html': html})
345
+ else:
346
+ # Return form with errors
347
+ html = render_to_string('users/partials/department_form.html', {'form': form, 'department_id': pk}, request=request)
348
+ return JsonResponse({'success': False, 'html': html})
349
+
350
+ return JsonResponse({'success': False, 'error': 'Invalid method'})
351
+
352
+ @login_required
353
+ @user_passes_test(is_staff)
354
+ def delete_department(request, pk):
355
+ return JsonResponse({'success': False, 'error': 'تم تعطيل حذف الأقسام لأسباب أمنية.'})
@@ -1,70 +0,0 @@
1
- {% extends "base.html" %}
2
- {% load django_tables2 %}
3
- {% load crispy_forms_tags %}
4
-
5
- {% block title %}الاعدادت - إدارة المستخدمين{% endblock %}
6
-
7
- {% block content %}
8
-
9
- <form method="get" class="py-3 g-2 no-print">
10
- {% crispy filter.form %}
11
- </form>
12
-
13
- <div class="card border-light shadow">
14
- <div class="card-body p-0 table-responsive-lg">
15
- <!-- Render the table -->
16
- {% render_table table %}
17
- </div>
18
- </div>
19
-
20
- <div class="mt-3">
21
- <a href="{% url 'create_user' %}" class="btn btn-secondary no-print" title="إضافة مستخدم جديد">
22
- <i class="bi bi-person-plus-fill text-light me-1 h4"></i> إضافة مستخدم جديد
23
- </a>
24
- </div>
25
-
26
- <!-- Delete Modal -->
27
- {% if request.user.is_superuser %}
28
- <form id="deleteForm" method="post">
29
- {% csrf_token %}
30
- <div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
31
- <div class="modal-dialog">
32
- <div class="modal-content">
33
- <div class="modal-header">
34
- <h5 class="modal-title" id="deleteModalLabel">تأكيد الحذف</h5>
35
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
36
- </div>
37
- <div class="modal-body">
38
- <p>هل انت متأكد انك تريد حذف العضو <strong><span id="userName"></span></strong>؟</p>
39
- </div>
40
- <div class="modal-footer">
41
- <button type="submit" class="btn btn-danger">نعم، احذف</button>
42
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إلغاء</button>
43
- </div>
44
- </div>
45
- </div>
46
- </div>
47
- </form>
48
- {% endif %}
49
-
50
- {% endblock %}
51
-
52
- {% block scripts %}
53
-
54
- <script nonce="{{ request.csp_nonce }}">
55
- document.addEventListener("DOMContentLoaded", function () {
56
- const deleteModal = document.getElementById("deleteModal");
57
- deleteModal.addEventListener("show.bs.modal", function (event) {
58
- let button = event.relatedTarget; // Button that triggered the modal
59
- let userId = button.getAttribute("data-user-id");
60
- let userName = button.getAttribute("data-user-name");
61
- let form = document.getElementById("deleteForm");
62
-
63
- // Update the modal content
64
- document.getElementById("userName").textContent = userName;
65
- form.action = "{% url 'delete_user' 0 %}".replace("/0/", `/${userId}/`);
66
- });
67
- });
68
- </script>
69
-
70
- {% endblock %}
File without changes
File without changes
File without changes
File without changes