django-chassis 1.0.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.
Files changed (313) hide show
  1. django_chassis/__init__.py +1 -0
  2. django_chassis/admins/__init__.py +15 -0
  3. django_chassis/admins/admin_audit_log.py +107 -0
  4. django_chassis/admins/group.py +56 -0
  5. django_chassis/admins/login_attempt.py +69 -0
  6. django_chassis/admins/login_attempt_schedule.py +44 -0
  7. django_chassis/admins/user.py +52 -0
  8. django_chassis/admins/user_session.py +100 -0
  9. django_chassis/apps.py +18 -0
  10. django_chassis/components/__init__.py +22 -0
  11. django_chassis/components/actions.py +145 -0
  12. django_chassis/components/alerts.py +80 -0
  13. django_chassis/components/base.py +69 -0
  14. django_chassis/components/charts.py +82 -0
  15. django_chassis/components/geo_ip_database_alert.py +46 -0
  16. django_chassis/components/metrics.py +97 -0
  17. django_chassis/components/navbar.py +101 -0
  18. django_chassis/components/toggle_actions.py +122 -0
  19. django_chassis/conf.py +32 -0
  20. django_chassis/decorators/__init__.py +25 -0
  21. django_chassis/decorators/object_action.py +99 -0
  22. django_chassis/decorators/related_items.py +25 -0
  23. django_chassis/dto/__init__.py +71 -0
  24. django_chassis/dto/admin_information_badge.py +14 -0
  25. django_chassis/dto/admin_information_row.py +21 -0
  26. django_chassis/dto/admin_information_section.py +21 -0
  27. django_chassis/dto/admin_navbar_child_item.py +16 -0
  28. django_chassis/dto/admin_navbar_item.py +21 -0
  29. django_chassis/dto/admin_page_action.py +17 -0
  30. django_chassis/dto/admin_page_alert.py +15 -0
  31. django_chassis/dto/admin_profile.py +22 -0
  32. django_chassis/dto/admin_sidebar_child_item.py +16 -0
  33. django_chassis/dto/admin_sidebar_item.py +28 -0
  34. django_chassis/dto/admin_timeline.py +14 -0
  35. django_chassis/dto/admin_timeline_group.py +14 -0
  36. django_chassis/dto/admin_timeline_item.py +17 -0
  37. django_chassis/dto/brand.py +13 -0
  38. django_chassis/dto/brand_badge.py +14 -0
  39. django_chassis/dto/client_device.py +13 -0
  40. django_chassis/dto/dashboard_action.py +23 -0
  41. django_chassis/dto/dashboard_action_variant.py +18 -0
  42. django_chassis/dto/dashboard_alert.py +20 -0
  43. django_chassis/dto/dashboard_block.py +21 -0
  44. django_chassis/dto/dashboard_chart.py +29 -0
  45. django_chassis/dto/dashboard_chart_data.py +15 -0
  46. django_chassis/dto/dashboard_chart_point.py +12 -0
  47. django_chassis/dto/dashboard_chart_series.py +15 -0
  48. django_chassis/dto/dashboard_metric.py +23 -0
  49. django_chassis/dto/dashboard_stacked_item.py +16 -0
  50. django_chassis/dto/export_field.py +16 -0
  51. django_chassis/dto/export_filter.py +17 -0
  52. django_chassis/dto/import_field.py +16 -0
  53. django_chassis/dto/import_preview.py +34 -0
  54. django_chassis/dto/login_attempt_availability.py +14 -0
  55. django_chassis/dto/request_client_metadata.py +17 -0
  56. django_chassis/dto/user_profile_activity.py +14 -0
  57. django_chassis/enums/__init__.py +57 -0
  58. django_chassis/enums/admin_audit_action.py +40 -0
  59. django_chassis/enums/admin_audit_result.py +30 -0
  60. django_chassis/enums/admin_error_status.py +73 -0
  61. django_chassis/enums/admin_navbar_item_type.py +11 -0
  62. django_chassis/enums/button_color.py +14 -0
  63. django_chassis/enums/choices_enum.py +36 -0
  64. django_chassis/enums/dashboard_alert_style.py +22 -0
  65. django_chassis/enums/dashboard_block_type.py +13 -0
  66. django_chassis/enums/dashboard_chart_color.py +11 -0
  67. django_chassis/enums/dashboard_chart_type.py +11 -0
  68. django_chassis/enums/dashboard_component_status.py +12 -0
  69. django_chassis/enums/dashboard_live_widget_type.py +12 -0
  70. django_chassis/enums/dashboard_metric_result_type.py +11 -0
  71. django_chassis/enums/dashboard_stacked_item_tone.py +12 -0
  72. django_chassis/enums/export_backend.py +22 -0
  73. django_chassis/enums/export_format.py +42 -0
  74. django_chassis/enums/export_status.py +28 -0
  75. django_chassis/enums/import_backend.py +22 -0
  76. django_chassis/enums/import_format.py +35 -0
  77. django_chassis/enums/import_status.py +30 -0
  78. django_chassis/enums/permission_check.py +10 -0
  79. django_chassis/enums/permission_name_language.py +9 -0
  80. django_chassis/enums/row_action_display.py +10 -0
  81. django_chassis/enums/row_action_type.py +12 -0
  82. django_chassis/enums/sidebar_icon_color.py +33 -0
  83. django_chassis/enums/tab_display.py +10 -0
  84. django_chassis/enums/user_session_end_reason.py +28 -0
  85. django_chassis/exceptions.py +33 -0
  86. django_chassis/filters/__init__.py +0 -0
  87. django_chassis/filters/user_session_state.py +42 -0
  88. django_chassis/forms/__init__.py +13 -0
  89. django_chassis/forms/admin_authentication.py +87 -0
  90. django_chassis/forms/confirmed_action.py +11 -0
  91. django_chassis/forms/export_form.py +103 -0
  92. django_chassis/forms/import_export_base.py +92 -0
  93. django_chassis/forms/import_form.py +65 -0
  94. django_chassis/forms/self_password_change.py +19 -0
  95. django_chassis/management/__init__.py +0 -0
  96. django_chassis/management/commands/__init__.py +0 -0
  97. django_chassis/management/commands/run_gunicorn.py +91 -0
  98. django_chassis/middlewares/__init__.py +5 -0
  99. django_chassis/middlewares/admin_audit.py +43 -0
  100. django_chassis/middlewares/admin_error_page.py +66 -0
  101. django_chassis/middlewares/user_session.py +46 -0
  102. django_chassis/migrations/0001_initial.py +217 -0
  103. django_chassis/migrations/__init__.py +0 -0
  104. django_chassis/mixins/__init__.py +6 -0
  105. django_chassis/mixins/admin.py +300 -0
  106. django_chassis/mixins/admin_site.py +355 -0
  107. django_chassis/mixins/core/__init__.py +3 -0
  108. django_chassis/mixins/core/actions.py +164 -0
  109. django_chassis/mixins/core/audit.py +68 -0
  110. django_chassis/mixins/core/base.py +73 -0
  111. django_chassis/mixins/core/changelist.py +207 -0
  112. django_chassis/mixins/core/import_export.py +193 -0
  113. django_chassis/mixins/core/object_page.py +306 -0
  114. django_chassis/mixins/core/permissions.py +75 -0
  115. django_chassis/mixins/core/presentation.py +395 -0
  116. django_chassis/mixins/no_delete_admin.py +14 -0
  117. django_chassis/mixins/read_only_admin.py +29 -0
  118. django_chassis/models/__init__.py +17 -0
  119. django_chassis/models/admin_audit_log.py +105 -0
  120. django_chassis/models/export_history.py +93 -0
  121. django_chassis/models/history_storage.py +32 -0
  122. django_chassis/models/import_history.py +93 -0
  123. django_chassis/models/login_attempt.py +52 -0
  124. django_chassis/models/login_attempt_schedule.py +39 -0
  125. django_chassis/models/managers.py +43 -0
  126. django_chassis/models/user_profile.py +58 -0
  127. django_chassis/models/user_session.py +99 -0
  128. django_chassis/navbar/__init__.py +3 -0
  129. django_chassis/navbar/admin_profile.py +40 -0
  130. django_chassis/navbar/password_change.py +40 -0
  131. django_chassis/navbar/theme.py +24 -0
  132. django_chassis/options/__init__.py +82 -0
  133. django_chassis/options/admin_page_group.py +15 -0
  134. django_chassis/options/badge_fields.py +14 -0
  135. django_chassis/options/date_hierarchy.py +15 -0
  136. django_chassis/options/decimal_amount.py +17 -0
  137. django_chassis/options/export.py +39 -0
  138. django_chassis/options/field_tabs.py +21 -0
  139. django_chassis/options/filters.py +16 -0
  140. django_chassis/options/foreign_key_tab_group.py +18 -0
  141. django_chassis/options/foreign_key_tabs.py +23 -0
  142. django_chassis/options/import_option.py +35 -0
  143. django_chassis/options/list_action.py +19 -0
  144. django_chassis/options/list_actions.py +12 -0
  145. django_chassis/options/object_actions.py +17 -0
  146. django_chassis/options/permission.py +35 -0
  147. django_chassis/options/pretty_json.py +10 -0
  148. django_chassis/options/related_entities.py +21 -0
  149. django_chassis/options/related_entity.py +23 -0
  150. django_chassis/options/row_action.py +19 -0
  151. django_chassis/options/row_actions.py +30 -0
  152. django_chassis/options/search.py +16 -0
  153. django_chassis/options/settings.py +13 -0
  154. django_chassis/options/sidebar_link.py +15 -0
  155. django_chassis/options/sidebar_model_item.py +14 -0
  156. django_chassis/options/sidebar_page_item.py +14 -0
  157. django_chassis/options/sidebar_section.py +22 -0
  158. django_chassis/options/table.py +21 -0
  159. django_chassis/options/table_action.py +24 -0
  160. django_chassis/options/table_action_url_kwarg.py +11 -0
  161. django_chassis/options/table_column.py +13 -0
  162. django_chassis/permissions.py +53 -0
  163. django_chassis/protocols.py +40 -0
  164. django_chassis/py.typed +0 -0
  165. django_chassis/registries/__init__.py +3 -0
  166. django_chassis/registries/permission_registry.py +33 -0
  167. django_chassis/renderers/__init__.py +3 -0
  168. django_chassis/renderers/admin_error_page_renderer.py +167 -0
  169. django_chassis/services/__init__.py +65 -0
  170. django_chassis/services/action_permission_validation_service.py +50 -0
  171. django_chassis/services/admin_audit_log_service.py +155 -0
  172. django_chassis/services/admin_navbar_service.py +118 -0
  173. django_chassis/services/admin_page_service.py +108 -0
  174. django_chassis/services/admin_permission_service.py +24 -0
  175. django_chassis/services/admin_profile_service.py +54 -0
  176. django_chassis/services/admin_sidebar_service.py +304 -0
  177. django_chassis/services/celery_availability_service.py +25 -0
  178. django_chassis/services/client_device_service.py +193 -0
  179. django_chassis/services/client_geo_ip_service.py +158 -0
  180. django_chassis/services/client_ip_service.py +56 -0
  181. django_chassis/services/confirmed_action_service.py +46 -0
  182. django_chassis/services/dashboard_analytics_cache_service.py +28 -0
  183. django_chassis/services/dashboard_live_serialization_service.py +68 -0
  184. django_chassis/services/dashboard_service.py +379 -0
  185. django_chassis/services/deleted_objects_service.py +164 -0
  186. django_chassis/services/import_apply_service.py +262 -0
  187. django_chassis/services/import_export_dispatch_service.py +146 -0
  188. django_chassis/services/import_export_field_service.py +157 -0
  189. django_chassis/services/import_export_filter_service.py +183 -0
  190. django_chassis/services/import_export_process_service.py +226 -0
  191. django_chassis/services/import_export_python_queue_service.py +91 -0
  192. django_chassis/services/import_export_serialization_service.py +493 -0
  193. django_chassis/services/login_attempt_limit_service.py +341 -0
  194. django_chassis/services/pagination_service.py +61 -0
  195. django_chassis/services/permission_initialization_service.py +35 -0
  196. django_chassis/services/permission_name_normalization_service.py +125 -0
  197. django_chassis/services/session_key_crypto_service.py +26 -0
  198. django_chassis/services/table_service.py +135 -0
  199. django_chassis/services/user_profile_service.py +117 -0
  200. django_chassis/services/user_session_service.py +266 -0
  201. django_chassis/signals/__init__.py +10 -0
  202. django_chassis/signals/initialize_permissions.py +15 -0
  203. django_chassis/signals/normalize_default_permission_names.py +13 -0
  204. django_chassis/signals/user_profile.py +42 -0
  205. django_chassis/singleton.py +17 -0
  206. django_chassis/static/admin/chassis/admin.css +1815 -0
  207. django_chassis/static/admin/chassis/alerts.css +264 -0
  208. django_chassis/static/admin/chassis/alerts.js +46 -0
  209. django_chassis/static/admin/chassis/apexcharts/apexcharts.css +1203 -0
  210. django_chassis/static/admin/chassis/apexcharts/apexcharts.min.js +5 -0
  211. django_chassis/static/admin/chassis/auth.css +311 -0
  212. django_chassis/static/admin/chassis/collapsibles.js +46 -0
  213. django_chassis/static/admin/chassis/dashboard.css +854 -0
  214. django_chassis/static/admin/chassis/dashboard_charts.js +234 -0
  215. django_chassis/static/admin/chassis/dashboard_live.js +332 -0
  216. django_chassis/static/admin/chassis/date_input_mask.js +237 -0
  217. django_chassis/static/admin/chassis/error_page.css +116 -0
  218. django_chassis/static/admin/chassis/filters.js +19 -0
  219. django_chassis/static/admin/chassis/fontawesome/css/all.min.css +9 -0
  220. django_chassis/static/admin/chassis/fontawesome/webfonts/fa-brands-400.woff2 +0 -0
  221. django_chassis/static/admin/chassis/fontawesome/webfonts/fa-regular-400.woff2 +0 -0
  222. django_chassis/static/admin/chassis/fontawesome/webfonts/fa-solid-900.woff2 +0 -0
  223. django_chassis/static/admin/chassis/fontawesome/webfonts/fa-v4compatibility.woff2 +0 -0
  224. django_chassis/static/admin/chassis/icon.svg +7 -0
  225. django_chassis/static/admin/chassis/navbar.css +374 -0
  226. django_chassis/static/admin/chassis/navbar_dropdown.js +29 -0
  227. django_chassis/static/admin/chassis/related_pagination.js +176 -0
  228. django_chassis/static/admin/chassis/secret_field.js +105 -0
  229. django_chassis/static/admin/chassis/sidebar.css +765 -0
  230. django_chassis/static/admin/chassis/sidebar.js +63 -0
  231. django_chassis/static/admin/chassis/table_selection.js +22 -0
  232. django_chassis/tasks/__init__.py +3 -0
  233. django_chassis/tasks/process_import_export.py +26 -0
  234. django_chassis/templates/admin/base.html +328 -0
  235. django_chassis/templates/admin/change_form.html +35 -0
  236. django_chassis/templates/admin/chassis/auth_alert.html +9 -0
  237. django_chassis/templates/admin/chassis/auth_alerts.html +40 -0
  238. django_chassis/templates/admin/chassis/blocked_profiles.html +80 -0
  239. django_chassis/templates/admin/chassis/brand.html +16 -0
  240. django_chassis/templates/admin/chassis/change_form.html +63 -0
  241. django_chassis/templates/admin/chassis/change_list.html +141 -0
  242. django_chassis/templates/admin/chassis/confirmed_action.html +29 -0
  243. django_chassis/templates/admin/chassis/dashboard/actions/base.html +21 -0
  244. django_chassis/templates/admin/chassis/dashboard/actions/toggle.html +45 -0
  245. django_chassis/templates/admin/chassis/dashboard/index.html +113 -0
  246. django_chassis/templates/admin/chassis/dashboard/sections/actions.html +14 -0
  247. django_chassis/templates/admin/chassis/dashboard/sections/alerts.html +27 -0
  248. django_chassis/templates/admin/chassis/dashboard/sections/chart.html +40 -0
  249. django_chassis/templates/admin/chassis/dashboard/sections/items/action.html +29 -0
  250. django_chassis/templates/admin/chassis/dashboard/sections/items/metric.html +75 -0
  251. django_chassis/templates/admin/chassis/dashboard/sections/metrics.html +14 -0
  252. django_chassis/templates/admin/chassis/dashboard/sections/metrics_and_actions.html +18 -0
  253. django_chassis/templates/admin/chassis/dashboard/sections/panel_heading.html +12 -0
  254. django_chassis/templates/admin/chassis/date_hierarchy.html +23 -0
  255. django_chassis/templates/admin/chassis/filters.html +49 -0
  256. django_chassis/templates/admin/chassis/form_rows.html +44 -0
  257. django_chassis/templates/admin/chassis/import_export_form.html +128 -0
  258. django_chassis/templates/admin/chassis/import_export_landing.html +42 -0
  259. django_chassis/templates/admin/chassis/import_export_preview.html +113 -0
  260. django_chassis/templates/admin/chassis/information_alert.html +7 -0
  261. django_chassis/templates/admin/chassis/information_page.html +24 -0
  262. django_chassis/templates/admin/chassis/information_sections.html +108 -0
  263. django_chassis/templates/admin/chassis/object_page.html +26 -0
  264. django_chassis/templates/admin/chassis/page.html +21 -0
  265. django_chassis/templates/admin/chassis/pagination.html +18 -0
  266. django_chassis/templates/admin/chassis/related_entities.html +41 -0
  267. django_chassis/templates/admin/chassis/row_actions.html +19 -0
  268. django_chassis/templates/admin/chassis/search.html +29 -0
  269. django_chassis/templates/admin/chassis/self_password_change.html +34 -0
  270. django_chassis/templates/admin/chassis/sidebar.html +74 -0
  271. django_chassis/templates/admin/chassis/table.html +54 -0
  272. django_chassis/templates/admin/chassis/timeline.html +37 -0
  273. django_chassis/templates/admin/chassis/widgets/date.html +1 -0
  274. django_chassis/templates/admin/chassis/widgets/split_datetime.html +16 -0
  275. django_chassis/templates/admin/chassis/widgets/time.html +1 -0
  276. django_chassis/templates/admin/error.html +37 -0
  277. django_chassis/templates/admin/footer.html +15 -0
  278. django_chassis/templates/admin/login.html +86 -0
  279. django_chassis/templates/admin/object_actions/base_action_view.html +18 -0
  280. django_chassis/templatetags/__init__.py +1 -0
  281. django_chassis/templatetags/admin_footer.py +15 -0
  282. django_chassis/templatetags/chassis_admin.py +95 -0
  283. django_chassis/type_aliases.py +5 -0
  284. django_chassis/utils/__init__.py +8 -0
  285. django_chassis/utils/fixed_point_decimal.py +39 -0
  286. django_chassis/utils/table_exists.py +8 -0
  287. django_chassis/validators/__init__.py +0 -0
  288. django_chassis/validators/login_attempt_unlock.py +16 -0
  289. django_chassis/validators/object_action_condition.py +23 -0
  290. django_chassis/validators/translation_management.py +15 -0
  291. django_chassis/validators/user_session_termination.py +20 -0
  292. django_chassis/views/__init__.py +19 -0
  293. django_chassis/views/admin_information_page.py +44 -0
  294. django_chassis/views/admin_object_page.py +214 -0
  295. django_chassis/views/admin_page.py +113 -0
  296. django_chassis/views/admin_profile.py +232 -0
  297. django_chassis/views/import_export.py +575 -0
  298. django_chassis/views/permission_admin_page.py +29 -0
  299. django_chassis/views/personal_admin_page.py +22 -0
  300. django_chassis/views/self_password_change.py +54 -0
  301. django_chassis/views/superuser_admin_page.py +24 -0
  302. django_chassis/widgets/__init__.py +33 -0
  303. django_chassis/widgets/date_input.py +33 -0
  304. django_chassis/widgets/date_time_input.py +27 -0
  305. django_chassis/widgets/execution_backend_select.py +35 -0
  306. django_chassis/widgets/input_mask.py +35 -0
  307. django_chassis/widgets/split_date_time.py +19 -0
  308. django_chassis/widgets/time_input.py +33 -0
  309. django_chassis-1.0.1.dist-info/METADATA +175 -0
  310. django_chassis-1.0.1.dist-info/RECORD +313 -0
  311. django_chassis-1.0.1.dist-info/WHEEL +5 -0
  312. django_chassis-1.0.1.dist-info/licenses/LICENSE +21 -0
  313. django_chassis-1.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,15 @@
1
+ from django_chassis.admins.admin_audit_log import AdminAuditLogAdmin
2
+ from django_chassis.admins.group import GroupAdmin
3
+ from django_chassis.admins.login_attempt import LoginAttemptAdmin
4
+ from django_chassis.admins.login_attempt_schedule import LoginAttemptScheduleAdmin
5
+ from django_chassis.admins.user import UserAdmin
6
+ from django_chassis.admins.user_session import UserSessionAdmin
7
+
8
+ __all__ = [
9
+ 'AdminAuditLogAdmin',
10
+ 'GroupAdmin',
11
+ 'LoginAttemptAdmin',
12
+ 'LoginAttemptScheduleAdmin',
13
+ 'UserAdmin',
14
+ 'UserSessionAdmin'
15
+ ]
@@ -0,0 +1,107 @@
1
+ from django.contrib import admin
2
+ from django.db.models import Model
3
+ from django.http import HttpRequest
4
+ from django.utils.translation import gettext_lazy as _
5
+
6
+ from django_chassis.mixins.admin import ChassisAdminMixin
7
+ from django_chassis.mixins.read_only_admin import ReadOnlyAdminMixin
8
+ from django_chassis.options import FiltersOption, SearchOption
9
+
10
+
11
+ class AdminAuditLogAdmin(ChassisAdminMixin, ReadOnlyAdminMixin, admin.ModelAdmin):
12
+ """
13
+ Просматривать журнал HTTP-действий Django Admin.
14
+ """
15
+
16
+ options = [
17
+ SearchOption(fields=['username', 'path', 'object_repr', 'client_ip']),
18
+ FiltersOption(fields=['action', 'result', 'is_superuser'])
19
+ ]
20
+ fieldsets = [
21
+ (
22
+ _('Действие'),
23
+ {
24
+ 'classes': ['wide'],
25
+ 'fields': [
26
+ 'id',
27
+ 'user',
28
+ 'username',
29
+ 'is_superuser',
30
+ 'action',
31
+ 'result',
32
+ 'action_name',
33
+ 'created_at',
34
+ 'completed_at',
35
+ 'status_code',
36
+ 'response_time_ms'
37
+ ]
38
+ }
39
+ ),
40
+ (
41
+ _('Объект'),
42
+ {
43
+ 'classes': ['wide'],
44
+ 'fields': ['app_label', 'model_name', 'object_id', 'object_repr', 'object_count', 'changes']
45
+ }
46
+ ),
47
+ (
48
+ _('Запрос'),
49
+ {
50
+ 'classes': ['wide'],
51
+ 'fields': [
52
+ 'method',
53
+ 'path',
54
+ 'route_name',
55
+ 'query_params',
56
+ 'client_ip',
57
+ 'remote_ip',
58
+ 'forwarded_for',
59
+ 'user_agent',
60
+ 'browser',
61
+ 'operating_system',
62
+ 'is_mobile',
63
+ 'referer',
64
+ 'session_hash'
65
+ ]
66
+ }
67
+ )
68
+ ]
69
+ readonly_fields = [
70
+ 'id',
71
+ 'user',
72
+ 'username',
73
+ 'is_superuser',
74
+ 'action',
75
+ 'result',
76
+ 'action_name',
77
+ 'created_at',
78
+ 'completed_at',
79
+ 'status_code',
80
+ 'response_time_ms',
81
+ 'app_label',
82
+ 'model_name',
83
+ 'object_id',
84
+ 'object_repr',
85
+ 'object_count',
86
+ 'changes',
87
+ 'method',
88
+ 'path',
89
+ 'route_name',
90
+ 'query_params',
91
+ 'client_ip',
92
+ 'remote_ip',
93
+ 'forwarded_for',
94
+ 'user_agent',
95
+ 'browser',
96
+ 'operating_system',
97
+ 'is_mobile',
98
+ 'referer',
99
+ 'session_hash'
100
+ ]
101
+
102
+ def has_view_permission(self, request: HttpRequest, obj: Model | None = None) -> bool:
103
+ """
104
+ Сохранить стандартную проверку view журнала.
105
+ """
106
+ del obj
107
+ return getattr(request.user, 'has_perm')('django_chassis.view_adminauditlog')
@@ -0,0 +1,56 @@
1
+ from django.contrib.auth.admin import GroupAdmin as DjangoGroupAdmin
2
+ from django.db.models import Model
3
+ from django.http import HttpRequest
4
+
5
+ from django_chassis.mixins.admin import ChassisAdminMixin
6
+ from django_chassis.options import SearchOption
7
+ from django_chassis.protocols import cast_admin_user
8
+
9
+
10
+ class GroupAdmin(ChassisAdminMixin, DjangoGroupAdmin):
11
+ """
12
+ Управлять стандартными группами Django через Chassis Admin.
13
+ """
14
+
15
+ options = [SearchOption(fields=['name'])]
16
+
17
+ @classmethod
18
+ def _is_active_staff_superuser(cls, request: HttpRequest) -> bool:
19
+ """
20
+ Разрешить управление группами только полному суперпользователю.
21
+ """
22
+ user = cast_admin_user(request.user)
23
+ return user.is_authenticated and user.is_active and user.is_staff and user.is_superuser
24
+
25
+ def has_module_permission(self, request: HttpRequest) -> bool:
26
+ """
27
+ Скрыть раздел групп от несуперпользователей.
28
+ """
29
+ return self._is_active_staff_superuser(request)
30
+
31
+ def has_view_permission(self, request: HttpRequest, obj: Model | None = None) -> bool:
32
+ """
33
+ Закрыть список, карточку и прямые URL групп.
34
+ """
35
+ del obj
36
+ return self._is_active_staff_superuser(request)
37
+
38
+ def has_add_permission(self, request: HttpRequest) -> bool:
39
+ """
40
+ Запретить создание групп несуперпользователям.
41
+ """
42
+ return self._is_active_staff_superuser(request)
43
+
44
+ def has_change_permission(self, request: HttpRequest, obj: Model | None = None) -> bool:
45
+ """
46
+ Запретить изменение группы несуперпользователям.
47
+ """
48
+ del obj
49
+ return self._is_active_staff_superuser(request)
50
+
51
+ def has_delete_permission(self, request: HttpRequest, obj: Model | None = None) -> bool:
52
+ """
53
+ Запретить удаление группы несуперпользователям.
54
+ """
55
+ del obj
56
+ return self._is_active_staff_superuser(request)
@@ -0,0 +1,69 @@
1
+ from django.contrib import admin, messages
2
+ from django.contrib.auth.base_user import AbstractBaseUser
3
+ from django.db.models import Model
4
+ from django.http import HttpRequest, HttpResponse
5
+ from django.utils.translation import gettext_lazy as _
6
+
7
+ from django_chassis.decorators import object_action
8
+ from django_chassis.enums import ButtonColor
9
+ from django_chassis.mixins.admin import ChassisAdminMixin
10
+ from django_chassis.mixins.read_only_admin import ReadOnlyAdminMixin
11
+ from django_chassis.models.login_attempt import LoginAttempt
12
+ from django_chassis.options import ObjectActionsOption, SearchOption
13
+ from django_chassis.services.login_attempt_limit_service import LoginAttemptLimitService
14
+ from django_chassis.validators.login_attempt_unlock import can_unlock_login_attempt
15
+
16
+ class LoginAttemptAdmin(ChassisAdminMixin, ReadOnlyAdminMixin, admin.ModelAdmin):
17
+ """
18
+ Просматривать журнал попыток входа и снимать блокировку области.
19
+ """
20
+
21
+ options = [SearchOption(fields=['username', 'client_ip', 'browser']), ObjectActionsOption(actions=['unlock_scope'])]
22
+ fieldsets = [
23
+ (_('Попытка'), {'classes': ['wide'], 'fields': [
24
+ 'id',
25
+ 'username',
26
+ 'user',
27
+ 'client_ip',
28
+ 'browser',
29
+ 'attempt_number',
30
+ 'is_successful',
31
+ 'is_unlock',
32
+ 'created_at'
33
+ ]})
34
+ ]
35
+ readonly_fields = [
36
+ 'id',
37
+ 'username',
38
+ 'user',
39
+ 'client_ip',
40
+ 'browser',
41
+ 'attempt_number',
42
+ 'is_successful',
43
+ 'is_unlock',
44
+ 'created_at'
45
+ ]
46
+
47
+ @object_action(
48
+ description=_('Снять блокировку'),
49
+ permission='unlock_loginattempt',
50
+ confirmation=_('Снять блокировку входа для этой области логин/IP/браузер?'),
51
+ color=ButtonColor.WARNING,
52
+ condition=can_unlock_login_attempt
53
+ )
54
+ def unlock_scope(self, request: HttpRequest, obj: LoginAttempt) -> HttpResponse | None:
55
+ """
56
+ Начать новый цикл ограничений для области этой записи.
57
+ """
58
+ if not isinstance(request.user, AbstractBaseUser):
59
+ return None
60
+ LoginAttemptLimitService().unlock(username=obj.username, client_ip=obj.client_ip, browser=obj.browser)
61
+ messages.success(request, _('Блокировка входа снята.'))
62
+ return None
63
+
64
+ def has_view_permission(self, request: HttpRequest, obj: Model | None = None) -> bool:
65
+ """
66
+ Сохранить стандартную проверку view журнала попыток.
67
+ """
68
+ del obj
69
+ return getattr(request.user, 'has_perm')('django_chassis.view_loginattempt')
@@ -0,0 +1,44 @@
1
+ from django.contrib import admin
2
+ from django.db.models import Model
3
+ from django.http import HttpRequest
4
+ from django.utils.translation import gettext_lazy as _
5
+
6
+ from django_chassis.mixins.admin import ChassisAdminMixin
7
+ from django_chassis.mixins.no_delete_admin import NoDeleteAdminMixin
8
+ from django_chassis.models.login_attempt_schedule import LoginAttemptSchedule
9
+ from django_chassis.options import SearchOption
10
+ from django_chassis.protocols import cast_admin_user
11
+
12
+ class LoginAttemptScheduleAdmin(ChassisAdminMixin, NoDeleteAdminMixin, admin.ModelAdmin):
13
+ """
14
+ Настраивать задержки между попытками локального входа.
15
+ """
16
+
17
+ options = [SearchOption(fields=['attempt_number'])]
18
+ fieldsets = [(_('Правило'), {'classes': ['wide'], 'fields': ['attempt_number', 'delay_seconds', 'is_active']})]
19
+
20
+ def has_module_permission(self, request: HttpRequest) -> bool:
21
+ """
22
+ Показать расписание только суперпользователю.
23
+ """
24
+ return bool(cast_admin_user(request.user).is_superuser)
25
+
26
+ def has_view_permission(self, request: HttpRequest, obj: Model | None = None) -> bool:
27
+ """
28
+ Закрыть карточку расписания от несуперпользователей.
29
+ """
30
+ del obj
31
+ return bool(cast_admin_user(request.user).is_superuser)
32
+
33
+ def has_add_permission(self, request: HttpRequest) -> bool:
34
+ """
35
+ Разрешить создание правил только суперпользователю.
36
+ """
37
+ return bool(cast_admin_user(request.user).is_superuser)
38
+
39
+ def has_change_permission(self, request: HttpRequest, obj: Model | None = None) -> bool:
40
+ """
41
+ Разрешить изменение правил только суперпользователю.
42
+ """
43
+ del obj
44
+ return bool(cast_admin_user(request.user).is_superuser)
@@ -0,0 +1,52 @@
1
+ from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
2
+ from django.contrib.auth.models import AbstractUser
3
+ from django.db.models import QuerySet
4
+ from django.http import HttpRequest
5
+ from django.utils.translation import gettext_lazy as _
6
+
7
+ from django_chassis.decorators import chassis_related_items
8
+ from django_chassis.mixins.admin import ChassisAdminMixin
9
+ from django_chassis.models.user_session import UserSession
10
+ from django_chassis.options import (
11
+ RelatedEntitiesOption,
12
+ RelatedEntityOption,
13
+ SearchOption,
14
+ TableColumnOption,
15
+ TableOption
16
+ )
17
+
18
+ class UserAdmin(ChassisAdminMixin, DjangoUserAdmin):
19
+ """
20
+ Управлять стандартными пользователями Django через Chassis Admin.
21
+ """
22
+
23
+ options = [
24
+ SearchOption(fields=['username', 'first_name', 'last_name', 'email']),
25
+ RelatedEntitiesOption(
26
+ sections=[
27
+ RelatedEntityOption(
28
+ slug='sessions',
29
+ title=_('Сессии'),
30
+ get_items_method='get_user_sessions',
31
+ table=TableOption(
32
+ columns=[
33
+ TableColumnOption(field='started_at', label=_('Начало')),
34
+ TableColumnOption(field='client_ip', label=_('IP')),
35
+ TableColumnOption(field='country_code', label=_('Страна')),
36
+ TableColumnOption(field='browser', label=_('Браузер')),
37
+ TableColumnOption(field='last_seen_at', label=_('Активность')),
38
+ TableColumnOption(field='is_active', label=_('Активна'))
39
+ ]
40
+ )
41
+ )
42
+ ]
43
+ )
44
+ ]
45
+
46
+ @classmethod
47
+ @chassis_related_items
48
+ def get_user_sessions(cls, _request: HttpRequest, obj: AbstractUser) -> QuerySet[UserSession]:
49
+ """
50
+ Показать сессии выбранного пользователя.
51
+ """
52
+ return UserSession.objects.filter(user=obj).order_by('-last_seen_at', '-started_at')
@@ -0,0 +1,100 @@
1
+ from django.contrib import admin, messages
2
+ from django.contrib.auth.base_user import AbstractBaseUser
3
+ from django.db.models import Model
4
+ from django.http import HttpRequest, HttpResponse
5
+ from django.utils.translation import gettext_lazy as _
6
+
7
+ from django_chassis.decorators import object_action
8
+ from django_chassis.enums import ButtonColor, UserSessionEndReason
9
+ from django_chassis.filters.user_session_state import UserSessionStateFilter
10
+ from django_chassis.mixins.admin import ChassisAdminMixin
11
+ from django_chassis.mixins.read_only_admin import ReadOnlyAdminMixin
12
+ from django_chassis.models.user_session import UserSession
13
+ from django_chassis.options import FiltersOption, ObjectActionsOption, SearchOption
14
+ from django_chassis.services.user_session_service import UserSessionService
15
+ from django_chassis.validators.user_session_termination import can_terminate_user_session
16
+
17
+ class UserSessionAdmin(ChassisAdminMixin, ReadOnlyAdminMixin, admin.ModelAdmin):
18
+ """
19
+ Просматривать карточки сессий и завершать доступные сессии.
20
+ """
21
+
22
+ options = [
23
+ SearchOption(fields=['user__username', 'client_ip', 'browser', 'country_code']),
24
+ FiltersOption(fields=[UserSessionStateFilter, 'is_mobile']),
25
+ ObjectActionsOption(actions=['terminate_session'])
26
+ ]
27
+ fieldsets = [
28
+ (
29
+ _('Сессия'),
30
+ {
31
+ 'classes': ['wide'],
32
+ 'fields': [
33
+ 'id',
34
+ 'user',
35
+ 'is_active',
36
+ 'started_at',
37
+ 'last_seen_at',
38
+ 'expires_at',
39
+ 'ended_at',
40
+ 'end_reason',
41
+ 'revoked_by'
42
+ ]
43
+ }
44
+ ),
45
+ (
46
+ _('Клиент'),
47
+ {
48
+ 'classes': ['wide'],
49
+ 'fields': ['client_ip', 'country_code', 'browser', 'operating_system', 'device_label', 'user_agent']
50
+ }
51
+ )
52
+ ]
53
+ readonly_fields = [
54
+ 'id',
55
+ 'user',
56
+ 'is_active',
57
+ 'started_at',
58
+ 'last_seen_at',
59
+ 'expires_at',
60
+ 'ended_at',
61
+ 'end_reason',
62
+ 'revoked_by',
63
+ 'client_ip',
64
+ 'country_code',
65
+ 'browser',
66
+ 'operating_system',
67
+ 'device_label',
68
+ 'user_agent'
69
+ ]
70
+
71
+ @object_action(
72
+ description=_('Завершить сессию'),
73
+ permission='terminate_usersession',
74
+ confirmation=_('Завершить эту сессию? Пользователь будет разлогинен.'),
75
+ color=ButtonColor.DANGER,
76
+ condition=can_terminate_user_session
77
+ )
78
+ def terminate_session(self, request: HttpRequest, obj: UserSession) -> HttpResponse | None:
79
+ """
80
+ Отозвать выбранную активную сессию.
81
+ """
82
+ actor = request.user
83
+ if not isinstance(actor, AbstractBaseUser):
84
+ return None
85
+ terminated = UserSessionService().terminate(
86
+ user_session=obj,
87
+ actor=actor,
88
+ reason=UserSessionEndReason.REVOKED,
89
+ delete_backend=True
90
+ )
91
+ if terminated:
92
+ messages.success(request, _('Сессия завершена.'))
93
+ return None
94
+
95
+ def has_view_permission(self, request: HttpRequest, obj: Model | None = None) -> bool:
96
+ """
97
+ Сохранить стандартную проверку view и разрешить карточку для object action.
98
+ """
99
+ del obj
100
+ return getattr(request.user, 'has_perm')('django_chassis.view_usersession')
django_chassis/apps.py ADDED
@@ -0,0 +1,18 @@
1
+ from django.apps import AppConfig
2
+ from django.utils.translation import gettext_lazy as _
3
+
4
+
5
+ class Config(AppConfig):
6
+ """
7
+ Конфигурация общих компонентов интерфейса Django Admin.
8
+ """
9
+
10
+ default_auto_field = 'django.db.models.BigAutoField'
11
+ name = 'django_chassis'
12
+ verbose_name = _('Компоненты интерфейса Django Admin')
13
+
14
+ def ready(self) -> None:
15
+ """
16
+ Подключить создание и нормализацию разрешений Chassis.
17
+ """
18
+ from django_chassis import signals # noqa: F401
@@ -0,0 +1,22 @@
1
+ from django_chassis.components.actions import DashboardAction
2
+ from django_chassis.components.alerts import DashboardAlert
3
+ from django_chassis.components.base import DashboardComponent
4
+ from django_chassis.components.charts import DashboardChart
5
+ from django_chassis.components.geo_ip_database_alert import GeoIpDatabaseDashboardAlert
6
+ from django_chassis.components.metrics import DashboardMetric
7
+ from django_chassis.components.navbar import AdminNavbarButton, AdminNavbarDropdown, AdminNavbarItem, AdminUserMenuItem
8
+ from django_chassis.components.toggle_actions import DashboardToggleAction
9
+
10
+ __all__ = [
11
+ 'AdminNavbarButton',
12
+ 'AdminNavbarDropdown',
13
+ 'AdminNavbarItem',
14
+ 'AdminUserMenuItem',
15
+ 'DashboardAction',
16
+ 'DashboardAlert',
17
+ 'DashboardChart',
18
+ 'DashboardComponent',
19
+ 'DashboardMetric',
20
+ 'DashboardToggleAction',
21
+ 'GeoIpDatabaseDashboardAlert'
22
+ ]
@@ -0,0 +1,145 @@
1
+ import logging
2
+ from abc import ABC, abstractmethod
3
+ from collections.abc import Callable
4
+
5
+ from django.contrib import messages
6
+ from django.core.exceptions import PermissionDenied
7
+ from django.http import HttpRequest, HttpResponse
8
+ from django.shortcuts import redirect
9
+ from django.urls import URLPattern, path, reverse
10
+ from django.utils.encoding import force_str
11
+ from django.utils.translation import gettext_lazy as _
12
+
13
+ from django_chassis.components.base import DashboardComponent
14
+ from django_chassis.dto import DashboardActionDTO
15
+ from django_chassis.enums import ButtonColor
16
+ from django_chassis.type_aliases import LocalizedString
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class DashboardAction(DashboardComponent, ABC):
22
+ """
23
+ Базовое действие главной страницы Django Admin.
24
+ """
25
+
26
+ description: LocalizedString = ''
27
+ style: ButtonColor = ButtonColor.PRIMARY
28
+ route_prefix: str = 'dashboard/actions'
29
+
30
+ @property
31
+ def url_name(self) -> str:
32
+ """
33
+ Получить имя маршрута действия.
34
+ """
35
+ return f'dashboard_action_{self.slug}'
36
+
37
+ def get_urls(self) -> list[URLPattern]:
38
+ """
39
+ URL отдельной страницы действия.
40
+ """
41
+ return [
42
+ path(f'{self.route_prefix}/{self.slug}/', self.admin_site.admin_view(self.dispatch), name=self.url_name)
43
+ ]
44
+
45
+ def get_url(self) -> str:
46
+ """
47
+ Получить URL страницы действия.
48
+ """
49
+ return reverse(f'{self.admin_site.name}:{self.url_name}')
50
+
51
+ def get_label(self, request: HttpRequest) -> LocalizedString:
52
+ """
53
+ Получить заголовок действия.
54
+ """
55
+ return self.label
56
+
57
+ def get_description(self, request: HttpRequest) -> LocalizedString:
58
+ """
59
+ Получить описание действия.
60
+ """
61
+ return self.description
62
+
63
+ def get_style(self, request: HttpRequest) -> ButtonColor:
64
+ """
65
+ Получить визуальный стиль действия.
66
+ """
67
+ return self.style
68
+
69
+ def complete_action(
70
+ self,
71
+ request: HttpRequest,
72
+ message: LocalizedString,
73
+ message_level: int = messages.SUCCESS
74
+ ) -> HttpResponse:
75
+ """
76
+ Завершить действие и вернуться на главную админки.
77
+ """
78
+ messages.add_message(request=request, level=message_level, message=force_str(message))
79
+ return redirect(f'{self.admin_site.name}:index')
80
+
81
+ def execute_and_complete(
82
+ self,
83
+ request: HttpRequest,
84
+ action: Callable[[], None],
85
+ success_message: LocalizedString
86
+ ) -> HttpResponse:
87
+ """
88
+ Выполнить action, показать alert и перенаправить.
89
+ """
90
+ action()
91
+ return self.complete_action(request=request, message=success_message)
92
+
93
+ def complete_unavailable_action(self, request: HttpRequest) -> HttpResponse:
94
+ """
95
+ Обработать переход к недоступному действию.
96
+ """
97
+ raise PermissionDenied
98
+
99
+ @staticmethod
100
+ def get_exception_message(exception: Exception) -> LocalizedString:
101
+ """
102
+ Сформировать сообщение об ошибке действия.
103
+ """
104
+ exception_text = str(exception) or exception.__class__.__name__
105
+ return _('Ошибка выполнения действия: %(exception)s') % {'exception': exception_text}
106
+
107
+ def dispatch(self, request: HttpRequest) -> HttpResponse:
108
+ """
109
+ Проверить доступ и обработать исключения действия.
110
+ """
111
+ if not self.is_visible(request):
112
+ raise PermissionDenied
113
+ if not self.is_available(request):
114
+ return self.complete_unavailable_action(request)
115
+
116
+ try:
117
+ return self.view(request)
118
+ except Exception as e:
119
+ logger.exception({'message': 'Dashboard action failed.', 'data': {'action_slug': self.slug}})
120
+ return self.complete_action(
121
+ request=request,
122
+ message=self.get_exception_message(e),
123
+ message_level=messages.ERROR
124
+ )
125
+
126
+ def as_dto(self, request: HttpRequest) -> DashboardActionDTO:
127
+ """
128
+ Преобразовать действие в DTO для шаблона.
129
+ """
130
+ return DashboardActionDTO(
131
+ slug=self.slug,
132
+ label=self.get_label(request),
133
+ description=self.get_description(request),
134
+ url=self.get_url(),
135
+ style=self.get_style(request),
136
+ status=self.get_status(request),
137
+ is_available=self.is_available(request)
138
+ )
139
+
140
+ @abstractmethod
141
+ def view(self, request: HttpRequest) -> HttpResponse:
142
+ """
143
+ GET/POST-представление отдельной страницы действия.
144
+ """
145
+ raise NotImplementedError