Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions backend/src/baserow/api/decorators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import typing
from datetime import datetime, timezone
from functools import wraps
from typing import Any, Callable, Dict, Optional, Type
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

from django.db import OperationalError
Expand Down Expand Up @@ -249,6 +249,7 @@ def func_wrapper(*args, **kwargs):
def validate_body_custom_fields(
registry,
base_serializer_class=None,
serializer_class_context: Optional[Dict[str, Any]] = None,
type_attribute_name="type",
partial=False,
allow_empty_type=False,
Expand All @@ -266,6 +267,8 @@ def validate_body_custom_fields(
:param base_serializer_class: The base serializer class that will be used when
generating the serializer.
:type base_serializer_class: ModelSerializer
:param serializer_class_context: If provided, this context will be passed to the
`get_serializer_class` method of the type instance.
:param type_attribute_name: The attribute name containing the type value in the
request data.
:type type_attribute_name: str
Expand Down Expand Up @@ -301,6 +304,7 @@ def func_wrapper(*args, **kwargs):
registry,
request.data,
base_serializer_class=base_serializer_class,
serializer_class_context=serializer_class_context,
type_attribute_name=type_attribute_name,
partial=partial,
allow_empty_type=allow_empty_type,
Expand Down Expand Up @@ -400,7 +404,7 @@ def func_wrapper(*args, **kwargs):
return validate_decorator


def require_request_data_type(*rtypes: typing.Type) -> typing.Callable:
def require_request_data_type(*rtypes: Type) -> Callable:
"""
Decorate a view function to restrict allowed request.data to specific types,
allowing request.data type checks before actual view is called. This may be
Expand Down
21 changes: 16 additions & 5 deletions backend/src/baserow/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ def validate_data(
many: bool = False,
return_validated: bool = False,
instance=None,
context: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""
Validates the provided data via the provided serializer class. If the data doesn't
Expand All @@ -189,10 +190,13 @@ def validate_data(
:param many: Indicates whether the serializer should be constructed as a list.
:param return_validated: Returns validated_data from DRF serializer
:param instance: The instance that is being updated.
:param context: Optional context dict passed to the serializer.
:return: The data after being validated by the serializer.
"""

serializer = serializer_class(instance, data=data, partial=partial, many=many)
serializer = serializer_class(
instance, data=data, partial=partial, many=many, context=context or {}
)
if not serializer.is_valid():
detail = serialize_validation_errors_recursive(serializer.errors)
raise exception_to_raise(detail)
Expand All @@ -208,6 +212,7 @@ def validate_data_custom_fields(
registry: "Registry",
data: Dict[str, Any],
base_serializer_class: Optional[Type[ModelSerializer]] = None,
serializer_class_context: Optional[Dict[str, Any]] = None,
type_attribute_name: str = "type",
partial: bool = False,
allow_empty_type: bool = False,
Expand All @@ -217,12 +222,14 @@ def validate_data_custom_fields(
Validates the provided data with the serializer generated by the registry based on
the provided type_name and provided base_serializer_class.

:param type_name: The type name of the type instance that is needed to generated
:param type_name: The type name of the type instance that is needed to generate
the serializer.
:param registry: The registry where to get the type instance from.
:param data: The data that needs to be validated.
:param base_serializer_class: The base serializer class that is used when
generating the serializer for validation.
:param serializer_class_context: If provided, this context will be passed to the
`get_serializer_class` method of the type instance.
:param type_attribute_name: The attribute key name that contains the type value.
:param partial: Whether the data is a partial update.
:param allow_empty_type: Whether the type can be empty.
Expand All @@ -237,8 +244,8 @@ def validate_data_custom_fields(
try:
type_instance = registry.get(type_name)
except InstanceTypeDoesNotExist:
# If the provided type name doesn't exist we will raise a machine
# readable validation error.
# If the provided type name doesn't exist we will raise a
# machine-readable validation error.
raise RequestBodyValidationException(
{
type_attribute_name: [
Expand All @@ -259,7 +266,11 @@ def validate_data_custom_fields(
serializer_class = type_instance.get_serializer_class(**serializer_kwargs)

return validate_data(
serializer_class, data, partial=partial, return_validated=return_validated
serializer_class,
data,
partial=partial,
return_validated=return_validated,
context=serializer_class_context or {},
)


Expand Down
2 changes: 2 additions & 0 deletions backend/src/baserow/contrib/automation/api/nodes/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from baserow.contrib.automation.api.workflows.errors import (
ERROR_AUTOMATION_WORKFLOW_DOES_NOT_EXIST,
)
from baserow.contrib.automation.application_types import AutomationApplicationType
from baserow.contrib.automation.nodes.actions import (
CreateAutomationNodeActionType,
DeleteAutomationNodeActionType,
Expand Down Expand Up @@ -244,6 +245,7 @@ def patch(self, request, node_id: int):
automation_node_type_registry,
request.data,
base_serializer_class=UpdateAutomationNodeSerializer,
serializer_class_context={"application_type": AutomationApplicationType},
partial=True,
return_validated=True,
)
Expand Down
4 changes: 4 additions & 0 deletions backend/src/baserow/contrib/automation/application_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
AutomationApplicationTypeInitApplication,
)
from baserow.contrib.automation.constants import IMPORT_SERIALIZED_IMPORTING
from baserow.contrib.automation.data_providers.registries import (
automation_data_provider_type_registry,
)
from baserow.contrib.automation.models import Automation, AutomationWorkflow
from baserow.contrib.automation.operations import ListAutomationWorkflowsOperationType
from baserow.contrib.automation.types import AutomationDict
Expand Down Expand Up @@ -41,6 +44,7 @@ class AutomationApplicationType(ApplicationType):
supports_integrations = True
request_serializer_field_names = []
serializer_mixins = [lazy_get_instance_serializer_class]
data_provider_type_registry = automation_data_provider_type_registry

# Automation applications are imported third (after database, builder)
import_application_priority = 0
Expand Down
2 changes: 2 additions & 0 deletions backend/src/baserow/contrib/builder/api/data_sources/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
)
from baserow.contrib.builder.api.elements.errors import ERROR_ELEMENT_DOES_NOT_EXIST
from baserow.contrib.builder.api.pages.errors import ERROR_PAGE_DOES_NOT_EXIST
from baserow.contrib.builder.application_types import BuilderApplicationType
from baserow.contrib.builder.data_sources.builder_dispatch_context import (
BuilderDispatchContext,
)
Expand Down Expand Up @@ -310,6 +311,7 @@ def patch(self, request, data_source_id: int):
service_type_registry,
request.data,
base_serializer_class=UpdateDataSourceSerializer,
serializer_class_context={"application_type": BuilderApplicationType},
return_validated=True,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ def to_representation(self, instance):
else:
instance_type = self.get_type_from_instance(instance)

serializer = instance_type.get_serializer(instance)
serializer = instance_type.get_serializer(instance, context=self.context)

if isinstance(instance, Mapping):
ret = serializer.to_representation(instance["config"])
Expand Down Expand Up @@ -359,9 +359,9 @@ def to_internal_value(self, data):
code="INVALID_FIELD_PROPERTY",
)

ret = instance_type.get_serializer(field_config_data).to_internal_value(
field_config_data
)
ret = instance_type.get_serializer(
field_config_data, context=self.context
).to_internal_value(field_config_data)

data["config"] = ret

Expand Down
6 changes: 5 additions & 1 deletion backend/src/baserow/contrib/builder/api/elements/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
UpdateElementSerializer,
)
from baserow.contrib.builder.api.pages.errors import ERROR_PAGE_DOES_NOT_EXIST
from baserow.contrib.builder.application_types import BuilderApplicationType
from baserow.contrib.builder.data_sources.exceptions import DataSourceDoesNotExist
from baserow.contrib.builder.elements.exceptions import (
CollectionElementPropertyOptionsNotUnique,
Expand Down Expand Up @@ -148,7 +149,9 @@ def get(self, request, page_id):
}
)
@validate_body_custom_fields(
element_type_registry, base_serializer_class=CreateElementSerializer
element_type_registry,
base_serializer_class=CreateElementSerializer,
serializer_class_context={"application_type": BuilderApplicationType},
)
def post(self, request, data: Dict, page_id: int):
"""Creates a new element."""
Expand Down Expand Up @@ -228,6 +231,7 @@ def patch(self, request, element_id: int):
element_type_registry,
request.data,
base_serializer_class=UpdateElementSerializer,
serializer_class_context={"application_type": BuilderApplicationType},
partial=True,
return_validated=True,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
OrderWorkflowActionsSerializer,
UpdateBuilderWorkflowActionsSerializer,
)
from baserow.contrib.builder.application_types import BuilderApplicationType
from baserow.contrib.builder.data_sources.builder_dispatch_context import (
BuilderDispatchContext,
)
Expand Down Expand Up @@ -285,6 +286,7 @@ def patch(self, request, workflow_action_id: int):
builder_workflow_action_type_registry,
request.data,
base_serializer_class=UpdateBuilderWorkflowActionsSerializer,
serializer_class_context={"application_type": BuilderApplicationType},
partial=True,
)

Expand Down
4 changes: 4 additions & 0 deletions backend/src/baserow/contrib/builder/application_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
BuilderApplicationTypeInitApplication,
)
from baserow.contrib.builder.constants import IMPORT_SERIALIZED_IMPORTING
from baserow.contrib.builder.data_providers.registries import (
builder_data_provider_type_registry,
)
from baserow.contrib.builder.models import Builder
from baserow.contrib.builder.operations import ListPagesBuilderOperationType
from baserow.contrib.builder.pages.handler import PageHandler
Expand Down Expand Up @@ -78,6 +81,7 @@ class BuilderApplicationType(ApplicationType):
serializer_mixins = [lazy_get_instance_serializer_class]
public_serializer_mixins = [lazy_get_instance_public_serializer_class]
request_serializer_mixins = []
data_provider_type_registry = builder_data_provider_type_registry

# Builder applications are imported second.
import_application_priority = 1
Expand Down
3 changes: 3 additions & 0 deletions backend/src/baserow/contrib/database/api/fields/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
)
from baserow.contrib.database.api.tokens.authentications import TokenAuthentication
from baserow.contrib.database.api.tokens.errors import ERROR_NO_PERMISSION_TO_TABLE
from baserow.contrib.database.application_types import DatabaseApplicationType
from baserow.contrib.database.fields.actions import (
ChangePrimaryFieldActionType,
CreateFieldActionType,
Expand Down Expand Up @@ -278,6 +279,7 @@ def get(self, request, table_id):
@validate_body_custom_fields(
field_type_registry,
base_serializer_class=CreateFieldSerializer,
serializer_class_context={"application_type": DatabaseApplicationType},
)
@map_exceptions(
{
Expand Down Expand Up @@ -463,6 +465,7 @@ def patch(self, request, field_id):
field_type_registry,
request.data,
base_serializer_class=UpdateFieldSerializer,
serializer_class_context={"application_type": DatabaseApplicationType},
)

# Because each field type can raise custom exceptions at while updating the
Expand Down
2 changes: 2 additions & 0 deletions backend/src/baserow/contrib/database/application_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
IMPORT_SERIALIZED_IMPORTING_TABLE_DATA,
IMPORT_SERIALIZED_IMPORTING_TABLE_STRUCTURE,
)
from .data_providers.registries import database_data_provider_type_registry
from .data_sync.registries import data_sync_type_registry
from .db.atomic import read_repeatable_single_database_atomic_transaction
from .export_serialized import DatabaseExportSerializedStructure
Expand Down Expand Up @@ -71,6 +72,7 @@ class DatabaseApplicationType(ApplicationType):
# Mark the request serializer field names as empty, otherwise
# the polymorphic request serializer will try and serialize tables.
request_serializer_field_names = []
data_provider_type_registry = database_data_provider_type_registry

# Database applications are imported first.
import_application_priority = 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def to_representation(self, instance):
representation = super().to_representation(instance)
representation["sortings"] = LocalBaserowTableServiceSortSerializer(
instance.service_sorts.all(),
context=self.context,
many=True,
).data
return representation
Expand All @@ -44,7 +45,9 @@ def to_internal_value(self, data):
data = super().to_internal_value(data)
if sortings is not None:
data["service_sorts"] = [
LocalBaserowTableServiceSortSerializer().to_internal_value(ss)
LocalBaserowTableServiceSortSerializer(
context=self.context
).to_internal_value(ss)
for ss in sortings
]
return data
Expand Down Expand Up @@ -93,6 +96,7 @@ def to_representation(self, instance):
representation["filters"] = LocalBaserowTableServiceFilterSerializer(
instance.service_filters.all(),
many=True,
context=self.context,
).data
return representation

Expand All @@ -101,7 +105,9 @@ def to_internal_value(self, data):
data = super().to_internal_value(data)
if filters is not None:
data["service_filters"] = [
LocalBaserowTableServiceFilterSerializer().to_internal_value(sf)
LocalBaserowTableServiceFilterSerializer(
context=self.context
).to_internal_value(sf)
for sf in filters
]
return data
Expand Down
6 changes: 4 additions & 2 deletions backend/src/baserow/core/formula/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
BaserowFormulaSyntaxError,
]

from baserow.core.formula.parser.formula_execution_visitor import (
BaserowFormulaExecutionVisitor,
)
from baserow.core.formula.parser.parser import get_parse_tree_for_formula
from baserow.core.formula.parser.python_executor import BaserowPythonExecutor


def resolve_formula(
Expand All @@ -51,4 +53,4 @@ def resolve_formula(
return formula["formula"]

tree = get_parse_tree_for_formula(formula["formula"])
return BaserowPythonExecutor(functions, formula_context).visit(tree)
return BaserowFormulaExecutionVisitor(functions, formula_context).visit(tree)
4 changes: 2 additions & 2 deletions backend/src/baserow/core/formula/argument_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ def parse(self, value):
class BooleanBaserowRuntimeFormulaArgumentType(BaserowRuntimeFormulaArgumentType):
def test(self, value):
try:
ensure_boolean(value)
ensure_boolean(value, False)
return True
except ValidationError:
return False

def parse(self, value):
return ensure_boolean(value)
return ensure_boolean(value, False)


class TimezoneBaserowRuntimeFormulaArgumentType(BaserowRuntimeFormulaArgumentType):
Expand Down
16 changes: 12 additions & 4 deletions backend/src/baserow/core/formula/parser/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,17 @@ def __init__(self, function_def, num_args):
error_prefix = "1 argument was"
else:
error_prefix = f"{num_args} arguments were"

if function_def.num_args is None:
# This function doesn't take a specific set of `args`, but instead a
# variable number of arguments with a minimum number of args.
expected = f"at least {function_def.min_args}"
else:
expected = str(function_def.num_args)

super().__init__(
f"{error_prefix} given to the {function_def}, it must instead "
f"be given {function_def.num_args}"
f"{error_prefix} given to the '{function_def.type}' function, it must "
f"instead be given {expected}"
)


Expand Down Expand Up @@ -51,8 +59,8 @@ def __init__(self):


class UnknownOperator(BaserowFormulaException):
def __init__(self, operatorText):
super().__init__(f"it used the unknown operator {operatorText}")
def __init__(self, operator_text):
super().__init__(f"it used the unknown operator {operator_text}")


class BaserowFormulaSyntaxError(BaserowFormulaException):
Expand Down
Loading
Loading