Skip to content

Commit f6a7d30

Browse files
authored
fix: bug in public view with hidden group by (baserow#4859)
* Fix public view with hidden group by * Show group by feature in PublicView when fields are hidden * Review feedback * Review feedback v2
1 parent 52a5188 commit f6a7d30

14 files changed

Lines changed: 214 additions & 51 deletions

File tree

backend/src/baserow/contrib/database/api/views/views.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2122,11 +2122,14 @@ def get(self, request: Request, slug: str) -> Response:
21222122
raise ViewDoesNotExist()
21232123

21242124
field_options = view_type.get_visible_field_options_in_order(view_specific)
2125+
ordered_field_ids = list(field_options.values_list("field_id", flat=True))
21252126
fields = specific_iterator(
2126-
Field.objects.filter(id__in=field_options.values_list("field_id"))
2127+
Field.objects.filter(id__in=ordered_field_ids)
21272128
.select_related("content_type")
21282129
.prefetch_related("select_options")
21292130
)
2131+
field_id_order = {fid: idx for idx, fid in enumerate(ordered_field_ids)}
2132+
fields = sorted(fields, key=lambda f: field_id_order.get(f.id, 0))
21302133

21312134
return Response(
21322135
PublicViewInfoSerializer(

backend/src/baserow/contrib/database/views/view_types.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,12 @@ def after_fields_type_change(self, fields):
263263
)
264264

265265
def get_visible_field_options_in_order(self, grid_view):
266+
group_by_field_ids = grid_view.viewgroupby_set.values_list(
267+
"field_id", flat=True
268+
)
266269
return (
267270
grid_view.get_field_options(create_if_missing=True)
268-
.filter(hidden=False)
271+
.filter(Q(hidden=False) | Q(field_id__in=group_by_field_ids))
269272
.order_by("-field__primary", "order", "field__id")
270273
)
271274

backend/src/baserow/contrib/database/ws/public/views/signals.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,23 @@ def public_view_filter_deleted(sender, view_filter_id, view_filter, user, **kwar
7777
_send_force_rows_refresh_if_view_public(view_filter.view)
7878

7979

80+
@receiver(view_signals.view_group_by_created)
81+
def public_view_group_by_created(sender, view_group_by, user, **kwargs):
82+
_send_force_view_refresh_if_view_public(view_group_by.view)
83+
84+
85+
@receiver(view_signals.view_group_by_updated)
86+
def public_view_group_by_updated(sender, view_group_by, user, **kwargs):
87+
_send_force_view_refresh_if_view_public(view_group_by.view)
88+
89+
90+
@receiver(view_signals.view_group_by_deleted)
91+
def public_view_group_by_deleted(
92+
sender, view_group_by_id, view_group_by, user, **kwargs
93+
):
94+
_send_force_view_refresh_if_view_public(view_group_by.view)
95+
96+
8097
@receiver(view_signals.view_field_options_updated)
8198
def public_view_field_options_updated(sender, view, user, **kwargs):
8299
_send_force_view_refresh_if_view_public(view)

backend/tests/baserow/contrib/database/api/views/grid/test_grid_view_views.py

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3236,14 +3236,16 @@ def test_get_public_grid_view(api_client, data_fixture):
32363236
data_fixture.create_grid_view_field_option(grid_view, public_field, hidden=False)
32373237
data_fixture.create_grid_view_field_option(grid_view, hidden_field, hidden=True)
32383238

3239-
# This view sort shouldn't be exposed as it is for a hidden field
3240-
data_fixture.create_view_sort(view=grid_view, field=hidden_field, order="ASC")
3239+
hidden_sort = data_fixture.create_view_sort(
3240+
view=grid_view, field=hidden_field, order="ASC"
3241+
)
32413242
visible_sort = data_fixture.create_view_sort(
32423243
view=grid_view, field=public_field, order="DESC"
32433244
)
32443245

3245-
# This group by shouldn't be exposed as it is for a hidden field
3246-
data_fixture.create_view_group_by(view=grid_view, field=hidden_field, order="ASC")
3246+
hidden_group_by = data_fixture.create_view_group_by(
3247+
view=grid_view, field=hidden_field, order="ASC"
3248+
)
32473249
visible_group_by = data_fixture.create_view_group_by(
32483250
view=grid_view, field=public_field, order="DESC"
32493251
)
@@ -3281,7 +3283,24 @@ def test_get_public_grid_view(api_client, data_fixture):
32813283
"workspace_id": PUBLIC_PLACEHOLDER_ENTITY_ID,
32823284
"db_index": False,
32833285
"field_constraints": [],
3284-
}
3286+
},
3287+
{
3288+
"id": hidden_field.id,
3289+
"table_id": PUBLIC_PLACEHOLDER_ENTITY_ID,
3290+
"name": "hidden",
3291+
"order": 0,
3292+
"primary": False,
3293+
"text_default": "",
3294+
"type": "text",
3295+
"read_only": False,
3296+
"description": None,
3297+
"immutable_properties": False,
3298+
"immutable_type": False,
3299+
"database_id": PUBLIC_PLACEHOLDER_ENTITY_ID,
3300+
"workspace_id": PUBLIC_PLACEHOLDER_ENTITY_ID,
3301+
"db_index": False,
3302+
"field_constraints": [],
3303+
},
32853304
],
32863305
"view": {
32873306
"id": grid_view.slug,
@@ -3290,25 +3309,38 @@ def test_get_public_grid_view(api_client, data_fixture):
32903309
"public": True,
32913310
"slug": grid_view.slug,
32923311
"sortings": [
3293-
# Note the sorting for the hidden field is not returned
3312+
{
3313+
"field": hidden_sort.field.id,
3314+
"id": hidden_sort.id,
3315+
"order": "ASC",
3316+
"type": "default",
3317+
"view": grid_view.slug,
3318+
},
32943319
{
32953320
"field": visible_sort.field.id,
32963321
"id": visible_sort.id,
32973322
"order": "DESC",
32983323
"type": "default",
32993324
"view": grid_view.slug,
3300-
}
3325+
},
33013326
],
33023327
"group_bys": [
3303-
# Note the group by for the hidden field is not returned
3328+
{
3329+
"field": hidden_group_by.field.id,
3330+
"id": hidden_group_by.id,
3331+
"order": "ASC",
3332+
"view": grid_view.slug,
3333+
"width": 200,
3334+
"type": "default",
3335+
},
33043336
{
33053337
"field": visible_group_by.field.id,
33063338
"id": visible_group_by.id,
33073339
"order": "DESC",
33083340
"view": grid_view.slug,
33093341
"width": 200,
33103342
"type": "default",
3311-
}
3343+
},
33123344
],
33133345
"table": {
33143346
"database_id": PUBLIC_PLACEHOLDER_ENTITY_ID,
@@ -3875,6 +3907,64 @@ def test_list_rows_public_with_query_param_group_by(api_client, data_fixture):
38753907
assert response_json["error"] == "ERROR_ORDER_BY_FIELD_NOT_POSSIBLE"
38763908

38773909

3910+
@pytest.mark.django_db
3911+
def test_list_rows_public_with_query_param_group_by_hidden_field_with_stored_group_by(
3912+
api_client, data_fixture
3913+
):
3914+
user, token = data_fixture.create_user_and_token()
3915+
table = data_fixture.create_database_table(user=user)
3916+
public_field = data_fixture.create_text_field(table=table, name="public")
3917+
hidden_field = data_fixture.create_text_field(table=table, name="hidden")
3918+
grid_view = data_fixture.create_grid_view(
3919+
table=table, user=user, public=True, create_options=False
3920+
)
3921+
data_fixture.create_grid_view_field_option(grid_view, public_field, hidden=False)
3922+
data_fixture.create_grid_view_field_option(grid_view, hidden_field, hidden=True)
3923+
3924+
data_fixture.create_view_group_by(view=grid_view, field=hidden_field, order="ASC")
3925+
3926+
first_row = RowHandler().create_row(
3927+
user,
3928+
table,
3929+
values={"public": "a", "hidden": "y"},
3930+
user_field_names=True,
3931+
)
3932+
second_row = RowHandler().create_row(
3933+
user,
3934+
table,
3935+
values={"public": "b", "hidden": "x"},
3936+
user_field_names=True,
3937+
)
3938+
third_row = RowHandler().create_row(
3939+
user,
3940+
table,
3941+
values={"public": "c", "hidden": "y"},
3942+
user_field_names=True,
3943+
)
3944+
3945+
url = reverse(
3946+
"api:database:views:grid:public_rows", kwargs={"slug": grid_view.slug}
3947+
)
3948+
response = api_client.get(
3949+
f"{url}?group_by=field_{hidden_field.id}",
3950+
)
3951+
response_json = response.json()
3952+
assert response.status_code == HTTP_200_OK
3953+
assert len(response_json["results"]) == 3
3954+
assert response_json["results"][0]["id"] == second_row.id
3955+
assert response_json["results"][1]["id"] == first_row.id
3956+
assert response_json["results"][2]["id"] == third_row.id
3957+
assert response_json["group_by_metadata"] == {
3958+
f"field_{hidden_field.id}": unordered(
3959+
[
3960+
{"count": 1, f"field_{hidden_field.id}": "x"},
3961+
{"count": 2, f"field_{hidden_field.id}": "y"},
3962+
]
3963+
)
3964+
}
3965+
assert f"field_{hidden_field.id}" in response_json["results"][0]
3966+
3967+
38783968
@pytest.mark.django_db
38793969
def test_list_rows_public_with_query_param_group_by_and_empty_order_by(
38803970
api_client, data_fixture
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "bug",
3+
"message": "Fix public view with hidden group by field",
4+
"issue_origin": "github",
5+
"issue_number": 4858,
6+
"domain": "database",
7+
"bullet_points": [],
8+
"created_at": "2026-02-24"
9+
}

web-frontend/modules/database/components/view/grid/GridViewGroup.vue

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ export default {
1919
type: Object,
2020
required: true,
2121
},
22-
allFieldsInTable: {
23-
type: Array,
22+
field: {
23+
type: Object,
2424
required: true,
2525
},
2626
value: {
@@ -33,21 +33,9 @@ export default {
3333
},
3434
},
3535
computed: {
36-
field() {
37-
return this.getField(this.allFieldsInTable, this.groupBy)
38-
},
3936
groupByComponent() {
40-
return this.getGroupByComponent(this.field, this)
41-
},
42-
},
43-
methods: {
44-
getField(allFieldsInTable, groupBy) {
45-
const field = allFieldsInTable.find((f) => f.id === groupBy.field)
46-
return field
47-
},
48-
getGroupByComponent(field, parent) {
49-
const fieldType = parent.$registry.get('field', field.type)
50-
return fieldType.getGroupByComponent(field)
37+
const fieldType = this.$registry.get('field', this.field.type)
38+
return fieldType.getGroupByComponent(this.field)
5139
},
5240
},
5341
}

web-frontend/modules/database/components/view/grid/GridViewGroups.vue

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
>
2121
<GridViewGroup
2222
:group-by="groupBy"
23-
:all-fields-in-table="allFieldsInTable"
23+
:field="getGroupByField(groupBy)"
2424
:value="groupSpan.value"
2525
:count="groupSpan.count"
2626
></GridViewGroup>
@@ -40,14 +40,6 @@ export default {
4040
components: { GridViewGroup },
4141
mixins: [gridViewHelpers],
4242
props: {
43-
/**
44-
* All the fields in the table, regardless of the visibility, or whether they
45-
* should be rendered.
46-
*/
47-
allFieldsInTable: {
48-
type: Array,
49-
required: true,
50-
},
5143
groupByValueSets: {
5244
type: Array,
5345
required: true,

web-frontend/modules/database/components/view/grid/GridViewHead.vue

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
<template>
22
<div class="grid-view__head">
33
<div
4-
v-for="groupBy in includeGroupBy ? activeGroupBys : []"
4+
v-for="{ groupBy, field } in activeGroupBysWithFields"
55
:key="'field-group-' + groupBy.field"
66
class="grid-view__head-group"
77
:style="{ width: groupBy.width + 'px' }"
8-
:set="field = $options.methods.getField(allFieldsInTable, groupBy)"
98
>
109
<div class="grid-view__group-cell">
1110
<div class="grid-view__group-name">
@@ -151,6 +150,15 @@ export default {
151150
},
152151
},
153152
emits: ['dragging', 'field-created', 'refresh'],
153+
computed: {
154+
activeGroupBysWithFields() {
155+
if (!this.includeGroupBy) return []
156+
return this.activeGroupBys.map((groupBy) => ({
157+
groupBy,
158+
field: this.getGroupByField(groupBy),
159+
}))
160+
},
161+
},
154162
methods: {
155163
/**
156164
* After newField is created pressing "insert left" or "insert right" button,
@@ -192,10 +200,6 @@ export default {
192200
onShownCreateFieldContext() {
193201
this.$refs.createFieldContext.showFieldTypesDropdown(this.$el)
194202
},
195-
getField(allFieldsInTable, groupBy) {
196-
const field = allFieldsInTable.find((f) => f.id === groupBy.field)
197-
return field
198-
},
199203
},
200204
}
201205
</script>

web-frontend/modules/database/components/view/grid/GridViewSection.vue

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@
7575
></GridViewPlaceholder>
7676
<GridViewGroups
7777
v-if="includeGroupBy && activeGroupBys.length > 0"
78-
:all-fields-in-table="allFieldsInTable"
7978
:group-by-value-sets="groupByValueSets"
8079
:store-prefix="storePrefix"
8180
></GridViewGroups>
@@ -423,9 +422,7 @@ export default {
423422
return false
424423
}
425424
return groupBys.slice(0, groupByIndex + 1).every((groupBy) => {
426-
const groupByField = this.allFieldsInTable.find(
427-
(f) => f.id === groupBy.field
428-
)
425+
const groupByField = this.getGroupByField(groupBy)
429426
const groupByFieldType = this.$registry.get(
430427
'field',
431428
groupByField.type
@@ -448,9 +445,7 @@ export default {
448445
const groupByFields = groupBys
449446
.slice(0, groupByIndex + 1)
450447
.map((groupBy) => {
451-
return this.allFieldsInTable.find(
452-
(f) => f.id === groupBy.field
453-
)
448+
return this.getGroupByField(groupBy)
454449
})
455450
return fieldValuesAreEqualInObjects(
456451
groupByFields,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<template>
2+
<div class="card-many-to-many__list-wrapper">
3+
<div class="card-many-to-many__list">
4+
<div
5+
v-for="item in value"
6+
:key="item.id"
7+
class="card-many-to-many__item card-link-row"
8+
:class="{
9+
'card-link-row--unnamed': item.value === null || item.value === '',
10+
}"
11+
>
12+
<span class="card-many-to-many__name">
13+
{{ item.value || 'unnamed row ' + item.id }}
14+
</span>
15+
</div>
16+
<div v-if="isTruncated" class="card-many-to-many__item card-link-row">
17+
<span>...</span>
18+
</div>
19+
</div>
20+
</div>
21+
</template>
22+
23+
<script>
24+
import { LINKED_ITEMS_DEFAULT_LOAD_COUNT } from '@baserow/modules/database/constants'
25+
26+
export default {
27+
name: 'GroupByFieldLinkRow',
28+
props: {
29+
value: {
30+
type: Array,
31+
default: () => [],
32+
},
33+
},
34+
computed: {
35+
isTruncated() {
36+
return this.value?.length >= LINKED_ITEMS_DEFAULT_LOAD_COUNT
37+
},
38+
},
39+
}
40+
</script>

0 commit comments

Comments
 (0)