micro-users 1.4.1__py3-none-any.whl → 1.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of micro-users might be problematic. Click here for more details.
- {micro_users-1.4.1.dist-info → micro_users-1.6.1.dist-info}/METADATA +57 -133
- micro_users-1.6.1.dist-info/RECORD +38 -0
- users/admin.py +21 -2
- users/apps.py +2 -1
- users/filters.py +6 -6
- users/forms.py +37 -14
- users/middleware.py +32 -0
- users/migrations/0003_scope_alter_customuser_options_and_more.py +47 -0
- users/models.py +20 -1
- 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 +29 -7
- users/templates/registration/login.html +29 -69
- users/templates/users/manage_users.html +88 -0
- users/templates/users/partials/scope_actions.html +9 -0
- users/templates/users/partials/scope_form.html +19 -0
- users/templates/users/partials/scope_manager.html +12 -0
- users/templates/{user_activity_log.html → users/user_activity_log.html} +2 -0
- users/urls.py +9 -1
- users/views.py +165 -24
- micro_users-1.4.1.dist-info/RECORD +0 -29
- {micro_users-1.4.1.dist-info → micro_users-1.6.1.dist-info}/LICENSE +0 -0
- {micro_users-1.4.1.dist-info → micro_users-1.6.1.dist-info}/WHEEL +0 -0
- {micro_users-1.4.1.dist-info → micro_users-1.6.1.dist-info}/top_level.txt +0 -0
- /users/templates/users/{user_actions.html → partials/user_actions.html} +0 -0
- /users/templates/users/{profile.html → profile/profile.html} +0 -0
- /users/templates/users/{profile_edit.html → profile/profile_edit.html} +0 -0
users/views.py
CHANGED
|
@@ -7,24 +7,27 @@ 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
|
|
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
|
|
@@ -59,12 +71,19 @@ class UserListView(LoginRequiredMixin, UserPassesTestMixin, FilterView, SingleTa
|
|
|
59
71
|
def test_func(self):
|
|
60
72
|
return self.request.user.is_staff
|
|
61
73
|
|
|
74
|
+
|
|
75
|
+
|
|
62
76
|
def get_queryset(self):
|
|
63
77
|
# Apply the filter and order by any logic you need
|
|
64
78
|
qs = super().get_queryset().order_by('date_joined')
|
|
79
|
+
# Exclude soft-deleted users
|
|
80
|
+
qs = qs.filter(deleted_at__isnull=True)
|
|
65
81
|
# Hide superuser entries from non-superusers
|
|
66
82
|
if not self.request.user.is_superuser:
|
|
67
83
|
qs = qs.exclude(is_superuser=True)
|
|
84
|
+
# Restrict to same scope
|
|
85
|
+
if self.request.user.scope:
|
|
86
|
+
qs = qs.filter(scope=self.request.user.scope)
|
|
68
87
|
return qs
|
|
69
88
|
|
|
70
89
|
def get_context_data(self, **kwargs):
|
|
@@ -83,15 +102,19 @@ class UserListView(LoginRequiredMixin, UserPassesTestMixin, FilterView, SingleTa
|
|
|
83
102
|
@user_passes_test(is_staff)
|
|
84
103
|
def create_user(request):
|
|
85
104
|
if request.method == "POST":
|
|
86
|
-
form = CustomUserCreationForm(request.POST or None)
|
|
105
|
+
form = CustomUserCreationForm(request.POST or None, user=request.user)
|
|
87
106
|
if form.is_valid():
|
|
88
|
-
user = form.save()
|
|
89
|
-
|
|
107
|
+
user = form.save(commit=False)
|
|
108
|
+
# Auto-assign scope for non-superusers
|
|
109
|
+
if not request.user.is_superuser and request.user.scope:
|
|
110
|
+
user.scope = request.user.scope
|
|
111
|
+
user.save()
|
|
112
|
+
user.user_permissions.set(form.cleaned_data["permissions"])
|
|
90
113
|
return redirect("manage_users")
|
|
91
114
|
else:
|
|
92
115
|
return render(request, "users/user_form.html", {"form": form})
|
|
93
116
|
else:
|
|
94
|
-
form = CustomUserCreationForm()
|
|
117
|
+
form = CustomUserCreationForm(user=request.user)
|
|
95
118
|
|
|
96
119
|
return render(request, "users/user_form.html", {"form": form})
|
|
97
120
|
|
|
@@ -104,22 +127,29 @@ def edit_user(request, pk):
|
|
|
104
127
|
# 🚫 Block staff users from editing superuser accounts
|
|
105
128
|
if user.is_superuser and not request.user.is_superuser:
|
|
106
129
|
messages.error(request, "لا يمكن تعديل هذا الحساب!")
|
|
107
|
-
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# Restrict to same scope
|
|
133
|
+
if not request.user.is_superuser:
|
|
134
|
+
if request.user.scope and user.scope != request.user.scope:
|
|
135
|
+
messages.error(request, "ليس لديك صلاحية لتعديل هذا المستخدم!")
|
|
136
|
+
return redirect('manage_users')
|
|
108
137
|
|
|
109
138
|
form_reset = ResetPasswordForm(user, data=request.POST or None)
|
|
110
139
|
|
|
111
140
|
if request.method == "POST":
|
|
112
|
-
form = CustomUserChangeForm(request.POST, instance=user)
|
|
141
|
+
form = CustomUserChangeForm(request.POST, instance=user, user=request.user)
|
|
113
142
|
if form.is_valid():
|
|
114
|
-
user = form.save()
|
|
115
|
-
|
|
143
|
+
user = form.save(commit=False)
|
|
144
|
+
user.save()
|
|
145
|
+
user.user_permissions.set(form.cleaned_data["permissions"])
|
|
116
146
|
return redirect("manage_users")
|
|
117
147
|
else:
|
|
118
148
|
# Validation errors will be automatically handled by the form object
|
|
119
149
|
return render(request, "users/user_form.html", {"form": form, "edit_mode": True, "form_reset": form_reset})
|
|
120
150
|
|
|
121
151
|
else:
|
|
122
|
-
form = CustomUserChangeForm(instance=user)
|
|
152
|
+
form = CustomUserChangeForm(instance=user, user=request.user)
|
|
123
153
|
|
|
124
154
|
return render(request, "users/user_form.html", {"form": form, "edit_mode": True, "form_reset": form_reset})
|
|
125
155
|
|
|
@@ -128,30 +158,51 @@ def edit_user(request, pk):
|
|
|
128
158
|
@user_passes_test(is_superuser)
|
|
129
159
|
def delete_user(request, pk):
|
|
130
160
|
user = get_object_or_404(User, pk=pk)
|
|
161
|
+
|
|
162
|
+
# Restrict to same scope
|
|
163
|
+
if not request.user.is_superuser:
|
|
164
|
+
if request.user.scope and user.scope != request.user.scope:
|
|
165
|
+
messages.error(request, "ليس لديك صلاحية لحذف هذا المستخدم!")
|
|
166
|
+
return redirect('manage_users')
|
|
167
|
+
|
|
131
168
|
if request.method == "POST":
|
|
132
|
-
|
|
133
|
-
user.
|
|
169
|
+
# Soft delete the user
|
|
170
|
+
user.is_active = False
|
|
171
|
+
user.deleted_at = timezone.now()
|
|
172
|
+
user.save()
|
|
134
173
|
return redirect("manage_users")
|
|
135
174
|
return redirect("manage_users") # Redirect instead of rendering a separate page
|
|
136
175
|
|
|
137
176
|
|
|
138
177
|
# Class Function for the Log
|
|
139
|
-
class UserActivityLogView(LoginRequiredMixin, UserPassesTestMixin,
|
|
140
|
-
model = UserActivityLog
|
|
141
|
-
table_class = UserActivityLogTable
|
|
142
|
-
filterset_class = UserActivityLogFilter
|
|
143
|
-
template_name = "user_activity_log.html"
|
|
178
|
+
class UserActivityLogView(LoginRequiredMixin, UserPassesTestMixin, SingleTableMixin, FilterView):
|
|
179
|
+
model = apps.get_model('users', 'UserActivityLog')
|
|
180
|
+
table_class = import_string('users.tables.UserActivityLogTable')
|
|
181
|
+
filterset_class = import_string('users.filters.UserActivityLogFilter')
|
|
182
|
+
template_name = "users/user_activity_log.html"
|
|
144
183
|
|
|
145
184
|
def test_func(self):
|
|
146
185
|
return self.request.user.is_staff # Only staff can access logs
|
|
147
186
|
|
|
148
187
|
def get_queryset(self):
|
|
149
188
|
# Order by timestamp descending by default
|
|
150
|
-
|
|
189
|
+
qs = super().get_queryset().order_by('-timestamp')
|
|
190
|
+
if not self.request.user.is_superuser:
|
|
191
|
+
qs = qs.exclude(user__is_superuser=True)
|
|
192
|
+
if self.request.user.scope:
|
|
193
|
+
qs = qs.filter(user__scope=self.request.user.scope)
|
|
194
|
+
return qs
|
|
195
|
+
|
|
196
|
+
def get_table(self, **kwargs):
|
|
197
|
+
table = super().get_table(**kwargs)
|
|
198
|
+
if self.request.user.scope:
|
|
199
|
+
table.exclude = ('scope',)
|
|
200
|
+
return table
|
|
151
201
|
|
|
152
202
|
def get_context_data(self, **kwargs):
|
|
153
203
|
context = super().get_context_data(**kwargs)
|
|
154
|
-
|
|
204
|
+
# Handle the filter object
|
|
205
|
+
context['filter'] = self.filterset
|
|
155
206
|
return context
|
|
156
207
|
|
|
157
208
|
|
|
@@ -167,9 +218,11 @@ class UserDetailView(LoginRequiredMixin, UserPassesTestMixin, DetailView):
|
|
|
167
218
|
context = super().get_context_data(**kwargs)
|
|
168
219
|
|
|
169
220
|
# self.object is the User instance
|
|
221
|
+
UserActivityLog = apps.get_model('users', 'UserActivityLog')
|
|
170
222
|
logs_qs = UserActivityLog.objects.filter(user=self.object).order_by('-timestamp')
|
|
171
223
|
|
|
172
224
|
# Create table manually
|
|
225
|
+
UserActivityLogTableNoUser = import_string('users.tables.UserActivityLogTableNoUser')
|
|
173
226
|
table = UserActivityLogTableNoUser(logs_qs)
|
|
174
227
|
RequestConfig(self.request, paginate={'per_page': 10}).configure(table)
|
|
175
228
|
|
|
@@ -182,6 +235,12 @@ class UserDetailView(LoginRequiredMixin, UserPassesTestMixin, DetailView):
|
|
|
182
235
|
def reset_password(request, pk):
|
|
183
236
|
user = get_object_or_404(User, id=pk)
|
|
184
237
|
|
|
238
|
+
# Restrict to same scope
|
|
239
|
+
if not request.user.is_superuser:
|
|
240
|
+
if request.user.scope and user.scope != request.user.scope:
|
|
241
|
+
messages.error(request, "ليس لديك صلاحية لتعديل هذا المستخدم!")
|
|
242
|
+
return redirect('manage_users')
|
|
243
|
+
|
|
185
244
|
if request.method == "POST":
|
|
186
245
|
form = ResetPasswordForm(user=user, data=request.POST) # ✅ Correct usage with SetPasswordForm
|
|
187
246
|
if form.is_valid():
|
|
@@ -213,7 +272,7 @@ def user_profile(request):
|
|
|
213
272
|
messages.error(request, "هناك خطأ في البيانات المدخلة")
|
|
214
273
|
print(password_form.errors) # You can log or print errors here for debugging
|
|
215
274
|
|
|
216
|
-
return render(request, 'users/profile.html', {
|
|
275
|
+
return render(request, 'users/profile/profile.html', {
|
|
217
276
|
'user': user,
|
|
218
277
|
'password_form': password_form
|
|
219
278
|
})
|
|
@@ -233,4 +292,86 @@ def edit_profile(request):
|
|
|
233
292
|
messages.error(request, 'حدث خطأ أثناء حفظ التغييرات')
|
|
234
293
|
else:
|
|
235
294
|
form = UserProfileEditForm(instance=request.user)
|
|
236
|
-
return render(request, 'users/profile_edit.html', {'form': form})
|
|
295
|
+
return render(request, 'users/profile/profile_edit.html', {'form': form})
|
|
296
|
+
|
|
297
|
+
# Scope Management Views
|
|
298
|
+
# ###########################
|
|
299
|
+
from django.template.loader import render_to_string
|
|
300
|
+
|
|
301
|
+
@login_required # staff check handled in template or can be added here
|
|
302
|
+
@user_passes_test(is_staff)
|
|
303
|
+
def manage_scopes(request):
|
|
304
|
+
"""
|
|
305
|
+
Returns the initial modal content with the table.
|
|
306
|
+
"""
|
|
307
|
+
if request.user.scope:
|
|
308
|
+
return JsonResponse({'error': 'Permission denied.'}, status=403)
|
|
309
|
+
|
|
310
|
+
Scope = apps.get_model('users', 'Scope')
|
|
311
|
+
ScopeTable = import_string('users.tables.ScopeTable')
|
|
312
|
+
table = ScopeTable(Scope.objects.all())
|
|
313
|
+
RequestConfig(request, paginate={'per_page': 5}).configure(table)
|
|
314
|
+
|
|
315
|
+
context = {'table': table}
|
|
316
|
+
html = render_to_string('users/partials/scope_manager.html', context, request=request)
|
|
317
|
+
return JsonResponse({'html': html})
|
|
318
|
+
|
|
319
|
+
@login_required
|
|
320
|
+
@user_passes_test(is_staff)
|
|
321
|
+
def get_scope_form(request, pk=None):
|
|
322
|
+
"""
|
|
323
|
+
Returns the Add/Edit form partial.
|
|
324
|
+
"""
|
|
325
|
+
if request.user.scope:
|
|
326
|
+
return JsonResponse({'error': 'Permission denied.'}, status=403)
|
|
327
|
+
|
|
328
|
+
ScopeForm = import_string('users.forms.ScopeForm')
|
|
329
|
+
Scope = apps.get_model('users', 'Scope')
|
|
330
|
+
|
|
331
|
+
if pk:
|
|
332
|
+
scope = get_object_or_404(Scope, pk=pk)
|
|
333
|
+
form = ScopeForm(instance=scope)
|
|
334
|
+
else:
|
|
335
|
+
form = ScopeForm()
|
|
336
|
+
|
|
337
|
+
html = render_to_string('users/partials/scope_form.html', {'form': form, 'scope_id': pk}, request=request)
|
|
338
|
+
return JsonResponse({'html': html})
|
|
339
|
+
|
|
340
|
+
@login_required
|
|
341
|
+
@user_passes_test(is_staff)
|
|
342
|
+
def save_scope(request, pk=None):
|
|
343
|
+
"""
|
|
344
|
+
Handles form submission. Returns updated table on success, or form with errors on failure.
|
|
345
|
+
"""
|
|
346
|
+
if request.user.scope:
|
|
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')
|
|
352
|
+
|
|
353
|
+
if request.method == "POST":
|
|
354
|
+
if pk:
|
|
355
|
+
scope = get_object_or_404(Scope, pk=pk)
|
|
356
|
+
form = ScopeForm(request.POST, instance=scope)
|
|
357
|
+
else:
|
|
358
|
+
form = ScopeForm(request.POST)
|
|
359
|
+
|
|
360
|
+
if form.is_valid():
|
|
361
|
+
form.save()
|
|
362
|
+
# Return updated table
|
|
363
|
+
table = ScopeTable(Scope.objects.all())
|
|
364
|
+
RequestConfig(request, paginate={'per_page': 5}).configure(table)
|
|
365
|
+
html = render_to_string('users/partials/scope_manager.html', {'table': table}, request=request)
|
|
366
|
+
return JsonResponse({'success': True, 'html': html})
|
|
367
|
+
else:
|
|
368
|
+
# Return form with errors
|
|
369
|
+
html = render_to_string('users/partials/scope_form.html', {'form': form, 'scope_id': pk}, request=request)
|
|
370
|
+
return JsonResponse({'success': False, 'html': html})
|
|
371
|
+
|
|
372
|
+
return JsonResponse({'success': False, 'error': 'Invalid method'})
|
|
373
|
+
|
|
374
|
+
@login_required
|
|
375
|
+
@user_passes_test(is_staff)
|
|
376
|
+
def delete_scope(request, pk):
|
|
377
|
+
return JsonResponse({'success': False, 'error': 'تم تعطيل حذف النطاقات لأسباب أمنية.'})
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
users/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
users/admin.py,sha256=VF0V6hQ9Obcdinnjb8nBHaknas2p3O5w-6yAJ-DeARQ,636
|
|
3
|
-
users/apps.py,sha256=Xb1nGvCl08KaUVqcUG82-jYdG6-KTVjaw_lgr5GIuYY,1133
|
|
4
|
-
users/filters.py,sha256=neOdbyOSYVQXAQ2vKAW-0bcj7KIh9xc8UboHTlaZU4Q,4785
|
|
5
|
-
users/forms.py,sha256=xWMSGNcsHj7eTj8o1IkOAC5jshaOrMHDXTn7l9P88Ho,15703
|
|
6
|
-
users/models.py,sha256=V_SIyGGq2w_bww7YufMjqXMSKN1u9CkSMPuOLiwPjtc,2100
|
|
7
|
-
users/signals.py,sha256=5Kd3KyfPT6740rvwZj4vy1yXsmjVhmaQ__RB8p5R5aE,1336
|
|
8
|
-
users/tables.py,sha256=2HiDXa_4Hq1at86vfbhg1U3NobMjMWXTVQIJz3AizmQ,2088
|
|
9
|
-
users/urls.py,sha256=FwQ9GVOBRQ4iXQ9UyLFI0aEAga0d5qL_miPNpmFPA-Q,1022
|
|
10
|
-
users/views.py,sha256=oJLsr_G7TJP3Y6lRdkoP2oNVGe8tYD3x8I4ARO_iDA8,8730
|
|
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/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
users/static/css/login.css,sha256=SiJ6jBbWQAP2Nxt7DOTZbTcFYP9JEp557AuQZ9Eirb0,2120
|
|
15
|
-
users/static/img/default_profile.webp,sha256=BKUoQHo4z_fZnmc6z6I-KFvLEHahDr98U9LnDQKHLAM,3018
|
|
16
|
-
users/templates/user_activity_log.html,sha256=41G7Wjv8ehBTSALwLLVzzoIBIo5hSM3FOw36olDINF8,481
|
|
17
|
-
users/templates/registration/login.html,sha256=owbzO_XjqMeSncwWxkTzsvbkhjEZd7LdbblC3HBnld0,4091
|
|
18
|
-
users/templates/users/manage_users.html,sha256=qWmlIHeuxEldI2sc_ERedbxq5BtUyxtBbNt3MZ0qLyc,2801
|
|
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
|
|
22
|
-
users/templates/users/user_detail.html,sha256=yPiuOGF96rV8t2H1Fl2hhIq78N1588ZFbh5gbAezaxw,2053
|
|
23
|
-
users/templates/users/user_form.html,sha256=jcyI7OQZOY4ue4DajPtfjAt2SmAYO5ZgHNOqTp2-FO0,1352
|
|
24
|
-
users/templates/users/widgets/grouped_permissions.html,sha256=q51WO-xMvg0aAqn6Ey8pMINDbFOHap_BgHcMxOvfLBw,9878
|
|
25
|
-
micro_users-1.4.1.dist-info/LICENSE,sha256=Fco89ULLSSxKkC2KKnx57SaT0R7WOkZfuk8IYcGiN50,1063
|
|
26
|
-
micro_users-1.4.1.dist-info/METADATA,sha256=cdGwqpDvTHTkM9JuionfJZPG9bJ80ds2VdWaLEfQ2Nw,10256
|
|
27
|
-
micro_users-1.4.1.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
28
|
-
micro_users-1.4.1.dist-info/top_level.txt,sha256=tWT24ZcWau2wrlbpU_h3mP2jRukyLaVYiyHBuOezpLQ,6
|
|
29
|
-
micro_users-1.4.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|