def test_project_role_and_above(): assert ProjectRole.and_above(ProjectRole.AUTHOR) == [ ProjectRole.AUTHOR, ProjectRole.MANAGER, ProjectRole.OWNER, ] assert ProjectRole.and_above(ProjectRole.MANAGER) == [ ProjectRole.MANAGER, ProjectRole.OWNER, ] assert ProjectRole.and_above(ProjectRole.OWNER) == [ ProjectRole.OWNER, ]
def create(self, request: Request) -> Response: """ Create a node. Receives a request with the `node` (as JSON) and possibly other information e.g. the `project` the node is associated with, the `app` it was Returns the URL of the node. """ serializer = NodeCreateRequest(data=request.data) serializer.is_valid(raise_exception=True) project = serializer.validated_data.get("project") app = serializer.validated_data.get("app", "api") host = serializer.validated_data.get("host") node = serializer.validated_data.get("node") # Check that the user has EDIT permissions for the project, # if provided. if project: try: get_projects(request.user).get( id=project.id, role__in=[ role.name for role in ProjectRole.and_above(ProjectRole.AUTHOR) ], ) except Project.DoesNotExist: raise PermissionDenied # Create the node try: node = Node.objects.create(creator=request.user, project=project, app=app, host=host, json=node) except IntegrityError: raise ValidationError( dict(key="Attempting to create a duplicate key")) serializer = NodeCreateResponse(node, context={"request": request}) return Response( serializer.data, status=status.HTTP_201_CREATED, # Most of the time this action will requested with `Accept: application/json`. # However, in case it is not, `template_name` is required. template_name="projects/nodes/retrieve.html", )
def get_project(self) -> Project: """ Get the project, checking that user has necessary role for the current action. Allow everyone to list and retrieve reviews. Only allows EDITOR and above to create or update review objects. """ if not hasattr(self, "project"): self.project = get_project( self.kwargs, self.request.user, ProjectRole.and_above(ProjectRole.EDITOR) if self.action in ["create", "partial_update", "extract"] else None, ) return self.project
def get_queryset(self): """ Get a list of reviews for a project. Only list reviews that are EXTRACTED (ie. complete) unless the user is a project EDITOR or above. """ project = self.get_project() return (Review.objects.filter( Q(status=ReviewStatus.EXTRACTED.name) if project.role not in [ role.name for role in ProjectRole.and_above(ProjectRole.EDITOR) ] else Q(), project=project, ).order_by("-created").select_related( "reviewer", "reviewer__personal_account", "review").prefetch_related("review__dois"))
def get_response_context(self, *args, **kwargs): """ Get the context for rendering templates for this view set. """ project = self.get_project() instance = kwargs.get("instance") is_editor = (project.role in [ role.name for role in ProjectRole.and_above(ProjectRole.EDITOR) ] if instance else None) is_reviewer = ((self.request.user == instance.reviewer or self.request.GET.get("key") == instance.key) if instance else None) return super().get_response_context(*args, **kwargs, account=project.account, project=project, is_editor=is_editor, is_reviewer=is_reviewer)
def get_queryset(self): """ Get the set of projects that the user has access to and which meet filter criteria. Does not return temporary projects. TODO: Currently this ignores an authenticated user's access to projects inherited from membership of a team. """ queryset = get_projects(self.request.user).select_related("account") account = self.request.GET.get("account") if account: queryset = queryset.filter(account_id=account) role = self.request.GET.get("role") if self.request.user.is_authenticated and role: roles = re.split(r"\s*,\s*", role) q = Q() for part in roles: match = re.match(r"([a-zA-Z]+)(\+)?", part) if match: role_name, and_above = match.groups() if role_name.lower() == "member": q |= Q(role__isnull=False) else: try: project_role = ProjectRole.from_string(role_name) except ValueError as exc: raise exceptions.ValidationError({"role": str(exc)}) else: if and_above: q |= Q( role__in=[ role.name for role in ProjectRole.and_above(project_role) ] ) else: q |= Q(role=project_role.name) else: raise exceptions.ValidationError( {"role": "Invalid role specification {}".format(part)} ) queryset = queryset.filter(q) public = self.request.GET.get("public") if public: if public.lower() in ["false", "no", "0"]: queryset = queryset.filter(public=False) else: queryset = queryset.filter(public=True) source = self.request.GET.get("source") if source: try: query = Source.query_from_address(source, prefix="sources") except ValueError as exc: raise exceptions.ValidationError({"source": str(exc)}) else: queryset = queryset.filter(query) search = self.request.GET.get("search") if search: queryset = queryset.filter( Q(name__icontains=search) | Q(title__icontains=search) | Q(description__icontains=search) ) # Ordering favoring those that the user has a role # on, has an image set, has a description set, etc return ( queryset.filter(temporary=False) .annotate( role_rank=Case( When(role=ProjectRole.OWNER.name, then=Value(6)), When(role=ProjectRole.MANAGER.name, then=Value(5)), When(role=ProjectRole.AUTHOR.name, then=Value(4)), When(role=ProjectRole.EDITOR.name, then=Value(3)), When(role=ProjectRole.REVIEWER.name, then=Value(2)), When(role=ProjectRole.READER.name, then=Value(1)), When(role__isnull=True, then=Value(0)), output_field=IntegerField(), ) if self.request.user.is_authenticated else Value(0, output_field=IntegerField()), has_image=Case( When(image_file__isnull=False, then=Value(True)), default=Value(False), output_field=BooleanField(), ), # Use regex filter here to exclude nulls, blanks and very short strings has_title=Case( When(title__regex=r"^.{1,}$", then=Value(True)), default=Value(False), output_field=BooleanField(), ), has_description=Case( When(description__regex=r"^.{1,}$", then=Value(True)), default=Value(False), output_field=BooleanField(), ), ) .order_by( "-featured", "-has_image", "-has_title", "-has_description", "-role_rank", "-created", ) )
def update( self, status: str, response_message: Optional[str] = None, cancel_message: Optional[str] = None, user: Optional[User] = None, filters: Dict = {}, ): """ Update the status of a review. Checks that the status update makes logical sense and records the message and user (if any). Note that a status update to `ACCEPTED`, `DECLINED` or `COMPLETED` can be made by an anonymous users (a reviewer who has the review key but is not an authenticated user). """ if ( status == ReviewStatus.CANCELLED.name and self.status == ReviewStatus.REQUESTED.name ): self.cancel_message = cancel_message or None elif ( status == ReviewStatus.ACCEPTED.name and self.status == ReviewStatus.REQUESTED.name ): self.reviewer = user self.response_message = response_message or None # Add user as a REVIEWER to the project (if necessary) try: agent = ProjectAgent.objects.get(project_id=self.project, user=user) except ProjectAgent.DoesNotExist: ProjectAgent.objects.create( project_id=self.project, user=user, role=ProjectRole.REVIEWER.name, ) else: if agent.role not in ProjectRole.and_above(ProjectRole.REVIEWER): agent.role = ProjectRole.REVIEWER.name agent.save() elif ( status == ReviewStatus.DECLINED.name and self.status == ReviewStatus.REQUESTED.name ): self.reviewer = user self.response_message = response_message or None elif status == ReviewStatus.COMPLETED.name and self.status in ( ReviewStatus.PENDING.name, ReviewStatus.ACCEPTED.name, ReviewStatus.FAILED.name, ): return self.extract(user, filters) elif ( status == ReviewStatus.REGISTERED.name and self.status == ReviewStatus.EXTRACTED.name ): return self.register(user) else: raise ValueError( f"Review can not be updated from {self.status} to {status}." ) self.status = status self.save()
def test_project_role_from_string(): assert ProjectRole.from_string("author") == ProjectRole.AUTHOR assert ProjectRole.from_string("AUTHOR") == ProjectRole.AUTHOR assert ProjectRole.from_string("AutHOr") == ProjectRole.AUTHOR with pytest.raises(ValueError, match='No project role matching "foo"'): ProjectRole.from_string("foo")