diff --git a/api/projects/admin.py b/api/projects/admin.py index 8e8f1001a993..95c93296329f 100644 --- a/api/projects/admin.py +++ b/api/projects/admin.py @@ -2,6 +2,8 @@ from __future__ import unicode_literals from django.contrib import admin +from django.db.models import QuerySet +from django.http import HttpRequest from environments.models import Environment from features.models import Feature @@ -47,6 +49,7 @@ class TagInline(admin.StackedInline): @admin.register(Project) class ProjectAdmin(admin.ModelAdmin): + actions = ["delete_all_segments"] date_hierarchy = "created_date" inlines = [EnvironmentInline, FeatureInline, SegmentInline, TagInline] list_display = ( @@ -67,3 +70,15 @@ class ProjectAdmin(admin.ModelAdmin): "max_features_allowed", "max_segment_overrides_allowed", ) + + @admin.action( + description="Delete all segments for project", + permissions=["delete_all_segments"], + ) + def delete_all_segments(self, request: HttpRequest, queryset: QuerySet): + Segment.objects.filter(project__in=queryset).delete() + + def has_delete_all_segments_permission( + self, request: HttpRequest, obj: Project = None + ) -> bool: + return request.user.is_superuser diff --git a/api/tests/unit/projects/test_unit_projects_admin.py b/api/tests/unit/projects/test_unit_projects_admin.py new file mode 100644 index 000000000000..0be0f847cad3 --- /dev/null +++ b/api/tests/unit/projects/test_unit_projects_admin.py @@ -0,0 +1,80 @@ +import typing +from unittest.mock import MagicMock + +import pytest +from django.contrib.admin import AdminSite + +from environments.models import Environment +from features.models import Feature, FeatureSegment, FeatureState +from projects.admin import ProjectAdmin +from projects.models import Project +from segments.models import EQUAL, Condition, Segment, SegmentRule + +if typing.TYPE_CHECKING: + from django.contrib.auth.models import AbstractUser + + from organisations.models import Organisation + + +def test_project_admin_delete_all_segments(organisation: "Organisation"): + # Given + project_1 = Project.objects.create(name="project_1", organisation=organisation) + project_2 = Project.objects.create(name="project_2", organisation=organisation) + + for project in (project_1, project_2): + segment = Segment.objects.create(name="segment", project=project) + parent_rule = SegmentRule.objects.create( + segment=segment, type=SegmentRule.ALL_RULE + ) + child_rule = SegmentRule.objects.create( + rule=parent_rule, type=SegmentRule.ANY_RULE + ) + Condition.objects.create( + rule=child_rule, property="foo", operator=EQUAL, value="bar" + ) + + environment = Environment.objects.create(name="test", project=project) + feature = Feature.objects.create(name="test", project=project) + + feature_segment = FeatureSegment.objects.create( + feature=feature, environment=environment, segment=segment + ) + FeatureState.objects.create( + feature=feature, environment=environment, feature_segment=feature_segment + ) + + project_admin = ProjectAdmin(Project, AdminSite()) + + # When + project_admin.delete_all_segments( + request=MagicMock(), queryset=Project.objects.filter(id=project_1.id) + ) + + # Then + assert not project_1.segments.exists() + assert not FeatureState.objects.filter( + feature=feature, environment__project=project_1, feature_segment__isnull=False + ).exists() + + assert project_2.segments.exists() + assert FeatureState.objects.filter( + feature=feature, environment__project=project_2, feature_segment__isnull=False + ).exists() + + +@pytest.mark.parametrize( + "is_superuser, expected_result", ((True, True), (False, False)) +) +def test_project_admin_has_delete_all_segments_permission( + is_superuser: bool, expected_result: bool, django_user_model: type["AbstractUser"] +): + # Given + request = MagicMock(user=django_user_model(is_superuser=is_superuser)) + + # Then + assert ( + ProjectAdmin(Project, AdminSite()).has_delete_all_segments_permission( + request=request + ) + is expected_result + )