micro-users 1.2.4__tar.gz → 1.3.1__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 (35) hide show
  1. {micro_users-1.2.4 → micro_users-1.3.1}/PKG-INFO +16 -7
  2. {micro_users-1.2.4 → micro_users-1.3.1}/README.md +16 -7
  3. {micro_users-1.2.4 → micro_users-1.3.1}/micro_users.egg-info/PKG-INFO +16 -7
  4. {micro_users-1.2.4 → micro_users-1.3.1}/micro_users.egg-info/SOURCES.txt +1 -0
  5. {micro_users-1.2.4 → micro_users-1.3.1}/pyproject.toml +1 -1
  6. {micro_users-1.2.4 → micro_users-1.3.1}/setup.py +1 -1
  7. {micro_users-1.2.4 → micro_users-1.3.1}/users/filters.py +0 -12
  8. micro_users-1.3.1/users/migrations/0002_alter_useractivitylog_action.py +18 -0
  9. {micro_users-1.2.4 → micro_users-1.3.1}/users/models.py +1 -0
  10. {micro_users-1.2.4 → micro_users-1.3.1}/users/tables.py +8 -1
  11. {micro_users-1.2.4 → micro_users-1.3.1}/users/templates/users/user_actions.html +7 -8
  12. {micro_users-1.2.4 → micro_users-1.3.1}/users/views.py +35 -21
  13. {micro_users-1.2.4 → micro_users-1.3.1}/LICENSE +0 -0
  14. {micro_users-1.2.4 → micro_users-1.3.1}/MANIFEST.in +0 -0
  15. {micro_users-1.2.4 → micro_users-1.3.1}/micro_users.egg-info/dependency_links.txt +0 -0
  16. {micro_users-1.2.4 → micro_users-1.3.1}/micro_users.egg-info/requires.txt +0 -0
  17. {micro_users-1.2.4 → micro_users-1.3.1}/micro_users.egg-info/top_level.txt +0 -0
  18. {micro_users-1.2.4 → micro_users-1.3.1}/setup.cfg +0 -0
  19. {micro_users-1.2.4 → micro_users-1.3.1}/users/__init__.py +0 -0
  20. {micro_users-1.2.4 → micro_users-1.3.1}/users/admin.py +0 -0
  21. {micro_users-1.2.4 → micro_users-1.3.1}/users/apps.py +0 -0
  22. {micro_users-1.2.4 → micro_users-1.3.1}/users/forms.py +0 -0
  23. {micro_users-1.2.4 → micro_users-1.3.1}/users/migrations/0001_initial.py +0 -0
  24. {micro_users-1.2.4 → micro_users-1.3.1}/users/migrations/__init__.py +0 -0
  25. {micro_users-1.2.4 → micro_users-1.3.1}/users/signals.py +0 -0
  26. {micro_users-1.2.4 → micro_users-1.3.1}/users/static/css/login.css +0 -0
  27. {micro_users-1.2.4 → micro_users-1.3.1}/users/static/img/default_profile.webp +0 -0
  28. {micro_users-1.2.4 → micro_users-1.3.1}/users/templates/registration/login.html +0 -0
  29. {micro_users-1.2.4 → micro_users-1.3.1}/users/templates/user_activity_log.html +0 -0
  30. {micro_users-1.2.4 → micro_users-1.3.1}/users/templates/users/manage_users.html +0 -0
  31. {micro_users-1.2.4 → micro_users-1.3.1}/users/templates/users/profile.html +0 -0
  32. {micro_users-1.2.4 → micro_users-1.3.1}/users/templates/users/profile_edit.html +0 -0
  33. {micro_users-1.2.4 → micro_users-1.3.1}/users/templates/users/user_detail.html +0 -0
  34. {micro_users-1.2.4 → micro_users-1.3.1}/users/templates/users/user_form.html +0 -0
  35. {micro_users-1.2.4 → micro_users-1.3.1}/users/urls.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: micro_users
3
- Version: 1.2.4
3
+ Version: 1.3.1
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
- ├── models.py # User model, permissions, activity logs
103
- ├── views.py # CRUD operations
104
- ├── urls.py # URL routing
105
- ├── admin.py # Admin integration
106
- ├── templates/ # HTML templates
107
- └── migrations/ # Database migrations
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
@@ -122,3 +129,5 @@ users/
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 |
124
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 |
133
+ | v1.3.1 | • replaced a misplaced code that caused a crash when editing profile |
@@ -68,12 +68,19 @@ python manage.py migrate users
68
68
  ## Structure
69
69
  ```
70
70
  users/
71
- ├── models.py # User model, permissions, activity logs
72
- ├── views.py # CRUD operations
73
- ├── urls.py # URL routing
74
- ├── admin.py # Admin integration
75
- ├── templates/ # HTML templates
76
- └── migrations/ # Database migrations
71
+ ├── views.py # CRUD operations
72
+ ├── urls.py # URL routing
73
+ ├── tables.py # User and Activity Log tables
74
+ ├── signals.py # Logging signals
75
+ ├── models.py # User model, permissions, activity logs
76
+ ├── forms.py # Creation, edit,. etc.
77
+ ├── filter.py # Search filters
78
+ ├── apps.py # Permissions Localization
79
+ ├── admin.py # Admin UI integration
80
+ ├── __init__.py # Python init
81
+ ├── templates/ # HTML templates
82
+ ├── static/ # CSS classes
83
+ └── migrations/ # Database migrations
77
84
  ```
78
85
 
79
86
  ## Version History
@@ -90,4 +97,6 @@ users/
90
97
  | v1.2.0 | • Added User Details view with specific user activity log |
91
98
  | v1.2.1 | • Fixed a minor import bug |
92
99
  | v1.2.3 | • Separated user detail view from table for consistency<br> • Optimized the new detail + log view for optimal compatibiliyy with users |
93
- | v1.2.4 | • Fixed a couple of visual inconsistencies |
100
+ | v1.2.4 | • Fixed a couple of visual inconsistencies |
101
+ | 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 |
102
+ | v1.3.1 | • replaced a misplaced code that caused a crash when editing profile |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: micro-users
3
- Version: 1.2.4
3
+ Version: 1.3.1
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
- ├── models.py # User model, permissions, activity logs
103
- ├── views.py # CRUD operations
104
- ├── urls.py # URL routing
105
- ├── admin.py # Admin integration
106
- ├── templates/ # HTML templates
107
- └── migrations/ # Database migrations
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
@@ -122,3 +129,5 @@ users/
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 |
124
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 |
133
+ | v1.3.1 | • replaced a misplaced code that caused a crash when editing profile |
@@ -19,6 +19,7 @@ users/tables.py
19
19
  users/urls.py
20
20
  users/views.py
21
21
  users/migrations/0001_initial.py
22
+ users/migrations/0002_alter_useractivitylog_action.py
22
23
  users/migrations/__init__.py
23
24
  users/static/css/login.css
24
25
  users/static/img/default_profile.webp
@@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
8
8
 
9
9
  [project]
10
10
  name = "micro_users"
11
- version = "1.2.4"
11
+ version = "1.3.1"
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"
@@ -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.2.4",
8
+ version="1.3.1",
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",
@@ -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
+ ]
@@ -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)
@@ -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", "user.full_name", "action", "model_name", "object_id", "number")
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):
@@ -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
- {% comment %} <li>
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-dash-fill text-dark me-1 h5"> </i> عرض
11
+ <i class="bi bi-person-lines-fill text-dark me-1 h5"> </i> عرض
15
12
  </a>
16
13
  </li>
17
- {% if not record.is_superuser %}
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"> </i> تعديل
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>
@@ -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
- # Apply ordering here if needed, for example:
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})
@@ -84,12 +100,19 @@ def create_user(request):
84
100
  @user_passes_test(is_staff)
85
101
  def edit_user(request, pk):
86
102
  user = get_object_or_404(User, pk=pk)
103
+
104
+ # 🚫 Block staff users from editing superuser accounts
105
+ if user.is_superuser and not request.user.is_superuser:
106
+ messages.error(request, "لا يمكن تعديل هذا الحساب!")
107
+ return redirect('manage_users')
108
+
87
109
  form_reset = ResetPasswordForm(user, data=request.POST or None)
88
110
 
89
111
  if request.method == "POST":
90
112
  form = CustomUserChangeForm(request.POST, instance=user)
91
113
  if form.is_valid():
92
- form.save()
114
+ user = form.save()
115
+ log_user_action(request, user, "UPDATE", "مستخدم")
93
116
  return redirect("manage_users")
94
117
  else:
95
118
  # Validation errors will be automatically handled by the form object
@@ -106,17 +129,8 @@ def edit_user(request, pk):
106
129
  def delete_user(request, pk):
107
130
  user = get_object_or_404(User, pk=pk)
108
131
  if request.method == "POST":
132
+ log_user_action(request, user, "DELETE", "مستخدم")
109
133
  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
134
  return redirect("manage_users")
121
135
  return redirect("manage_users") # Redirect instead of rendering a separate page
122
136
 
@@ -172,10 +186,11 @@ def reset_password(request, pk):
172
186
  form = ResetPasswordForm(user=user, data=request.POST) # ✅ Correct usage with SetPasswordForm
173
187
  if form.is_valid():
174
188
  form.save()
175
- return redirect("manage_users") # Redirect after successful reset
189
+ log_user_action(request, user, "RESET", "رمز سري")
190
+ return redirect("manage_users")
176
191
  else:
177
- print("Form errors:", form.errors) # Debugging
178
- return redirect("edit_user", pk=pk) # Redirect to edit user on failure
192
+ print("Form errors:", form.errors)
193
+ return redirect("edit_user", pk=pk)
179
194
 
180
195
  return redirect("manage_users") # Fallback redirect
181
196
 
@@ -189,6 +204,7 @@ def user_profile(request):
189
204
  password_form = ArabicPasswordChangeForm(user, request.POST)
190
205
  if password_form.is_valid():
191
206
  password_form.save()
207
+ log_user_action(request, user, "UPDATE", "رمز سري")
192
208
  update_session_auth_hash(request, password_form.user) # Prevent user from being logged out
193
209
  messages.success(request, 'تم تغيير كلمة المرور بنجاح!')
194
210
  return redirect('user_profile')
@@ -209,14 +225,12 @@ def edit_profile(request):
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:
216
233
  messages.error(request, 'حدث خطأ أثناء حفظ التغييرات')
217
-
218
234
  else:
219
235
  form = UserProfileEditForm(instance=request.user)
220
-
221
236
  return render(request, 'users/profile_edit.html', {'form': form})
222
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes