diff --git a/AGENTS.md b/AGENTS.md index e53dcd7cadf..ed261984c8a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 --- diff --git a/dojo/announcement/__init__.py b/dojo/announcement/__init__.py index e69de29bb2d..d22235e108d 100644 --- a/dojo/announcement/__init__.py +++ b/dojo/announcement/__init__.py @@ -0,0 +1 @@ +import dojo.announcement.admin # noqa: F401 diff --git a/dojo/announcement/admin.py b/dojo/announcement/admin.py new file mode 100644 index 00000000000..23507595678 --- /dev/null +++ b/dojo/announcement/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin + +from dojo.announcement.models import Announcement, UserAnnouncement + +admin.site.register(Announcement) +admin.site.register(UserAnnouncement) diff --git a/dojo/announcement/api/__init__.py b/dojo/announcement/api/__init__.py new file mode 100644 index 00000000000..0e64dd504ef --- /dev/null +++ b/dojo/announcement/api/__init__.py @@ -0,0 +1 @@ +path = "announcements" # noqa: RUF067 diff --git a/dojo/announcement/api/serializer.py b/dojo/announcement/api/serializer.py new file mode 100644 index 00000000000..0022e2f22ef --- /dev/null +++ b/dojo/announcement/api/serializer.py @@ -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 diff --git a/dojo/announcement/api/urls.py b/dojo/announcement/api/urls.py new file mode 100644 index 00000000000..1da04620311 --- /dev/null +++ b/dojo/announcement/api/urls.py @@ -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 diff --git a/dojo/announcement/api/views.py b/dojo/announcement/api/views.py new file mode 100644 index 00000000000..cf6a411adc9 --- /dev/null +++ b/dojo/announcement/api/views.py @@ -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") diff --git a/dojo/announcement/models.py b/dojo/announcement/models.py new file mode 100644 index 00000000000..9266b96c019 --- /dev/null +++ b/dojo/announcement/models.py @@ -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 https://example.com"), + 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) diff --git a/dojo/announcement/signals.py b/dojo/announcement/signals.py index c74fd0e5d50..677fafe93c2 100644 --- a/dojo/announcement/signals.py +++ b/dojo/announcement/signals.py @@ -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) diff --git a/dojo/announcement/ui/__init__.py b/dojo/announcement/ui/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dojo/announcement/ui/forms.py b/dojo/announcement/ui/forms.py new file mode 100644 index 00000000000..47e4f97721d --- /dev/null +++ b/dojo/announcement/ui/forms.py @@ -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 diff --git a/dojo/announcement/urls.py b/dojo/announcement/ui/urls.py similarity index 88% rename from dojo/announcement/urls.py rename to dojo/announcement/ui/urls.py index 9dc91187653..2ac2f2b8af0 100644 --- a/dojo/announcement/urls.py +++ b/dojo/announcement/ui/urls.py @@ -1,6 +1,6 @@ from django.urls import re_path -from dojo.announcement import views +from dojo.announcement.ui import views urlpatterns = [ re_path( diff --git a/dojo/announcement/views.py b/dojo/announcement/ui/views.py similarity index 95% rename from dojo/announcement/views.py rename to dojo/announcement/ui/views.py index 7afe915210b..f668901e86f 100644 --- a/dojo/announcement/views.py +++ b/dojo/announcement/ui/views.py @@ -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__) diff --git a/dojo/api_v2/serializers.py b/dojo/api_v2/serializers.py index e52b1a3a04a..7714214c49a 100644 --- a/dojo/api_v2/serializers.py +++ b/dojo/api_v2/serializers.py @@ -28,7 +28,6 @@ SEVERITIES, SEVERITY_CHOICES, STATS_FIELDS, - Announcement, App_Analysis, Development_Environment, DojoMeta, @@ -43,7 +42,6 @@ Notes, Product, Product_API_Scan_Configuration, - Regulation, SLA_Configuration, Sonarqube_Issue, Sonarqube_Issue_Transition, @@ -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, @@ -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): @@ -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 @@ -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 diff --git a/dojo/api_v2/views.py b/dojo/api_v2/views.py index d4b763ea167..a90210d8b89 100644 --- a/dojo/api_v2/views.py +++ b/dojo/api_v2/views.py @@ -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, @@ -57,7 +55,6 @@ Languages, Network_Locations, Product, - Regulation, SLA_Configuration, Sonarqube_Issue, Sonarqube_Issue_Transition, @@ -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 @@ -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 diff --git a/dojo/banner/__init__.py b/dojo/banner/__init__.py index e69de29bb2d..b0433151e3a 100644 --- a/dojo/banner/__init__.py +++ b/dojo/banner/__init__.py @@ -0,0 +1 @@ +import dojo.banner.admin # noqa: F401 diff --git a/dojo/banner/admin.py b/dojo/banner/admin.py new file mode 100644 index 00000000000..02dc5f7d321 --- /dev/null +++ b/dojo/banner/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin + +from dojo.banner.models import BannerConf + +admin.site.register(BannerConf) diff --git a/dojo/banner/models.py b/dojo/banner/models.py new file mode 100644 index 00000000000..7c885b8ea34 --- /dev/null +++ b/dojo/banner/models.py @@ -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 https://example.com"), default="") diff --git a/dojo/banner/ui/__init__.py b/dojo/banner/ui/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dojo/banner/ui/forms.py b/dojo/banner/ui/forms.py new file mode 100644 index 00000000000..78a1fbbcf4b --- /dev/null +++ b/dojo/banner/ui/forms.py @@ -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() diff --git a/dojo/banner/urls.py b/dojo/banner/ui/urls.py similarity index 82% rename from dojo/banner/urls.py rename to dojo/banner/ui/urls.py index c0b75f1ff77..3751ac59d63 100644 --- a/dojo/banner/urls.py +++ b/dojo/banner/ui/urls.py @@ -1,6 +1,6 @@ from django.urls import re_path -from dojo.banner import views +from dojo.banner.ui import views urlpatterns = [ re_path( diff --git a/dojo/banner/views.py b/dojo/banner/ui/views.py similarity index 94% rename from dojo/banner/views.py rename to dojo/banner/ui/views.py index 1bdf8ce2e68..03646c7f914 100644 --- a/dojo/banner/views.py +++ b/dojo/banner/ui/views.py @@ -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__) diff --git a/dojo/development_environment/__init__.py b/dojo/development_environment/__init__.py index e69de29bb2d..adc8d51562b 100644 --- a/dojo/development_environment/__init__.py +++ b/dojo/development_environment/__init__.py @@ -0,0 +1 @@ +import dojo.development_environment.admin # noqa: F401 diff --git a/dojo/development_environment/admin.py b/dojo/development_environment/admin.py new file mode 100644 index 00000000000..a6ea8885e39 --- /dev/null +++ b/dojo/development_environment/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin + +from dojo.development_environment.models import Development_Environment + +admin.site.register(Development_Environment) diff --git a/dojo/development_environment/api/__init__.py b/dojo/development_environment/api/__init__.py new file mode 100644 index 00000000000..d48867a443d --- /dev/null +++ b/dojo/development_environment/api/__init__.py @@ -0,0 +1 @@ +path = "development_environments" # noqa: RUF067 diff --git a/dojo/development_environment/api/serializer.py b/dojo/development_environment/api/serializer.py new file mode 100644 index 00000000000..393c6ac2e98 --- /dev/null +++ b/dojo/development_environment/api/serializer.py @@ -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__" diff --git a/dojo/development_environment/api/urls.py b/dojo/development_environment/api/urls.py new file mode 100644 index 00000000000..6d4937f37f7 --- /dev/null +++ b/dojo/development_environment/api/urls.py @@ -0,0 +1,7 @@ +from dojo.development_environment.api import path +from dojo.development_environment.api.views import DevelopmentEnvironmentViewSet + + +def add_development_environment_urls(router): + router.register(path, DevelopmentEnvironmentViewSet, basename="development_environment") + return router diff --git a/dojo/development_environment/api/views.py b/dojo/development_environment/api/views.py new file mode 100644 index 00000000000..e79748e89be --- /dev/null +++ b/dojo/development_environment/api/views.py @@ -0,0 +1,20 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework.permissions import IsAuthenticated + +from dojo.api_v2.views import DojoModelViewSet +from dojo.authorization import api_permissions as permissions +from dojo.development_environment.api.serializer import DevelopmentEnvironmentSerializer +from dojo.development_environment.models import Development_Environment + + +# Authorization: authenticated, configuration +class DevelopmentEnvironmentViewSet( + DojoModelViewSet, +): + serializer_class = 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") diff --git a/dojo/development_environment/models.py b/dojo/development_environment/models.py new file mode 100644 index 00000000000..d8803d6e576 --- /dev/null +++ b/dojo/development_environment/models.py @@ -0,0 +1,13 @@ +from django.db import models +from django.urls import reverse + + +class Development_Environment(models.Model): + name = models.CharField(max_length=200) + + def __str__(self): + return self.name + + def get_breadcrumbs(self): + return [{"title": str(self), + "url": reverse("edit_dev_env", args=(self.id,))}] diff --git a/dojo/development_environment/ui/__init__.py b/dojo/development_environment/ui/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dojo/development_environment/ui/forms.py b/dojo/development_environment/ui/forms.py new file mode 100644 index 00000000000..4df9ceca798 --- /dev/null +++ b/dojo/development_environment/ui/forms.py @@ -0,0 +1,15 @@ +from django import forms + +from dojo.development_environment.models import Development_Environment + + +class Development_EnvironmentForm(forms.ModelForm): + class Meta: + model = Development_Environment + fields = ["name"] + + +class Delete_Dev_EnvironmentForm(forms.ModelForm): + class Meta: + model = Development_Environment + fields = ["id"] diff --git a/dojo/development_environment/urls.py b/dojo/development_environment/ui/urls.py similarity index 85% rename from dojo/development_environment/urls.py rename to dojo/development_environment/ui/urls.py index 1c1c60393d7..918789fcf79 100644 --- a/dojo/development_environment/urls.py +++ b/dojo/development_environment/ui/urls.py @@ -1,6 +1,6 @@ from django.urls import re_path -from dojo.development_environment import views +from dojo.development_environment.ui import views urlpatterns = [ # dev envs diff --git a/dojo/development_environment/views.py b/dojo/development_environment/ui/views.py similarity index 95% rename from dojo/development_environment/views.py rename to dojo/development_environment/ui/views.py index 8705fdd4c7c..8429c73bff0 100644 --- a/dojo/development_environment/views.py +++ b/dojo/development_environment/ui/views.py @@ -9,9 +9,9 @@ from django.urls import reverse from dojo.authorization.authorization import user_has_configuration_permission_or_403 +from dojo.development_environment.models import Development_Environment +from dojo.development_environment.ui.forms import Delete_Dev_EnvironmentForm, Development_EnvironmentForm from dojo.filters import DevelopmentEnvironmentFilter -from dojo.forms import Delete_Dev_EnvironmentForm, Development_EnvironmentForm -from dojo.models import Development_Environment from dojo.utils import add_breadcrumb, get_page_items logger = logging.getLogger(__name__) diff --git a/dojo/forms.py b/dojo/forms.py index 0a349d0c481..0ef434a0fcc 100644 --- a/dojo/forms.py +++ b/dojo/forms.py @@ -45,7 +45,6 @@ from dojo.location.utils import validate_locations_to_add from dojo.models import ( SEVERITY_CHOICES, - Announcement, App_Analysis, Check_List, Development_Environment, @@ -54,10 +53,8 @@ Endpoint, FileUpload, Finding_Group, - Objects_Product, Product_API_Scan_Configuration, Product_Type, - Regulation, SLA_Configuration, Test_Type, User, @@ -178,17 +175,10 @@ def clean_name(self): return self.cleaned_data["name"] -class Development_EnvironmentForm(forms.ModelForm): - class Meta: - model = Development_Environment - fields = ["name"] - - -class Delete_Dev_EnvironmentForm(forms.ModelForm): - class Meta: - model = Development_Environment - fields = ["id"] - +from dojo.development_environment.ui.forms import ( # noqa: E402, F401 -- re-export + Delete_Dev_EnvironmentForm, + Development_EnvironmentForm, +) # Re-exported for external consumers (finding_group/test/engagement/product views + unittests). # The remaining finding forms live only in dojo.finding.ui.forms and are imported there by finding's own views. @@ -788,12 +778,7 @@ class Meta: BenchmarkForm, DeleteBenchmarkForm, ) - - -class RegulationForm(forms.ModelForm): - class Meta: - model = Regulation - exclude = ["product"] +from dojo.regulations.ui.forms import RegulationForm # noqa: E402, F401 -- re-export class AppAnalysisForm(forms.ModelForm): @@ -859,40 +844,16 @@ class Meta: fields = ["id"] -class DeleteObjectsSettingsForm(forms.ModelForm): - id = forms.IntegerField(required=True, - widget=forms.widgets.HiddenInput()) - - class Meta: - model = Objects_Product - fields = ["id"] - - -class ObjectSettingsForm(forms.ModelForm): - - # tags = forms.CharField(widget=forms.SelectMultiple(choices=[]), - # required=False, - # help_text="Add tags that help describe this object. " - # "Choose from the list or add new tags. Press TAB key to add.") - - class Meta: - model = Objects_Product - fields = ["path", "folder", "artifact", "name", "review_status", "tags"] - exclude = ["product"] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def clean(self): - return self.cleaned_data - - from dojo.notifications.ui.forms import ( # noqa: E402, F401 -- backward compat DeleteNotificationsWebhookForm, NotificationsForm, NotificationsWebhookForm, ProductNotificationsForm, ) +from dojo.object.ui.forms import ( # noqa: E402, F401 -- re-export + DeleteObjectsSettingsForm, + ObjectSettingsForm, +) class AjaxChoiceField(forms.ChoiceField): @@ -900,35 +861,11 @@ def valid_value(self, value): return True -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() - - -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 +from dojo.announcement.ui.forms import ( # noqa: E402, F401 -- re-export + AnnouncementCreateForm, + AnnouncementRemoveForm, +) +from dojo.banner.ui.forms import LoginBanner # noqa: E402, F401 -- re-export class ConfigurationPermissionsForm(forms.Form): diff --git a/dojo/models.py b/dojo/models.py index c5eb30b0d15..c4d3d0aaa68 100644 --- a/dojo/models.py +++ b/dojo/models.py @@ -119,44 +119,11 @@ def __call__(self, model_instance, filename): return Path(now().strftime(self.directory)) / filename -class Regulation(models.Model): - PRIVACY_CATEGORY = "privacy" - FINANCE_CATEGORY = "finance" - EDUCATION_CATEGORY = "education" - MEDICAL_CATEGORY = "medical" - CORPORATE_CATEGORY = "corporate" - SECURITY_CATEGORY = "security" - GOVERNMENT_CATEGORY = "government" - OTHER_CATEGORY = "other" - CATEGORY_CHOICES = ( - (PRIVACY_CATEGORY, _("Privacy")), - (FINANCE_CATEGORY, _("Finance")), - (EDUCATION_CATEGORY, _("Education")), - (MEDICAL_CATEGORY, _("Medical")), - (CORPORATE_CATEGORY, _("Corporate")), - (SECURITY_CATEGORY, _("Security")), - (GOVERNMENT_CATEGORY, _("Government")), - (OTHER_CATEGORY, _("Other")), - ) - - name = models.CharField(max_length=128, unique=True, help_text=_("The name of the regulation.")) - acronym = models.CharField(max_length=20, unique=True, help_text=_("A shortened representation of the name.")) - category = models.CharField(max_length=16, choices=CATEGORY_CHOICES, help_text=_("The subject of the regulation.")) - jurisdiction = models.CharField(max_length=64, help_text=_("The territory over which the regulation applies.")) - description = models.TextField(blank=True, help_text=_("Information about the regulation's purpose.")) - reference = models.URLField(blank=True, help_text=_("An external URL for more information.")) - - class Meta: - ordering = ["name"] - - def __str__(self): - return self.acronym + " (" + self.jurisdiction + ")" - - User = get_user_model() -from dojo.user.models import Contact, Dojo_User, UserContactInfo # noqa: E402, F401, I001 -- must precede system_settings (middleware load-order) +from dojo.regulations.models import Regulation # noqa: E402, F401, I001 -- re-export; user/system_settings block intentionally out-of-order (load-order) +from dojo.user.models import Contact, Dojo_User, UserContactInfo # noqa: E402, F401 -- must precede system_settings (middleware load-order) from dojo.system_settings.models import System_Settings # noqa: E402, F401 -- re-export @@ -390,6 +357,7 @@ def __str__(self): return self.location +from dojo.development_environment.models import Development_Environment # noqa: E402, F401 -- re-export from dojo.endpoint.models import Endpoint, Endpoint_Params, Endpoint_Status # noqa: E402, F401 -- re-export from dojo.engagement.models import ( # noqa: E402 -- re-export; class-body FKs below reference these ENGAGEMENT_STATUS_CHOICES, # noqa: F401 -- re-export @@ -398,17 +366,6 @@ def __str__(self): ) -class Development_Environment(models.Model): - name = models.CharField(max_length=200) - - def __str__(self): - return self.name - - def get_breadcrumbs(self): - return [{"title": str(self), - "url": reverse("edit_dev_env", args=(self.id,))}] - - class Sonarqube_Issue(models.Model): key = models.CharField(max_length=60, unique=True, help_text=_("SonarQube issue key")) status = models.CharField(max_length=20, help_text=_("SonarQube issue status")) @@ -491,40 +448,12 @@ def get_breadcrumb(self): return bc -from dojo.risk_acceptance.models import Risk_Acceptance # noqa: E402, F401 -- re-export - -ANNOUNCEMENT_STYLE_CHOICES = ( - ("info", "Info"), - ("success", "Success"), - ("warning", "Warning"), - ("danger", "Danger"), +from dojo.announcement.models import ( # noqa: E402 -- re-export + ANNOUNCEMENT_STYLE_CHOICES, # noqa: F401 -- re-export + Announcement, # noqa: F401 -- re-export + UserAnnouncement, # noqa: F401 -- re-export ) - - -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 https://example.com"), - 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(Announcement, null=True, editable=False, on_delete=models.CASCADE, related_name="user_announcement") - user = models.ForeignKey(Dojo_User, null=True, editable=False, on_delete=models.CASCADE) - - -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 https://example.com"), default="") - - +from dojo.banner.models import BannerConf # noqa: E402, F401 -- re-export from dojo.github.models import ( # noqa: E402, F401 -- backward compat GITHUB_Clone, GITHUB_Conf, @@ -551,6 +480,7 @@ class BannerConf(models.Model): Notification_Webhooks, Notifications, ) +from dojo.risk_acceptance.models import Risk_Acceptance # noqa: E402, F401 -- re-export from dojo.tool_product.models import Tool_Product_History, Tool_Product_Settings # noqa: E402, F401 -- re-export @@ -596,38 +526,7 @@ def __str__(self): return self.name + " | " + self.product.name -class Objects_Review(models.Model): - name = models.CharField(max_length=100, null=True, blank=True) - created = models.DateTimeField(auto_now_add=True, null=False) - - def __str__(self): - return self.name - - -class Objects_Product(models.Model): - product = models.ForeignKey(Product, on_delete=models.CASCADE) - name = models.CharField(max_length=100, null=True, blank=True) - path = models.CharField(max_length=600, verbose_name=_("Full file path"), - null=True, blank=True) - folder = models.CharField(max_length=400, verbose_name=_("Folder"), - null=True, blank=True) - artifact = models.CharField(max_length=400, verbose_name=_("Artifact"), - null=True, blank=True) - review_status = models.ForeignKey(Objects_Review, on_delete=models.CASCADE) - created = models.DateTimeField(auto_now_add=True, null=False) - - tags = TagField(blank=True, force_lowercase=True, help_text=_("Add tags that help describe this object. Choose from the list or add new tags. Press Enter key to add.")) - - def __str__(self): - name = None - if self.path is not None: - name = self.path - elif self.folder is not None: - name = self.folder - elif self.artifact is not None: - name = self.artifact - - return name +from dojo.object.models import Objects_Product, Objects_Review # noqa: E402, F401 -- re-export class Testing_Guide_Category(models.Model): @@ -696,15 +595,14 @@ def __str__(self): tagulous.admin.register(Engagement.inherited_tags) tagulous.admin.register(Finding_Template.tags) tagulous.admin.register(App_Analysis.tags) -tagulous.admin.register(Objects_Product.tags) +# Objects_Product.tags registered in dojo/object/admin.py # Testing admin.site.register(Testing_Guide_Category) admin.site.register(Testing_Guide) admin.site.register(Network_Locations) -admin.site.register(Objects_Product) -admin.site.register(Objects_Review) +# Objects_Product + Objects_Review admin registered in dojo/object/admin.py admin.site.register(Languages) admin.site.register(Language_Type) admin.site.register(App_Analysis) @@ -713,7 +611,7 @@ def __str__(self): # Notes + NoteHistory admin registered in dojo/notes/admin.py # Note_Type admin registered in dojo/note_type/admin.py admin.site.register(SLA_Configuration) -admin.site.register(Regulation) +# Regulation admin registered in dojo/regulations/admin.py from dojo.authorization.models import ( # noqa: E402 Dojo_Group, Dojo_Group_Member, @@ -742,7 +640,6 @@ def __str__(self): # NoteHistory admin registered in dojo/notes/admin.py # Report_Type admin registered in dojo/reports/admin.py admin.site.register(DojoMeta) -admin.site.register(Development_Environment) -admin.site.register(Announcement) -admin.site.register(UserAnnouncement) -admin.site.register(BannerConf) +# Development_Environment admin registered in dojo/development_environment/admin.py +# Announcement + UserAnnouncement admin registered in dojo/announcement/admin.py +# BannerConf admin registered in dojo/banner/admin.py diff --git a/dojo/object/__init__.py b/dojo/object/__init__.py index e69de29bb2d..2a0769e5c0e 100644 --- a/dojo/object/__init__.py +++ b/dojo/object/__init__.py @@ -0,0 +1 @@ +import dojo.object.admin # noqa: F401 diff --git a/dojo/object/admin.py b/dojo/object/admin.py new file mode 100644 index 00000000000..5782d037789 --- /dev/null +++ b/dojo/object/admin.py @@ -0,0 +1,8 @@ +import tagulous.admin +from django.contrib import admin + +from dojo.object.models import Objects_Product, Objects_Review + +admin.site.register(Objects_Product) +admin.site.register(Objects_Review) +tagulous.admin.register(Objects_Product.tags) diff --git a/dojo/object/models.py b/dojo/object/models.py new file mode 100644 index 00000000000..37f99568d40 --- /dev/null +++ b/dojo/object/models.py @@ -0,0 +1,37 @@ +from django.db import models +from django.utils.translation import gettext as _ +from tagulous.models import TagField + + +class Objects_Review(models.Model): + name = models.CharField(max_length=100, null=True, blank=True) + created = models.DateTimeField(auto_now_add=True, null=False) + + def __str__(self): + return self.name + + +class Objects_Product(models.Model): + product = models.ForeignKey("dojo.Product", on_delete=models.CASCADE) + name = models.CharField(max_length=100, null=True, blank=True) + path = models.CharField(max_length=600, verbose_name=_("Full file path"), + null=True, blank=True) + folder = models.CharField(max_length=400, verbose_name=_("Folder"), + null=True, blank=True) + artifact = models.CharField(max_length=400, verbose_name=_("Artifact"), + null=True, blank=True) + review_status = models.ForeignKey("dojo.Objects_Review", on_delete=models.CASCADE) + created = models.DateTimeField(auto_now_add=True, null=False) + + tags = TagField(blank=True, force_lowercase=True, help_text=_("Add tags that help describe this object. Choose from the list or add new tags. Press Enter key to add.")) + + def __str__(self): + name = None + if self.path is not None: + name = self.path + elif self.folder is not None: + name = self.folder + elif self.artifact is not None: + name = self.artifact + + return name diff --git a/dojo/object/ui/__init__.py b/dojo/object/ui/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dojo/object/ui/forms.py b/dojo/object/ui/forms.py new file mode 100644 index 00000000000..1c94c6bd9e8 --- /dev/null +++ b/dojo/object/ui/forms.py @@ -0,0 +1,26 @@ +from django import forms + +from dojo.object.models import Objects_Product + + +class DeleteObjectsSettingsForm(forms.ModelForm): + id = forms.IntegerField(required=True, + widget=forms.widgets.HiddenInput()) + + class Meta: + model = Objects_Product + fields = ["id"] + + +class ObjectSettingsForm(forms.ModelForm): + + class Meta: + model = Objects_Product + fields = ["path", "folder", "artifact", "name", "review_status", "tags"] + exclude = ["product"] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def clean(self): + return self.cleaned_data diff --git a/dojo/object/urls.py b/dojo/object/ui/urls.py similarity index 93% rename from dojo/object/urls.py rename to dojo/object/ui/urls.py index b31e9350648..9aa1d7cbbdc 100644 --- a/dojo/object/urls.py +++ b/dojo/object/ui/urls.py @@ -1,6 +1,6 @@ from django.urls import re_path -from . import views +from dojo.object.ui import views urlpatterns = [ re_path(r"^product/(?P\d+)/object/add$", views.new_object, name="new_object"), diff --git a/dojo/object/views.py b/dojo/object/ui/views.py similarity index 88% rename from dojo/object/views.py rename to dojo/object/ui/views.py index 40cc57a45b2..c0f9342c6c1 100644 --- a/dojo/object/views.py +++ b/dojo/object/ui/views.py @@ -6,9 +6,9 @@ from django.shortcuts import get_object_or_404, render from django.urls import reverse -from dojo.forms import DeleteObjectsSettingsForm, ObjectSettingsForm from dojo.labels import get_labels -from dojo.models import Objects_Product, Product +from dojo.object.models import Objects_Product +from dojo.object.ui.forms import DeleteObjectsSettingsForm, ObjectSettingsForm from dojo.utils import Product_Tab logger = logging.getLogger(__name__) @@ -18,6 +18,7 @@ def new_object(request, pid): page_name = labels.ASSET_TRACKED_FILES_ADD_LABEL + from dojo.models import Product # noqa: PLC0415 -- lazy import, avoids circular dependency prod = get_object_or_404(Product, id=pid) if request.method == "POST": tform = ObjectSettingsForm(request.POST) @@ -44,6 +45,7 @@ def new_object(request, pid): def view_objects(request, pid): + from dojo.models import Product # noqa: PLC0415 -- lazy import, avoids circular dependency product = get_object_or_404(Product, id=pid) object_queryset = Objects_Product.objects.filter(product=pid).order_by("path", "folder", "artifact") @@ -59,6 +61,7 @@ def view_objects(request, pid): def edit_object(request, pid, ttid): object_prod = get_object_or_404(Objects_Product, pk=ttid) + from dojo.models import Product # noqa: PLC0415 -- lazy import, avoids circular dependency product = get_object_or_404(Product, id=pid) if object_prod.product != product: raise PermissionDenied @@ -87,6 +90,7 @@ def edit_object(request, pid, ttid): def delete_object(request, pid, ttid): object_prod = get_object_or_404(Objects_Product, pk=ttid) + from dojo.models import Product # noqa: PLC0415 -- lazy import, avoids circular dependency product = get_object_or_404(Product, id=pid) if object_prod.product != product: raise PermissionDenied diff --git a/dojo/regulations/__init__.py b/dojo/regulations/__init__.py index e69de29bb2d..a6ad8a993aa 100644 --- a/dojo/regulations/__init__.py +++ b/dojo/regulations/__init__.py @@ -0,0 +1 @@ +import dojo.regulations.admin # noqa: F401 diff --git a/dojo/regulations/admin.py b/dojo/regulations/admin.py new file mode 100644 index 00000000000..6d5961769f5 --- /dev/null +++ b/dojo/regulations/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin + +from dojo.regulations.models import Regulation + +admin.site.register(Regulation) diff --git a/dojo/regulations/api/__init__.py b/dojo/regulations/api/__init__.py new file mode 100644 index 00000000000..de5e580ef42 --- /dev/null +++ b/dojo/regulations/api/__init__.py @@ -0,0 +1 @@ +path = "regulations" # noqa: RUF067 diff --git a/dojo/regulations/api/serializer.py b/dojo/regulations/api/serializer.py new file mode 100644 index 00000000000..519d5c0ef10 --- /dev/null +++ b/dojo/regulations/api/serializer.py @@ -0,0 +1,9 @@ +from rest_framework import serializers + +from dojo.regulations.models import Regulation + + +class RegulationSerializer(serializers.ModelSerializer): + class Meta: + model = Regulation + fields = "__all__" diff --git a/dojo/regulations/api/urls.py b/dojo/regulations/api/urls.py new file mode 100644 index 00000000000..fa88fd0086f --- /dev/null +++ b/dojo/regulations/api/urls.py @@ -0,0 +1,7 @@ +from dojo.regulations.api import path +from dojo.regulations.api.views import RegulationsViewSet + + +def add_regulations_urls(router): + router.register(path, RegulationsViewSet, basename="regulations") + return router diff --git a/dojo/regulations/api/views.py b/dojo/regulations/api/views.py new file mode 100644 index 00000000000..8d0574afc89 --- /dev/null +++ b/dojo/regulations/api/views.py @@ -0,0 +1,21 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework.permissions import IsAuthenticated + +from dojo.api_v2.views import DojoModelViewSet +from dojo.authorization import api_permissions as permissions +from dojo.regulations.api.serializer import RegulationSerializer +from dojo.regulations.models import Regulation + + +# Authorization: authenticated, configuration +class RegulationsViewSet( + DojoModelViewSet, +): + serializer_class = 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") diff --git a/dojo/regulations/models.py b/dojo/regulations/models.py new file mode 100644 index 00000000000..4910f32f481 --- /dev/null +++ b/dojo/regulations/models.py @@ -0,0 +1,36 @@ +from django.db import models +from django.utils.translation import gettext as _ + + +class Regulation(models.Model): + PRIVACY_CATEGORY = "privacy" + FINANCE_CATEGORY = "finance" + EDUCATION_CATEGORY = "education" + MEDICAL_CATEGORY = "medical" + CORPORATE_CATEGORY = "corporate" + SECURITY_CATEGORY = "security" + GOVERNMENT_CATEGORY = "government" + OTHER_CATEGORY = "other" + CATEGORY_CHOICES = ( + (PRIVACY_CATEGORY, _("Privacy")), + (FINANCE_CATEGORY, _("Finance")), + (EDUCATION_CATEGORY, _("Education")), + (MEDICAL_CATEGORY, _("Medical")), + (CORPORATE_CATEGORY, _("Corporate")), + (SECURITY_CATEGORY, _("Security")), + (GOVERNMENT_CATEGORY, _("Government")), + (OTHER_CATEGORY, _("Other")), + ) + + name = models.CharField(max_length=128, unique=True, help_text=_("The name of the regulation.")) + acronym = models.CharField(max_length=20, unique=True, help_text=_("A shortened representation of the name.")) + category = models.CharField(max_length=16, choices=CATEGORY_CHOICES, help_text=_("The subject of the regulation.")) + jurisdiction = models.CharField(max_length=64, help_text=_("The territory over which the regulation applies.")) + description = models.TextField(blank=True, help_text=_("Information about the regulation's purpose.")) + reference = models.URLField(blank=True, help_text=_("An external URL for more information.")) + + class Meta: + ordering = ["name"] + + def __str__(self): + return self.acronym + " (" + self.jurisdiction + ")" diff --git a/dojo/regulations/ui/__init__.py b/dojo/regulations/ui/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dojo/regulations/ui/forms.py b/dojo/regulations/ui/forms.py new file mode 100644 index 00000000000..8e3a7c5f89b --- /dev/null +++ b/dojo/regulations/ui/forms.py @@ -0,0 +1,9 @@ +from django import forms + +from dojo.regulations.models import Regulation + + +class RegulationForm(forms.ModelForm): + class Meta: + model = Regulation + exclude = ["product"] diff --git a/dojo/regulations/urls.py b/dojo/regulations/ui/urls.py similarity index 88% rename from dojo/regulations/urls.py rename to dojo/regulations/ui/urls.py index 324669f6759..21acf979edf 100644 --- a/dojo/regulations/urls.py +++ b/dojo/regulations/ui/urls.py @@ -1,6 +1,6 @@ from django.urls import re_path -from . import views +from dojo.regulations.ui import views urlpatterns = [ re_path(r"^regulations/add", views.new_regulation, name="new_regulation"), diff --git a/dojo/regulations/views.py b/dojo/regulations/ui/views.py similarity index 96% rename from dojo/regulations/views.py rename to dojo/regulations/ui/views.py index 9bbb3296190..6fd127921a0 100644 --- a/dojo/regulations/views.py +++ b/dojo/regulations/ui/views.py @@ -8,8 +8,8 @@ from django.urls import reverse from dojo.authorization.authorization import user_has_configuration_permission_or_403 -from dojo.forms import RegulationForm -from dojo.models import Regulation +from dojo.regulations.models import Regulation +from dojo.regulations.ui.forms import RegulationForm from dojo.utils import add_breadcrumb logger = logging.getLogger(__name__) diff --git a/dojo/urls.py b/dojo/urls.py index be79a7f8bd0..063d65ef220 100644 --- a/dojo/urls.py +++ b/dojo/urls.py @@ -10,13 +10,12 @@ from rest_framework.routers import DefaultRouter from dojo import views -from dojo.announcement.urls import urlpatterns as announcement_urls +from dojo.announcement.api.urls import add_announcement_urls +from dojo.announcement.ui.urls import urlpatterns as announcement_urls from dojo.api_v2.views import ( - AnnouncementViewSet, AppAnalysisViewSet, CeleryViewSet, ConfigurationPermissionViewSet, - DevelopmentEnvironmentViewSet, DojoMetaViewSet, ImportLanguagesView, ImportScanView, @@ -26,7 +25,6 @@ LanguageTypeViewSet, LanguageViewSet, NetworkLocationsViewset, - RegulationsViewSet, ReImportScanView, SLAConfigurationViewset, SonarqubeIssueTransitionViewSet, @@ -35,10 +33,11 @@ from dojo.api_v2.views import DojoSpectacularAPIView as SpectacularAPIView from dojo.asset.api.urls import add_asset_urls from dojo.asset.urls import urlpatterns as asset_urls -from dojo.banner.urls import urlpatterns as banner_urls +from dojo.banner.ui.urls import urlpatterns as banner_urls from dojo.benchmark.ui.urls import urlpatterns as benchmark_urls from dojo.components.urls import urlpatterns as component_urls -from dojo.development_environment.urls import urlpatterns as dev_env_urls +from dojo.development_environment.api.urls import add_development_environment_urls +from dojo.development_environment.ui.urls import urlpatterns as dev_env_urls from dojo.endpoint.api.urls import add_endpoint_urls, register_endpoint_meta_import from dojo.endpoint.ui.urls import urlpatterns as endpoint_urls from dojo.engagement.api.urls import add_engagement_urls @@ -58,12 +57,13 @@ from dojo.notes.ui.urls import urlpatterns as notes_urls from dojo.notifications.api.urls import add_notifications_urls from dojo.notifications.ui.urls import urlpatterns as notifications_urls -from dojo.object.urls import urlpatterns as object_urls +from dojo.object.ui.urls import urlpatterns as object_urls from dojo.organization.api.urls import add_organization_urls from dojo.organization.urls import urlpatterns as organization_urls from dojo.product.api.urls import add_product_urls from dojo.product_type.api.urls import add_product_type_urls -from dojo.regulations.urls import urlpatterns as regulations +from dojo.regulations.api.urls import add_regulations_urls +from dojo.regulations.ui.urls import urlpatterns as regulations from dojo.reports.ui.urls import urlpatterns as reports_urls from dojo.risk_acceptance.api.urls import add_risk_acceptance_urls from dojo.search.urls import urlpatterns as search_urls @@ -98,9 +98,9 @@ # v2 api written in django-rest-framework v2_api = DefaultRouter() -v2_api.register(r"announcements", AnnouncementViewSet, basename="announcement") +v2_api = add_announcement_urls(v2_api) v2_api.register(r"configuration_permissions", ConfigurationPermissionViewSet, basename="permission") -v2_api.register(r"development_environments", DevelopmentEnvironmentViewSet, basename="development_environment") +v2_api = add_development_environment_urls(v2_api) # RBAC endpoints moved to Pro under legacy authorization: # dojo_groups, dojo_group_members → pro/groups, pro/group_members v2_api = register_endpoint_meta_import(v2_api) @@ -127,7 +127,7 @@ v2_api = add_finding_urls(v2_api) # RBAC endpoints moved to Pro under legacy authorization: # product_type_members, product_type_groups → pro/product_type_members, pro/product_type_groups -v2_api.register(r"regulations", RegulationsViewSet, basename="regulations") +v2_api = add_regulations_urls(v2_api) v2_api.register(r"reimport-scan", ReImportScanView, basename="reimportscan") v2_api = add_risk_acceptance_urls(v2_api) # RBAC endpoint moved to Pro under legacy authorization: roles → pro/roles diff --git a/unittests/test_rest_framework.py b/unittests/test_rest_framework.py index c661d4678b3..f82fe0a10fc 100644 --- a/unittests/test_rest_framework.py +++ b/unittests/test_rest_framework.py @@ -33,14 +33,13 @@ ) from rest_framework.test import APIClient +from dojo.announcement.api.views import AnnouncementViewSet from dojo.api_v2.mixins import DeletePreviewModelMixin from dojo.api_v2.prefetch import PrefetchListMixin, PrefetchRetrieveMixin from dojo.api_v2.prefetch.utils import get_prefetchable_fields from dojo.api_v2.views import ( - AnnouncementViewSet, AppAnalysisViewSet, ConfigurationPermissionViewSet, - DevelopmentEnvironmentViewSet, ImportLanguagesView, ImportScanView, JiraInstanceViewSet, @@ -57,6 +56,7 @@ AssetViewSet, ) from dojo.authorization.roles_permissions import Permissions, permission_to_action +from dojo.development_environment.api.views import DevelopmentEnvironmentViewSet from dojo.endpoint.api.views import EndpointStatusViewSet, EndPointViewSet from dojo.engagement.api.views import EngagementViewSet from dojo.finding.api.views import (