Skip to content

Commit

Permalink
moderation: added request type and service
Browse files Browse the repository at this point in the history
  • Loading branch information
alejandromumo committed Jul 20, 2023
1 parent 7908655 commit c22ab3d
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 0 deletions.
11 changes: 11 additions & 0 deletions invenio_requests/customizations/user_moderation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2023 CERN.
#
# Invenio-Requests is free software; you can redistribute it and/or modify
# it under the terms of the MIT License; see LICENSE file for more details.
"""User moderation request type."""

from .user_moderation import UserModeration

__all__ = ("UserModeration",)
56 changes: 56 additions & 0 deletions invenio_requests/customizations/user_moderation/user_moderation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2023 CERN.
#
# Invenio-Requests is free software; you can redistribute it and/or modify
# it under the terms of the MIT License; see LICENSE file for more details.
"""User moderation requests."""

from invenio_i18n import lazy_gettext as _

from invenio_requests.customizations import RequestType, actions


class BlockUserAction(actions.DeclineAction):
"""Represents a decline action used to block an user."""

def execute(self, identity, uow):
"""Executes block action."""
# TODO add specific user block actions
super().execute(identity, uow)


class ApproveUserAction(actions.AcceptAction):
"""Represents an accept action used to aprove an user."""

def execute(self, identity, uow):
"""Executes aprove action."""
# TODO add specific user block actions
super().execute(identity, uow)


class UserModeration(RequestType):
"""Request to moderate an user."""

type_id = "user-moderation"
name = _("User moderation")

creator_can_be_none = False
topic_can_be_none = False
allowed_creator_ref_types = ["user"]
allowed_receiver_ref_types = ["user"]
allowed_topic_ref_types = ["user"]

# TODO missing permissions
# AdminNeed = RoleNeed("admin")
# needs_context = {"user.role": AdminNeed, "user.role": AdminNeed}

available_actions = {
"delete": actions.DeleteAction,
"submit": actions.SubmitAction,
"create": actions.CreateAction,
"cancel": actions.CancelAction,
# Custom implemented actions
"accept": ApproveUserAction,
"decline": BlockUserAction,
}
4 changes: 4 additions & 0 deletions invenio_requests/ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
RequestEventsServiceConfig,
RequestsService,
RequestsServiceConfig,
UserModerationRequestService,
)


Expand Down Expand Up @@ -73,6 +74,9 @@ def init_services(self, app):
self.request_events_service = RequestEventsService(
config=service_configs.request_events,
)
self.user_moderation_requests_service = UserModerationRequestService(
self.requests_service
)

def init_resources(self):
"""Init resources."""
Expand Down
5 changes: 5 additions & 0 deletions invenio_requests/proxies.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@
lambda: current_app.extensions["invenio-requests"].requests_resource
)
"""Proxy to the instantiated requests resource."""

current_user_moderation_service = LocalProxy(
lambda: current_app.extensions["invenio-requests"].user_moderation_requests_service
)
"""Proxy to the instantiated user moderation requests service."""
2 changes: 2 additions & 0 deletions invenio_requests/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@

from .events import RequestEventsService, RequestEventsServiceConfig
from .requests import RequestsService, RequestsServiceConfig
from .user_moderation import UserModerationRequestService

__all__ = (
"RequestEventsService",
"RequestEventsServiceConfig",
"RequestsService",
"RequestsServiceConfig",
"UserModerationRequestService",
)
1 change: 1 addition & 0 deletions invenio_requests/services/requests/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ def search_user_requests(
should=[
dsl.Q("term", **{"receiver.user": identity.id}),
dsl.Q("term", **{"created_by.user": identity.id}),
dsl.Q("term", **{"topic.user": identity.id}),
],
must=[~dsl.Q("term", **{"status": "created"})],
minimum_should_match=1,
Expand Down
11 changes: 11 additions & 0 deletions invenio_requests/services/user_moderation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2023 CERN.
#
# Invenio-Requests is free software; you can redistribute it and/or modify
# it under the terms of the MIT License; see LICENSE file for more details.
"""User moderation requests service."""

from .service import UserModerationRequestService

__all__ = ("UserModerationRequestService",)
14 changes: 14 additions & 0 deletions invenio_requests/services/user_moderation/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2023 CERN.
#
# Invenio-Requests is free software; you can redistribute it and/or modify
# it under the terms of the MIT License; see LICENSE file for more details.
"""User moderation requests service errors."""
from invenio_i18n import lazy_gettext as _


class InvalidCreator(Exception):
"""Reauest creator is invalid."""

description = _("Invalid creator for user moderation request.")
78 changes: 78 additions & 0 deletions invenio_requests/services/user_moderation/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2023 CERN.
#
# Invenio-Requests is free software; you can redistribute it and/or modify
# it under the terms of the MIT License; see LICENSE file for more details.
"""User moderation service."""

from invenio_access.permissions import system_identity, system_user_id
from invenio_i18n import gettext as _

from invenio_requests.customizations.user_moderation.user_moderation import (
UserModeration,
)
from invenio_requests.resolvers.registry import ResolverRegistry
from invenio_requests.services.user_moderation.errors import InvalidCreator


class UserModerationRequestService:
"""Service for User Moderation requests."""

def __init__(self, requests_service):
"""Service initialisation as a sub-service of requests."""
self.requests_service = requests_service

@property
def request_type_cls(self):
"""User moderation request type."""
return UserModeration

# /users/moderation action="block"

# service.moderate(identity, user, action)
# get user request
# request_service.execute_action(identity, action=action)

def request_moderation(
self, identity, creator, topic, data=None, uow=None, **kwargs
):
"""Creates a UserModeration request and submits it."""
if creator != system_user_id:
raise InvalidCreator("Moderation request creator can only be system.")

data = data or {}

# For user moderation, topic is the user to be moderated
topic = ResolverRegistry.resolve_entity_proxy({"user": topic}).resolve()

receiver = {"user": system_user_id}

creator = {"user": creator}

request_item = self.requests_service.create(
identity,
data,
self.request_type_cls,
receiver,
creator,
topic=topic,
)

return self.requests_service.execute_action(
identity=identity,
id_=request_item.id,
action="submit",
data=data,
)

def search_moderation_requests(self, identity, params=None):
"""Searchs for user moderation requests.
Returns only requests that concern the current user.
"""
params = params or {}

# Search for UserModeration requests only
q = f"type:{self.request_type_cls.type_id}"
return self.requests_service.search_user_requests(identity, q=q, params=params)
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ invenio_i18n.translations =
messages = invenio_requests
invenio_assets.webpack =
invenio_requests = invenio_requests.webpack:requests
invenio_requests.types =
users_moderation = invenio_requests.customizations.user_moderation:UserModeration

[build_sphinx]
source-dir = docs/
Expand Down
71 changes: 71 additions & 0 deletions tests/services/user_moderation/test_user_moderation_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2023 CERN.
#
# Invenio-Requests is free software; you can redistribute it and/or modify
# it under the terms of the MIT License; see LICENSE file for more details.
"""Test user moderation service."""

import pytest
from flask_principal import Need
from invenio_access.permissions import system_identity, system_user_id
from invenio_access.utils import get_identity

from invenio_requests.proxies import current_user_moderation_service
from invenio_requests.services.user_moderation.errors import InvalidCreator


def test_request_moderation(app, users, identity_simple):
"""Tests the service for request moderation."""

user = users[0]

service = current_user_moderation_service

# Use system identity
request_item = service.request_moderation(
system_identity, creator=system_user_id, topic=user.id
)
assert request_item
# Request moderation creates and submits the moderation request
assert request_item._request.status == "submitted"

# User identity is not allowed to submit moderation requests
with pytest.raises(Exception):
service.request_moderation(
identity_simple, creator=system_user_id, topic=user.id
)

# Only system user id can be the creator
with pytest.raises(InvalidCreator):
service.request_moderation(system_identity, creator=user.id, topic=user.id)


def test_search_moderation(app, users, submit_request):
"""Tests the search for request moderation."""

user = users[1]

service = current_user_moderation_service

request_item = service.request_moderation(
system_identity, creator=system_user_id, topic=user.id
)
assert request_item

# Retrieve user requests (user has to be authenticated user)
user_identity = get_identity(user)
user_identity.provides.add(Need(method="system_role", value="authenticated_user"))

# Create a generic request to test the user moderation search filter
request = submit_request(user_identity, receiver=users[2])
assert request

# Should only return one request (user moderation)
search = service.search_moderation_requests(user_identity)
assert search.total == 1

hits = search.to_dict()["hits"]["hits"]
hit = hits[0]
assert hit["topic"]["user"] == str(user.id)
assert hit["type"] == service.request_type_cls.type_id

0 comments on commit c22ab3d

Please sign in to comment.