From f6a7d302ffa46fb8f7a9db9031355665f4221741 Mon Sep 17 00:00:00 2001 From: dimmur-brw Date: Tue, 24 Mar 2026 06:25:27 +0100 Subject: [PATCH 1/2] fix: bug in public view with hidden group by (#4859) * Fix public view with hidden group by * Show group by feature in PublicView when fields are hidden * Review feedback * Review feedback v2 --- .../contrib/database/api/views/views.py | 5 +- .../contrib/database/views/view_types.py | 5 +- .../database/ws/public/views/signals.py | 17 +++ .../api/views/grid/test_grid_view_views.py | 108 ++++++++++++++++-- ...ublic_view_with_hidden_group_by_field.json | 9 ++ .../components/view/grid/GridViewGroup.vue | 20 +--- .../components/view/grid/GridViewGroups.vue | 10 +- .../components/view/grid/GridViewHead.vue | 16 ++- .../components/view/grid/GridViewSection.vue | 9 +- .../view/grid/GroupByFieldLinkRow.vue | 40 +++++++ web-frontend/modules/database/fieldTypes.js | 13 +++ .../database/mixins/gridViewHelpers.js | 5 + .../modules/database/store/view/grid.js | 7 +- web-frontend/modules/database/viewTypes.js | 1 + 14 files changed, 214 insertions(+), 51 deletions(-) create mode 100644 changelog/entries/unreleased/bug/4858_fix_public_view_with_hidden_group_by_field.json create mode 100644 web-frontend/modules/database/components/view/grid/GroupByFieldLinkRow.vue diff --git a/backend/src/baserow/contrib/database/api/views/views.py b/backend/src/baserow/contrib/database/api/views/views.py index 9bca76a6cb..af1c229d2f 100644 --- a/backend/src/baserow/contrib/database/api/views/views.py +++ b/backend/src/baserow/contrib/database/api/views/views.py @@ -2122,11 +2122,14 @@ def get(self, request: Request, slug: str) -> Response: raise ViewDoesNotExist() field_options = view_type.get_visible_field_options_in_order(view_specific) + ordered_field_ids = list(field_options.values_list("field_id", flat=True)) fields = specific_iterator( - Field.objects.filter(id__in=field_options.values_list("field_id")) + Field.objects.filter(id__in=ordered_field_ids) .select_related("content_type") .prefetch_related("select_options") ) + field_id_order = {fid: idx for idx, fid in enumerate(ordered_field_ids)} + fields = sorted(fields, key=lambda f: field_id_order.get(f.id, 0)) return Response( PublicViewInfoSerializer( diff --git a/backend/src/baserow/contrib/database/views/view_types.py b/backend/src/baserow/contrib/database/views/view_types.py index a0502b94f8..c916466dc0 100644 --- a/backend/src/baserow/contrib/database/views/view_types.py +++ b/backend/src/baserow/contrib/database/views/view_types.py @@ -263,9 +263,12 @@ def after_fields_type_change(self, fields): ) def get_visible_field_options_in_order(self, grid_view): + group_by_field_ids = grid_view.viewgroupby_set.values_list( + "field_id", flat=True + ) return ( grid_view.get_field_options(create_if_missing=True) - .filter(hidden=False) + .filter(Q(hidden=False) | Q(field_id__in=group_by_field_ids)) .order_by("-field__primary", "order", "field__id") ) diff --git a/backend/src/baserow/contrib/database/ws/public/views/signals.py b/backend/src/baserow/contrib/database/ws/public/views/signals.py index b62bde29e5..3ba4aecb03 100644 --- a/backend/src/baserow/contrib/database/ws/public/views/signals.py +++ b/backend/src/baserow/contrib/database/ws/public/views/signals.py @@ -77,6 +77,23 @@ def public_view_filter_deleted(sender, view_filter_id, view_filter, user, **kwar _send_force_rows_refresh_if_view_public(view_filter.view) +@receiver(view_signals.view_group_by_created) +def public_view_group_by_created(sender, view_group_by, user, **kwargs): + _send_force_view_refresh_if_view_public(view_group_by.view) + + +@receiver(view_signals.view_group_by_updated) +def public_view_group_by_updated(sender, view_group_by, user, **kwargs): + _send_force_view_refresh_if_view_public(view_group_by.view) + + +@receiver(view_signals.view_group_by_deleted) +def public_view_group_by_deleted( + sender, view_group_by_id, view_group_by, user, **kwargs +): + _send_force_view_refresh_if_view_public(view_group_by.view) + + @receiver(view_signals.view_field_options_updated) def public_view_field_options_updated(sender, view, user, **kwargs): _send_force_view_refresh_if_view_public(view) diff --git a/backend/tests/baserow/contrib/database/api/views/grid/test_grid_view_views.py b/backend/tests/baserow/contrib/database/api/views/grid/test_grid_view_views.py index 84bfa4442b..2f27e6868e 100644 --- a/backend/tests/baserow/contrib/database/api/views/grid/test_grid_view_views.py +++ b/backend/tests/baserow/contrib/database/api/views/grid/test_grid_view_views.py @@ -3236,14 +3236,16 @@ def test_get_public_grid_view(api_client, data_fixture): data_fixture.create_grid_view_field_option(grid_view, public_field, hidden=False) data_fixture.create_grid_view_field_option(grid_view, hidden_field, hidden=True) - # This view sort shouldn't be exposed as it is for a hidden field - data_fixture.create_view_sort(view=grid_view, field=hidden_field, order="ASC") + hidden_sort = data_fixture.create_view_sort( + view=grid_view, field=hidden_field, order="ASC" + ) visible_sort = data_fixture.create_view_sort( view=grid_view, field=public_field, order="DESC" ) - # This group by shouldn't be exposed as it is for a hidden field - data_fixture.create_view_group_by(view=grid_view, field=hidden_field, order="ASC") + hidden_group_by = data_fixture.create_view_group_by( + view=grid_view, field=hidden_field, order="ASC" + ) visible_group_by = data_fixture.create_view_group_by( view=grid_view, field=public_field, order="DESC" ) @@ -3281,7 +3283,24 @@ def test_get_public_grid_view(api_client, data_fixture): "workspace_id": PUBLIC_PLACEHOLDER_ENTITY_ID, "db_index": False, "field_constraints": [], - } + }, + { + "id": hidden_field.id, + "table_id": PUBLIC_PLACEHOLDER_ENTITY_ID, + "name": "hidden", + "order": 0, + "primary": False, + "text_default": "", + "type": "text", + "read_only": False, + "description": None, + "immutable_properties": False, + "immutable_type": False, + "database_id": PUBLIC_PLACEHOLDER_ENTITY_ID, + "workspace_id": PUBLIC_PLACEHOLDER_ENTITY_ID, + "db_index": False, + "field_constraints": [], + }, ], "view": { "id": grid_view.slug, @@ -3290,17 +3309,30 @@ def test_get_public_grid_view(api_client, data_fixture): "public": True, "slug": grid_view.slug, "sortings": [ - # Note the sorting for the hidden field is not returned + { + "field": hidden_sort.field.id, + "id": hidden_sort.id, + "order": "ASC", + "type": "default", + "view": grid_view.slug, + }, { "field": visible_sort.field.id, "id": visible_sort.id, "order": "DESC", "type": "default", "view": grid_view.slug, - } + }, ], "group_bys": [ - # Note the group by for the hidden field is not returned + { + "field": hidden_group_by.field.id, + "id": hidden_group_by.id, + "order": "ASC", + "view": grid_view.slug, + "width": 200, + "type": "default", + }, { "field": visible_group_by.field.id, "id": visible_group_by.id, @@ -3308,7 +3340,7 @@ def test_get_public_grid_view(api_client, data_fixture): "view": grid_view.slug, "width": 200, "type": "default", - } + }, ], "table": { "database_id": PUBLIC_PLACEHOLDER_ENTITY_ID, @@ -3875,6 +3907,64 @@ def test_list_rows_public_with_query_param_group_by(api_client, data_fixture): assert response_json["error"] == "ERROR_ORDER_BY_FIELD_NOT_POSSIBLE" +@pytest.mark.django_db +def test_list_rows_public_with_query_param_group_by_hidden_field_with_stored_group_by( + api_client, data_fixture +): + user, token = data_fixture.create_user_and_token() + table = data_fixture.create_database_table(user=user) + public_field = data_fixture.create_text_field(table=table, name="public") + hidden_field = data_fixture.create_text_field(table=table, name="hidden") + grid_view = data_fixture.create_grid_view( + table=table, user=user, public=True, create_options=False + ) + data_fixture.create_grid_view_field_option(grid_view, public_field, hidden=False) + data_fixture.create_grid_view_field_option(grid_view, hidden_field, hidden=True) + + data_fixture.create_view_group_by(view=grid_view, field=hidden_field, order="ASC") + + first_row = RowHandler().create_row( + user, + table, + values={"public": "a", "hidden": "y"}, + user_field_names=True, + ) + second_row = RowHandler().create_row( + user, + table, + values={"public": "b", "hidden": "x"}, + user_field_names=True, + ) + third_row = RowHandler().create_row( + user, + table, + values={"public": "c", "hidden": "y"}, + user_field_names=True, + ) + + url = reverse( + "api:database:views:grid:public_rows", kwargs={"slug": grid_view.slug} + ) + response = api_client.get( + f"{url}?group_by=field_{hidden_field.id}", + ) + response_json = response.json() + assert response.status_code == HTTP_200_OK + assert len(response_json["results"]) == 3 + assert response_json["results"][0]["id"] == second_row.id + assert response_json["results"][1]["id"] == first_row.id + assert response_json["results"][2]["id"] == third_row.id + assert response_json["group_by_metadata"] == { + f"field_{hidden_field.id}": unordered( + [ + {"count": 1, f"field_{hidden_field.id}": "x"}, + {"count": 2, f"field_{hidden_field.id}": "y"}, + ] + ) + } + assert f"field_{hidden_field.id}" in response_json["results"][0] + + @pytest.mark.django_db def test_list_rows_public_with_query_param_group_by_and_empty_order_by( api_client, data_fixture diff --git a/changelog/entries/unreleased/bug/4858_fix_public_view_with_hidden_group_by_field.json b/changelog/entries/unreleased/bug/4858_fix_public_view_with_hidden_group_by_field.json new file mode 100644 index 0000000000..6db420058c --- /dev/null +++ b/changelog/entries/unreleased/bug/4858_fix_public_view_with_hidden_group_by_field.json @@ -0,0 +1,9 @@ +{ + "type": "bug", + "message": "Fix public view with hidden group by field", + "issue_origin": "github", + "issue_number": 4858, + "domain": "database", + "bullet_points": [], + "created_at": "2026-02-24" +} \ No newline at end of file diff --git a/web-frontend/modules/database/components/view/grid/GridViewGroup.vue b/web-frontend/modules/database/components/view/grid/GridViewGroup.vue index 56cd61d65c..64970b4b0d 100644 --- a/web-frontend/modules/database/components/view/grid/GridViewGroup.vue +++ b/web-frontend/modules/database/components/view/grid/GridViewGroup.vue @@ -19,8 +19,8 @@ export default { type: Object, required: true, }, - allFieldsInTable: { - type: Array, + field: { + type: Object, required: true, }, value: { @@ -33,21 +33,9 @@ export default { }, }, computed: { - field() { - return this.getField(this.allFieldsInTable, this.groupBy) - }, groupByComponent() { - return this.getGroupByComponent(this.field, this) - }, - }, - methods: { - getField(allFieldsInTable, groupBy) { - const field = allFieldsInTable.find((f) => f.id === groupBy.field) - return field - }, - getGroupByComponent(field, parent) { - const fieldType = parent.$registry.get('field', field.type) - return fieldType.getGroupByComponent(field) + const fieldType = this.$registry.get('field', this.field.type) + return fieldType.getGroupByComponent(this.field) }, }, } diff --git a/web-frontend/modules/database/components/view/grid/GridViewGroups.vue b/web-frontend/modules/database/components/view/grid/GridViewGroups.vue index df0dbbd537..215237ef61 100644 --- a/web-frontend/modules/database/components/view/grid/GridViewGroups.vue +++ b/web-frontend/modules/database/components/view/grid/GridViewGroups.vue @@ -20,7 +20,7 @@ > @@ -40,14 +40,6 @@ export default { components: { GridViewGroup }, mixins: [gridViewHelpers], props: { - /** - * All the fields in the table, regardless of the visibility, or whether they - * should be rendered. - */ - allFieldsInTable: { - type: Array, - required: true, - }, groupByValueSets: { type: Array, required: true, diff --git a/web-frontend/modules/database/components/view/grid/GridViewHead.vue b/web-frontend/modules/database/components/view/grid/GridViewHead.vue index 89b882cf03..f09c44bb9d 100644 --- a/web-frontend/modules/database/components/view/grid/GridViewHead.vue +++ b/web-frontend/modules/database/components/view/grid/GridViewHead.vue @@ -1,11 +1,10 @@