Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,22 @@ Modules in various stages of reorganization:
| **test** | In module | N/A | Done | Done | **Complete** (#14971) |
| **engagement** | In module | In module | Done | Done | **Complete** (#14972) |
| **product** | In module | N/A | Done | Done | **Complete** (#14973) |
| **finding** | In module | N/A (helper.py) | Done | Done | **Complete** (#14974); CWE+Burp pending |
| **peripheral (×18)** | In dojo/models.py | — | Partial/none | Partial/none | **Phase 10** (PRs #6–10, see below) |
| **finding** | In module | N/A (helper.py) | Done | Done | **Complete** (#14974, incl. CWE + BurpRawRequestResponse) |
| **user / system_settings** | In module | N/A | Done | Done | **Complete** (#14981) |
| **endpoint / tool_type / tool_config / tool_product** | In module | N/A | Done | Done | **Complete** (#14982) |
| **survey / benchmark** | In module | N/A | Done | N/A (no API) | **Complete** (#14983) |
| **notes / note_type / file_uploads / reports / risk_acceptance** | In module | N/A | Done | Done | **Complete** (#14986) |
| **regulations / banner / announcement / development_environment / object** | In module | N/A | Done | Partial (API where one exists) | **Complete** (#14987) |

### Monolithic Files Being Decomposed

These files still contain code for multiple modules. Extract code to the target module's subdirectory and leave a re-export stub.
These files still contain code for multiple modules. Extract code to the target module's subdirectory and leave a re-export stub. (Counts reflect the completed Phase 10 stack; they shrink as branches merge to `dev`.)

- `dojo/models.py` (4,973 lines) — All model definitions
- `dojo/forms.py` (4,127 lines) — All Django forms
- `dojo/filters.py` (4,016 lines) — All UI and API filter classes
- `dojo/api_v2/serializers.py` (3,387 lines) — All DRF serializers
- `dojo/api_v2/views.py` (3,519 lines) — All API viewsets
- `dojo/models.py` (~645 lines) — re-export hub + the few models intentionally left here (`DojoMeta`, `Network_Locations`, `Sonarqube_Issue`/`Sonarqube_Issue_Transition`, `Check_List`, `Testing_Guide_Category`/`Testing_Guide`, `Language_Type`/`Languages`, `App_Analysis`, `SLA_Configuration`) plus shared utilities (`copy_model_util`, `get_current_date`, `tomorrow`, `UniqueUploadNameProvider`)
- `dojo/forms.py` (~914 lines) — remaining/shared Django forms
- `dojo/filters.py` (~1,376 lines) — remaining/shared filter classes + shared bases
- `dojo/api_v2/serializers.py` (~1,101 lines) — remaining serializers + re-exports for prefetcher discovery
- `dojo/api_v2/views.py` (~901 lines) — remaining viewsets + shared base classes/helpers

---

Expand Down
1 change: 1 addition & 0 deletions dojo/announcement/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import dojo.announcement.admin # noqa: F401
6 changes: 6 additions & 0 deletions dojo/announcement/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.contrib import admin

from dojo.announcement.models import Announcement, UserAnnouncement

admin.site.register(Announcement)
admin.site.register(UserAnnouncement)
1 change: 1 addition & 0 deletions dojo/announcement/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
path = "announcements" # noqa: RUF067
21 changes: 21 additions & 0 deletions dojo/announcement/api/serializer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.db import IntegrityError
from rest_framework import serializers

from dojo.announcement.models import Announcement


class AnnouncementSerializer(serializers.ModelSerializer):

class Meta:
model = Announcement
fields = "__all__"

def create(self, validated_data):
validated_data["id"] = 1
try:
return super().create(validated_data)
except IntegrityError as e:
if 'duplicate key value violates unique constraint "dojo_announcement_pkey"' in str(e):
msg = "No more than one Announcement is allowed"
raise serializers.ValidationError(msg)
raise
7 changes: 7 additions & 0 deletions dojo/announcement/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from dojo.announcement.api import path
from dojo.announcement.api.views import AnnouncementViewSet


def add_announcement_urls(router):
router.register(path, AnnouncementViewSet, basename="announcement")
return router
20 changes: 20 additions & 0 deletions dojo/announcement/api/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from django_filters.rest_framework import DjangoFilterBackend

from dojo.announcement.api.serializer import AnnouncementSerializer
from dojo.announcement.models import Announcement
from dojo.api_v2.views import DojoModelViewSet
from dojo.authorization import api_permissions as permissions


# Authorization: configuration
class AnnouncementViewSet(
DojoModelViewSet,
):
serializer_class = AnnouncementSerializer
queryset = Announcement.objects.none()
filter_backends = (DjangoFilterBackend,)
filterset_fields = "__all__"
permission_classes = (permissions.UserHasConfigurationPermissionStaff,)

def get_queryset(self):
return Announcement.objects.all().order_by("id")
28 changes: 28 additions & 0 deletions dojo/announcement/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from django.db import models
from django.utils.translation import gettext as _

ANNOUNCEMENT_STYLE_CHOICES = (
("info", "Info"),
("success", "Success"),
("warning", "Warning"),
("danger", "Danger"),
)


class Announcement(models.Model):
message = models.CharField(max_length=500,
help_text=_("This dismissable message will be displayed on all pages for authenticated users. It can contain basic html tags, for example <a href='https://www.fred.com' style='color: #337ab7;' target='_blank'>https://example.com</a>"),
default="")
style = models.CharField(max_length=64, choices=ANNOUNCEMENT_STYLE_CHOICES, default="info",
help_text=_("The style of banner to display. (info, success, warning, danger)"))
dismissable = models.BooleanField(default=False,
null=False,
blank=True,
verbose_name=_("Dismissable?"),
help_text=_("Ticking this box allows users to dismiss the current announcement"),
)


class UserAnnouncement(models.Model):
announcement = models.ForeignKey("dojo.Announcement", null=True, editable=False, on_delete=models.CASCADE, related_name="user_announcement")
user = models.ForeignKey("dojo.Dojo_User", null=True, editable=False, on_delete=models.CASCADE)
3 changes: 2 additions & 1 deletion dojo/announcement/signals.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from django.db.models.signals import post_save
from django.dispatch import receiver

from dojo.models import Announcement, Dojo_User, UserAnnouncement
from dojo.announcement.models import Announcement, UserAnnouncement
from dojo.user.models import Dojo_User


@receiver(post_save, sender=Dojo_User)
Expand Down
Empty file.
17 changes: 17 additions & 0 deletions dojo/announcement/ui/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django import forms

from dojo.announcement.models import Announcement


class AnnouncementCreateForm(forms.ModelForm):
class Meta:
model = Announcement
fields = "__all__"


class AnnouncementRemoveForm(AnnouncementCreateForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["dismissable"].disabled = True
self.fields["message"].disabled = True
self.fields["style"].disabled = True
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.urls import re_path

from dojo.announcement import views
from dojo.announcement.ui import views

urlpatterns = [
re_path(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _

from dojo.forms import AnnouncementCreateForm, AnnouncementRemoveForm
from dojo.models import Announcement, UserAnnouncement
from dojo.announcement.models import Announcement, UserAnnouncement
from dojo.announcement.ui.forms import AnnouncementCreateForm, AnnouncementRemoveForm
from dojo.utils import add_breadcrumb

logger = logging.getLogger(__name__)
Expand Down
41 changes: 8 additions & 33 deletions dojo/api_v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
SEVERITIES,
SEVERITY_CHOICES,
STATS_FIELDS,
Announcement,
App_Analysis,
Development_Environment,
DojoMeta,
Expand All @@ -43,7 +42,6 @@
Notes,
Product,
Product_API_Scan_Configuration,
Regulation,
SLA_Configuration,
Sonarqube_Issue,
Sonarqube_Issue_Transition,
Expand Down Expand Up @@ -327,15 +325,6 @@ class Meta:
fields = "__all__"


from dojo.tool_type.api.serializer import ToolTypeSerializer # noqa: E402, F401 -- re-export


class RegulationSerializer(serializers.ModelSerializer):
class Meta:
model = Regulation
fields = "__all__"


from dojo.endpoint.api.serializer import ( # noqa: E402, F401 -- re-export; prefetcher discovery requires all moved ModelSerializers here
EndpointParamsSerializer,
EndpointSerializer,
Expand All @@ -346,8 +335,10 @@ class Meta:
JIRAIssueSerializer,
JIRAProjectSerializer,
)
from dojo.regulations.api.serializer import RegulationSerializer # noqa: E402, F401 -- re-export; prefetcher discovery
from dojo.tool_config.api.serializer import ToolConfigurationSerializer # noqa: E402, F401 -- re-export
from dojo.tool_product.api.serializer import ToolProductSettingsSerializer # noqa: E402, F401 -- re-export
from dojo.tool_type.api.serializer import ToolTypeSerializer # noqa: E402, F401 -- re-export; prefetcher discovery


class SonarqubeIssueSerializer(serializers.ModelSerializer):
Expand All @@ -362,11 +353,9 @@ class Meta:
fields = "__all__"


class DevelopmentEnvironmentSerializer(serializers.ModelSerializer):
class Meta:
model = Development_Environment
fields = "__all__"

from dojo.development_environment.api.serializer import ( # noqa: E402 -- re-export; prefetcher discovery
DevelopmentEnvironmentSerializer, # noqa: F401 -- re-export; prefetcher discovery
)

# Risk acceptance serializers live in dojo/risk_acceptance/api/serializer.py. Re-exported here
# for backward compat: RiskAcceptanceSerializer is lazy-imported by dojo/finding/api/serializer.py
Expand Down Expand Up @@ -1106,21 +1095,7 @@ class Meta:
exclude = ("content_type",)


class AnnouncementSerializer(serializers.ModelSerializer):

class Meta:
model = Announcement
fields = "__all__"

def create(self, validated_data):
validated_data["id"] = 1
try:
return super().create(validated_data)
except IntegrityError as e:
if 'duplicate key value violates unique constraint "dojo_announcement_pkey"' in str(e):
msg = "No more than one Announcement is allowed"
raise serializers.ValidationError(msg)
raise


from dojo.announcement.api.serializer import ( # noqa: E402 -- re-export; prefetcher discovery
AnnouncementSerializer, # noqa: F401 -- re-export; prefetcher discovery
)
from dojo.notifications.api.serializer import NotificationWebhooksSerializer # noqa: E402, F401 -- backward compat
43 changes: 3 additions & 40 deletions dojo/api_v2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@
from dojo.jira import services as jira_services
from dojo.labels import get_labels
from dojo.models import (
Announcement,
App_Analysis,
Development_Environment,
Dojo_User,
DojoMeta,
Endpoint,
Expand All @@ -57,7 +55,6 @@
Languages,
Network_Locations,
Product,
Regulation,
SLA_Configuration,
Sonarqube_Issue,
Sonarqube_Issue_Transition,
Expand Down Expand Up @@ -316,31 +313,8 @@ def process_patch(self, request):
raise ValidationError(msg)


# Authorization: authenticated, configuration
class DevelopmentEnvironmentViewSet(
DojoModelViewSet,
):
serializer_class = serializers.DevelopmentEnvironmentSerializer
queryset = Development_Environment.objects.none()
filter_backends = (DjangoFilterBackend,)
permission_classes = (IsAuthenticated, permissions.UserHasDevelopmentEnvironmentPermission)

def get_queryset(self):
return Development_Environment.objects.all().order_by("id")


# Authorization: authenticated, configuration
class RegulationsViewSet(
DojoModelViewSet,
):
serializer_class = serializers.RegulationSerializer
queryset = Regulation.objects.none()
filter_backends = (DjangoFilterBackend,)
filterset_fields = ["id", "name", "description"]
permission_classes = (IsAuthenticated, permissions.UserHasRegulationPermission)

def get_queryset(self):
return Regulation.objects.all().order_by("id")
# DevelopmentEnvironmentViewSet moved to dojo/development_environment/api/views.py
# RegulationsViewSet moved to dojo/regulations/api/views.py


# Authorization: authenticated users, DjangoModelPermissions
Expand Down Expand Up @@ -924,15 +898,4 @@ def get_queryset(self):
return SLA_Configuration.objects.all().order_by("id")


# Authorization: configuration
class AnnouncementViewSet(
DojoModelViewSet,
):
serializer_class = serializers.AnnouncementSerializer
queryset = Announcement.objects.none()
filter_backends = (DjangoFilterBackend,)
filterset_fields = "__all__"
permission_classes = (permissions.UserHasConfigurationPermissionStaff,)

def get_queryset(self):
return Announcement.objects.all().order_by("id")
# AnnouncementViewSet moved to dojo/announcement/api/views.py
1 change: 1 addition & 0 deletions dojo/banner/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import dojo.banner.admin # noqa: F401
5 changes: 5 additions & 0 deletions dojo/banner/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.contrib import admin

from dojo.banner.models import BannerConf

admin.site.register(BannerConf)
7 changes: 7 additions & 0 deletions dojo/banner/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.db import models
from django.utils.translation import gettext as _


class BannerConf(models.Model):
banner_enable = models.BooleanField(default=False, null=True, blank=True)
banner_message = models.CharField(max_length=500, help_text=_("This message will be displayed on the login page. It can contain basic html tags, for example <a href='https://www.fred.com' style='color: #337ab7;' target='_blank'>https://example.com</a>"), default="")
Empty file added dojo/banner/ui/__init__.py
Empty file.
18 changes: 18 additions & 0 deletions dojo/banner/ui/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django import forms


class LoginBanner(forms.Form):
banner_enable = forms.BooleanField(
label="Enable login banner",
initial=False,
required=False,
help_text="Tick this box to enable a text banner on the login page",
)

banner_message = forms.CharField(
required=False,
label="Message to display on the login page",
)

def clean(self):
return super().clean()
2 changes: 1 addition & 1 deletion dojo/banner/urls.py → dojo/banner/ui/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.urls import re_path

from dojo.banner import views
from dojo.banner.ui import views

urlpatterns = [
re_path(
Expand Down
4 changes: 2 additions & 2 deletions dojo/banner/views.py → dojo/banner/ui/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from django.shortcuts import get_object_or_404, render
from django.urls import reverse

from dojo.forms import LoginBanner
from dojo.models import BannerConf
from dojo.banner.models import BannerConf
from dojo.banner.ui.forms import LoginBanner
from dojo.utils import add_breadcrumb

logger = logging.getLogger(__name__)
Expand Down
1 change: 1 addition & 0 deletions dojo/development_environment/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import dojo.development_environment.admin # noqa: F401
5 changes: 5 additions & 0 deletions dojo/development_environment/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.contrib import admin

from dojo.development_environment.models import Development_Environment

admin.site.register(Development_Environment)
1 change: 1 addition & 0 deletions dojo/development_environment/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
path = "development_environments" # noqa: RUF067
9 changes: 9 additions & 0 deletions dojo/development_environment/api/serializer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from rest_framework import serializers

from dojo.development_environment.models import Development_Environment


class DevelopmentEnvironmentSerializer(serializers.ModelSerializer):
class Meta:
model = Development_Environment
fields = "__all__"
Loading
Loading