From 677631e045cce0b70a6ba886f676d0e7e92069ce Mon Sep 17 00:00:00 2001 From: Sushil Tiwari Date: Fri, 19 Jul 2024 14:40:25 +0545 Subject: [PATCH] Remove permission for public AF and Changes on mutation Add PR Changes --- apps/analysis_framework/models.py | 10 ++- apps/analysis_framework/mutation.py | 9 ++- apps/analysis_framework/serializers.py | 25 +++++++- .../tests/test_mutations.py | 63 +++++++++---------- apps/analysis_framework/views.py | 3 +- schema.graphql | 3 +- 6 files changed, 65 insertions(+), 48 deletions(-) diff --git a/apps/analysis_framework/models.py b/apps/analysis_framework/models.py index 88a583d120..30908ec616 100644 --- a/apps/analysis_framework/models.py +++ b/apps/analysis_framework/models.py @@ -1,5 +1,5 @@ import copy -from typing import Union +from typing import Union, Optional from django.db import models from django.core.exceptions import ValidationError @@ -63,15 +63,13 @@ def __init__(self, *args, **kwargs): def __str__(self): return self.title - def clone(self, user, overrides={}): + def clone(self, user, title: Optional[str] = None, description: Optional[str] = None): """ Clone analysis framework along with all widgets, filters and exportables """ - title = overrides.get( - 'title', '{} (cloned)'.format(self.title[:230]) - ) # Strip off extra chars from title - description = overrides.get('description', '') + title = title or "{} (cloned)".format(self.title[:230]) # Strip off extra chars from title + description = description or "" clone_analysis_framework = AnalysisFramework( title=title, description=description, diff --git a/apps/analysis_framework/mutation.py b/apps/analysis_framework/mutation.py index 2ff0a81598..fda86480bd 100644 --- a/apps/analysis_framework/mutation.py +++ b/apps/analysis_framework/mutation.py @@ -102,13 +102,16 @@ def get_valid_delete_items(cls, info, delete_ids): ) -class CloneAnalysisFramework(AfGrapheneMutation): +class CloneAnalysisFramework(GrapheneMutation): class Arguments: data = AnalysisFrameworkCloneInputType(required=True) result = graphene.Field(AnalysisFrameworkDetailType) serializer_class = AnalysisFrameworkCloneSerializer - permissions = [AfP.Permission.CAN_CLONE_FRAMEWORK] + + @classmethod + def check_permissions(cls, info, **_): + return True # NOTE: Checking permissions in serializer class AnalysisFrameworkMutationType(DjangoObjectType): @@ -117,7 +120,6 @@ class AnalysisFrameworkMutationType(DjangoObjectType): """ analysis_framework_update = UpdateAnalysisFramework.Field() analysis_framework_membership_bulk = BulkUpdateAnalysisFrameworkMembership.Field() - analysis_framework_clone = CloneAnalysisFramework.Field() class Meta: model = AnalysisFramework @@ -136,4 +138,5 @@ def get_custom_node(_, info, id): class Mutation(object): analysis_framework_create = CreateAnalysisFramework.Field() + analysis_framework_clone = CloneAnalysisFramework.Field() analysis_framework = DjangoObjectField(AnalysisFrameworkMutationType) diff --git a/apps/analysis_framework/serializers.py b/apps/analysis_framework/serializers.py index c840ec7cad..1605a637ab 100644 --- a/apps/analysis_framework/serializers.py +++ b/apps/analysis_framework/serializers.py @@ -712,18 +712,39 @@ def create(self, validated_data): class AnalysisFrameworkCloneSerializer(serializers.Serializer): + af_id = IntegerIDField(required=True) title = serializers.CharField(required=True) description = serializers.CharField(required=False) project = serializers.PrimaryKeyRelatedField(queryset=Project.objects.all(), required=False) + def validate_af_id(self, af_id): + af = ( + AnalysisFramework + .get_for_gq(self.context['request'].user, only_member=False) + .filter(pk=af_id) + .first() + ) + if af is None: + raise serializers.ValidationError('Analysis Framework does not exist') + if not af.can_clone(self.context['request'].user): + raise serializers.ValidationError( + 'User does not have permission to modify the AF' + ) + return af + def validate_project(self, project): if not project.can_modify(self.context['request'].user): raise serializers.ValidationError('User does not have permission to modify the project') return project def create(self, validated_data): - af = self.context['request'].active_af - new_af = af.clone(self.context['request'].user, validated_data) + # NOTE: af is an obj, check validate_af_id + af = validated_data['af_id'] + new_af = af.clone( + self.context['request'].user, + title=validated_data['title'], + description=validated_data.get('description') + ) new_af.add_member(self.context['request'].user, new_af.get_or_create_owner_role()) if project := validated_data.get('project'): diff --git a/apps/analysis_framework/tests/test_mutations.py b/apps/analysis_framework/tests/test_mutations.py index 9b616a47a2..2e67357b91 100644 --- a/apps/analysis_framework/tests/test_mutations.py +++ b/apps/analysis_framework/tests/test_mutations.py @@ -786,18 +786,16 @@ def _query_check(**kwargs): def test_analysis_framework_clone(self): query = ''' - mutation MyMutation ($id: ID!, $input: AnalysisFrameworkCloneInputType!) { + mutation MyMutation ($input: AnalysisFrameworkCloneInputType!) { __typename - analysisFramework (id: $id ) { - analysisFrameworkClone(data: $input) { - ok - errors - result { - id - title - description - clonedFrom - } + analysisFrameworkClone(data: $input) { + ok + errors + result { + id + title + description + clonedFrom } } } @@ -815,65 +813,60 @@ def test_analysis_framework_clone(self): private_af = AnalysisFrameworkFactory.create(created_by=member_user, title='AF Private Orginal', is_private=True) private_af.add_member(low_permission_user) - minput = dict( + public_minput = dict( + title='AF (TEST)', + description='Af description', + afId=af.id, + ) + private_minput = dict( title='AF (TEST)', description='Af description', + afId=private_af.id, ) def _public_af_query_check(**kwargs): return self.query_check( query, - minput=minput, - mnested=['analysisFramework'], - variables={'id': af.id}, + minput=public_minput, **kwargs, ) def _private_af_query_check(**kwargs): return self.query_check( query, - minput=minput, - mnested=['analysisFramework'], - variables={'id': private_af.id}, + minput=private_minput, **kwargs, ) # ---------- Without login _public_af_query_check(assert_for_error=True) - # ---------- With login - self.force_login(non_member_user) - # ---------- Let's Clone a new AF (Using create test data) - _public_af_query_check(assert_for_error=True) - # ---------- With login (with access member) - self.force_login(member_user) + # ---------- With login (with non access member) on public AF + self.force_login(non_member_user) response = _public_af_query_check() - self.assertEqual(response['data']['analysisFramework']['analysisFrameworkClone']['result']['clonedFrom'], str(af.id)) + self.assertEqual(response['data']['analysisFrameworkClone']['result']['clonedFrom'], str(af.id)) - # adding project to the input - minput['project'] = project.id + # adding project to the inputs + public_minput['project'] = project.id + private_minput['project'] = project.id # with Login (non project member) self.force_login(non_member_user) - _public_af_query_check(assert_for_error=True) - - # with Login (project member with no permission on AF) - self.force_login(low_permission_user) - _public_af_query_check(assert_for_error=True) + _public_af_query_check(okay=False) # with Login (project member with no permission on Private AF) self.force_login(member_user) - _private_af_query_check(assert_for_error=True) + _private_af_query_check(okay=False) # With Login (project member with permission on Public AF) self.force_login(member_user) - response = _public_af_query_check()['data']['analysisFramework'] + response = _public_af_query_check()['data'] project.refresh_from_db() self.assertEqual(str(project.analysis_framework_id), response['analysisFrameworkClone']['result']['id']) # with Login (project member with permission on Private AF) private_af.add_member(member_user, role=self.af_owner) - response = _private_af_query_check()['data']['analysisFramework'] + response = _private_af_query_check()['data'] project.refresh_from_db() self.assertEqual(str(project.analysis_framework_id), response['analysisFrameworkClone']['result']['id']) diff --git a/apps/analysis_framework/views.py b/apps/analysis_framework/views.py index 3caf3f4418..080ee1113a 100644 --- a/apps/analysis_framework/views.py +++ b/apps/analysis_framework/views.py @@ -108,7 +108,8 @@ def post(self, request, af_id, version=None): new_af = analysis_framework.clone( request.user, - request.data or {}, + title=cloned_title, + description=request.data.get('description'), ) # Set the requesting user as owner member, don't create other memberships of old framework new_af.add_member(request.user, new_af.get_or_create_owner_role()) diff --git a/schema.graphql b/schema.graphql index d1baba3d18..b9d9d9fc10 100644 --- a/schema.graphql +++ b/schema.graphql @@ -56,6 +56,7 @@ type AnalysisAutomaticSummaryType { } input AnalysisFrameworkCloneInputType { + afId: ID! title: String! description: String project: ID @@ -146,7 +147,6 @@ type AnalysisFrameworkMutationType { title: String! analysisFrameworkUpdate(data: AnalysisFrameworkInputType!): UpdateAnalysisFramework analysisFrameworkMembershipBulk(deleteIds: [ID!], items: [BulkAnalysisFrameworkMembershipInputType!]): BulkUpdateAnalysisFrameworkMembership - analysisFrameworkClone(data: AnalysisFrameworkCloneInputType!): CloneAnalysisFramework } enum AnalysisFrameworkPermission { @@ -5064,6 +5064,7 @@ type Mutation { updateMe(data: UserMeInputType!): UpdateMe deleteUser: UserDelete analysisFrameworkCreate(data: AnalysisFrameworkInputType!): CreateAnalysisFramework + analysisFrameworkClone(data: AnalysisFrameworkCloneInputType!): CloneAnalysisFramework analysisFramework(id: ID!): AnalysisFrameworkMutationType }