Skip to content

Commit b94876f

Browse files
Maffoochclaude
andcommitted
Merge branch 'dev' into security/remove-pickle
Resolved conflicts after dev's notifications module reorganization: - dojo/tasks.py: accepted dev's re-export module (Celery tasks moved to dojo/notifications/tasks.py). - dojo/notifications/helper.py: kept dev's import structure, retained the per-channel `.run()` synchronous calls so model instances no longer cross the wire as task arguments. - dojo/notifications/tasks.py: applied the add_alerts signature change here (drop runinterval positional arg, inline timedelta). - unittests/test_survey_forms.py: dropped the per-file pickle guard to satisfy ruff PLC0415; the broader guard in test_no_pickle.py already covers the entire dojo/ tree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 parents 10981c8 + 16f1701 commit b94876f

105 files changed

Lines changed: 1083 additions & 923 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
title: 'Upgrading to DefectDojo Version 2.58.x'
3+
toc_hide: true
4+
weight: -20260504
5+
description: Notification .tpl templates relocated under dojo/notifications/
6+
---
7+
8+
## Notification `.tpl` templates relocated
9+
10+
The notification domain has been consolidated under a new `dojo/notifications/` package, and the 62 channel `.tpl` templates that drive alert, mail, MS Teams, Slack, and webhook notifications have moved on disk. The Django template lookup name (e.g. `notifications/mail/scan_added.tpl`) is unchanged, so most customizations keep working without any edits — but operators who override `.tpl` files by mounting them into the source tree need to update their paths.
11+
12+
### What moved
13+
14+
The channel templates under `alert/`, `mail/`, `msteams/`, `slack/`, `webhooks/`, and `webhooks_summary/` have been relocated:
15+
16+
| Old on-disk location | New on-disk location |
17+
| --- | --- |
18+
| `dojo/templates/notifications/{channel}/{event}.tpl` | `dojo/notifications/templates/notifications/{channel}/{event}.tpl` |
19+
20+
For example, `dojo/templates/notifications/mail/scan_added.tpl` now lives at `dojo/notifications/templates/notifications/mail/scan_added.tpl`. A new `TEMPLATES["DIRS"]` entry pointing at `dojo/notifications/templates/` is registered automatically, so the lookup path used by `render_to_string()` (e.g. `notifications/slack/sla_breach.tpl`) resolves exactly as before.
21+
22+
### Required actions
23+
24+
- **Customizing `.tpl` files via your own templates directory (recommended pattern):** No action required. Overrides resolved by lookup name continue to take precedence.
25+
- **Customizing `.tpl` files via a Docker volume mount or in-tree patch at the old `dojo/templates/notifications/...` path:** Update the mount/patch target to the new `dojo/notifications/templates/notifications/...` path, or move your override into a project-level templates directory keyed by the lookup name.
26+
- **No customizations:** No action required.
27+
28+
For more information, check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.58.0).

dojo/api_v2/serializers.py

Lines changed: 3 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from rest_framework import serializers
2222
from rest_framework.exceptions import NotFound
2323
from rest_framework.exceptions import ValidationError as RestFrameworkValidationError
24-
from rest_framework.fields import DictField, MultipleChoiceField
24+
from rest_framework.fields import DictField
2525

2626
import dojo.finding.helper as finding_helper
2727
import dojo.risk_acceptance.helper as ra_helper
@@ -43,9 +43,7 @@
4343
from dojo.jira import services as jira_services
4444
from dojo.location.models import Location, LocationFindingReference
4545
from dojo.models import (
46-
DEFAULT_NOTIFICATION,
4746
IMPORT_ACTIONS,
48-
NOTIFICATION_CHOICES,
4947
SEVERITIES,
5048
SEVERITY_CHOICES,
5149
STATS_FIELDS,
@@ -82,8 +80,6 @@
8280
Note_Type,
8381
NoteHistory,
8482
Notes,
85-
Notification_Webhooks,
86-
Notifications,
8783
Product,
8884
Product_API_Scan_Configuration,
8985
Product_Group,
@@ -3069,110 +3065,7 @@ class FindingNoteSerializer(serializers.Serializer):
30693065
note_id = serializers.IntegerField()
30703066

30713067

3072-
class NotificationsSerializer(serializers.ModelSerializer):
3073-
product = serializers.PrimaryKeyRelatedField(
3074-
queryset=Product.objects.all(),
3075-
required=False,
3076-
default=None,
3077-
allow_null=True,
3078-
)
3079-
user = serializers.PrimaryKeyRelatedField(
3080-
queryset=Dojo_User.objects.all(),
3081-
required=False,
3082-
default=None,
3083-
allow_null=True,
3084-
)
3085-
product_type_added = MultipleChoiceField(
3086-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3087-
)
3088-
product_added = MultipleChoiceField(
3089-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3090-
)
3091-
engagement_added = MultipleChoiceField(
3092-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3093-
)
3094-
test_added = MultipleChoiceField(
3095-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3096-
)
3097-
scan_added = MultipleChoiceField(
3098-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3099-
)
3100-
jira_update = MultipleChoiceField(
3101-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3102-
)
3103-
upcoming_engagement = MultipleChoiceField(
3104-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3105-
)
3106-
stale_engagement = MultipleChoiceField(
3107-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3108-
)
3109-
auto_close_engagement = MultipleChoiceField(
3110-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3111-
)
3112-
close_engagement = MultipleChoiceField(
3113-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3114-
)
3115-
user_mentioned = MultipleChoiceField(
3116-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3117-
)
3118-
code_review = MultipleChoiceField(
3119-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3120-
)
3121-
review_requested = MultipleChoiceField(
3122-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3123-
)
3124-
other = MultipleChoiceField(
3125-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3126-
)
3127-
sla_breach = MultipleChoiceField(
3128-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3129-
)
3130-
sla_breach_combined = MultipleChoiceField(
3131-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3132-
)
3133-
risk_acceptance_expiration = MultipleChoiceField(
3134-
choices=NOTIFICATION_CHOICES, default=DEFAULT_NOTIFICATION,
3135-
)
3136-
template = serializers.BooleanField(default=False)
3137-
3138-
class Meta:
3139-
model = Notifications
3140-
fields = "__all__"
3141-
3142-
def validate(self, data):
3143-
user = None
3144-
product = None
3145-
template = False
3146-
3147-
if self.instance is not None:
3148-
user = self.instance.user
3149-
product = self.instance.product
3150-
3151-
if "user" in data:
3152-
user = data.get("user")
3153-
if "product" in data:
3154-
product = data.get("product")
3155-
if "template" in data:
3156-
template = data.get("template")
3157-
3158-
if (
3159-
template
3160-
and Notifications.objects.filter(template=True).count() > 0
3161-
):
3162-
msg = "Notification template already exists"
3163-
raise ValidationError(msg)
3164-
if (
3165-
self.instance is None
3166-
or user != self.instance.user
3167-
or product != self.instance.product
3168-
):
3169-
notifications = Notifications.objects.filter(
3170-
user=user, product=product, template=template,
3171-
).count()
3172-
if notifications > 0:
3173-
msg = "Notification for user and product already exists"
3174-
raise ValidationError(msg)
3175-
return data
3068+
from dojo.notifications.api.serializer import NotificationsSerializer # noqa: E402, F401 -- backward compat
31763069

31773070

31783071
class EngagementPresetsSerializer(serializers.ModelSerializer):
@@ -3349,7 +3242,4 @@ def create(self, validated_data):
33493242
raise
33503243

33513244

3352-
class NotificationWebhooksSerializer(serializers.ModelSerializer):
3353-
class Meta:
3354-
model = Notification_Webhooks
3355-
fields = "__all__"
3245+
from dojo.notifications.api.serializer import NotificationWebhooksSerializer # noqa: E402, F401 -- backward compat

dojo/api_v2/views.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,6 @@
119119
Note_Type,
120120
NoteHistory,
121121
Notes,
122-
Notification_Webhooks,
123-
Notifications,
124122
Product,
125123
Product_API_Scan_Configuration,
126124
Product_Group,
@@ -3406,21 +3404,6 @@ def queue_task_purge(self, request):
34063404
return Response({"purged": purged})
34073405

34083406

3409-
# Authorization: superuser
3410-
@extend_schema_view(**schema_with_prefetch())
3411-
class NotificationsViewSet(
3412-
PrefetchDojoModelViewSet,
3413-
):
3414-
serializer_class = serializers.NotificationsSerializer
3415-
queryset = Notifications.objects.none()
3416-
filter_backends = (DjangoFilterBackend,)
3417-
filterset_fields = ["id", "user", "product", "template"]
3418-
permission_classes = (permissions.IsSuperUser, DjangoModelPermissions)
3419-
3420-
def get_queryset(self):
3421-
return Notifications.objects.all().order_by("id")
3422-
3423-
34243407
@extend_schema_view(**schema_with_prefetch())
34253408
class EngagementPresetsViewset(
34263409
PrefetchDojoModelViewSet,
@@ -3683,13 +3666,3 @@ class AnnouncementViewSet(
36833666

36843667
def get_queryset(self):
36853668
return Announcement.objects.all().order_by("id")
3686-
3687-
3688-
class NotificationWebhooksViewSet(
3689-
PrefetchDojoModelViewSet,
3690-
):
3691-
serializer_class = serializers.NotificationWebhooksSerializer
3692-
queryset = Notification_Webhooks.objects.all()
3693-
filter_backends = (DjangoFilterBackend,)
3694-
filterset_fields = "__all__"
3695-
permission_classes = (permissions.IsSuperUser, DjangoModelPermissions) # TODO: add permission also for other users

dojo/apps.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ def ready(self):
8484
import dojo.file_uploads.signals # noqa: PLC0415, F401 raised: AppRegistryNotReady
8585
import dojo.finding_group.signals # noqa: PLC0415, F401 raised: AppRegistryNotReady
8686
import dojo.notes.signals # noqa: PLC0415, F401 raised: AppRegistryNotReady
87+
import dojo.notifications.admin # noqa: PLC0415, F401 raised: AppRegistryNotReady
88+
import dojo.notifications.signals # noqa: PLC0415, F401 raised: AppRegistryNotReady
8789
import dojo.product.signals # noqa: PLC0415, F401 raised: AppRegistryNotReady
8890
import dojo.product_type.signals # noqa: PLC0415, F401 raised: AppRegistryNotReady
8991
import dojo.risk_acceptance.signals # noqa: PLC0415, F401 raised: AppRegistryNotReady

dojo/context_processors.py

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import contextlib
2-
import time
32

43
# import the settings file
54
from django.conf import settings
65
from django.contrib import messages
76

87
from dojo.announcement.os_message import get_os_banner
98
from dojo.labels import get_labels
10-
from dojo.models import Alerts, System_Settings, UserAnnouncement
9+
from dojo.models import System_Settings, UserAnnouncement
1110

1211

1312
def globalize_vars(request):
@@ -86,14 +85,6 @@ def bind_system_settings(request):
8685
return {"system_settings": system_settings}
8786

8887

89-
def bind_alert_count(request):
90-
if not settings.DISABLE_ALERT_COUNTER:
91-
92-
if hasattr(request, "user") and request.user.is_authenticated:
93-
return {"alert_count": Alerts.objects.filter(user_id=request.user).count()}
94-
return {}
95-
96-
9788
def bind_announcement(request):
9889
with contextlib.suppress(Exception): # TODO: this should be replaced with more meaningful exception
9990
if request.user.is_authenticated:
@@ -104,21 +95,10 @@ def bind_announcement(request):
10495
return {}
10596

10697

107-
def session_expiry_notification(request):
108-
try:
109-
if request.user.is_authenticated:
110-
last_activity = request.session.get("_last_activity", time.time())
111-
expiry_time = last_activity + settings.SESSION_COOKIE_AGE # When the session will expire
112-
warning_time = settings.SESSION_EXPIRE_WARNING # Show warning X seconds before expiry
113-
notify_time = expiry_time - warning_time
114-
else:
115-
notify_time = None
116-
except Exception:
117-
return {}
118-
else:
119-
return {
120-
"session_notify_time": notify_time,
121-
}
98+
from dojo.notifications.context_processors import ( # noqa: E402, F401 -- backward compat
99+
bind_alert_count,
100+
session_expiry_notification,
101+
)
122102

123103

124104
def labels(request):

dojo/forms.py

Lines changed: 6 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,6 @@
9090
Global_Role,
9191
Note_Type,
9292
Notes,
93-
Notification_Webhooks,
94-
Notifications,
9593
Objects_Product,
9694
Product,
9795
Product_API_Scan_Configuration,
@@ -3124,55 +3122,12 @@ class Meta:
31243122
exclude = [""]
31253123

31263124

3127-
class NotificationsForm(forms.ModelForm):
3128-
3129-
class Meta:
3130-
model = Notifications
3131-
exclude = ["template"]
3132-
3133-
3134-
class NotificationsWebhookForm(forms.ModelForm):
3135-
class Meta:
3136-
model = Notification_Webhooks
3137-
exclude = []
3138-
3139-
def __init__(self, *args, **kwargs):
3140-
is_superuser = kwargs.pop("is_superuser", False)
3141-
super().__init__(*args, **kwargs)
3142-
if not is_superuser: # Only superadmins can edit owner
3143-
self.fields["owner"].disabled = True # TODO: needs to be tested
3144-
3145-
3146-
class DeleteNotificationsWebhookForm(forms.ModelForm):
3147-
id = forms.IntegerField(required=True,
3148-
widget=forms.widgets.HiddenInput())
3149-
3150-
def __init__(self, *args, **kwargs):
3151-
super().__init__(*args, **kwargs)
3152-
self.fields["name"].disabled = True
3153-
self.fields["url"].disabled = True
3154-
3155-
class Meta:
3156-
model = Notification_Webhooks
3157-
fields = ["id", "name", "url"]
3158-
3159-
3160-
class ProductNotificationsForm(forms.ModelForm):
3161-
3162-
def __init__(self, *args, **kwargs):
3163-
super().__init__(*args, **kwargs)
3164-
if not self.instance.id:
3165-
self.initial["engagement_added"] = ""
3166-
self.initial["close_engagement"] = ""
3167-
self.initial["test_added"] = ""
3168-
self.initial["scan_added"] = ""
3169-
self.initial["sla_breach"] = ""
3170-
self.initial["sla_breach_combined"] = ""
3171-
self.initial["risk_acceptance_expiration"] = ""
3172-
3173-
class Meta:
3174-
model = Notifications
3175-
fields = ["engagement_added", "close_engagement", "test_added", "scan_added", "sla_breach", "sla_breach_combined", "risk_acceptance_expiration"]
3125+
from dojo.notifications.ui.forms import ( # noqa: E402, F401 -- backward compat
3126+
DeleteNotificationsWebhookForm,
3127+
NotificationsForm,
3128+
NotificationsWebhookForm,
3129+
ProductNotificationsForm,
3130+
)
31763131

31773132

31783133
class AjaxChoiceField(forms.ChoiceField):

0 commit comments

Comments
 (0)