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
4 changes: 2 additions & 2 deletions backend/src/baserow/contrib/builder/elements/element_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1519,13 +1519,13 @@ def is_valid(
:return: Whether the value is valid or not for this element.
"""

if value == "":
if value == "" or value is None:
if element.required:
raise ValueError("The value is required")

elif element.validation_type == "integer":
try:
return ensure_numeric(value)
return ensure_numeric(value, True)
except (InvalidOperation, ValidationError) as exc:
raise TypeError(f"{value} is not a valid number") from exc

Expand Down
9 changes: 6 additions & 3 deletions backend/src/baserow/contrib/builder/handler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Union

from django.conf import settings
from django.contrib.auth import get_user_model
from django.db.models.query import QuerySet

from baserow.contrib.builder.formula_property_extractor import (
Expand All @@ -25,6 +26,8 @@

SENTINEL = "__no_results__"

User = get_user_model()


class BuilderHandler:
def get_builder(self, builder_id: int) -> Builder:
Expand Down Expand Up @@ -56,14 +59,14 @@ def _get_builder_public_properties_version_cache(cls, builder: Builder) -> str:
return f"{USED_PROPERTIES_CACHE_KEY_PREFIX}_version_{builder.id}"

def get_builder_used_properties_cache_key(
self, user: UserSourceUser, builder: Builder
self, user: Union[User, UserSourceUser], builder: Builder
) -> str:
"""
Returns a cache key that can be used to key the results of making the
expensive function call to get_builder_used_property_names().
"""

if user.is_anonymous or not user.role:
if user.is_anonymous or not hasattr(user, "role"):
# When the user is anonymous, only use the prefix + page ID.
role = ""
else:
Expand Down
10 changes: 5 additions & 5 deletions backend/src/baserow/contrib/database/rows/data_providers.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from typing import List, Union

from baserow.contrib.builder.data_sources.builder_dispatch_context import (
BuilderDispatchContext,
from baserow.contrib.database.rows.runtime_formula_contexts import (
HumanReadableRowContext,
)
from baserow.core.formula.registries import DataProviderType


class HumanReadableFieldsDataProviderType(DataProviderType):
"""
This data provider type is used to read the human readable values for the row
This data provider type is used to read the human-readable values for the row
fields. This is used for example in the AI field to be able to reference other
fields in the same row to generate a different prompt for each row based on the
values of the other fields.
Expand All @@ -17,8 +17,8 @@ class HumanReadableFieldsDataProviderType(DataProviderType):
type = "fields"

def get_data_chunk(
self, dispatch_context: BuilderDispatchContext, path: List[str]
) -> Union[int, str]:
self, dispatch_context: HumanReadableRowContext, path: List[str]
) -> Union[int, str] | None:
"""
When a page parameter is read, returns the value previously saved from the
request object.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,7 @@ def test_choice_element_import_export_formula(data_fixture):
(True, "integer", "42", 42),
(True, "integer", "horse", TypeError),
(False, "integer", "", ""),
(False, "integer", None, None),
(True, "email", "", ValueError),
(True, "email", "foo@bar.com", "foo@bar.com"),
(True, "email", "foobar.com", ValueError),
Expand Down
55 changes: 27 additions & 28 deletions backend/tests/baserow/contrib/builder/test_builder_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.contrib.auth import get_user_model

import pytest
from faker import Faker

from baserow.contrib.builder.handler import (
USED_PROPERTIES_CACHE_KEY_PREFIX,
Expand All @@ -11,9 +12,18 @@
from baserow.core.exceptions import ApplicationDoesNotExist
from baserow.core.user_sources.user_source_user import UserSourceUser

fake = Faker()
User = get_user_model()


def fake_user_source_user(role: str = "") -> UserSourceUser:
user_source = MagicMock()
original_user = MagicMock()
return UserSourceUser(
user_source, original_user, 123, fake.name(), fake.email(), role=role
)


@pytest.mark.django_db
def test_get_builder(data_fixture):
builder = data_fixture.create_builder_application()
Expand Down Expand Up @@ -44,49 +54,38 @@ def test_get_builder_select_related_theme_config(


@pytest.mark.parametrize(
"is_anonymous,user_role,expected_cache_key",
"user,expected_cache_key",
[
# An anonymous User.
(
True,
"",
MagicMock(is_anonymous=True, spec=["is_anonymous"]),
f"{USED_PROPERTIES_CACHE_KEY_PREFIX}_100",
),
# An authenticated User
(
True,
"foo_role",
MagicMock(is_anonymous=False, spec=["is_anonymous"]),
f"{USED_PROPERTIES_CACHE_KEY_PREFIX}_100",
),
# A UserSourceUser, with no role.
(
False,
"foo_role",
f"{USED_PROPERTIES_CACHE_KEY_PREFIX}_100_foo_role",
fake_user_source_user(role=""),
f"{USED_PROPERTIES_CACHE_KEY_PREFIX}_100_",
),
# A UserSourceUser, with a role.
(
fake_user_source_user(role="admin"),
f"{USED_PROPERTIES_CACHE_KEY_PREFIX}_100_admin",
),
],
)
def test_get_builder_used_properties_cache_key_returned_expected_cache_key(
is_anonymous, user_role, expected_cache_key
user, expected_cache_key
):
"""
Test the BuilderHandler::get_builder_used_properties_cache_key() method.

Ensure the expected cache key is returned.
"""

user_source_user = MagicMock()
user_source_user.is_anonymous = is_anonymous
user_source_user.role = user_role

mock_builder = MagicMock()
mock_builder.id = 100

handler = BuilderHandler()

cache_key = handler.get_builder_used_properties_cache_key(
user_source_user, mock_builder
assert (
BuilderHandler().get_builder_used_properties_cache_key(user, MagicMock(id=100))
== expected_cache_key
)

assert cache_key == expected_cache_key


@pytest.mark.django_db
def test_public_allowed_properties_is_cached(data_fixture, django_assert_num_queries):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "bug",
"message": "Resolved a bug which prevented builder data source and workflow action filtering with formula values from working correctly.",
"issue_origin": "github",
"issue_number": 4860,
"domain": "integration",
"bullet_points": [],
"created_at": "2026-02-24"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "bug",
"message": "Resolved a bug which caused table and repeat element load more buttons to only load more after a second click.",
"issue_origin": "github",
"issue_number": 4862,
"domain": "builder",
"bullet_points": [],
"created_at": "2026-02-25"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "bug",
"message": "Resolved a caching issue when applications users had no role set.",
"issue_origin": "github",
"issue_number": null,
"domain": "builder",
"bullet_points": [],
"created_at": "2026-02-25"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "bug",
"message": "Resolved a data input bug which prevented optional numeric inputs from being accepted.",
"issue_origin": "github",
"issue_number": null,
"domain": "builder",
"bullet_points": [],
"created_at": "2026-02-25"
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default {
computed: {
submitIsDisabled() {
return (
this.loading || !this.changed || this.$refs.dataSourceForm.v$.$anyError
this.loading || !this.changed || this.$refs.dataSourceForm?.v$.$anyError
)
},
dataSources() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ export default {
},
},
emits: ['update:modelValue'],
setup() {
return useDatePickerLanguage()
},
data() {
return {
dateInputValue: '',
Expand Down Expand Up @@ -138,9 +141,6 @@ export default {
immediate: true,
},
},
setup() {
return useDatePickerLanguage()
},
methods: {
refreshDate(value) {
if (!value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export default {
* @property {Object} fields - The fields of the data source.
* @property {int} items_per_page - The number of items per page.
* @property {string} button_color - The color of the button.
* @property {string} orientation - The orientation for eaceh device.
* @property {string} orientation - The orientation for each device.
*/
element: {
type: Object,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function useCollectionElement(props) {
const adhocFilters = ref()
const adhocSortings = ref()
const adhocSearch = ref()
const currentOffset = ref(0)
const currentOffset = useState(`element-offset-${unref(element).id}`, () => 0)
const errorNotified = ref(false)
const resetTimeout = ref(null)
const contentFetchEnabled = ref(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
>
<InjectedFormulaInput
v-if="filter.value_is_formula && propFilterType.hasEditableValue"
:value="getFormulaObject(filter)"
:model-value="getFormulaObject(filter)"
class="filters__value--formula-input"
:placeholder="
$t(
Expand Down
Loading