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.
- django_chassis/__init__.py +1 -0
- django_chassis/admins/__init__.py +15 -0
- django_chassis/admins/admin_audit_log.py +107 -0
- django_chassis/admins/group.py +56 -0
- django_chassis/admins/login_attempt.py +69 -0
- django_chassis/admins/login_attempt_schedule.py +44 -0
- django_chassis/admins/user.py +52 -0
- django_chassis/admins/user_session.py +100 -0
- django_chassis/apps.py +18 -0
- django_chassis/components/__init__.py +22 -0
- django_chassis/components/actions.py +145 -0
- django_chassis/components/alerts.py +80 -0
- django_chassis/components/base.py +69 -0
- django_chassis/components/charts.py +82 -0
- django_chassis/components/geo_ip_database_alert.py +46 -0
- django_chassis/components/metrics.py +97 -0
- django_chassis/components/navbar.py +101 -0
- django_chassis/components/toggle_actions.py +122 -0
- django_chassis/conf.py +32 -0
- django_chassis/decorators/__init__.py +25 -0
- django_chassis/decorators/object_action.py +99 -0
- django_chassis/decorators/related_items.py +25 -0
- django_chassis/dto/__init__.py +71 -0
- django_chassis/dto/admin_information_badge.py +14 -0
- django_chassis/dto/admin_information_row.py +21 -0
- django_chassis/dto/admin_information_section.py +21 -0
- django_chassis/dto/admin_navbar_child_item.py +16 -0
- django_chassis/dto/admin_navbar_item.py +21 -0
- django_chassis/dto/admin_page_action.py +17 -0
- django_chassis/dto/admin_page_alert.py +15 -0
- django_chassis/dto/admin_profile.py +22 -0
- django_chassis/dto/admin_sidebar_child_item.py +16 -0
- django_chassis/dto/admin_sidebar_item.py +28 -0
- django_chassis/dto/admin_timeline.py +14 -0
- django_chassis/dto/admin_timeline_group.py +14 -0
- django_chassis/dto/admin_timeline_item.py +17 -0
- django_chassis/dto/brand.py +13 -0
- django_chassis/dto/brand_badge.py +14 -0
- django_chassis/dto/client_device.py +13 -0
- django_chassis/dto/dashboard_action.py +23 -0
- django_chassis/dto/dashboard_action_variant.py +18 -0
- django_chassis/dto/dashboard_alert.py +20 -0
- django_chassis/dto/dashboard_block.py +21 -0
- django_chassis/dto/dashboard_chart.py +29 -0
- django_chassis/dto/dashboard_chart_data.py +15 -0
- django_chassis/dto/dashboard_chart_point.py +12 -0
- django_chassis/dto/dashboard_chart_series.py +15 -0
- django_chassis/dto/dashboard_metric.py +23 -0
- django_chassis/dto/dashboard_stacked_item.py +16 -0
- django_chassis/dto/export_field.py +16 -0
- django_chassis/dto/export_filter.py +17 -0
- django_chassis/dto/import_field.py +16 -0
- django_chassis/dto/import_preview.py +34 -0
- django_chassis/dto/login_attempt_availability.py +14 -0
- django_chassis/dto/request_client_metadata.py +17 -0
- django_chassis/dto/user_profile_activity.py +14 -0
- django_chassis/enums/__init__.py +57 -0
- django_chassis/enums/admin_audit_action.py +40 -0
- django_chassis/enums/admin_audit_result.py +30 -0
- django_chassis/enums/admin_error_status.py +73 -0
- django_chassis/enums/admin_navbar_item_type.py +11 -0
- django_chassis/enums/button_color.py +14 -0
- django_chassis/enums/choices_enum.py +36 -0
- django_chassis/enums/dashboard_alert_style.py +22 -0
- django_chassis/enums/dashboard_block_type.py +13 -0
- django_chassis/enums/dashboard_chart_color.py +11 -0
- django_chassis/enums/dashboard_chart_type.py +11 -0
- django_chassis/enums/dashboard_component_status.py +12 -0
- django_chassis/enums/dashboard_live_widget_type.py +12 -0
- django_chassis/enums/dashboard_metric_result_type.py +11 -0
- django_chassis/enums/dashboard_stacked_item_tone.py +12 -0
- django_chassis/enums/export_backend.py +22 -0
- django_chassis/enums/export_format.py +42 -0
- django_chassis/enums/export_status.py +28 -0
- django_chassis/enums/import_backend.py +22 -0
- django_chassis/enums/import_format.py +35 -0
- django_chassis/enums/import_status.py +30 -0
- django_chassis/enums/permission_check.py +10 -0
- django_chassis/enums/permission_name_language.py +9 -0
- django_chassis/enums/row_action_display.py +10 -0
- django_chassis/enums/row_action_type.py +12 -0
- django_chassis/enums/sidebar_icon_color.py +33 -0
- django_chassis/enums/tab_display.py +10 -0
- django_chassis/enums/user_session_end_reason.py +28 -0
- django_chassis/exceptions.py +33 -0
- django_chassis/filters/__init__.py +0 -0
- django_chassis/filters/user_session_state.py +42 -0
- django_chassis/forms/__init__.py +13 -0
- django_chassis/forms/admin_authentication.py +87 -0
- django_chassis/forms/confirmed_action.py +11 -0
- django_chassis/forms/export_form.py +103 -0
- django_chassis/forms/import_export_base.py +92 -0
- django_chassis/forms/import_form.py +65 -0
- django_chassis/forms/self_password_change.py +19 -0
- django_chassis/management/__init__.py +0 -0
- django_chassis/management/commands/__init__.py +0 -0
- django_chassis/management/commands/run_gunicorn.py +91 -0
- django_chassis/middlewares/__init__.py +5 -0
- django_chassis/middlewares/admin_audit.py +43 -0
- django_chassis/middlewares/admin_error_page.py +66 -0
- django_chassis/middlewares/user_session.py +46 -0
- django_chassis/migrations/0001_initial.py +217 -0
- django_chassis/migrations/__init__.py +0 -0
- django_chassis/mixins/__init__.py +6 -0
- django_chassis/mixins/admin.py +300 -0
- django_chassis/mixins/admin_site.py +355 -0
- django_chassis/mixins/core/__init__.py +3 -0
- django_chassis/mixins/core/actions.py +164 -0
- django_chassis/mixins/core/audit.py +68 -0
- django_chassis/mixins/core/base.py +73 -0
- django_chassis/mixins/core/changelist.py +207 -0
- django_chassis/mixins/core/import_export.py +193 -0
- django_chassis/mixins/core/object_page.py +306 -0
- django_chassis/mixins/core/permissions.py +75 -0
- django_chassis/mixins/core/presentation.py +395 -0
- django_chassis/mixins/no_delete_admin.py +14 -0
- django_chassis/mixins/read_only_admin.py +29 -0
- django_chassis/models/__init__.py +17 -0
- django_chassis/models/admin_audit_log.py +105 -0
- django_chassis/models/export_history.py +93 -0
- django_chassis/models/history_storage.py +32 -0
- django_chassis/models/import_history.py +93 -0
- django_chassis/models/login_attempt.py +52 -0
- django_chassis/models/login_attempt_schedule.py +39 -0
- django_chassis/models/managers.py +43 -0
- django_chassis/models/user_profile.py +58 -0
- django_chassis/models/user_session.py +99 -0
- django_chassis/navbar/__init__.py +3 -0
- django_chassis/navbar/admin_profile.py +40 -0
- django_chassis/navbar/password_change.py +40 -0
- django_chassis/navbar/theme.py +24 -0
- django_chassis/options/__init__.py +82 -0
- django_chassis/options/admin_page_group.py +15 -0
- django_chassis/options/badge_fields.py +14 -0
- django_chassis/options/date_hierarchy.py +15 -0
- django_chassis/options/decimal_amount.py +17 -0
- django_chassis/options/export.py +39 -0
- django_chassis/options/field_tabs.py +21 -0
- django_chassis/options/filters.py +16 -0
- django_chassis/options/foreign_key_tab_group.py +18 -0
- django_chassis/options/foreign_key_tabs.py +23 -0
- django_chassis/options/import_option.py +35 -0
- django_chassis/options/list_action.py +19 -0
- django_chassis/options/list_actions.py +12 -0
- django_chassis/options/object_actions.py +17 -0
- django_chassis/options/permission.py +35 -0
- django_chassis/options/pretty_json.py +10 -0
- django_chassis/options/related_entities.py +21 -0
- django_chassis/options/related_entity.py +23 -0
- django_chassis/options/row_action.py +19 -0
- django_chassis/options/row_actions.py +30 -0
- django_chassis/options/search.py +16 -0
- django_chassis/options/settings.py +13 -0
- django_chassis/options/sidebar_link.py +15 -0
- django_chassis/options/sidebar_model_item.py +14 -0
- django_chassis/options/sidebar_page_item.py +14 -0
- django_chassis/options/sidebar_section.py +22 -0
- django_chassis/options/table.py +21 -0
- django_chassis/options/table_action.py +24 -0
- django_chassis/options/table_action_url_kwarg.py +11 -0
- django_chassis/options/table_column.py +13 -0
- django_chassis/permissions.py +53 -0
- django_chassis/protocols.py +40 -0
- django_chassis/py.typed +0 -0
- django_chassis/registries/__init__.py +3 -0
- django_chassis/registries/permission_registry.py +33 -0
- django_chassis/renderers/__init__.py +3 -0
- django_chassis/renderers/admin_error_page_renderer.py +167 -0
- django_chassis/services/__init__.py +65 -0
- django_chassis/services/action_permission_validation_service.py +50 -0
- django_chassis/services/admin_audit_log_service.py +155 -0
- django_chassis/services/admin_navbar_service.py +118 -0
- django_chassis/services/admin_page_service.py +108 -0
- django_chassis/services/admin_permission_service.py +24 -0
- django_chassis/services/admin_profile_service.py +54 -0
- django_chassis/services/admin_sidebar_service.py +304 -0
- django_chassis/services/celery_availability_service.py +25 -0
- django_chassis/services/client_device_service.py +193 -0
- django_chassis/services/client_geo_ip_service.py +158 -0
- django_chassis/services/client_ip_service.py +56 -0
- django_chassis/services/confirmed_action_service.py +46 -0
- django_chassis/services/dashboard_analytics_cache_service.py +28 -0
- django_chassis/services/dashboard_live_serialization_service.py +68 -0
- django_chassis/services/dashboard_service.py +379 -0
- django_chassis/services/deleted_objects_service.py +164 -0
- django_chassis/services/import_apply_service.py +262 -0
- django_chassis/services/import_export_dispatch_service.py +146 -0
- django_chassis/services/import_export_field_service.py +157 -0
- django_chassis/services/import_export_filter_service.py +183 -0
- django_chassis/services/import_export_process_service.py +226 -0
- django_chassis/services/import_export_python_queue_service.py +91 -0
- django_chassis/services/import_export_serialization_service.py +493 -0
- django_chassis/services/login_attempt_limit_service.py +341 -0
- django_chassis/services/pagination_service.py +61 -0
- django_chassis/services/permission_initialization_service.py +35 -0
- django_chassis/services/permission_name_normalization_service.py +125 -0
- django_chassis/services/session_key_crypto_service.py +26 -0
- django_chassis/services/table_service.py +135 -0
- django_chassis/services/user_profile_service.py +117 -0
- django_chassis/services/user_session_service.py +266 -0
- django_chassis/signals/__init__.py +10 -0
- django_chassis/signals/initialize_permissions.py +15 -0
- django_chassis/signals/normalize_default_permission_names.py +13 -0
- django_chassis/signals/user_profile.py +42 -0
- django_chassis/singleton.py +17 -0
- django_chassis/static/admin/chassis/admin.css +1815 -0
- django_chassis/static/admin/chassis/alerts.css +264 -0
- django_chassis/static/admin/chassis/alerts.js +46 -0
- django_chassis/static/admin/chassis/apexcharts/apexcharts.css +1203 -0
- django_chassis/static/admin/chassis/apexcharts/apexcharts.min.js +5 -0
- django_chassis/static/admin/chassis/auth.css +311 -0
- django_chassis/static/admin/chassis/collapsibles.js +46 -0
- django_chassis/static/admin/chassis/dashboard.css +854 -0
- django_chassis/static/admin/chassis/dashboard_charts.js +234 -0
- django_chassis/static/admin/chassis/dashboard_live.js +332 -0
- django_chassis/static/admin/chassis/date_input_mask.js +237 -0
- django_chassis/static/admin/chassis/error_page.css +116 -0
- django_chassis/static/admin/chassis/filters.js +19 -0
- django_chassis/static/admin/chassis/fontawesome/css/all.min.css +9 -0
- django_chassis/static/admin/chassis/fontawesome/webfonts/fa-brands-400.woff2 +0 -0
- django_chassis/static/admin/chassis/fontawesome/webfonts/fa-regular-400.woff2 +0 -0
- django_chassis/static/admin/chassis/fontawesome/webfonts/fa-solid-900.woff2 +0 -0
- django_chassis/static/admin/chassis/fontawesome/webfonts/fa-v4compatibility.woff2 +0 -0
- django_chassis/static/admin/chassis/icon.svg +7 -0
- django_chassis/static/admin/chassis/navbar.css +374 -0
- django_chassis/static/admin/chassis/navbar_dropdown.js +29 -0
- django_chassis/static/admin/chassis/related_pagination.js +176 -0
- django_chassis/static/admin/chassis/secret_field.js +105 -0
- django_chassis/static/admin/chassis/sidebar.css +765 -0
- django_chassis/static/admin/chassis/sidebar.js +63 -0
- django_chassis/static/admin/chassis/table_selection.js +22 -0
- django_chassis/tasks/__init__.py +3 -0
- django_chassis/tasks/process_import_export.py +26 -0
- django_chassis/templates/admin/base.html +328 -0
- django_chassis/templates/admin/change_form.html +35 -0
- django_chassis/templates/admin/chassis/auth_alert.html +9 -0
- django_chassis/templates/admin/chassis/auth_alerts.html +40 -0
- django_chassis/templates/admin/chassis/blocked_profiles.html +80 -0
- django_chassis/templates/admin/chassis/brand.html +16 -0
- django_chassis/templates/admin/chassis/change_form.html +63 -0
- django_chassis/templates/admin/chassis/change_list.html +141 -0
- django_chassis/templates/admin/chassis/confirmed_action.html +29 -0
- django_chassis/templates/admin/chassis/dashboard/actions/base.html +21 -0
- django_chassis/templates/admin/chassis/dashboard/actions/toggle.html +45 -0
- django_chassis/templates/admin/chassis/dashboard/index.html +113 -0
- django_chassis/templates/admin/chassis/dashboard/sections/actions.html +14 -0
- django_chassis/templates/admin/chassis/dashboard/sections/alerts.html +27 -0
- django_chassis/templates/admin/chassis/dashboard/sections/chart.html +40 -0
- django_chassis/templates/admin/chassis/dashboard/sections/items/action.html +29 -0
- django_chassis/templates/admin/chassis/dashboard/sections/items/metric.html +75 -0
- django_chassis/templates/admin/chassis/dashboard/sections/metrics.html +14 -0
- django_chassis/templates/admin/chassis/dashboard/sections/metrics_and_actions.html +18 -0
- django_chassis/templates/admin/chassis/dashboard/sections/panel_heading.html +12 -0
- django_chassis/templates/admin/chassis/date_hierarchy.html +23 -0
- django_chassis/templates/admin/chassis/filters.html +49 -0
- django_chassis/templates/admin/chassis/form_rows.html +44 -0
- django_chassis/templates/admin/chassis/import_export_form.html +128 -0
- django_chassis/templates/admin/chassis/import_export_landing.html +42 -0
- django_chassis/templates/admin/chassis/import_export_preview.html +113 -0
- django_chassis/templates/admin/chassis/information_alert.html +7 -0
- django_chassis/templates/admin/chassis/information_page.html +24 -0
- django_chassis/templates/admin/chassis/information_sections.html +108 -0
- django_chassis/templates/admin/chassis/object_page.html +26 -0
- django_chassis/templates/admin/chassis/page.html +21 -0
- django_chassis/templates/admin/chassis/pagination.html +18 -0
- django_chassis/templates/admin/chassis/related_entities.html +41 -0
- django_chassis/templates/admin/chassis/row_actions.html +19 -0
- django_chassis/templates/admin/chassis/search.html +29 -0
- django_chassis/templates/admin/chassis/self_password_change.html +34 -0
- django_chassis/templates/admin/chassis/sidebar.html +74 -0
- django_chassis/templates/admin/chassis/table.html +54 -0
- django_chassis/templates/admin/chassis/timeline.html +37 -0
- django_chassis/templates/admin/chassis/widgets/date.html +1 -0
- django_chassis/templates/admin/chassis/widgets/split_datetime.html +16 -0
- django_chassis/templates/admin/chassis/widgets/time.html +1 -0
- django_chassis/templates/admin/error.html +37 -0
- django_chassis/templates/admin/footer.html +15 -0
- django_chassis/templates/admin/login.html +86 -0
- django_chassis/templates/admin/object_actions/base_action_view.html +18 -0
- django_chassis/templatetags/__init__.py +1 -0
- django_chassis/templatetags/admin_footer.py +15 -0
- django_chassis/templatetags/chassis_admin.py +95 -0
- django_chassis/type_aliases.py +5 -0
- django_chassis/utils/__init__.py +8 -0
- django_chassis/utils/fixed_point_decimal.py +39 -0
- django_chassis/utils/table_exists.py +8 -0
- django_chassis/validators/__init__.py +0 -0
- django_chassis/validators/login_attempt_unlock.py +16 -0
- django_chassis/validators/object_action_condition.py +23 -0
- django_chassis/validators/translation_management.py +15 -0
- django_chassis/validators/user_session_termination.py +20 -0
- django_chassis/views/__init__.py +19 -0
- django_chassis/views/admin_information_page.py +44 -0
- django_chassis/views/admin_object_page.py +214 -0
- django_chassis/views/admin_page.py +113 -0
- django_chassis/views/admin_profile.py +232 -0
- django_chassis/views/import_export.py +575 -0
- django_chassis/views/permission_admin_page.py +29 -0
- django_chassis/views/personal_admin_page.py +22 -0
- django_chassis/views/self_password_change.py +54 -0
- django_chassis/views/superuser_admin_page.py +24 -0
- django_chassis/widgets/__init__.py +33 -0
- django_chassis/widgets/date_input.py +33 -0
- django_chassis/widgets/date_time_input.py +27 -0
- django_chassis/widgets/execution_backend_select.py +35 -0
- django_chassis/widgets/input_mask.py +35 -0
- django_chassis/widgets/split_date_time.py +19 -0
- django_chassis/widgets/time_input.py +33 -0
- django_chassis-1.0.1.dist-info/METADATA +175 -0
- django_chassis-1.0.1.dist-info/RECORD +313 -0
- django_chassis-1.0.1.dist-info/WHEEL +5 -0
- django_chassis-1.0.1.dist-info/licenses/LICENSE +21 -0
- 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
|