diff --git a/apps/geo/mutations.py b/apps/geo/mutations.py index 3ad1260724..e43cf8090a 100644 --- a/apps/geo/mutations.py +++ b/apps/geo/mutations.py @@ -1,5 +1,4 @@ import graphene -from django.utils.translation import gettext from geo.models import Region, AdminLevel from geo.schema import RegionType, RegionDetailType, AdminLevelType @@ -29,7 +28,7 @@ class Arguments: @classmethod def check_permissions(cls, info, **_): return True # global permission is always true - # NOTE: Region permission is checked using serializers + # NOTE: Project permission is checked using serializers class CreateAdminLevel(GrapheneMutation): @@ -45,6 +44,19 @@ def check_permissions(cls, info, **_): # NOTE: Region permission is checked using serializers +class UpdateRegion(GrapheneMutation): + class Arguments: + data = RegionInputType(required=True) + id = graphene.ID(required=True) + model = Region + serializer_class = RegionGqSerializer + result = graphene.Field(RegionDetailType) + + @classmethod + def check_permissions(cls, info, **_): + return True # global permission is always true + + class UpdateAdminLevel(GrapheneMutation): class Arguments: data = AdminLevelInputType(required=True) @@ -68,21 +80,26 @@ class Arguments: @staticmethod def mutate(root, info, admin_level_id): - admin_level_qs = AdminLevel.objects.filter( + admin_level = AdminLevel.objects.filter( id=admin_level_id, - region__is_published=False - ) - if not admin_level_qs: + ).first() + + error_data = [] + if admin_level is None: + error_data.append("AdminLevel doesn't exist") + elif admin_level.region.created_by_id != info.context.user.id: + error_data.append("Only region owner can delete admin level") + elif admin_level.region.is_published: + error_data.append("Published region can't be changed. Please contact system admin") + if error_data: return DeleteAdminLevel(errors=[ dict( field='nonFieldErrors', - messages=gettext( - 'You should be Region owner to delete admin level or region is published' - ), + messages=error_data ) ], ok=False) - admin_level_qs.delete() - return DeleteAdminLevel(result=admin_level_qs, errors=None, ok=True) + admin_level.delete() + return DeleteAdminLevel(errors=None, ok=True) class PublishRegion(graphene.Mutation): @@ -95,18 +112,23 @@ class Arguments: @staticmethod def mutate(root, info, id): - try: - instance = Region.objects.get( - created_by=info.context.user, - id=id - ) - except Region.DoesNotExist: + instance = Region.objects.filter( + id=id + ).first() + + error_data = [] + if instance is None: + error_data.append('Region does\'t exist') + elif instance.created_by != info.context.user: + error_data.append('Authorized User can only published the region') + if error_data: return PublishRegion(errors=[ dict( field='nonFieldErrors', - messages="Authorized User can only published the region" + messages=error_data ) ], ok=False) + instance.is_published = True instance.save(update_fields=['is_published']) return PublishRegion(result=instance, errors=None, ok=True) @@ -114,8 +136,8 @@ def mutate(root, info, id): class Mutation(): create_region = CreateRegion.Field() + update_region = UpdateRegion.Field() create_admin_level = CreateAdminLevel.Field() publish_region = PublishRegion.Field() - create_admin_level = CreateAdminLevel.Field() update_admin_level = UpdateAdminLevel.Field() delete_admin_level = DeleteAdminLevel.Field() diff --git a/apps/geo/schema.py b/apps/geo/schema.py index f002eb5de4..0055464d69 100644 --- a/apps/geo/schema.py +++ b/apps/geo/schema.py @@ -34,7 +34,7 @@ def get_geo_area_queryset_for_project_geo_area_type(queryset=None, defer_fields= return _queryset -class AdminLevelType(DjangoObjectType): +class AdminLevelType(DjangoObjectType, ClientIdMixin): class Meta: model = AdminLevel only_fields = ( diff --git a/apps/geo/serializers.py b/apps/geo/serializers.py index e52e3f306f..cf70db9571 100644 --- a/apps/geo/serializers.py +++ b/apps/geo/serializers.py @@ -3,7 +3,6 @@ from drf_dynamic_fields import DynamicFieldsMixin from deep.serializers import ( - ProjectPropertySerializerMixin, RemoveNullFieldsMixin, TempClientIdMixin, URLCachedFileField, @@ -161,15 +160,13 @@ class RegionGqSerializer(UserResourceSerializer, TempClientIdMixin): help_text="Project is only used while creating region" ) - client_id = serializers.CharField(required=False) - class Meta: model = Region fields = ['title', 'code', 'project', 'client_id'] def validate_project(self, project): if not project.can_modify(self.context['request'].user): - raise serializers.ValidationError('Permission Denied') + raise serializers.ValidationError('You Don\'t have permission in project') return project def validate(self, data): @@ -178,13 +175,13 @@ def validate(self, data): return data def create(self, validated_data): - project = validated_data.pop('project', None) + project = validated_data.pop('project') region = super().create(validated_data) project.regions.add(region) return region -class AdminLevelGqlSerializer(UserResourceSerializer): +class AdminLevelGqlSerializer(UserResourceSerializer, TempClientIdMixin): region = serializers.PrimaryKeyRelatedField(queryset=Region.objects.all()) parent_code_prop = serializers.CharField(required=False, allow_null=True, allow_blank=True) parent_name_prop = serializers.CharField(required=False, allow_null=True, allow_blank=True) @@ -201,20 +198,21 @@ class Meta: 'parent_code_prop', 'parent_name_prop', 'geo_shape_file', - 'geo_shape_file', - 'bounds_file', + 'client_id', ] def validate(self, data): region = data.get('region', (self.instance and self.instance.region)) - if not region.can_modify(self.context['request'].user): - raise serializers.ValidationError('You don\'t have the access to the region or region is published') + if region.created_by != self.context['request'].user: + raise serializers.ValidationError('You don\'t have the access to the region') + if region.is_published: + raise serializers.ValidationError('Published region can\'t be changed. Please contact Admin') return data def create(self, validated_data): admin_level = super().create(validated_data) - transaction.on_commit(lambda: load_geo_areas.delay(admin_level.region.id)) + transaction.on_commit(lambda: load_geo_areas.delay(admin_level.region_id)) return admin_level @@ -230,20 +228,3 @@ def update(self, instance, validated_data): transaction.on_commit(lambda: load_geo_areas.delay(region.id)) return admin_level - - -class PublishRegionGqSerializer(ProjectPropertySerializerMixin, UserResourceSerializer): - - class Meta: - model = Region - fields = ['is_published'] - - def validate(self, data): - if not self.instance.can_publish(self.context['request'].user): - raise serializers.ValidationError("Authorized User can only published the region") - return data - - def update(self, instance, validated_data): - instance.is_published = True - data = super().update(instance, validated_data) - return data diff --git a/apps/geo/tests/test_mutations.py b/apps/geo/tests/test_mutations.py index 533a216e9a..839f591e21 100644 --- a/apps/geo/tests/test_mutations.py +++ b/apps/geo/tests/test_mutations.py @@ -6,7 +6,7 @@ from geo.factories import RegionFactory, AdminLevelFactory -class CreateTestMutation(GraphQLTestCase): +class CreateAdminLevelTestMutation(GraphQLTestCase): def setUp(self): super().setUp() self.user = UserFactory.create() @@ -16,7 +16,7 @@ def setUp(self): ) def test_add_admin_level(self): - self.add_admin_level_query = ''' + mutation_query = ''' mutation MyMutation($input: AdminLevelInputType!){ createAdminLevel(data: $input) { @@ -40,12 +40,13 @@ def test_add_admin_level(self): def _query_check(minput, **kwargs): return self.query_check( - self.add_admin_level_query, + mutation_query, minput=minput, **kwargs ) + minput = { - 'region': self.region.id, + 'region': str(self.region.id), 'title': "Test-admin-level" } @@ -53,27 +54,27 @@ def _query_check(minput, **kwargs): _query_check(minput, assert_for_error=True) # with normal user - self.force_login(self.user) - content = _query_check(minput)['data']['createAdminLevel']['result'] - self.assertEqual(content['title'], minput['title'], content) - - # with other_user - self.force_login(self.other_user) - content = _query_check(minput, okay=False) - - # with login user # when region is published - self.region.is_published = True - self.region.save(update_fields=['is_published']) + region = RegionFactory.create( + created_by=self.user, + is_published=True + ) self.force_login(self.user) + minput['region'] = region.id _query_check(minput, okay=False) # with other user self.force_login(self.other_user) _query_check(minput, okay=False) + # with normal user + minput['region'] = self.region.id + self.force_login(self.user) + content = _query_check(minput)['data']['createAdminLevel']['result'] + self.assertEqual(content['title'], minput['title'], content) + def test_update_admin_level(self): - update_admin_level_query = ''' + mutation_query = ''' mutation MyMutation($adminLevelId:ID! $input:AdminLevelInputType!){ updateAdminLevel(id: $adminLevelId data:$input){ ok @@ -96,15 +97,14 @@ def test_update_admin_level(self): admin_level = AdminLevelFactory.create( region=self.region ) - minput = { "title": "Update Admin Level", - "region": self.region.id + "region": str(self.region.id) } def _query_check(minput, **kwargs): return self.query_check( - update_admin_level_query, + mutation_query, minput=minput, variables={'adminLevelId': admin_level.id}, **kwargs @@ -113,34 +113,52 @@ def _query_check(minput, **kwargs): # without login _query_check(minput, assert_for_error=True) - # with normal user - self.force_login(self.user) - content = _query_check(minput)['data']['updateAdminLevel']['result'] - self.assertEqual(content['title'], minput['title'], content) - admin_level.refresh_from_db() - - self.assertEqual(admin_level.title, minput['title']) - # login with other user self.force_login(self.other_user) minput['title'] = "Reupdated Admin Level" _query_check(minput, okay=False) self.assertNotEqual(admin_level.title, minput['title']) - # login with normal user - # when region is published - self.region.is_published = True - self.region.save(update_fields=['is_published']) + # login with normal user but region is published + + region = RegionFactory.create( + created_by=self.user, + is_published=True + ) + minput['region'] = str(region.id) self.force_login(self.user) _query_check(minput, okay=False) # login with other user # when region is published + minput['region'] = str(self.region.id) self.force_login(self.other_user) _query_check(minput, okay=False) + # with normal user + self.force_login(self.user) + content = _query_check(minput)['data']['updateAdminLevel']['result'] + self.assertEqual(content['title'], minput['title'], content) + admin_level.refresh_from_db() + self.assertEqual(admin_level.title, minput['title']) + + +class CreateRegionTestMutation(GraphQLTestCase): + + def setUp(self): + super().setUp() + self.non_project_member_user = UserFactory.create() + self.project_member_user = UserFactory.create() + self.region = RegionFactory.create( + created_by=self.project_member_user + ) + self.project = ProjectFactory.create( + created_by=self.project_member_user + ) + self.project.add_member(self.project_member_user) + def test_create_region_in_project(self): - self.region_query = ''' + mutation_query = ''' mutation MyMutation($input: RegionInputType!){ createRegion(data: $input) { ok @@ -152,39 +170,38 @@ def test_create_region_in_project(self): } } ''' - project_member_user = UserFactory.create() - non_project_member_user = UserFactory.create() - project = ProjectFactory.create( - created_by=project_member_user - ) - project.add_member(project_member_user) def _query_check(minput, **kwargs): return self.query_check( - self.region_query, + mutation_query, minput=minput, **kwargs ) minput = { - 'project': project.id, + 'project': str(self.project.id), 'code': 'NPL', 'title': 'Test' } # without login _query_check(minput, assert_for_error=True) - # with login with project_member_user - self.force_login(project_member_user) + # login with non_project_member_user + self.force_login(self.non_project_member_user) + content = _query_check(minput) + self.assertNotIn(self.non_project_member_user, self.project.members.all()) + self.assertEqual(content['data']['createRegion']['errors'][0]['messages'], "You Don't have permission in project") + # with login with project_member_user + self.force_login(self.project_member_user) content = _query_check(minput) - self.assertIn(project_member_user, project.members.all()) + self.assertIn(self.project_member_user, self.project.members.all()) self.assertEqual(content['data']['createRegion']['errors'], None) self.assertEqual(content['data']['createRegion']['result']['title'], "Test") # make sure region is attached with project self.assertIn( - Region.objects.get(id=content['data']['createRegion']['result']['id']), project.regions.all() + Region.objects.get(id=content['data']['createRegion']['result']['id']), self.project.regions.all() ) # make sure region is not pubhished @@ -192,14 +209,65 @@ def _query_check(minput, **kwargs): Region.objects.get(id=content['data']['createRegion']['result']['id']).is_published, False ) - # login with non_project_member_user - self.force_login(non_project_member_user) - content = _query_check(minput) - self.assertNotIn(non_project_member_user, project.members.all()) - self.assertEqual(content['data']['createRegion']['errors'][0]['messages'], "Permission Denied") + def test_update_region_mutation(self): + mutation_query = ''' + mutation MyMutation($id: ID! $input:RegionInputType!) { + updateRegion(id: $id data:$input) { + ok + errors + result { + centroid + clientId + id + isPublished + keyFigures + mediaSources + populationData + public + regionalGroups + title + } + } + } + ''' + + def _query_check(minput, **kwargs): + return self.query_check( + mutation_query, + minput=minput, + **kwargs + ) + + minput = { + 'project': self.project.id, + "title": "Updated Region", + "code": "123", + } + + # without login + _query_check(minput, variables={'id': self.region.id}, assert_for_error=True) + + # with non project memeber + self.force_login(self.non_project_member_user) + content = _query_check(minput, variables={'id': self.region.id}, okay=False) + + # with login with user and region is published + + region = RegionFactory.create( + created_by=self.project_member_user, + is_published=True + ) + + self.force_login(self.project_member_user) + content = _query_check(minput, variables={'id': region.id}, okay=False) + + # with normal user + self.force_login(self.project_member_user) + content = _query_check(minput, variables={'id': self.region.id})['data']['updateRegion']['result'] + self.assertEqual(content['title'], minput['title'], content) def test_publish_region(self): - self.publish_region_query = ''' + mutation_query = ''' mutation MyMutation($id:ID!){ publishRegion(id: $id) { ok @@ -207,33 +275,28 @@ def test_publish_region(self): } } ''' - user = UserFactory.create() - other_user = UserFactory.create() - project = ProjectFactory.create() - region = RegionFactory.create(created_by=user) - project.add_member(user) def _query_check(**kwargs): return self.query_check( - self.publish_region_query, - variables={'id': region.id}, + mutation_query, + variables={'id': self.region.id}, **kwargs ) # without login user _query_check(assert_for_error=True) - # login with user - self.force_login(user) - content = _query_check() - region.refresh_from_db() - self.assertEqual(content['data']['publishRegion']['errors'], None) - self.assertEqual(region.is_published, True) - # login with other user - self.force_login(other_user) + self.force_login(self.non_project_member_user) content = _query_check() self.assertEqual( content['data']['publishRegion']['errors'][0]['messages'], - 'Authorized User can only published the region' + ['Authorized User can only published the region'] ) + + # login with user + self.force_login(self.project_member_user) + content = _query_check() + self.region.refresh_from_db() + self.assertEqual(content['data']['publishRegion']['errors'], None) + self.assertEqual(self.region.is_published, True) diff --git a/schema.graphql b/schema.graphql index 9df5a39932..b69efbdeba 100644 --- a/schema.graphql +++ b/schema.graphql @@ -34,7 +34,7 @@ input AdminLevelInputType { parentCodeProp: String parentNameProp: String geoShapeFile: ID - boundsFile: Upload + clientId: String } type AdminLevelType { @@ -48,6 +48,7 @@ type AdminLevelType { geoShapeFile: GalleryFileType tolerance: Float! staleGeoAreas: Boolean! + clientId: ID! parent: ID geojsonFile: FileFieldType boundsFile: FileFieldType @@ -5072,8 +5073,9 @@ type MissingPredictionReviewType { type Mutation { createRegion(data: RegionInputType!): CreateRegion - publishRegion(id: ID!): PublishRegion + updateRegion(data: RegionInputType!, id: ID!): UpdateRegion createAdminLevel(data: AdminLevelInputType!): CreateAdminLevel + publishRegion(id: ID!): PublishRegion updateAdminLevel(data: AdminLevelInputType!, id: ID!): UpdateAdminLevel deleteAdminLevel(adminLevelId: ID!): DeleteAdminLevel organizationCreate(data: OrganizationInputType!): OrganizationCreate @@ -6455,6 +6457,12 @@ type UpdateProjectVizConfiguration { result: ProjectVizDataType } +type UpdateRegion { + errors: [GenericScalar!] + ok: Boolean + result: RegionDetailType +} + type UpdateUnifiedConnector { errors: [GenericScalar!] ok: Boolean