diff --git a/backend/src/baserow/contrib/database/fields/field_types.py b/backend/src/baserow/contrib/database/fields/field_types.py index 82831793db..fcc210dda0 100755 --- a/backend/src/baserow/contrib/database/fields/field_types.py +++ b/backend/src/baserow/contrib/database/fields/field_types.py @@ -910,16 +910,29 @@ def prepare_value_for_db(self, instance, value): return value def get_serializer_field(self, instance, **kwargs): - return serializers.IntegerField( - **{ - "required": False, - "allow_null": False, - "min_value": 0, - "default": 0, - "max_value": instance.max_value, - **kwargs, - } - ) + required = kwargs.get("required", False) + field_kwargs = { + "required": False, + "allow_null": False, + "min_value": 0, + "default": 0, + "max_value": instance.max_value, + **kwargs, + } + if required: + field_kwargs.pop("default", None) + validators = field_kwargs.get("validators", []) + validators.append(self._rating_required_validator) + field_kwargs["validators"] = validators + return serializers.IntegerField(**field_kwargs) + + @staticmethod + def _rating_required_validator(value): + if value == 0: + raise ValidationError( + "This field is required.", + code="required", + ) def force_same_type_alter_column(self, from_field, to_field): """ diff --git a/backend/tests/baserow/contrib/database/api/views/form/test_form_view_views.py b/backend/tests/baserow/contrib/database/api/views/form/test_form_view_views.py index 6780ff9999..9015393dcc 100644 --- a/backend/tests/baserow/contrib/database/api/views/form/test_form_view_views.py +++ b/backend/tests/baserow/contrib/database/api/views/form/test_form_view_views.py @@ -2995,6 +2995,89 @@ def test_submit_form_view_for_required_number_field_with_0(api_client, data_fixt } +@pytest.mark.django_db +def test_submit_form_view_for_required_rating_field_with_0(api_client, data_fixture): + user, token = data_fixture.create_user_and_token() + table = data_fixture.create_database_table(user=user) + form = data_fixture.create_form_view( + table=table, + submit_action_message="Test", + submit_action_redirect_url="https://baserow.io", + ) + rating_field = data_fixture.create_rating_field(table=table) + data_fixture.create_form_view_field_option( + form, rating_field, required=True, enabled=True, order=2 + ) + + url = reverse("api:database:views:form:submit", kwargs={"slug": form.slug}) + + # Submitting 0 should fail because 0 means "no rating" for a required rating field + response = api_client.post( + url, + {f"field_{rating_field.id}": 0}, + format="json", + HTTP_AUTHORIZATION=f"JWT {token}", + ) + assert response.status_code == HTTP_400_BAD_REQUEST + response_json = response.json() + assert response_json["detail"][f"field_{rating_field.id}"][0]["code"] == "required" + + # Submitting a valid rating should succeed + response = api_client.post( + url, + {f"field_{rating_field.id}": 3}, + format="json", + HTTP_AUTHORIZATION=f"JWT {token}", + ) + assert response.status_code == HTTP_200_OK + response_json = response.json() + assert response_json == { + "row_id": AnyInt(), + "submit_action": "MESSAGE", + "submit_action_message": "Test", + "submit_action_redirect_url": "https://baserow.io", + } + + +@pytest.mark.django_db +def test_submit_form_view_for_non_required_rating_field_with_0( + api_client, data_fixture +): + """ + Submitting 0 should succeed when the rating field is not required. + """ + + user, token = data_fixture.create_user_and_token() + table = data_fixture.create_database_table(user=user) + form = data_fixture.create_form_view( + table=table, + submit_action_message="Test", + submit_action_redirect_url="https://baserow.io", + ) + rating_field = data_fixture.create_rating_field(table=table) + data_fixture.create_form_view_field_option( + form, rating_field, required=False, enabled=True, order=2 + ) + + url = reverse("api:database:views:form:submit", kwargs={"slug": form.slug}) + + response = api_client.post( + url, + {f"field_{rating_field.id}": 0}, + format="json", + HTTP_AUTHORIZATION=f"JWT {token}", + ) + + assert response.status_code == HTTP_200_OK + response_json = response.json() + assert response_json == { + "row_id": AnyInt(), + "submit_action": "MESSAGE", + "submit_action_message": "Test", + "submit_action_redirect_url": "https://baserow.io", + } + + @pytest.mark.django_db def test_upload_file_view(api_client, data_fixture, tmpdir): user, token = data_fixture.create_user_and_token( diff --git a/changelog/entries/unreleased/bug/4985_rating_field_0.json b/changelog/entries/unreleased/bug/4985_rating_field_0.json new file mode 100644 index 0000000000..5905fbb9fb --- /dev/null +++ b/changelog/entries/unreleased/bug/4985_rating_field_0.json @@ -0,0 +1,9 @@ +{ + "type": "bug", + "message": "Rating field now doesn't accept 0 as a valid value in form views when the field is required", + "issue_origin": "github", + "issue_number": 4985, + "domain": "database", + "bullet_points": [], + "created_at": "2026-03-30" +} diff --git a/web-frontend/justfile b/web-frontend/justfile index 0148f15bd3..04300b604c 100644 --- a/web-frontend/justfile +++ b/web-frontend/justfile @@ -102,10 +102,10 @@ test *ARGS: ci-test: yarn test:coverage -# Update Jest snapshots +# Update Vitest snapshots [group('3 - testing')] update-snapshots: - yarn test --update + EXTRA_VITEST_PARAMS="--update" yarn test # ============================================================================= # Development diff --git a/web-frontend/modules/database/components/field/DuplicateFieldModal.vue b/web-frontend/modules/database/components/field/DuplicateFieldModal.vue index 9d72e67201..3ff96d7549 100644 --- a/web-frontend/modules/database/components/field/DuplicateFieldModal.vue +++ b/web-frontend/modules/database/components/field/DuplicateFieldModal.vue @@ -122,6 +122,7 @@ export default { position: 'right', fromField: this.fromField, undoRedoActionGroupId: this.actionGroupId, + visible: true, }) this.onDuplicationEnd() } diff --git a/web-frontend/modules/database/components/field/InsertFieldContext.vue b/web-frontend/modules/database/components/field/InsertFieldContext.vue index 648a7a4ecf..9129cf2e86 100644 --- a/web-frontend/modules/database/components/field/InsertFieldContext.vue +++ b/web-frontend/modules/database/components/field/InsertFieldContext.vue @@ -59,6 +59,7 @@ export default { position: this.position, fromField: this.fromField, undoRedoActionGroupId, + visible: true, // when inserting a new field, it should always be visible }) }, toggle(ref, position) { diff --git a/web-frontend/modules/database/components/row/RowEditFieldRating.vue b/web-frontend/modules/database/components/row/RowEditFieldRating.vue index f13c46aade..52811d6225 100644 --- a/web-frontend/modules/database/components/row/RowEditFieldRating.vue +++ b/web-frontend/modules/database/components/row/RowEditFieldRating.vue @@ -10,6 +10,9 @@ @update="update" /> +