1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-07 14:25:37 +00:00

Allow editors to subscribe to a form view

This commit is contained in:
Bram Wiepjes 2025-01-21 21:20:44 +00:00
parent 731c1c70d5
commit a9527603e9
11 changed files with 151 additions and 11 deletions
backend/src/baserow/contrib/database
changelog/entries/unreleased/bug
enterprise/backend
src/baserow_enterprise/role
tests/baserow_enterprise_tests/role
premium/backend/src/baserow_premium
web-frontend/modules/database/components/view/form

View file

@ -686,6 +686,7 @@ class DatabaseConfig(AppConfig):
object_scope_type_registry.register(TokenObjectScopeType())
from baserow.contrib.database.views.operations import (
CanReceiveNotificationOnSubmitFormViewOperationType,
UpdateViewFieldOptionsOperationType,
)
@ -826,6 +827,9 @@ class DatabaseConfig(AppConfig):
operation_type_registry.register(CreateAndUsePersonalViewOperationType())
operation_type_registry.register(ReadViewOperationType())
operation_type_registry.register(UpdateViewOperationType())
operation_type_registry.register(
CanReceiveNotificationOnSubmitFormViewOperationType()
)
operation_type_registry.register(DeleteViewOperationType())
operation_type_registry.register(DuplicateViewOperationType())
operation_type_registry.register(CreateViewFilterOperationType())

View file

@ -75,7 +75,6 @@ from baserow.contrib.database.views.operations import (
UpdateViewFilterGroupOperationType,
UpdateViewFilterOperationType,
UpdateViewGroupByOperationType,
UpdateViewOperationType,
UpdateViewPublicOperationType,
UpdateViewSlugOperationType,
UpdateViewSortOperationType,
@ -947,7 +946,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
:param user: The user on whose behalf the view is updated.
:param view: The view instance that needs to be updated.
:param data: The fields that need to be updated.
:param data: The properties that need to be updated.
:raises ValueError: When the provided view not an instance of View.
:return: The updated view instance.
"""
@ -955,16 +954,12 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
if not isinstance(view, View):
raise ValueError("The view is not an instance of View.")
workspace = view.table.database.workspace
CoreHandler().check_permissions(
user, UpdateViewOperationType.type, workspace=workspace, context=view
)
view_type = view_type_registry.get_by_model(view)
view_type.check_view_update_permissions(user, view, data)
view_type.before_view_update(data, view, user)
old_view = deepcopy(view)
view_type = view_type_registry.get_by_model(view)
view_type.before_view_update(data, view, user)
view_values = view_type.prepare_values(data, view.table, user)
allowed_fields = [
"name",
@ -1003,6 +998,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
)
view = set_allowed_attrs(view_values, allowed_attrs, view)
if previous_public_value != view.public:
workspace = view.table.database.workspace
CoreHandler().check_permissions(
user,
UpdateViewPublicOperationType.type,

View file

@ -6,7 +6,9 @@ from django.dispatch import receiver
from django.utils.translation import gettext as _
from django.utils.translation import ngettext
from baserow.contrib.database.views.operations import UpdateViewOperationType
from baserow.contrib.database.views.operations import (
CanReceiveNotificationOnSubmitFormViewOperationType,
)
from baserow.core.handler import CoreHandler
from baserow.core.notifications.handler import NotificationHandler
from baserow.core.notifications.registries import (
@ -116,7 +118,7 @@ def create_form_submitted_notification(sender, form, row, values, user, **kwargs
# Ensure all users still have permissions on the table to see the notification
allowed_users = CoreHandler().check_permission_for_multiple_actors(
users_to_notify,
UpdateViewOperationType.type,
CanReceiveNotificationOnSubmitFormViewOperationType.type,
workspace=form.table.database.workspace,
context=form,
)

View file

@ -124,6 +124,10 @@ class UpdateViewOperationType(ViewOperationType):
type = "database.table.view.update"
class CanReceiveNotificationOnSubmitFormViewOperationType(ViewOperationType):
type = "database.table.view.can_receive_notification_on_submit_form_view"
class DeleteViewOperationType(ViewOperationType):
type = "database.table.view.delete"

View file

@ -23,6 +23,7 @@ from rest_framework.serializers import Serializer
from baserow.contrib.database.fields.field_filters import OptionallyAnnotatedQ
from baserow.core.exceptions import PermissionDenied
from baserow.core.handler import CoreHandler
from baserow.core.models import Workspace, WorkspaceUser
from baserow.core.registries import OperationType
from baserow.core.registry import (
@ -837,6 +838,29 @@ class ViewType(
},
)
def check_view_update_permissions(
self, user: AbstractUser, view: "View", data: Dict[str, Any]
):
"""
Hook that's called just before a view is updated. By default, it checks the
`UpdateViewOperationType`, but when overwritten, it can optionally check for
different permissions depending on the data. It returns nothing if the user has
permissions, or raises a PermissionDenied error otherwise.
:param user: The user on whose behalf the view is updated.
:param view: The view instance that needs to be updated.
:param data: The properties that need to be updated.
:raises PermissionDenied: if the user doesn't have permissions to update the
view.
"""
from .operations import UpdateViewOperationType
workspace = view.table.database.workspace
CoreHandler().check_permissions(
user, UpdateViewOperationType.type, workspace=workspace, context=view
)
class ViewTypeRegistry(
APIUrlsRegistryMixin, CustomFieldsRegistryMixin, ModelRegistryMixin, Registry

View file

@ -46,6 +46,7 @@ from baserow.contrib.database.fields.models import Field, FileField, SelectOptio
from baserow.contrib.database.fields.registries import field_type_registry
from baserow.contrib.database.table.models import Table
from baserow.contrib.database.views.registries import view_aggregation_type_registry
from baserow.core.handler import CoreHandler
from baserow.core.import_export.utils import file_chunk_generator
from baserow.core.storage import ExportZipFile
from baserow.core.user_files.handler import UserFileHandler
@ -1403,3 +1404,26 @@ class FormViewType(ViewType):
return FormViewFieldOptions(
field_id=field_id, form_view_id=view.id, enabled=False
)
def check_view_update_permissions(self, user, view, data):
from .operations import CanReceiveNotificationOnSubmitFormViewOperationType
workspace = view.table.database.workspace
if "receive_notification_on_submit" in data:
# If `receive_notification_on_submit` is in the data, then we must check if
# the user has permissions to receive a notification on submit.
CoreHandler().check_permissions(
user,
CanReceiveNotificationOnSubmitFormViewOperationType.type,
workspace=workspace,
context=view,
)
# If only the `receive_notification_on_submit` is provided, then there is
# no need to check if the user has permissions to update the view because
# nothing else is changed.
if len(data) == 1:
return
return super().check_view_update_permissions(user, view, data)

View file

@ -0,0 +1,7 @@
{
"type": "bug",
"message": "Allow editors to subscribe to a form view.",
"issue_number": 3271,
"bullet_points": [],
"created_at": "2025-01-18"
}

View file

@ -126,6 +126,7 @@ from baserow.contrib.database.tokens.operations import (
UseTokenOperationType,
)
from baserow.contrib.database.views.operations import (
CanReceiveNotificationOnSubmitFormViewOperationType,
CreateAndUsePersonalViewOperationType,
CreatePublicViewOperationType,
CreateViewDecorationOperationType,
@ -347,6 +348,7 @@ default_roles[EDITOR_ROLE_UID].extend(
RestoreDatabaseRowOperationType,
ListTeamSubjectsOperationType,
ReadTeamSubjectOperationType,
CanReceiveNotificationOnSubmitFormViewOperationType,
]
)
default_roles[BUILDER_ROLE_UID].extend(

View file

@ -0,0 +1,68 @@
from django.test import override_settings
import pytest
from baserow.contrib.database.views.handler import ViewHandler
from baserow.core.exceptions import PermissionDenied
from baserow_enterprise.role.handler import RoleAssignmentHandler
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_update_form_view_as_editor_fails(enterprise_data_fixture):
enterprise_data_fixture.enable_enterprise()
user, token = enterprise_data_fixture.create_user_and_token(
email="test@test.nl", password="password", first_name="Test1"
)
table = enterprise_data_fixture.create_database_table(user)
form = enterprise_data_fixture.create_form_view(table=table)
editor_role = RoleAssignmentHandler().get_role_by_uid("EDITOR")
RoleAssignmentHandler().assign_role(
user, table.database.workspace, role=editor_role, scope=table
)
handler = ViewHandler()
with pytest.raises(PermissionDenied):
handler.update_view(user=user, view=form, name="Test 1")
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_update_form_view_notification_as_editor_succeeds(enterprise_data_fixture):
enterprise_data_fixture.enable_enterprise()
user, token = enterprise_data_fixture.create_user_and_token(
email="test@test.nl", password="password", first_name="Test1"
)
table = enterprise_data_fixture.create_database_table(user)
form = enterprise_data_fixture.create_form_view(table=table)
editor_role = RoleAssignmentHandler().get_role_by_uid("EDITOR")
RoleAssignmentHandler().assign_role(
user, table.database.workspace, role=editor_role, scope=table
)
handler = ViewHandler()
handler.update_view(user=user, view=form, receive_notification_on_submit=True)
assert form.users_to_notify_on_submit.count() == 1
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_update_form_view_and_notification_as_editor_fails(enterprise_data_fixture):
enterprise_data_fixture.enable_enterprise()
user, token = enterprise_data_fixture.create_user_and_token(
email="test@test.nl", password="password", first_name="Test1"
)
table = enterprise_data_fixture.create_database_table(user)
form = enterprise_data_fixture.create_form_view(table=table)
editor_role = RoleAssignmentHandler().get_role_by_uid("EDITOR")
RoleAssignmentHandler().assign_role(
user, table.database.workspace, role=editor_role, scope=table
)
handler = ViewHandler()
with pytest.raises(PermissionDenied):
handler.update_view(
user=user, view=form, receive_notification_on_submit=True, name="Test"
)

View file

@ -9,6 +9,7 @@ from baserow_premium.views.models import OWNERSHIP_TYPE_PERSONAL
from baserow.contrib.database.table.models import Table
from baserow.contrib.database.views.operations import (
CanReceiveNotificationOnSubmitFormViewOperationType,
CreateAndUsePersonalViewOperationType,
CreateViewDecorationOperationType,
CreateViewFilterGroupOperationType,
@ -91,6 +92,7 @@ class ViewOwnershipPermissionManagerType(PermissionManagerType):
DeleteViewSortOperationType.type,
ReadViewOperationType.type,
UpdateViewOperationType.type,
CanReceiveNotificationOnSubmitFormViewOperationType.type,
DeleteViewOperationType.type,
DuplicateViewOperationType.type,
CreateViewFilterOperationType.type,

View file

@ -1,6 +1,13 @@
<template>
<div class="form-view__meta-controls">
<SwitchInput
v-if="
$hasPermission(
'database.table.view.can_receive_notification_on_submit_form_view',
view,
database.workspace.id
)
"
small
:value="view.receive_notification_on_submit"
class="margin-bottom-3"