diff --git a/backend/core/models/product.py b/backend/core/models/product.py index 65405d9..abbf891 100644 --- a/backend/core/models/product.py +++ b/backend/core/models/product.py @@ -65,3 +65,8 @@ def delete(self, *args, **kwargs): # raise OSError("Failed to remove directory: [ %s ] %s" % (product_path, e)) super().delete(*args, **kwargs) + + def can_delete(self, user) -> bool: + if self.user.id == user.id or user.profile.is_admin(): + return True + return False diff --git a/backend/core/serializers/product.py b/backend/core/serializers/product.py index 3dd7c44..1c6bdde 100644 --- a/backend/core/serializers/product.py +++ b/backend/core/serializers/product.py @@ -1,6 +1,6 @@ +from core.models import Product, ProductType, Release from pkg_resources import require from rest_framework import serializers -from core.models import Release, ProductType, Product class ProductSerializer(serializers.ModelSerializer): @@ -20,6 +20,8 @@ class ProductSerializer(serializers.ModelSerializer): is_owner = serializers.SerializerMethodField() + can_delete = serializers.SerializerMethodField() + class Meta: model = Product read_only_fields = ("internal_name", "is_owner") @@ -43,3 +45,7 @@ def get_is_owner(self, obj): return True else: return False + + def get_can_delete(self, obj): + current_user = self.context["request"].user + return obj.can_delete(current_user) \ No newline at end of file diff --git a/backend/core/test/test_product.py b/backend/core/test/test_product.py index bcfe9ea..8e82eb5 100644 --- a/backend/core/test/test_product.py +++ b/backend/core/test/test_product.py @@ -288,6 +288,7 @@ def test_product_serialized_format(self): "product_type_name": self.product_type.display_name, "uploaded_by": self.user.username, "is_owner": True, + "can_delete": True, "internal_name": self.product.internal_name, "display_name": self.product.display_name, "official_product": self.product.official_product, @@ -336,6 +337,27 @@ def test_product_object_delete(self): self.assertEqual(204, response.status_code) + def test_product_object_delete_by_admin(self): + """Tests if the product admin can remove it""" + view = ProductViewSet.as_view({"delete": "destroy"}) + + # Cria um usuario que faz parte do grupo admin + adm_group = Group.objects.create(name="Admin") + user = User.objects.create_user("john2", "john2@snow.com", "you_know_nothing") + user.groups.add(adm_group) + token = Token.objects.create(user=user) + # Cria uma requisicao utilizando Factory + # para que o metodo destroy da view tenha acesso ao request.user + factory = APIRequestFactory() + request = factory.delete(self.url, format="json") + force_authenticate(request, user=user, token=user.auth_token) + request.user = user + + raw_response = view(request, pk=self.product.pk) + response = raw_response.render() + + self.assertEqual(204, response.status_code) + def test_access_another_user_product(self): """Verifica se é possivel um usuario ler produtos de outro usuario. Valida se a flag is_owner retorna False diff --git a/backend/core/views/product.py b/backend/core/views/product.py index b028f20..7d7cc4e 100644 --- a/backend/core/views/product.py +++ b/backend/core/views/product.py @@ -379,9 +379,11 @@ def zip_product(self, internal_name, path, tmpdir): return zip_path def destroy(self, request, pk=None, *args, **kwargs): - # TODO: Duvida, Admin pode remover produto que não seja dele? + """Produto só pode ser excluido pelo DONO ou se o usuario tiver profile de admin. + """ + # Regra do admin atualizada na issue: #192 - https://github.com/linea-it/pzserver_app/issues/192 instance = self.get_object() - if self.request.user.id == instance.user.pk: + if instance.can_delete(self.request.user): return super(ProductViewSet, self).destroy(request, pk, *args, **kwargs) else: raise exceptions.PermissionDenied() diff --git a/frontend/components/ProductGrid.js b/frontend/components/ProductGrid.js index 3b8efed..c6609c9 100644 --- a/frontend/components/ProductGrid.js +++ b/frontend/components/ProductGrid.js @@ -3,6 +3,7 @@ import DeleteIcon from '@mui/icons-material/Delete' import DownloadIcon from '@mui/icons-material/Download' import ShareIcon from '@mui/icons-material/Share' import Alert from '@mui/material/Alert' +import Tooltip from '@mui/material/Tooltip' import Link from '@mui/material/Link' import Snackbar from '@mui/material/Snackbar' import { DataGrid, GridActionsCellItem } from '@mui/x-data-grid' @@ -158,15 +159,30 @@ export default function ProductGrid(props) { width: 120, sortable: false, renderCell: params => ( - } - onClick={() => handleDelete(params.row)} - /> + + + } + onClick={() => handleDelete(params.row)} + disabled={!params.row.can_delete} + /> + + ) } ] }, [getProductUrl, router]) + function handleError(errorMessage) { + console.error(errorMessage) + } + return ( setDelRecordId(null)} recordId={delRecordId} onRemoveSuccess={loadProducts} + onError={handleError} /> )}