firefighter-incident 0.0.1rc2__py3-none-any.whl → 0.0.3__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.
- firefighter/_version.py +9 -4
- firefighter/api/admin.py +31 -12
- firefighter/api/migrations/0003_alter_apitokenproxy_options.py +30 -0
- firefighter/api/models.py +2 -2
- firefighter/api/renderer.py +1 -1
- firefighter/api/serializers.py +6 -4
- firefighter/api/urls.py +13 -19
- firefighter/components/avatar/avatar.py +24 -7
- firefighter/components/card/card.py +9 -4
- firefighter/components/export_button/export_button.html +2 -2
- firefighter/components/export_button/export_button.py +26 -7
- firefighter/components/form/form.html +1 -1
- firefighter/components/form/form.py +15 -12
- firefighter/components/form_field/form_field.html +4 -4
- firefighter/components/form_field/form_field.py +20 -12
- firefighter/components/messages/messages.html +39 -49
- firefighter/components/messages/messages.py +10 -5
- firefighter/components/modal/modal.html +20 -25
- firefighter/components/modal/modal.py +24 -9
- firefighter/confluence/models.py +2 -0
- firefighter/confluence/service.py +3 -3
- firefighter/confluence/tasks/archive_postmortems.py +7 -2
- firefighter/confluence/tasks/sync_postmortems.py +1 -1
- firefighter/confluence/tasks/sync_runbooks.py +3 -3
- firefighter/confluence/templates/oncall_team.xml +3 -3
- firefighter/confluence/templates/pages/runbook_list.html +4 -5
- firefighter/confluence/utils.py +4 -1
- firefighter/firefighter/filters.py +3 -2
- firefighter/firefighter/http_client.py +7 -9
- firefighter/firefighter/management/commands/task.py +1 -1
- firefighter/firefighter/settings/__init__.py +1 -0
- firefighter/firefighter/settings/components/common.py +8 -3
- firefighter/firefighter/settings/components/logging.py +1 -1
- firefighter/firefighter/settings/components/raid.py +3 -3
- firefighter/firefighter/settings/environments/dev.py +1 -3
- firefighter/firefighter/settings/environments/prod.py +1 -0
- firefighter/firefighter/sso.py +1 -1
- firefighter/firefighter/templates/admin/base.html +3 -2
- firefighter/firefighter/templates/admin/login.html +2 -2
- firefighter/firefighter/templates/admin/send_message_conversation.html +4 -4
- firefighter/firefighter/urls.py +7 -8
- firefighter/firefighter/utils.py +1 -3
- firefighter/firefighter/wsgi.py +2 -1
- firefighter/incidents/admin.py +11 -7
- firefighter/incidents/factories.py +33 -32
- firefighter/incidents/forms/edit.py +33 -0
- firefighter/incidents/forms/select_impact.py +4 -11
- firefighter/incidents/forms/update_key_events.py +1 -1
- firefighter/incidents/forms/update_roles.py +1 -1
- firefighter/incidents/forms/utils.py +3 -3
- firefighter/incidents/menus.py +5 -4
- firefighter/incidents/migrations/0002_alter_severity_name_alter_user_password_featureteam.py +35 -0
- firefighter/incidents/migrations/0003_delete_featureteam.py +16 -0
- firefighter/incidents/migrations/0004_incidentupdate_environment.py +27 -0
- firefighter/incidents/migrations/0005_enable_from_p1_to_p5_priority.py +30 -0
- firefighter/incidents/migrations/0006_update_group_names.py +102 -0
- firefighter/incidents/migrations/0007_update_component_name.py +148 -0
- firefighter/incidents/migrations/0008_impact_level.py +273 -0
- firefighter/incidents/models/component.py +8 -11
- firefighter/incidents/models/group.py +1 -0
- firefighter/incidents/models/impact.py +42 -12
- firefighter/incidents/models/incident.py +14 -11
- firefighter/incidents/models/incident_cost.py +2 -2
- firefighter/incidents/models/incident_membership.py +5 -5
- firefighter/incidents/models/incident_update.py +14 -5
- firefighter/incidents/models/metric_type.py +2 -2
- firefighter/incidents/signals.py +0 -5
- firefighter/incidents/static/css/main.min.css +1 -1
- firefighter/incidents/static/css/tailwind.css +18 -34
- firefighter/incidents/static/js/main.min.js +12 -12
- firefighter/incidents/tables.py +1 -5
- firefighter/incidents/tasks/updateoncall.py +1 -3
- firefighter/incidents/templates/incidents/errors/base.html +2 -1
- firefighter/incidents/templates/incidents/filter.html +33 -33
- firefighter/incidents/templates/incidents/table/priority_column.html +1 -1
- firefighter/incidents/templates/incidents/table.html +2 -3
- firefighter/incidents/templates/incidents/widgets/form_container.html +60 -61
- firefighter/incidents/templates/incidents/widgets/grouped_checkbox_nested.html +52 -52
- firefighter/incidents/templates/layouts/index.html +3 -3
- firefighter/incidents/templates/layouts/partials/created_at_help.html +2 -3
- firefighter/incidents/templates/layouts/partials/environment_pill.html +6 -6
- firefighter/incidents/templates/layouts/partials/footer.html +4 -7
- firefighter/incidents/templates/layouts/partials/header.html +41 -31
- firefighter/incidents/templates/layouts/partials/incident_card.html +6 -6
- firefighter/incidents/templates/layouts/partials/incident_metrics.html +1 -2
- firefighter/incidents/templates/layouts/partials/incident_timeline.html +35 -6
- firefighter/incidents/templates/layouts/partials/incident_update_key_events_view.html +3 -3
- firefighter/incidents/templates/layouts/partials/incident_update_key_events_view_modal.html +5 -7
- firefighter/incidents/templates/layouts/partials/partial_table_list_paginated.html +8 -9
- firefighter/incidents/templates/layouts/partials/priority_pill.html +1 -1
- firefighter/incidents/templates/layouts/partials/status_pill.html +15 -15
- firefighter/incidents/templates/layouts/partials/table.html +3 -3
- firefighter/incidents/templates/layouts/partials/user_card.html +3 -4
- firefighter/incidents/templates/layouts/partials/user_tooltip.html +1 -1
- firefighter/incidents/templates/layouts/view_filters.html +9 -9
- firefighter/incidents/templates/pages/component_detail.html +9 -9
- firefighter/incidents/templates/pages/component_list.html +5 -7
- firefighter/incidents/templates/pages/dashboard.html +4 -4
- firefighter/incidents/templates/pages/docs_metrics.html +43 -41
- firefighter/incidents/templates/pages/incident_create.html +9 -10
- firefighter/incidents/templates/pages/incident_detail.html +52 -46
- firefighter/incidents/templates/pages/incident_list.html +7 -7
- firefighter/incidents/templates/pages/incident_role_types_detail.html +3 -5
- firefighter/incidents/templates/pages/incident_role_types_list.html +3 -3
- firefighter/incidents/templates/pages/incident_statistics.html +10 -10
- firefighter/incidents/templates/pages/incident_statistics_partial.html +7 -11
- firefighter/incidents/templates/pages/incident_update_key_events_form.html +1 -1
- firefighter/incidents/templates/pages/user_detail.html +15 -15
- firefighter/incidents/views/docs/role_types.py +2 -1
- firefighter/incidents/views/errors.py +15 -16
- firefighter/incidents/views/reports.py +8 -8
- firefighter/incidents/views/users/details.py +1 -4
- firefighter/jira_app/client.py +3 -3
- firefighter/jira_app/models.py +2 -2
- firefighter/logging/custom_json_formatter.py +2 -2
- firefighter/pagerduty/models.py +9 -1
- firefighter/pagerduty/tasks/__init__.py +1 -0
- firefighter/pagerduty/tasks/trigger_oncall.py +7 -9
- firefighter/pagerduty/templates/pages/oncall_list.html +7 -7
- firefighter/pagerduty/templates/pages/oncall_trigger.html +1 -1
- firefighter/pagerduty/templates/partials/trigger_oncall_form_view.html +3 -3
- firefighter/pagerduty/templates/partials/trigger_oncall_form_view_modal.html +5 -7
- firefighter/raid/admin.py +13 -87
- firefighter/raid/apps.py +0 -1
- firefighter/raid/client.py +12 -7
- firefighter/raid/forms.py +5 -17
- firefighter/raid/messages.py +6 -90
- firefighter/raid/migrations/0002_featureteam_remove_qualifierrotation_jira_user_and_more.py +36 -0
- firefighter/raid/models.py +28 -46
- firefighter/raid/resources.py +15 -0
- firefighter/raid/service.py +5 -28
- firefighter/raid/signals/incident_created.py +2 -13
- firefighter/raid/tasks/__init__.py +0 -3
- firefighter/raid/views/open_normal.py +1 -1
- firefighter/slack/admin.py +39 -3
- firefighter/slack/factories.py +32 -32
- firefighter/slack/messages/slack_messages.py +47 -33
- firefighter/slack/migrations/0002_usergroup_tag.py +22 -0
- firefighter/slack/migrations/0003_alter_usergroup_tag.py +22 -0
- firefighter/slack/models/conversation.py +3 -3
- firefighter/slack/models/incident_channel.py +2 -2
- firefighter/slack/models/message.py +4 -4
- firefighter/slack/models/sos.py +2 -2
- firefighter/slack/models/user_group.py +6 -0
- firefighter/slack/rules.py +3 -4
- firefighter/slack/signals/create_incident_conversation.py +2 -1
- firefighter/slack/signals/get_users.py +29 -3
- firefighter/slack/signals/incident_updated.py +2 -2
- firefighter/slack/slack_app.py +18 -2
- firefighter/slack/slack_incident_context.py +8 -8
- firefighter/slack/tasks/fetch_conversations_members.py +1 -1
- firefighter/slack/tasks/send_reminders.py +1 -1
- firefighter/slack/tasks/update_usergroups_members.py +1 -1
- firefighter/slack/views/events/commands.py +3 -0
- firefighter/slack/views/events/home.py +22 -20
- firefighter/slack/views/modals/__init__.py +2 -0
- firefighter/slack/views/modals/base_modal/base.py +2 -2
- firefighter/slack/views/modals/base_modal/form_utils.py +6 -10
- firefighter/slack/views/modals/base_modal/mixins.py +1 -2
- firefighter/slack/views/modals/close.py +15 -13
- firefighter/slack/views/modals/downgrade_workflow.py +13 -17
- firefighter/slack/views/modals/edit.py +117 -0
- firefighter/slack/views/modals/key_event_message.py +9 -9
- firefighter/slack/views/modals/open.py +21 -21
- firefighter/slack/views/modals/opening/check_current_incidents.py +3 -3
- firefighter/slack/views/modals/opening/details/critical.py +7 -5
- firefighter/slack/views/modals/opening/select_impact.py +11 -14
- firefighter/slack/views/modals/opening/set_details.py +4 -2
- firefighter/slack/views/modals/opening/types.py +1 -1
- firefighter/slack/views/modals/postmortem.py +16 -20
- firefighter/slack/views/modals/select.py +1 -1
- firefighter/slack/views/modals/status.py +17 -21
- firefighter/slack/views/modals/trigger_oncall.py +19 -21
- firefighter/slack/views/modals/update.py +5 -0
- firefighter/slack/views/modals/update_status.py +7 -5
- firefighter_fixtures/incidents/components.json +619 -491
- firefighter_fixtures/incidents/groups.json +64 -119
- firefighter_fixtures/incidents/impact_level.json +124 -47
- {firefighter_incident-0.0.1rc2.dist-info → firefighter_incident-0.0.3.dist-info}/METADATA +13 -8
- {firefighter_incident-0.0.1rc2.dist-info → firefighter_incident-0.0.3.dist-info}/RECORD +210 -195
- {firefighter_incident-0.0.1rc2.dist-info → firefighter_incident-0.0.3.dist-info}/WHEEL +1 -1
- firefighter_tests/conftest.py +8 -9
- firefighter_tests/test_api/test_api_urls.py +4 -4
- firefighter_tests/test_firefighter/test_urls.py +12 -12
- firefighter_tests/test_incidents/test_forms/test_form_select_impact.py +27 -27
- firefighter_tests/test_incidents/test_forms/test_form_utils.py +4 -5
- firefighter_tests/test_incidents/test_forms/test_update_key_events.py +3 -3
- firefighter_tests/test_incidents/test_incident_urls.py +10 -10
- firefighter_tests/test_incidents/test_models/test_incident_model.py +1 -1
- firefighter_tests/test_incidents/test_utils/test_date_utils.py +3 -2
- firefighter_tests/test_incidents/test_views/test_incident_detail_view.py +3 -3
- firefighter_tests/test_incidents/test_views/test_index_view.py +6 -6
- firefighter_tests/test_raid/test_raid_client_users.py +12 -12
- firefighter_tests/test_slack/conftest.py +6 -6
- firefighter_tests/test_slack/test_models/test_conversations.py +2 -2
- firefighter_tests/test_slack/test_models/test_incident_channel.py +55 -46
- firefighter_tests/test_slack/test_models/test_slack_user.py +9 -9
- firefighter_tests/test_slack/test_slack_utils.py +6 -6
- firefighter_tests/test_slack/views/modals/test_close.py +7 -7
- firefighter_tests/test_slack/views/modals/test_open.py +7 -7
- firefighter_tests/test_slack/views/modals/test_send_sos.py +2 -2
- firefighter_tests/test_slack/views/modals/test_status.py +2 -2
- firefighter_tests/test_slack/views/modals/test_update_status.py +4 -4
- manage.py +1 -0
- package-lock.json +6442 -0
- package.json +49 -0
- scripts/gen_credits.py +165 -0
- scripts/hatch_build.py +15 -0
- firefighter/raid/signals/update_qualifiers_rotation.py +0 -97
- firefighter/raid/tasks/daily_qualifier.py +0 -67
- firefighter/raid/tasks/weekly_qualifier.py +0 -63
- {firefighter_incident-0.0.1rc2.dist-info → firefighter_incident-0.0.3.dist-info}/entry_points.txt +0 -0
- {firefighter_incident-0.0.1rc2.dist-info → firefighter_incident-0.0.3.dist-info}/licenses/LICENSE +0 -0
firefighter/_version.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '0.0.
|
|
16
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
20
|
+
__version__ = version = '0.0.3'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 0, 3)
|
firefighter/api/admin.py
CHANGED
|
@@ -29,7 +29,12 @@ class APITokenAdmin(TokenAdmin):
|
|
|
29
29
|
Add supports for custom permissions.
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
-
def formfield_for_foreignkey(
|
|
32
|
+
def formfield_for_foreignkey(
|
|
33
|
+
self,
|
|
34
|
+
db_field: ForeignKey[Any, Any],
|
|
35
|
+
request: HttpRequest, # type: ignore[override]
|
|
36
|
+
**kwargs: Any,
|
|
37
|
+
) -> ModelChoiceField: # type: ignore[type-arg]
|
|
33
38
|
"""Show all or only current user depending on permissions."""
|
|
34
39
|
if db_field.name == "user":
|
|
35
40
|
if request.user.has_perm("api.can_add_any") or request.user.has_perm(
|
|
@@ -38,9 +43,15 @@ class APITokenAdmin(TokenAdmin):
|
|
|
38
43
|
kwargs["queryset"] = User.objects.all()
|
|
39
44
|
elif request.user.has_perm("api.can_add_own"):
|
|
40
45
|
kwargs["queryset"] = User.objects.filter(id=request.user.id)
|
|
41
|
-
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
|
42
|
-
|
|
43
|
-
def get_form(
|
|
46
|
+
return super().formfield_for_foreignkey(db_field, request, **kwargs) # type: ignore[return-value]
|
|
47
|
+
|
|
48
|
+
def get_form(
|
|
49
|
+
self,
|
|
50
|
+
request: HttpRequest, # type: ignore[override]
|
|
51
|
+
obj: APITokenProxy | None = None,
|
|
52
|
+
change: bool = False, # noqa: FBT001, FBT002
|
|
53
|
+
**kwargs: Any,
|
|
54
|
+
) -> type[ModelForm[APITokenProxy]]:
|
|
44
55
|
"""Prefill the form with the current user."""
|
|
45
56
|
form: type[ModelForm[APITokenProxy]] = super().get_form(
|
|
46
57
|
request, obj, change, **kwargs
|
|
@@ -65,7 +76,11 @@ class APITokenAdmin(TokenAdmin):
|
|
|
65
76
|
)
|
|
66
77
|
return super().get_sortable_by(request)
|
|
67
78
|
|
|
68
|
-
def has_view_permission(
|
|
79
|
+
def has_view_permission(
|
|
80
|
+
self,
|
|
81
|
+
request: HttpRequest, # type: ignore[override]
|
|
82
|
+
obj: APITokenProxy | None = None,
|
|
83
|
+
) -> bool:
|
|
69
84
|
if obj is None:
|
|
70
85
|
return request.user.has_perm("api.can_view_any") or request.user.has_perm(
|
|
71
86
|
"api.can_view_own"
|
|
@@ -81,7 +96,11 @@ class APITokenAdmin(TokenAdmin):
|
|
|
81
96
|
"api.can_add_own"
|
|
82
97
|
)
|
|
83
98
|
|
|
84
|
-
def has_delete_permission(
|
|
99
|
+
def has_delete_permission(
|
|
100
|
+
self,
|
|
101
|
+
request: HttpRequest, # type: ignore[override]
|
|
102
|
+
obj: APITokenProxy | None = None,
|
|
103
|
+
) -> bool:
|
|
85
104
|
if obj is None:
|
|
86
105
|
return request.user.has_perm("api.can_delete_any") or request.user.has_perm(
|
|
87
106
|
"api.can_delete_own"
|
|
@@ -92,12 +111,12 @@ class APITokenAdmin(TokenAdmin):
|
|
|
92
111
|
return bool(obj.user == request.user)
|
|
93
112
|
return False
|
|
94
113
|
|
|
95
|
-
def has_change_permission(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return
|
|
114
|
+
def has_change_permission(
|
|
115
|
+
self,
|
|
116
|
+
request: HttpRequest, # type: ignore[override]
|
|
117
|
+
_obj: APITokenProxy | None = None,
|
|
118
|
+
) -> bool:
|
|
119
|
+
return request.user.has_perm("api.can_edit_any")
|
|
101
120
|
|
|
102
121
|
|
|
103
122
|
# Remove the default TokenAdmin created by DRF and replace it with our custom one.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Generated by Django 4.2.11 on 2024-04-30 15:39
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("api", "0002_alter_apitokenproxy_options"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterModelOptions(
|
|
14
|
+
name="apitokenproxy",
|
|
15
|
+
options={
|
|
16
|
+
"default_permissions": [],
|
|
17
|
+
"permissions": [
|
|
18
|
+
("can_edit_any", "Can reassign token to any user"),
|
|
19
|
+
("can_add_any", "Can add token to any user"),
|
|
20
|
+
("can_view_any", "Can view token of all users"),
|
|
21
|
+
("can_delete_any", "Can delete token of any user"),
|
|
22
|
+
("can_add_own", "Can add own tokens"),
|
|
23
|
+
("can_view_own", "Can view own tokens"),
|
|
24
|
+
("can_delete_own", "Can delete own tokens"),
|
|
25
|
+
],
|
|
26
|
+
"verbose_name": "API Token",
|
|
27
|
+
"verbose_name_plural": "API Tokens",
|
|
28
|
+
},
|
|
29
|
+
),
|
|
30
|
+
]
|
firefighter/api/models.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import uuid
|
|
4
3
|
from typing import TYPE_CHECKING, ClassVar, Self, cast
|
|
5
4
|
|
|
6
5
|
from django.conf import settings
|
|
@@ -9,6 +8,7 @@ from django_stubs_ext.db.models import TypedModelMeta
|
|
|
9
8
|
from rest_framework.authtoken.models import Token
|
|
10
9
|
|
|
11
10
|
if TYPE_CHECKING:
|
|
11
|
+
import uuid
|
|
12
12
|
from collections.abc import Sequence
|
|
13
13
|
|
|
14
14
|
|
|
@@ -26,7 +26,7 @@ class APITokenProxy(APIToken):
|
|
|
26
26
|
|
|
27
27
|
@property
|
|
28
28
|
def pk(self: Self) -> uuid.UUID:
|
|
29
|
-
return cast(uuid.UUID, self.user_id) # pyright: ignore[reportGeneralTypeIssues]
|
|
29
|
+
return cast("uuid.UUID", self.user_id) # pyright: ignore[reportGeneralTypeIssues]
|
|
30
30
|
|
|
31
31
|
class Meta(TypedModelMeta):
|
|
32
32
|
permissions = [
|
firefighter/api/renderer.py
CHANGED
|
@@ -71,7 +71,7 @@ class CSVRenderer(BaseCSVRenderer):
|
|
|
71
71
|
pass
|
|
72
72
|
|
|
73
73
|
def _get_headers(
|
|
74
|
-
self, data: Iterable[Any], header:
|
|
74
|
+
self, data: Iterable[Any], header: list[str] | None
|
|
75
75
|
) -> tuple[Iterable[Any], list[str]]:
|
|
76
76
|
# If we already have a header, and it does not contain any wildcards,
|
|
77
77
|
# we can use it as-is.
|
firefighter/api/serializers.py
CHANGED
|
@@ -72,11 +72,13 @@ class GroupedModelSerializerOpenAPI(OpenApiSerializerExtension): # type: ignore
|
|
|
72
72
|
child_schema = auto_schema.resolve_serializer(
|
|
73
73
|
self.target.child_serializer, direction
|
|
74
74
|
)
|
|
75
|
-
child_schema_ref = child_schema.ref
|
|
75
|
+
child_schema_ref = child_schema.ref or child_schema
|
|
76
76
|
return {"type": "object", "additionalProperties": child_schema_ref}
|
|
77
77
|
|
|
78
78
|
def get_name(
|
|
79
|
-
self,
|
|
79
|
+
self,
|
|
80
|
+
auto_schema: AutoSchema, # noqa: ARG002
|
|
81
|
+
direction: Direction, # noqa: ARG002
|
|
80
82
|
) -> str:
|
|
81
83
|
return f"GroupedModelSerializer_{self.target.child_serializer.__name__}"
|
|
82
84
|
|
|
@@ -217,12 +219,12 @@ class IncidentSerializer(TaggitSerializer, serializers.ModelSerializer[Incident]
|
|
|
217
219
|
created_by = UserSerializer(read_only=True)
|
|
218
220
|
slack_channel_name = serializers.SerializerMethodField()
|
|
219
221
|
|
|
220
|
-
created_by_email = CreatableSlugRelatedField[User](
|
|
222
|
+
created_by_email = CreatableSlugRelatedField[User](
|
|
221
223
|
source="created_by",
|
|
222
224
|
write_only=True,
|
|
223
225
|
slug_field="email",
|
|
224
226
|
queryset=User.objects.all(),
|
|
225
|
-
validators=[EmailValidator()],
|
|
227
|
+
validators=[EmailValidator()], # type: ignore[list-item]
|
|
226
228
|
)
|
|
227
229
|
|
|
228
230
|
tags = TagListSerializerField(read_only=True)
|
firefighter/api/urls.py
CHANGED
|
@@ -54,26 +54,20 @@ urlpatterns: list[URLPattern | URLResolver] = [
|
|
|
54
54
|
path("", include(router.urls)),
|
|
55
55
|
path(
|
|
56
56
|
"incidents",
|
|
57
|
-
views.incidents.CreateIncidentViewSet.as_view(
|
|
58
|
-
{"post": "create"}
|
|
59
|
-
), # pyright: ignore[reportGeneralTypeIssues]
|
|
57
|
+
views.incidents.CreateIncidentViewSet.as_view({"post": "create"}), # pyright: ignore[reportGeneralTypeIssues]
|
|
60
58
|
name="incidents",
|
|
61
59
|
),
|
|
62
60
|
]
|
|
63
61
|
if settings.FF_EXPOSE_API_DOCS:
|
|
64
|
-
urlpatterns.extend(
|
|
65
|
-
(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
name="swagger-ui",
|
|
77
|
-
),
|
|
78
|
-
)
|
|
79
|
-
)
|
|
62
|
+
urlpatterns.extend((
|
|
63
|
+
path(
|
|
64
|
+
"schema",
|
|
65
|
+
SpectacularAPIView.as_view(), # pyright: ignore[reportGeneralTypeIssues]
|
|
66
|
+
name="schema",
|
|
67
|
+
),
|
|
68
|
+
path(
|
|
69
|
+
"schema/swagger-ui",
|
|
70
|
+
SpectacularSwaggerView.as_view(url_name="api:schema"), # pyright: ignore[reportGeneralTypeIssues]
|
|
71
|
+
name="swagger-ui",
|
|
72
|
+
),
|
|
73
|
+
))
|
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Any, NotRequired, Required, TypedDict
|
|
5
5
|
|
|
6
|
-
from django_components import component
|
|
6
|
+
from django_components import EmptyTuple, component
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
from firefighter.incidents.models.user import User
|
|
8
|
+
from firefighter.incidents.models.user import User
|
|
10
9
|
|
|
11
10
|
logger = logging.getLogger(__name__)
|
|
12
11
|
|
|
13
12
|
|
|
13
|
+
class Data(TypedDict):
|
|
14
|
+
user: User
|
|
15
|
+
size_tailwind: int
|
|
16
|
+
size_px: int
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
Args = tuple[User]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Kwargs(TypedDict, total=False):
|
|
23
|
+
user: Required[User]
|
|
24
|
+
size: NotRequired[str]
|
|
25
|
+
|
|
26
|
+
|
|
14
27
|
@component.register("avatar")
|
|
15
|
-
class Avatar(component.Component):
|
|
28
|
+
class Avatar(component.Component[EmptyTuple, Kwargs, Data, Any]): # type: ignore[type-var]
|
|
16
29
|
template_name = "avatar/avatar.html"
|
|
17
30
|
|
|
18
|
-
def get_context_data(self, user: User,
|
|
31
|
+
def get_context_data(self, user: User, **kwargs: Any) -> Data:
|
|
19
32
|
size_px, size_tailwind = (40, 10) if kwargs.get("size") == "md" else (80, 20)
|
|
20
|
-
return {
|
|
33
|
+
return {
|
|
34
|
+
"user": user,
|
|
35
|
+
"size_tailwind": size_tailwind,
|
|
36
|
+
"size_px": size_px,
|
|
37
|
+
}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any, NotRequired, Required, TypedDict, Unpack
|
|
5
5
|
|
|
6
|
-
from django_components import component
|
|
6
|
+
from django_components import EmptyTuple, component
|
|
7
7
|
|
|
8
8
|
logger = logging.getLogger(__name__)
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
class Data(TypedDict, total=False):
|
|
12
|
+
id: Required[str]
|
|
13
|
+
card_title: NotRequired[str]
|
|
14
|
+
|
|
15
|
+
|
|
11
16
|
@component.register("card")
|
|
12
|
-
class Card(component.Component):
|
|
17
|
+
class Card(component.Component[EmptyTuple, Data, Data, Any]): # type: ignore[type-var]
|
|
13
18
|
template_name = "card/card.html"
|
|
14
19
|
|
|
15
|
-
def get_context_data(self, *args: Any, **kwargs:
|
|
20
|
+
def get_context_data(self, *args: Any, **kwargs: Unpack[Data]) -> Data:
|
|
16
21
|
return kwargs
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
{% for format in formats %}
|
|
25
25
|
<li>
|
|
26
26
|
<button method="get" formtarget="_blank" type="submit"
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
name="fields" value="{{format.fields}}"
|
|
28
|
+
form="main-filter" formaction="{{format.url}}" class="" role="menuitem" tabindex="-1">{{format.label}}</button>
|
|
29
29
|
</li>
|
|
30
30
|
{% endfor %}
|
|
31
31
|
</ul>
|
|
@@ -1,22 +1,41 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Mapping, Sequence
|
|
4
|
+
from typing import Any, NotRequired, Required, TypedDict
|
|
4
5
|
|
|
5
6
|
from django.urls import reverse
|
|
6
|
-
from django_components import component
|
|
7
|
+
from django_components import EmptyTuple, component
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _Format(TypedDict):
|
|
11
|
+
label: str
|
|
12
|
+
url: str
|
|
13
|
+
fields: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Data(TypedDict):
|
|
17
|
+
default_format: Mapping[str, Any] # _Format
|
|
18
|
+
formats: Sequence[Mapping[str, Any]] # list[_Format]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
Args = tuple[str]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Kwargs(TypedDict, total=False):
|
|
25
|
+
base_url: Required[str]
|
|
26
|
+
default_fmt: NotRequired[tuple[str, str | None, str | None]]
|
|
7
27
|
|
|
8
28
|
|
|
9
29
|
@component.register("export_button")
|
|
10
|
-
class ExportButton(component.Component):
|
|
30
|
+
class ExportButton(component.Component[EmptyTuple, Kwargs, Data, Any]): # type: ignore[type-var]
|
|
11
31
|
template_name = "export_button/export_button.html"
|
|
12
32
|
|
|
13
33
|
def get_context_data(
|
|
14
34
|
self,
|
|
15
35
|
base_url: str,
|
|
16
|
-
*args: Any,
|
|
17
36
|
default_fmt: tuple[str, str | None, str | None] | None = None,
|
|
18
37
|
**kwargs: Any,
|
|
19
|
-
) ->
|
|
38
|
+
) -> Data:
|
|
20
39
|
default_fmt = default_fmt or ("csv", None, None)
|
|
21
40
|
fmts: list[tuple[str, str | None, str | None]] = [
|
|
22
41
|
("json", None, None),
|
|
@@ -28,12 +47,12 @@ class ExportButton(component.Component):
|
|
|
28
47
|
default_format = self.make_fmt(default_fmt, base_url)
|
|
29
48
|
formats = [self.make_fmt(fmt, base_url) for fmt in fmts]
|
|
30
49
|
|
|
31
|
-
return
|
|
50
|
+
return Data(default_format=default_format, formats=formats)
|
|
32
51
|
|
|
33
52
|
@staticmethod
|
|
34
53
|
def make_fmt(
|
|
35
54
|
format_: tuple[str, str | None, str | None], base_reverse: str
|
|
36
|
-
) ->
|
|
55
|
+
) -> _Format:
|
|
37
56
|
return {
|
|
38
57
|
"label": f"Export {format_[0].upper()}{' ' + format_[2] if format_[2] else ''}",
|
|
39
58
|
"url": f"{reverse(base_reverse, args=[format_[0]])}",
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Any, NotRequired, TypedDict
|
|
5
5
|
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
from django import forms
|
|
6
|
+
from django import forms
|
|
7
|
+
from django_components import EmptyTuple, component
|
|
8
|
+
from django_components.slots import SlotContent
|
|
10
9
|
|
|
11
10
|
logger = logging.getLogger(__name__)
|
|
12
11
|
|
|
13
12
|
|
|
13
|
+
class Slots(TypedDict, total=False):
|
|
14
|
+
form_footer: NotRequired[SlotContent[Any]]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Data(TypedDict):
|
|
18
|
+
form: forms.Form
|
|
19
|
+
|
|
20
|
+
|
|
14
21
|
@component.register("form")
|
|
15
|
-
class Form(component.Component):
|
|
22
|
+
class Form(component.Component[EmptyTuple, Data, Data, Slots]): # type: ignore[type-var] # type: ignore[override]
|
|
16
23
|
template_name = "form/form.html"
|
|
17
24
|
|
|
18
|
-
def get_context_data(
|
|
19
|
-
|
|
20
|
-
) -> dict[str, Any]:
|
|
21
|
-
return {
|
|
22
|
-
"form": form,
|
|
23
|
-
}
|
|
25
|
+
def get_context_data(self, form: forms.Form, **kwargs: Any) -> Data: # type: ignore[override]
|
|
26
|
+
return Data(form=form)
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
{% render_field field class=field_input_class class+="input input-bordered w-full" %}
|
|
12
12
|
{% if field.help_text %}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
<label class="label">
|
|
14
|
+
<span class="label-text-alt">{{field.help_text}}</span>
|
|
15
|
+
</label>
|
|
16
|
+
{% endif %}
|
|
17
17
|
</div>
|
|
18
18
|
</div>
|
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Never,
|
|
7
|
+
TypedDict,
|
|
8
|
+
)
|
|
5
9
|
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
from django import forms
|
|
10
|
+
from django import forms
|
|
11
|
+
from django_components import EmptyTuple, component
|
|
10
12
|
|
|
11
13
|
logger = logging.getLogger(__name__)
|
|
12
14
|
|
|
13
15
|
|
|
16
|
+
class Data(TypedDict):
|
|
17
|
+
field: forms.Field | forms.BoundField
|
|
18
|
+
field_input_class: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Kwargs(TypedDict, total=True):
|
|
22
|
+
field: forms.Field | forms.BoundField
|
|
23
|
+
|
|
24
|
+
|
|
14
25
|
@component.register("form_field")
|
|
15
|
-
class FormField(component.Component):
|
|
26
|
+
class FormField(component.Component[EmptyTuple, Kwargs, Data, Any]): # type: ignore[type-var]
|
|
16
27
|
template_name = "form_field/form_field.html"
|
|
17
28
|
|
|
18
29
|
def get_context_data(
|
|
19
|
-
self, field: forms.BoundField | forms.Field,
|
|
20
|
-
) ->
|
|
21
|
-
|
|
22
|
-
raise ValueError("Field not set!")
|
|
23
|
-
|
|
24
|
-
input_class = "input-ff"
|
|
30
|
+
self, field: forms.BoundField | forms.Field, **kwargs: Never
|
|
31
|
+
) -> Data:
|
|
32
|
+
input_class = "input input-bordered input-md"
|
|
25
33
|
|
|
26
34
|
return {"field": field, "field_input_class": input_class}
|
|
@@ -1,52 +1,42 @@
|
|
|
1
1
|
{% if messages %}
|
|
2
|
-
<ul class="messages">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<span> {{ message }}</span>
|
|
2
|
+
<ul class="messages">
|
|
3
|
+
{% for message in messages %}
|
|
4
|
+
{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}
|
|
5
|
+
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
|
|
6
|
+
<div class="alert alert-error shadow-lg">
|
|
7
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current flex-shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
8
|
+
<span> {{ message }}</span>
|
|
10
9
|
</div>
|
|
11
|
-
</
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<div>
|
|
44
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-info flex-shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
45
|
-
<span> {{ message }}</span>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
</li>
|
|
49
|
-
{% endif %}
|
|
50
|
-
{% endfor %}
|
|
51
|
-
</ul>
|
|
10
|
+
</li>
|
|
11
|
+
{% elif message.level == DEFAULT_MESSAGE_LEVELS.INFO %}
|
|
12
|
+
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
|
|
13
|
+
<div class="alert alert-info shadow-lg">
|
|
14
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current flex-shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
15
|
+
<span> {{ message }}</span>
|
|
16
|
+
</div>
|
|
17
|
+
</li>
|
|
18
|
+
{% elif message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %}
|
|
19
|
+
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
|
|
20
|
+
<div class="alert alert-success shadow-lg">
|
|
21
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current flex-shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
22
|
+
<span> {{ message }}</span>
|
|
23
|
+
</div>
|
|
24
|
+
</li>
|
|
25
|
+
{% elif message.level == DEFAULT_MESSAGE_LEVELS.WARNING %}
|
|
26
|
+
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
|
|
27
|
+
<div class="alert alert-warning shadow-lg">
|
|
28
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current flex-shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
|
29
|
+
<span> {{ message }}</span>
|
|
30
|
+
</div>
|
|
31
|
+
</li>
|
|
32
|
+
{% else %}
|
|
33
|
+
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
|
|
34
|
+
<div class="alert shadow-lg">
|
|
35
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-info flex-shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
36
|
+
<span> {{ message }}</span>
|
|
37
|
+
</div>
|
|
38
|
+
</li>
|
|
39
|
+
{% endif %}
|
|
40
|
+
{% endfor %}
|
|
41
|
+
</ul>
|
|
52
42
|
{% endif %}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any, TypedDict
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from django.contrib.messages.storage.base import BaseStorage
|
|
7
|
+
from django_components import EmptyTuple, component
|
|
7
8
|
|
|
8
9
|
logger = logging.getLogger(__name__)
|
|
9
10
|
|
|
10
11
|
|
|
12
|
+
class Data(TypedDict):
|
|
13
|
+
messages: BaseStorage
|
|
14
|
+
|
|
15
|
+
|
|
11
16
|
@component.register("messages")
|
|
12
|
-
class Messages(component.Component):
|
|
17
|
+
class Messages(component.Component[EmptyTuple, Data, Data, Any]): # type: ignore[type-var]
|
|
13
18
|
template_name = "messages/messages.html"
|
|
14
19
|
|
|
15
|
-
def get_context_data(self,
|
|
16
|
-
return
|
|
20
|
+
def get_context_data(self, messages: BaseStorage, **kwargs: Any) -> Data:
|
|
21
|
+
return Data(messages=messages)
|