1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-10 23:50:12 +00:00

2981 form field condition groups restored on import

This commit is contained in:
Cezary Statkiewicz 2024-10-07 08:28:06 +00:00
parent 9130cbd8e2
commit 4d27dea888
4 changed files with 346 additions and 2 deletions
backend
src/baserow
contrib/database/views
test_utils
tests/baserow/contrib/database/view
changelog/entries/unreleased/bug

View file

@ -1026,6 +1026,7 @@ class FormViewType(ViewType):
"id": condition.id,
"field": condition.field_id,
"type": condition.type,
"group": condition.group_id,
"value": view_filter_type_registry.get(
condition.type
).get_export_serialized_value(condition.value, {}),
@ -1077,6 +1078,7 @@ class FormViewType(ViewType):
id_mapping["database_form_view_field_options"] = {}
condition_objects = []
condition_groups = {}
for field_option in field_options:
field_option_copy = field_option.copy()
field_option_id = field_option_copy.pop("id")
@ -1091,12 +1093,22 @@ class FormViewType(ViewType):
value = view_filter_type_registry.get(
condition["type"]
).set_import_serialized_value(condition["value"], id_mapping)
group = None
if condition["group"] and not (
group := condition_groups.get(condition["group"])
):
group = FormViewFieldOptionsConditionGroup.objects.create(
field_option=field_option_object
)
condition_groups[condition["group"]] = group
condition_objects.append(
FormViewFieldOptionsCondition(
field_option=field_option_object,
field_id=id_mapping["database_fields"][condition["field"]],
type=condition["type"],
value=value,
group=group,
)
)
id_mapping["database_form_view_field_options"][

View file

@ -6,7 +6,7 @@ from datetime import timedelta, timezone
from decimal import Decimal
from ipaddress import ip_network
from socket import AF_INET, AF_INET6, IPPROTO_TCP, SOCK_STREAM
from typing import Any, Dict, List, Optional, Type, Union
from typing import Any, Dict, Generator, List, Optional, Type, Union
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractUser
@ -573,6 +573,68 @@ def load_test_cases(name: str) -> Union[List, Dict]:
return json.load(file)
def counter_int(init: int = 0) -> Generator[int, None, None]:
current = init
while True:
yield current
current += 1
class ReplayValues:
"""
Helper class to test replayability of values. When we do a copy of a structure,
some fields may keep identifiers that should be unique per structure. When a copy
is being created, those identifiers are replaced with other values.
In such case we can't compare values directly, but we know that there's a certain
patter of values. This class helps to match the pattern.
>>> r = ReplayValues()
>>> r.record(None)
>>> r.record(1)
>>> r.record('B')
>>> r.record('C')
>>> r.record('A')
>>> r.record('A')
>>> r.record('B')
>>> assert r.stored == [0,1,2,3,1,1,2]
>>> r.reset()
>>> assert r.stored == []
"""
def __init__(self):
self.reset()
def record(self, value: Any) -> Any:
"""
Record a specific value. If a value wasn't recorded previously a new
placeholder value is generated. If a value has been recorded, existing value
is reused.
:param value:
:return: value
"""
try:
prev_idx = self.recorded.index(value)
stored = self.stored[prev_idx]
except ValueError:
stored = next(self.i)
self.stored.append(stored)
self.recorded.append(value)
return value
def reset(self):
"""
Resets the state.
:return:
"""
self.stored = []
self.recorded = []
self.i = iter(counter_int())
def get_dispatch_context(
url, data_fixture, api_request_factory, builder, page, data=None
):

View file

@ -8,6 +8,10 @@ from django.core.files.storage import FileSystemStorage
import pytest
from baserow.contrib.database.fields.field_filters import (
FILTER_TYPE_AND,
FILTER_TYPE_OR,
)
from baserow.contrib.database.fields.handler import FieldHandler
from baserow.contrib.database.views.handler import ViewHandler
from baserow.contrib.database.views.models import GalleryViewFieldOptions
@ -20,6 +24,7 @@ from baserow.contrib.database.views.view_ownership_types import (
)
from baserow.core.models import WorkspaceUser
from baserow.core.user_files.handler import UserFileHandler
from baserow.test_utils.helpers import ReplayValues, is_dict_subset
@pytest.mark.django_db
@ -319,13 +324,14 @@ def test_import_export_form_view(data_fixture, tmpdir):
order=1,
)
condition = data_fixture.create_form_view_field_options_condition(
field_option=field_option, field=text_field
field_option=field_option, field=text_field, group=None
)
condition_2 = data_fixture.create_form_view_field_options_condition(
field_option=field_option,
field=text_field,
type="multiple_select_has",
value="1",
group=None,
)
files_buffer = BytesIO()
@ -369,12 +375,14 @@ def test_import_export_form_view(data_fixture, tmpdir):
"id": condition.id,
"field": condition.field_id,
"type": condition.type,
"group": None,
"value": condition.value,
},
{
"id": condition_2.id,
"field": condition_2.field_id,
"type": condition_2.type,
"group": None,
"value": condition_2.value,
},
]
@ -436,6 +444,261 @@ def test_import_export_form_view(data_fixture, tmpdir):
assert imported_field_option_condition_2.value == "2"
@pytest.mark.django_db
def test_import_export_form_view_with_grouped_conditions(data_fixture, tmpdir):
user = data_fixture.create_user()
storage = FileSystemStorage(location=str(tmpdir), base_url="http://localhost")
handler = UserFileHandler()
user_file = handler.upload_user_file(
user, "test.jpg", ContentFile(b"Hello World"), storage=storage
)
table = data_fixture.create_database_table(user=user)
form_view = data_fixture.create_form_view(
table=table,
slug="public-test-slug",
public=True,
title="Title",
description="Description",
cover_image=user_file,
logo_image=user_file,
submit_text="My Submit",
submit_action="REDIRECT",
submit_action_message="TEst message",
submit_action_redirect_url="https://localhost",
)
text_field = data_fixture.create_text_field(table=table)
bool_field = data_fixture.create_boolean_field(table=table)
imported_text_field = data_fixture.create_text_field(table=form_view.table)
imported_bool_field = data_fixture.create_boolean_field(table=form_view.table)
# use ReplayValues helper to check for group patterns. Each condition group will
# have a different id after export/import, but the distribution will be similar
r_groups = ReplayValues()
field_option_text = data_fixture.create_form_view_field_option(
form_view,
text_field,
required=True,
enabled=True,
name="Test name",
description="Field description",
order=1,
)
field_option_bool = data_fixture.create_form_view_field_option(
form_view,
bool_field,
required=True,
enabled=True,
name="Bool field",
description="Field description",
condition_type=FILTER_TYPE_OR,
order=2,
)
condition_group = data_fixture.create_form_view_field_options_condition_group(
user=user, field_option=field_option_text
)
condition_group_2 = data_fixture.create_form_view_field_options_condition_group(
user=user, field_option=field_option_text
)
condition_group_3 = data_fixture.create_form_view_field_options_condition_group(
user=user, field_option=field_option_bool
)
condition = data_fixture.create_form_view_field_options_condition(
field_option=field_option_text, field=text_field, group=r_groups.record(None)
)
condition_2 = data_fixture.create_form_view_field_options_condition(
field_option=field_option_text,
field=text_field,
type="multiple_select_has",
value="1",
group=r_groups.record(condition_group),
)
condition_3 = data_fixture.create_form_view_field_options_condition(
field_option=field_option_text,
field=text_field,
type="contains_not",
value="2",
group=r_groups.record(condition_group_2),
)
condition_4 = data_fixture.create_form_view_field_options_condition(
field_option=field_option_text,
field=text_field,
type="contains_not",
value="3",
group=r_groups.record(condition_group_2),
)
condition_5 = data_fixture.create_form_view_field_options_condition(
field_option=field_option_bool,
field=text_field,
type="contains",
value="4",
group=r_groups.record(condition_group_3),
)
condition_6 = data_fixture.create_form_view_field_options_condition(
field_option=field_option_bool,
field=text_field,
type="contains",
value="5",
group=r_groups.record(condition_group_3),
)
recorded_groups_created = r_groups.stored
r_groups.reset()
files_buffer = BytesIO()
form_view_type = view_type_registry.get("form")
with ZipFile(files_buffer, "a", ZIP_DEFLATED, False) as files_zip:
serialized = form_view_type.export_serialized(
form_view, None, files_zip=files_zip, storage=storage
)
assert serialized["id"] == form_view.id
assert serialized["type"] == "form"
assert serialized["name"] == form_view.name
assert serialized["order"] == 0
assert "slug" not in serialized
assert serialized["public"] == form_view.public
assert serialized["title"] == form_view.title
assert serialized["cover_image"] == {
"name": form_view.cover_image.name,
"original_name": form_view.cover_image.original_name,
}
assert serialized["logo_image"] == {
"name": form_view.logo_image.name,
"original_name": form_view.logo_image.original_name,
}
assert serialized["submit_text"] == form_view.submit_text
assert serialized["submit_action"] == form_view.submit_action
assert serialized["submit_action_message"] == form_view.submit_action_message
assert (
serialized["submit_action_redirect_url"] == form_view.submit_action_redirect_url
)
assert len(serialized["field_options"]) == 2
assert serialized["field_options"][0]["id"] == field_option_text.id
assert serialized["field_options"][0]["field_id"] == field_option_text.field_id
assert serialized["field_options"][0]["name"] == field_option_text.name
assert (
serialized["field_options"][0]["description"] == field_option_text.description
)
assert serialized["field_options"][0]["enabled"] == field_option_text.enabled
assert serialized["field_options"][0]["required"] == field_option_text.required
assert serialized["field_options"][0]["condition_type"] == FILTER_TYPE_AND
assert serialized["field_options"][0]["conditions"] == [
{
"id": condition.id,
"field": condition.field_id,
"type": condition.type,
"group": r_groups.record(None),
"value": condition.value,
},
{
"id": condition_2.id,
"field": condition_2.field_id,
"type": condition_2.type,
"group": r_groups.record(condition_group.id),
"value": condition_2.value,
},
{
"id": condition_3.id,
"field": condition_3.field_id,
"type": condition_3.type,
"group": r_groups.record(condition_group_2.id),
"value": condition_3.value,
},
{
"id": condition_4.id,
"field": condition_4.field_id,
"type": condition_4.type,
"group": r_groups.record(condition_group_2.id),
"value": condition_4.value,
},
]
assert serialized["field_options"][1]["id"] == field_option_bool.id
assert serialized["field_options"][1]["field_id"] == field_option_bool.field_id
assert serialized["field_options"][1]["name"] == field_option_bool.name
assert (
serialized["field_options"][1]["description"] == field_option_bool.description
)
assert serialized["field_options"][1]["enabled"] == field_option_bool.enabled
assert serialized["field_options"][1]["required"] == field_option_bool.required
assert serialized["field_options"][1]["condition_type"] == FILTER_TYPE_OR
assert serialized["field_options"][1]["conditions"] == [
{
"id": condition_5.id,
"field": condition_5.field_id,
"type": condition_5.type,
"group": r_groups.record(condition_group_3.id),
"value": condition_5.value,
},
{
"id": condition_6.id,
"field": condition_6.field_id,
"type": condition_6.type,
"group": r_groups.record(condition_group_3.id),
"value": condition_6.value,
},
]
assert r_groups.stored == recorded_groups_created
with ZipFile(files_buffer, "r", ZIP_DEFLATED, False) as zip_file:
assert zip_file.read(user_file.name) == b"Hello World"
assert len(zip_file.infolist()) == 1
id_mapping = {
"database_fields": {
text_field.id: imported_text_field.id,
bool_field.id: imported_bool_field.id,
},
"database_field_select_options": {1: 2},
}
with ZipFile(files_buffer, "a", ZIP_DEFLATED, False) as files_zip:
imported_form_view = form_view_type.import_serialized(
form_view.table, serialized, id_mapping, files_zip, storage
)
form_expected = {
"name": imported_form_view.name,
"order": imported_form_view.order,
"slug": form_view.slug,
}
assert form_view.id != imported_form_view.id
assert form_view.slug != imported_form_view.slug
assert is_dict_subset(form_expected, vars(form_view))
imported_field_options = imported_form_view.get_field_options()
assert len(imported_field_options) == 2
fields_and_options = [field_option_text, field_option_bool]
r_groups.reset()
for source_option_field, imported_field_option in zip(
fields_and_options, imported_field_options
):
field_expected = {
k: getattr(source_option_field, k)
for k in ["name", "description", "enabled", "required", "order"]
}
assert source_option_field.id != imported_field_option.id
assert is_dict_subset(field_expected, vars(imported_field_option))
imported_field_option_conditions = imported_field_option.conditions.all()
for cond in imported_field_option_conditions:
r_groups.record(cond.group)
assert r_groups.stored == recorded_groups_created
@pytest.mark.django_db
def test_import_export_view_ownership_type(data_fixture):
workspace = data_fixture.create_workspace()

View file

@ -0,0 +1,7 @@
{
"type": "bug",
"message": "Form field condition groups restored on export/import",
"issue_number": 2981,
"bullet_points": [],
"created_at": "2024-10-01"
}