mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-14 00:59:06 +00:00
Allow editors to subscribe to a form view
This commit is contained in:
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
|
@ -686,6 +686,7 @@ class DatabaseConfig(AppConfig):
|
||||||
object_scope_type_registry.register(TokenObjectScopeType())
|
object_scope_type_registry.register(TokenObjectScopeType())
|
||||||
|
|
||||||
from baserow.contrib.database.views.operations import (
|
from baserow.contrib.database.views.operations import (
|
||||||
|
CanReceiveNotificationOnSubmitFormViewOperationType,
|
||||||
UpdateViewFieldOptionsOperationType,
|
UpdateViewFieldOptionsOperationType,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -826,6 +827,9 @@ class DatabaseConfig(AppConfig):
|
||||||
operation_type_registry.register(CreateAndUsePersonalViewOperationType())
|
operation_type_registry.register(CreateAndUsePersonalViewOperationType())
|
||||||
operation_type_registry.register(ReadViewOperationType())
|
operation_type_registry.register(ReadViewOperationType())
|
||||||
operation_type_registry.register(UpdateViewOperationType())
|
operation_type_registry.register(UpdateViewOperationType())
|
||||||
|
operation_type_registry.register(
|
||||||
|
CanReceiveNotificationOnSubmitFormViewOperationType()
|
||||||
|
)
|
||||||
operation_type_registry.register(DeleteViewOperationType())
|
operation_type_registry.register(DeleteViewOperationType())
|
||||||
operation_type_registry.register(DuplicateViewOperationType())
|
operation_type_registry.register(DuplicateViewOperationType())
|
||||||
operation_type_registry.register(CreateViewFilterOperationType())
|
operation_type_registry.register(CreateViewFilterOperationType())
|
||||||
|
|
|
@ -75,7 +75,6 @@ from baserow.contrib.database.views.operations import (
|
||||||
UpdateViewFilterGroupOperationType,
|
UpdateViewFilterGroupOperationType,
|
||||||
UpdateViewFilterOperationType,
|
UpdateViewFilterOperationType,
|
||||||
UpdateViewGroupByOperationType,
|
UpdateViewGroupByOperationType,
|
||||||
UpdateViewOperationType,
|
|
||||||
UpdateViewPublicOperationType,
|
UpdateViewPublicOperationType,
|
||||||
UpdateViewSlugOperationType,
|
UpdateViewSlugOperationType,
|
||||||
UpdateViewSortOperationType,
|
UpdateViewSortOperationType,
|
||||||
|
@ -947,7 +946,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
|
||||||
|
|
||||||
:param user: The user on whose behalf the view is updated.
|
:param user: The user on whose behalf the view is updated.
|
||||||
:param view: The view instance that needs to be 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.
|
:raises ValueError: When the provided view not an instance of View.
|
||||||
:return: The updated view instance.
|
:return: The updated view instance.
|
||||||
"""
|
"""
|
||||||
|
@ -955,16 +954,12 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
|
||||||
if not isinstance(view, View):
|
if not isinstance(view, View):
|
||||||
raise ValueError("The view is not an instance of View.")
|
raise ValueError("The view is not an instance of View.")
|
||||||
|
|
||||||
workspace = view.table.database.workspace
|
view_type = view_type_registry.get_by_model(view)
|
||||||
CoreHandler().check_permissions(
|
view_type.check_view_update_permissions(user, view, data)
|
||||||
user, UpdateViewOperationType.type, workspace=workspace, context=view
|
view_type.before_view_update(data, view, user)
|
||||||
)
|
|
||||||
|
|
||||||
old_view = deepcopy(view)
|
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)
|
view_values = view_type.prepare_values(data, view.table, user)
|
||||||
allowed_fields = [
|
allowed_fields = [
|
||||||
"name",
|
"name",
|
||||||
|
@ -1003,6 +998,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
|
||||||
)
|
)
|
||||||
view = set_allowed_attrs(view_values, allowed_attrs, view)
|
view = set_allowed_attrs(view_values, allowed_attrs, view)
|
||||||
if previous_public_value != view.public:
|
if previous_public_value != view.public:
|
||||||
|
workspace = view.table.database.workspace
|
||||||
CoreHandler().check_permissions(
|
CoreHandler().check_permissions(
|
||||||
user,
|
user,
|
||||||
UpdateViewPublicOperationType.type,
|
UpdateViewPublicOperationType.type,
|
||||||
|
|
|
@ -6,7 +6,9 @@ from django.dispatch import receiver
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.utils.translation import ngettext
|
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.handler import CoreHandler
|
||||||
from baserow.core.notifications.handler import NotificationHandler
|
from baserow.core.notifications.handler import NotificationHandler
|
||||||
from baserow.core.notifications.registries import (
|
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
|
# Ensure all users still have permissions on the table to see the notification
|
||||||
allowed_users = CoreHandler().check_permission_for_multiple_actors(
|
allowed_users = CoreHandler().check_permission_for_multiple_actors(
|
||||||
users_to_notify,
|
users_to_notify,
|
||||||
UpdateViewOperationType.type,
|
CanReceiveNotificationOnSubmitFormViewOperationType.type,
|
||||||
workspace=form.table.database.workspace,
|
workspace=form.table.database.workspace,
|
||||||
context=form,
|
context=form,
|
||||||
)
|
)
|
||||||
|
|
|
@ -124,6 +124,10 @@ class UpdateViewOperationType(ViewOperationType):
|
||||||
type = "database.table.view.update"
|
type = "database.table.view.update"
|
||||||
|
|
||||||
|
|
||||||
|
class CanReceiveNotificationOnSubmitFormViewOperationType(ViewOperationType):
|
||||||
|
type = "database.table.view.can_receive_notification_on_submit_form_view"
|
||||||
|
|
||||||
|
|
||||||
class DeleteViewOperationType(ViewOperationType):
|
class DeleteViewOperationType(ViewOperationType):
|
||||||
type = "database.table.view.delete"
|
type = "database.table.view.delete"
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ from rest_framework.serializers import Serializer
|
||||||
|
|
||||||
from baserow.contrib.database.fields.field_filters import OptionallyAnnotatedQ
|
from baserow.contrib.database.fields.field_filters import OptionallyAnnotatedQ
|
||||||
from baserow.core.exceptions import PermissionDenied
|
from baserow.core.exceptions import PermissionDenied
|
||||||
|
from baserow.core.handler import CoreHandler
|
||||||
from baserow.core.models import Workspace, WorkspaceUser
|
from baserow.core.models import Workspace, WorkspaceUser
|
||||||
from baserow.core.registries import OperationType
|
from baserow.core.registries import OperationType
|
||||||
from baserow.core.registry import (
|
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(
|
class ViewTypeRegistry(
|
||||||
APIUrlsRegistryMixin, CustomFieldsRegistryMixin, ModelRegistryMixin, Registry
|
APIUrlsRegistryMixin, CustomFieldsRegistryMixin, ModelRegistryMixin, Registry
|
||||||
|
|
|
@ -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.fields.registries import field_type_registry
|
||||||
from baserow.contrib.database.table.models import Table
|
from baserow.contrib.database.table.models import Table
|
||||||
from baserow.contrib.database.views.registries import view_aggregation_type_registry
|
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.import_export.utils import file_chunk_generator
|
||||||
from baserow.core.storage import ExportZipFile
|
from baserow.core.storage import ExportZipFile
|
||||||
from baserow.core.user_files.handler import UserFileHandler
|
from baserow.core.user_files.handler import UserFileHandler
|
||||||
|
@ -1403,3 +1404,26 @@ class FormViewType(ViewType):
|
||||||
return FormViewFieldOptions(
|
return FormViewFieldOptions(
|
||||||
field_id=field_id, form_view_id=view.id, enabled=False
|
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)
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
|
@ -126,6 +126,7 @@ from baserow.contrib.database.tokens.operations import (
|
||||||
UseTokenOperationType,
|
UseTokenOperationType,
|
||||||
)
|
)
|
||||||
from baserow.contrib.database.views.operations import (
|
from baserow.contrib.database.views.operations import (
|
||||||
|
CanReceiveNotificationOnSubmitFormViewOperationType,
|
||||||
CreateAndUsePersonalViewOperationType,
|
CreateAndUsePersonalViewOperationType,
|
||||||
CreatePublicViewOperationType,
|
CreatePublicViewOperationType,
|
||||||
CreateViewDecorationOperationType,
|
CreateViewDecorationOperationType,
|
||||||
|
@ -347,6 +348,7 @@ default_roles[EDITOR_ROLE_UID].extend(
|
||||||
RestoreDatabaseRowOperationType,
|
RestoreDatabaseRowOperationType,
|
||||||
ListTeamSubjectsOperationType,
|
ListTeamSubjectsOperationType,
|
||||||
ReadTeamSubjectOperationType,
|
ReadTeamSubjectOperationType,
|
||||||
|
CanReceiveNotificationOnSubmitFormViewOperationType,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
default_roles[BUILDER_ROLE_UID].extend(
|
default_roles[BUILDER_ROLE_UID].extend(
|
||||||
|
|
|
@ -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"
|
||||||
|
)
|
|
@ -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.table.models import Table
|
||||||
from baserow.contrib.database.views.operations import (
|
from baserow.contrib.database.views.operations import (
|
||||||
|
CanReceiveNotificationOnSubmitFormViewOperationType,
|
||||||
CreateAndUsePersonalViewOperationType,
|
CreateAndUsePersonalViewOperationType,
|
||||||
CreateViewDecorationOperationType,
|
CreateViewDecorationOperationType,
|
||||||
CreateViewFilterGroupOperationType,
|
CreateViewFilterGroupOperationType,
|
||||||
|
@ -91,6 +92,7 @@ class ViewOwnershipPermissionManagerType(PermissionManagerType):
|
||||||
DeleteViewSortOperationType.type,
|
DeleteViewSortOperationType.type,
|
||||||
ReadViewOperationType.type,
|
ReadViewOperationType.type,
|
||||||
UpdateViewOperationType.type,
|
UpdateViewOperationType.type,
|
||||||
|
CanReceiveNotificationOnSubmitFormViewOperationType.type,
|
||||||
DeleteViewOperationType.type,
|
DeleteViewOperationType.type,
|
||||||
DuplicateViewOperationType.type,
|
DuplicateViewOperationType.type,
|
||||||
CreateViewFilterOperationType.type,
|
CreateViewFilterOperationType.type,
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="form-view__meta-controls">
|
<div class="form-view__meta-controls">
|
||||||
<SwitchInput
|
<SwitchInput
|
||||||
|
v-if="
|
||||||
|
$hasPermission(
|
||||||
|
'database.table.view.can_receive_notification_on_submit_form_view',
|
||||||
|
view,
|
||||||
|
database.workspace.id
|
||||||
|
)
|
||||||
|
"
|
||||||
small
|
small
|
||||||
:value="view.receive_notification_on_submit"
|
:value="view.receive_notification_on_submit"
|
||||||
class="margin-bottom-3"
|
class="margin-bottom-3"
|
||||||
|
|
Loading…
Add table
Reference in a new issue