def create_role_without_change_related_project_permission(self): # create role 'can not change related project' which can read and change projects and contacts but can not # change the related project of the contact object role = Role.objects.create(name='CannotChangeRelatedProject') permissions = [] # get view_project permission view_project_permission = Permission.objects.filter( codename='view_project', content_type=Project.get_content_type()).first() permissions.append(view_project_permission) # get change_project permission edit_project_permission = Permission.objects.filter( codename='change_project', content_type=Project.get_content_type()).first() permissions.append(edit_project_permission) # get view_contact permission view_contact_permission = Permission.objects.filter( codename='view_contact', content_type=Contact.get_content_type()).first() permissions.append(view_contact_permission) # get change_contact permission edit_contact_permission = Permission.objects.filter( codename='change_contact', content_type=Contact.get_content_type()).first() permissions.append(edit_contact_permission) for p in permissions: # and assign it to this role RolePermissionAssignment.objects.create(role=role, permission=p) return role
def test_favourites_can_not_be_created_if_not_viewable_by_user(self): """ Tries to create favourites for a object that is not viewable by the current user (which should fail) :return: """ # verify that user2 has no access to project2 response = self.rest_get_project(self.token2, self.project2.pk, HTTP_USER_AGENT, REMOTE_ADDR) self.assertEquals(response.status_code, status.HTTP_404_NOT_FOUND) # verify that user2 can not favourite project2 (should not have access to project2 -> 403) response = self.rest_create_favourite(self.token2, self.project2.pk, Project.get_content_type().pk) self.assertEquals(response.status_code, status.HTTP_403_FORBIDDEN) # verify that user1 has access to project2 response = self.rest_get_project(self.token1, self.project2.pk, HTTP_USER_AGENT, REMOTE_ADDR) self.assertEquals(response.status_code, status.HTTP_200_OK) # verify that user1 can favourite project2 (should have access to project2 -> 201) response = self.rest_create_project_favourite(self.token1, self.project2.pk) self.assertEquals(response.status_code, status.HTTP_201_CREATED) # finally, there should have been one favourite created self.assertEquals(Favourite.objects.all().count(), 1, msg="There should be one favourite")
def create_notification_based_on_delete_user_from_project( sender, instance, *args, **kwargs): """Notifies the attended user that he was removed from the project""" if get_current_user().is_anonymous: return assigned_user = instance.user # check that the role of this instance has the view_project role if not instance.role.permissions.filter( codename='view_project', content_type=Project.get_content_type()).exists(): # this role does not have view_project, so we are not sending a notification return if assigned_user != get_current_user(): project = instance.project # render html message context = {'user': assigned_user, 'instance': project} html_message = render_to_string( 'notification/project_remove_user.html', context) Notification.objects.create( user=assigned_user, title=_("You have been removed from project {project}").format( project=project.name), message=html_message, content_type=project.get_content_type(), object_id=project.pk, notification_type=NotificationConfiguration. NOTIFICATION_CONF_PROJECT_USER_CHANGED)
def test_delete_favourites(self): """ tries to delete a favourite """ # verify that user1 has access to project1 response = self.rest_get_project(self.token1, self.project1.pk, HTTP_USER_AGENT, REMOTE_ADDR) self.assertEquals(response.status_code, status.HTTP_200_OK) # mark project1 as favourite of user1 favourite_response = self.rest_create_project_favourite( self.token1, self.project1.pk) # try to get the new favourite with student1 response = self.rest_get_favourite(self.token1, favourite_response.data['pk']) self.assertEquals(response.status_code, status.HTTP_200_OK) # delete favourite with student 1 (allowed) response = self.rest_delete_favourite(self.token1, self.project1.pk, Project.get_content_type()) self.assertEquals(response.status_code, status.HTTP_204_NO_CONTENT) # try to get the just deleted favourite with student1 (does not work obviously) response = self.rest_get_favourite(self.token1, favourite_response.data['pk']) self.assertEquals(response.status_code, status.HTTP_404_NOT_FOUND) # mark project1 as favourite for user1 favourite_response = self.rest_create_project_favourite( self.token1, self.project1.pk) # try to get favourite with student 2 (doesn't work) response = self.rest_get_favourite(self.token2, favourite_response.data['pk']) self.assertEquals(response.status_code, status.HTTP_404_NOT_FOUND) # delete favourite with student 2 (not allowed) response = self.rest_delete_favourite(self.token2, self.project1.pk, Project.get_content_type()) self.assertEquals(response.status_code, status.HTTP_404_NOT_FOUND) # verify the favourite still exists response = self.rest_get_favourite(self.token1, favourite_response.data['pk']) self.assertEquals(response.status_code, status.HTTP_200_OK)
def send_new_comment_notification_to_project_members(sender, instance, created, **kwargs): """ When a new comment is created, send a notification to all other project members """ # check if the left content type is a comment and the right content type is a project if instance.left_content_type != Comment.get_content_type() or \ instance.right_content_type != Project.get_content_type(): return # ignore raw inserts (e.g. from fixtures) and updates (not created) if kwargs.get('raw') or not created: return comment = instance.left_content_object project = instance.right_content_object project_assignments = ProjectRoleUserAssignment.objects.filter( project__pk=project.pk) # send notifications for all assignments for assignment in project_assignments: # If the user of the assignment is the comment creator then skip this iteration if assignment.user.pk == comment.created_by.pk: continue message = render_to_string('notification/project_comment.html', { 'comment': comment, 'project': project }) Notification.objects.create( user=assignment.user, title=_(f"A new comment has been posted for {project.name}"), message=message, content_type=Project.get_content_type(), object_id=project.pk, notification_type=NotificationConfiguration. NOTIFICATION_CONF_PROJECT_COMMENT, )
def duplicate(self, request, format=None, *args, **kwargs): """ Duplicates the project with all its sub-projects. The duplicated instance will not have a parent project. """ project_object = Project.objects.viewable().get(pk=kwargs['pk']) original_project_pk = project_object.pk duplicate_metadata = request.data.get('duplicate_metadata', False) # duplicates the project # change name to "Copy of" + project name # the duplicated project can not be a sub project so set parent_project_id to NONE if it was set duplicated_project = project_object.duplicate( name=_("Copy of") + f" {project_object.name}", parent_project_id=None, metadata=project_object.metadata.all() if duplicate_metadata else None, ) dict_duplicated_project_pk = dict() dict_duplicated_project_pk[original_project_pk] = duplicated_project.pk # duplicate all tasks assigned to the project from eric.shared_elements.models import Task tasks = Task.objects.viewable().filter( projects__in=[original_project_pk]) if tasks: for task in tasks: duplicated_task = task.duplicate(projects=[duplicated_project]) if duplicate_metadata: duplicated_task.metadata.set(task.metadata.all()) # duplicate sub projects Project.duplicate_sub_projects(dict_duplicated_project_pk, duplicate_metadata) serializer = self.get_serializer(duplicated_project) return Response(serializer.data)
def test_create_favourite_and_verify_response(self): """ Tries to create a project favourite and verifies the response-details """ self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token1) # store the favourite length initial_favourite_length = Favourite.objects.all().count() content_type = Project.get_content_type() # create favourite via REST API response = self.rest_create_project_favourite(self.token1, self.project1.pk) # favourite should have been created, HTTP response code should be 201 self.assertEqual(response.status_code, status.HTTP_201_CREATED) # decode response and load it into json content = response.content.decode() decoded_response = json.loads(content) self.assertTrue('"pk":' in content, msg="primary key (pk) in response") self.assertTrue(f'"content_type":{content_type.pk}' in content, msg="correct content_type in response") self.assertTrue(f'"object_id":"{self.project1.pk}"' in content, msg="correct object_id in response") # see what the actual Favourite element from database looks like pk = decoded_response['pk'] # get the favourite object from database favourite = Favourite.objects.get(pk=pk) # check if the favourite object was created self.assertEqual(Favourite.objects.all().count(), initial_favourite_length + 1, msg="check if the favourite was created") # verify several favourite attributes with api response self.assertEqual(str(favourite.pk), decoded_response['pk']) self.assertEqual(str(favourite.object_id), decoded_response['object_id']) # check if the correct content_type, object_id were saved self.assertEqual(favourite.content_type, content_type, msg="check if correct content_type was saved") self.assertEqual(favourite.object_id, self.project1.pk, msg="check if correct object_id was saved") return response
def create_notification_based_on_add_user_to_project(sender, instance, created, *args, **kwargs): """Notifies the assigned user that he was added to the project or the role changed""" if get_current_user().is_anonymous: return assigned_user = instance.user if assigned_user != get_current_user(): project = instance.project # define attributes for the html message context = { 'user': assigned_user, 'instance': project, 'new_role': instance.role.name } if created: # user was added to the project # verify that the user role has access to the project if not instance.role.permissions.filter( codename='view_project', content_type=Project.get_content_type()).exists(): # this role does not have view_project, so we are not sending a notification return html_message = render_to_string( 'notification/project_add_user.html', context) title = _("You have been added to project {project}").format( project=project.name) else: # user role has changed html_message = render_to_string( 'notification/project_change_user_role.html', context) title = _( "Your role in project {project} has been changed to {role}". format(role=instance.role.name, project=project.name)) Notification.objects.create( user=assigned_user, title=title, message=html_message, content_type=project.get_content_type(), object_id=project.pk, notification_type=NotificationConfiguration. NOTIFICATION_CONF_PROJECT_USER_CHANGED)
def create_student_role(self): # create role 'Student' which has read access to projects role = Role.objects.create(name='Student') # get view_project permission view_project_permission = Permission.objects.filter( codename='view_project', content_type=Project.get_content_type()).first() # and assign it to this role RolePermissionAssignment.objects.create( role=role, permission=view_project_permission) # get view_resource permission view_resource_permission = Permission.objects.filter( codename='view_resource', content_type=Resource.get_content_type()).first() # and assign it to this role RolePermissionAssignment.objects.create( role=role, permission=view_resource_permission) return role
def get_all_project_ids_with_permission(entity, permission, cache_id=None): """ Generic method that gets all projects (and respective sub projects) where the current user has a role with permission 'permission' :param entity: the entity to use for the lookup :type entity: eric.core.models.base.BaseModel :param permission: the permission to look up (e.g., view_project) :type permission: basestring :param cache_id: a unique id to make sure the request is not cached by @cache_for_request, not used in the function per se :type cache_id: basestring :return: a list of project primary keys :rtype: list """ from eric.projects.models import Project, ProjectRoleUserAssignment user = get_current_user() if not user.pk: # handle anonymous user return list() # if the user has the global permission "view_project", they can view all projects anyway if user.has_perm('projects.{}'.format(permission)): # return codeword "all" return list(["all"], ) else: # access ProjectRoleUserAssignment and get all projects where user has the permission projects_ids = ProjectRoleUserAssignment.objects.filter( user=user, role__permissions__content_type=entity.get_content_type(), role__permissions__codename=permission).distinct().values_list( 'project__id', flat=True) pk_list_with_subprojects = Project.get_all_projects_with_descendants( projects_ids) return pk_list_with_subprojects
def create_notification_based_on_project_changes(sender, instance, *args, **kwargs): """Notifies the user that something has changed in the project""" if get_current_user().is_anonymous: return assigned_users = instance.assigned_users_roles.filter( role__permissions__codename='view_project', role__permissions__content_type=Project.get_content_type()).exclude( user=get_current_user()) for assigned_user in assigned_users: context = {'user': assigned_user.user, 'instance': instance} html_message = render_to_string('notification/project_changed.html', context) Notification.objects.create( user=assigned_user.user, title=_("Project {name} has changed".format(name=instance.name)), message=html_message, content_type=instance.get_content_type(), object_id=instance.pk, notification_type=NotificationConfiguration. NOTIFICATION_CONF_PROJECT_CHANGED)
def validate_create_project(self, token, project_name='Test Project', project_description="Test Project Description", HTTP_USER_AGENT="APITestClient", REMOTE_ADDR="127.0.0.1"): """ Tries to create a project with the provided credentials Checks if the project has been created Checks if the user gets the permission "view_project" for the new project """ # store the project length initial_project_length = Project.objects.all().count() project = self.create_project(token, project_name, project_description, Project.INITIALIZED, HTTP_USER_AGENT, REMOTE_ADDR) self.assertEquals(project.name, project_name) self.assertEquals(project.description, project_description) self.assertEqual( Project.objects.all().count(), initial_project_length + 1, msg="Validate that a new project has been created in the database") # check that this user has the default role (e.g., project manager) has_default_role = ProjectRoleUserAssignment.objects.filter( project=project, role__default_role_on_project_create=True) self.assertEqual( has_default_role.count(), 1, msg="Validate that the user has the default role on project create" ) current_role = has_default_role.first().role # check that this user can view the project (has a role with permission 'view_project') role_permissions = current_role.permissions.all() self.assertTrue( role_permissions.filter(content_type=Project.get_content_type(), codename='view_project').exists(), msg= "Validate that the user has the view_project role for the newly created project" ) # try to get a list of projects from the API response = self.client.get('/api/projects/', HTTP_USER_AGENT=HTTP_USER_AGENT, REMOTE_ADDR=REMOTE_ADDR) self.assertEqual(response.status_code, status.HTTP_200_OK) api_data = json.loads(response.content.decode()) decoded = api_data['results'] for item in decoded: self.assertTrue( 'created_by' in item, msg= "projects endpoint returned a json object with created_by in the project" ) self.assertTrue( 'name' in item, msg= "projects endpoint returned a json object with name in the project" ) self.assertTrue( 'project_state' in item, msg= "projects endpoint returned a json object with project_state in the project" ) self.assertTrue( 'description' in item, msg= "projects endpoint returned a json object with description in the project" ) return project
def get_relations(self, filter_by_pk=None): # get all relations and prefetch the changesets relations = Relation.objects.viewable().for_model( self.__class__, self.pk).prefetch_related('created_by', 'created_by__userprofile', 'last_modified_by', 'last_modified_by__userprofile') if filter_by_pk: relations = relations.filter(pk__in=filter_by_pk) workbench_models = get_all_workbench_models_with_args( WorkbenchEntityMixin) pks_by_ct = {} model_by_ct = {} prefetched_objects_by_ct = {} for param, model_details in workbench_models.items(): ct = model_details['content_type'].id pks_by_ct[ct] = [] model_by_ct[ct] = model_details['entity'] from eric.projects.models import Project # add projects to the list project_content_type_id = Project.get_content_type().id pks_by_ct[project_content_type_id] = [] model_by_ct[project_content_type_id] = Project # collect all PKs of related objects for relation in relations: assert relation.left_content_type_id in pks_by_ct, "Left Content Type ID is not valid" assert relation.right_content_type_id in pks_by_ct, "Right Content Type ID is not valid" pks_by_ct[relation.left_content_type_id].append( relation.left_object_id) pks_by_ct[relation.right_content_type_id].append( relation.right_object_id) # prefetch all models for ct in pks_by_ct: pks = pks_by_ct[ct] if len(pks) > 0: prefetched_objects_by_ct[ct] = model_by_ct[ ct].objects.related_viewable().filter( pk__in=pks).prefetch_common() # prefetch projects (for most relations) if ct != project_content_type_id: prefetched_objects_by_ct[ct] = prefetched_objects_by_ct[ ct].prefetch_related('projects') # retrieve the prefetched objects (via in_bulk) prefetched_objects_by_ct[ct] = prefetched_objects_by_ct[ ct].in_bulk() # iterate over all relations and fill in the prefetched elements in left_content_object/right_content_object for relation in relations: # prefetch the left content object relation.left_content_object = prefetched_objects_by_ct[ relation.left_content_type_id].get( relation.left_object_id, None # get the object, or None if not available ) # prefetch the right content object relation.right_content_object = prefetched_objects_by_ct[ relation.right_content_type_id].get( relation.right_object_id, None # get the object, or None if not available ) return relations
def rest_create_project_favourite( self, auth_token, project_id, HTTP_USER_AGENT=HTTP_USER_AGENT, REMOTE_ADDR=REMOTE_ADDR ): content_type = Project.get_content_type() return self.rest_create_favourite(auth_token, project_id, content_type.pk, HTTP_USER_AGENT, REMOTE_ADDR)