diff --git a/django/thunderstore/repository/migrations/0044_add_deprecate_permission.py b/django/thunderstore/repository/migrations/0044_add_deprecate_permission.py new file mode 100644 index 000000000..3be6a0fce --- /dev/null +++ b/django/thunderstore/repository/migrations/0044_add_deprecate_permission.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1.7 on 2023-12-03 03:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("repository", "0043_add_package_index_task_schedule"), + ] + + operations = [ + migrations.AlterModelOptions( + name="package", + options={ + "permissions": ( + ("deprecate_package", "Can manage package deprecation status"), + ) + }, + ), + ] diff --git a/django/thunderstore/repository/models/package.py b/django/thunderstore/repository/models/package.py index 6abe70a8f..279500ae3 100644 --- a/django/thunderstore/repository/models/package.py +++ b/django/thunderstore/repository/models/package.py @@ -92,6 +92,7 @@ class Package(models.Model): ) class Meta: + permissions = (("deprecate_package", "Can manage package deprecation status"),) constraints = [ models.UniqueConstraint( fields=("owner", "name"), name="unique_name_per_namespace" @@ -300,7 +301,10 @@ def ensure_user_can_manage_deprecation(self, user: Optional[UserType]) -> None: raise ValidationError("Must be authenticated") if not user.is_active: raise ValidationError("User has been deactivated") - if user.is_staff and user.has_perm("repository.change_package"): + if user.is_staff and ( + user.has_perm("repository.change_package") + or user.has_perm("repository.deprecate_package") + ): return self.owner.ensure_user_can_manage_packages(user) diff --git a/django/thunderstore/repository/tests/test_package.py b/django/thunderstore/repository/tests/test_package.py index 7dda033fa..a80a70fd1 100644 --- a/django/thunderstore/repository/tests/test_package.py +++ b/django/thunderstore/repository/tests/test_package.py @@ -1,11 +1,15 @@ from typing import Any import pytest +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Permission +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from conftest import TestUserTypes from thunderstore.community.factories import SiteFactory from thunderstore.community.models.package_listing import PackageListing +from thunderstore.core.types import UserType from thunderstore.repository.factories import PackageFactory from thunderstore.repository.models import ( Namespace, @@ -16,6 +20,8 @@ ) from thunderstore.wiki.factories import WikiPageFactory +User = get_user_model() + @pytest.mark.django_db def test_package_get_page_url( @@ -112,6 +118,25 @@ def test_package_ensure_user_can_manage_deprecation( assert package.ensure_user_can_manage_deprecation(user) is None +@pytest.mark.django_db +def test_package_ensure_user_can_manage_deprecation_deprecate_package_perm( + namespace: Namespace, + user: UserType, +): + team = namespace.team + package = PackageFactory(owner=team, namespace=namespace) + user.is_staff = True + user.save() + assert package.can_user_manage_deprecation(user) is False + content_type = ContentType.objects.get_for_model(Package) + perm = Permission.objects.get( + content_type=content_type, codename="deprecate_package" + ) + user.user_permissions.add(perm) + user = User.objects.get(pk=user.pk) + assert package.can_user_manage_deprecation(user) is True + + @pytest.mark.django_db @pytest.mark.parametrize("user_type", TestUserTypes.options()) @pytest.mark.parametrize("role", TeamMemberRole.options() + [None]) diff --git a/django/thunderstore/repository/views/repository.py b/django/thunderstore/repository/views/repository.py index 401c51dbc..746f4e6ad 100644 --- a/django/thunderstore/repository/views/repository.py +++ b/django/thunderstore/repository/views/repository.py @@ -27,6 +27,7 @@ PackageListingSection, ) from thunderstore.core.types import UserType +from thunderstore.core.utils import check_validity from thunderstore.frontend.api.experimental.serializers.views import CommunitySerializer from thunderstore.frontend.url_reverse import get_community_url_reverse_args from thunderstore.repository.mixins import CommunityMixin @@ -470,6 +471,12 @@ def get_object(self, *args, **kwargs) -> PackageListing: def can_manage(self): return self.object.package.can_user_manage_deprecation(self.request.user) + @property + def can_manage_categories(self) -> bool: + return self.can_manage and check_validity( + lambda: self.object.ensure_update_categories_permission(self.request.user) + ) + @property def can_deprecate(self): return self.can_manage and self.object.package.is_deprecated is False @@ -510,7 +517,7 @@ def format_category(cat: PackageCategory): "canDeprecate": self.can_deprecate, "canUndeprecate": self.can_undeprecate, "canUnlist": self.can_unlist, - "canUpdateCategories": self.can_manage, + "canUpdateCategories": self.can_manage_categories, "csrfToken": csrf.get_token(self.request), "currentCategories": [ format_category(x) for x in package_listing.categories.all()