Skip to content

Commit 17d0860

Browse files
authored
Validate AI field's file field on import (baserow#5196)
1 parent ca3051e commit 17d0860

3 files changed

Lines changed: 216 additions & 4 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "bug",
3+
"message": "AI file field is now validated during import",
4+
"issue_origin": "github",
5+
"issue_number": 3090,
6+
"domain": "database",
7+
"bullet_points": [],
8+
"created_at": "2026-04-14"
9+
}

premium/backend/src/baserow_premium/fields/field_types.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,12 +511,18 @@ def import_serialized(
511511
serialized_values["ai_auto_update"] = False
512512

513513
ai_type = serialized_values.get("ai_generative_ai_type")
514+
generative_ai_type = None
514515
if ai_type is not None:
515516
try:
516-
generative_ai_model_type_registry.get(ai_type)
517+
generative_ai_type = generative_ai_model_type_registry.get(ai_type)
517518
except GenerativeAITypeDoesNotExist:
518519
serialized_values["ai_generative_ai_type"] = None
519520

521+
ai_file_field_id = serialized_values.get("ai_file_field_id")
522+
if generative_ai_type is not None and ai_file_field_id is not None:
523+
if not generative_ai_type.supports_files:
524+
serialized_values["ai_file_field_id"] = None
525+
520526
return super().import_serialized(
521527
table,
522528
serialized_values,
@@ -533,9 +539,25 @@ def after_import_serialized(
533539
):
534540
save = False
535541
if field.ai_file_field_id:
536-
field.ai_file_field_id = id_mapping["database_fields"][
542+
mapped_ai_file_field_id = id_mapping["database_fields"][
537543
field.ai_file_field_id
538544
]
545+
table_model = field_cache.get_model(field.table)
546+
547+
try:
548+
file_field_object = table_model.get_field_object_by_id(
549+
mapped_ai_file_field_id
550+
)
551+
file_field = file_field_object.get("field")
552+
field_type = file_field_object.get("type")
553+
except ValueError:
554+
file_field = None
555+
field_type = None
556+
557+
if field_type and field_type.can_represent_files(file_field):
558+
field.ai_file_field_id = mapped_ai_file_field_id
559+
else:
560+
field.ai_file_field_id = None
539561
save = True
540562

541563
if field.ai_prompt:

premium/backend/tests/baserow_premium_tests/fields/test_ai_field_type.py

Lines changed: 183 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
from unittest.mock import patch
23

34
from django.shortcuts import reverse
@@ -7,14 +8,17 @@
78
from pytest_unordered import unordered
89
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND
910

11+
from baserow.contrib.database.application_types import DatabaseApplicationType
1012
from baserow.contrib.database.fields.dependencies.models import FieldDependency
1113
from baserow.contrib.database.fields.handler import FieldHandler
14+
from baserow.contrib.database.fields.models import FileField
1215
from baserow.contrib.database.fields.registries import field_type_registry
1316
from baserow.contrib.database.fields.utils.deferred_foreign_key_updater import (
1417
DeferredForeignKeyUpdater,
1518
)
1619
from baserow.contrib.database.rows.handler import RowHandler
1720
from baserow.contrib.database.table.handler import TableHandler
21+
from baserow.contrib.database.table.models import Table
1822
from baserow.core.cache import local_cache
1923
from baserow.core.db import specific_iterator
2024
from baserow.core.registries import ImportExportConfig
@@ -743,7 +747,7 @@ def test_duplicate_table_with_ai_field(patched_job_creation, premium_data_fixtur
743747
table=table,
744748
order=2,
745749
name="ai",
746-
ai_generative_ai_type="test_generative_ai",
750+
ai_generative_ai_type="test_generative_ai_with_files",
747751
ai_generative_ai_model="test_1",
748752
ai_file_field=file_field,
749753
ai_prompt=f"concat('test:',get('fields.field_{text_field.id}'))",
@@ -761,7 +765,7 @@ def test_duplicate_table_with_ai_field(patched_job_creation, premium_data_fixtur
761765
duplicated_ai_field = duplicated_fields[2]
762766

763767
assert duplicated_ai_field.name == "ai"
764-
assert duplicated_ai_field.ai_generative_ai_type == "test_generative_ai"
768+
assert duplicated_ai_field.ai_generative_ai_type == "test_generative_ai_with_files"
765769
assert duplicated_ai_field.ai_generative_ai_model == "test_1"
766770
assert duplicated_ai_field.ai_file_field_id == duplicated_file_field.id
767771
assert (
@@ -1415,6 +1419,183 @@ def test_import_serialized_ai_field_missing_ai_generative_ai_type(premium_data_f
14151419
assert imported_field.ai_generative_ai_model == "test_1"
14161420

14171421

1422+
@pytest.mark.django_db
1423+
@pytest.mark.field_ai
1424+
def test_import_serialized_ai_field_file_field_mapped_correctly(
1425+
premium_data_fixture,
1426+
):
1427+
user = premium_data_fixture.create_user()
1428+
database = premium_data_fixture.create_database_application(user=user)
1429+
table = premium_data_fixture.create_database_table(database=database)
1430+
premium_data_fixture.register_fake_generate_ai_type()
1431+
premium_data_fixture.create_text_field(
1432+
table=table, order=0, name="text", primary=True
1433+
)
1434+
file_field = premium_data_fixture.create_file_field(
1435+
table=table, order=0, name="file", primary=True
1436+
)
1437+
ai_field = premium_data_fixture.create_ai_field(
1438+
table=table,
1439+
order=1,
1440+
name="ai",
1441+
ai_generative_ai_type="test_generative_ai_with_files",
1442+
ai_generative_ai_model="test_1",
1443+
ai_prompt="'What is in the file'",
1444+
ai_file_field=file_field,
1445+
)
1446+
serialized = DatabaseApplicationType().export_serialized(
1447+
database, ImportExportConfig(include_permission_data=False)
1448+
)
1449+
serialized = json.loads(json.dumps(serialized))
1450+
new_workspace = premium_data_fixture.create_workspace(user=user)
1451+
1452+
imported_database = DatabaseApplicationType().import_serialized(
1453+
new_workspace,
1454+
serialized,
1455+
ImportExportConfig(include_permission_data=True),
1456+
id_mapping={},
1457+
)
1458+
1459+
imported_table = Table.objects.get(database=imported_database)
1460+
new_ai_field = AIField.objects.get(table=imported_table)
1461+
1462+
assert new_ai_field.ai_file_field is not None
1463+
assert new_ai_field.ai_file_field.id != ai_field.id
1464+
1465+
FileField.objects.get(table=imported_table, id=new_ai_field.ai_file_field.id)
1466+
1467+
1468+
@pytest.mark.django_db
1469+
@pytest.mark.field_ai
1470+
def test_import_serialized_ai_field_file_field_not_correct_field_type(
1471+
premium_data_fixture,
1472+
):
1473+
user = premium_data_fixture.create_user()
1474+
database = premium_data_fixture.create_database_application(user=user)
1475+
table = premium_data_fixture.create_database_table(database=database)
1476+
premium_data_fixture.register_fake_generate_ai_type()
1477+
premium_data_fixture.create_text_field(
1478+
table=table, order=0, name="text", primary=True
1479+
)
1480+
fake_file_field = premium_data_fixture.create_text_field(
1481+
table=table, order=0, name="file", primary=True
1482+
)
1483+
ai_field = premium_data_fixture.create_ai_field(
1484+
table=table,
1485+
order=1,
1486+
name="ai",
1487+
ai_generative_ai_type="test_generative_ai_with_files",
1488+
ai_generative_ai_model="test_1",
1489+
ai_prompt="'What is in the file'",
1490+
ai_file_field=fake_file_field,
1491+
)
1492+
serialized = DatabaseApplicationType().export_serialized(
1493+
database, ImportExportConfig(include_permission_data=False)
1494+
)
1495+
serialized = json.loads(json.dumps(serialized))
1496+
new_workspace = premium_data_fixture.create_workspace(user=user)
1497+
1498+
imported_database = DatabaseApplicationType().import_serialized(
1499+
new_workspace,
1500+
serialized,
1501+
ImportExportConfig(include_permission_data=True),
1502+
id_mapping={},
1503+
)
1504+
1505+
imported_table = Table.objects.get(database=imported_database)
1506+
new_ai_field = AIField.objects.get(table=imported_table)
1507+
1508+
assert new_ai_field.ai_file_field is None
1509+
1510+
1511+
@pytest.mark.django_db
1512+
@pytest.mark.field_ai
1513+
def test_import_serialized_ai_field_file_field_not_in_correct_table(
1514+
premium_data_fixture,
1515+
):
1516+
user = premium_data_fixture.create_user()
1517+
database = premium_data_fixture.create_database_application(user=user)
1518+
table = premium_data_fixture.create_database_table(database=database, name="table1")
1519+
table_2 = premium_data_fixture.create_database_table(database=database)
1520+
premium_data_fixture.register_fake_generate_ai_type()
1521+
premium_data_fixture.create_text_field(
1522+
table=table, order=0, name="text", primary=True
1523+
)
1524+
file_field_wrong_table = premium_data_fixture.create_file_field(
1525+
table=table_2, order=0, name="file", primary=True
1526+
)
1527+
ai_field = premium_data_fixture.create_ai_field(
1528+
table=table,
1529+
order=1,
1530+
name="ai",
1531+
ai_generative_ai_type="test_generative_ai_with_files",
1532+
ai_generative_ai_model="test_1",
1533+
ai_prompt="'What is in the file'",
1534+
ai_file_field=file_field_wrong_table,
1535+
)
1536+
serialized = DatabaseApplicationType().export_serialized(
1537+
database, ImportExportConfig(include_permission_data=False)
1538+
)
1539+
serialized = json.loads(json.dumps(serialized))
1540+
new_workspace = premium_data_fixture.create_workspace(user=user)
1541+
1542+
imported_database = DatabaseApplicationType().import_serialized(
1543+
new_workspace,
1544+
serialized,
1545+
ImportExportConfig(include_permission_data=True),
1546+
id_mapping={},
1547+
)
1548+
1549+
imported_table = Table.objects.get(database=imported_database, name="table1")
1550+
1551+
new_ai_field = AIField.objects.get(table=imported_table)
1552+
1553+
assert new_ai_field.ai_file_field is None
1554+
1555+
1556+
@pytest.mark.django_db
1557+
@pytest.mark.field_ai
1558+
def test_import_serialized_ai_field_file_field_not_supported_by_ai_provider(
1559+
premium_data_fixture,
1560+
):
1561+
user = premium_data_fixture.create_user()
1562+
database = premium_data_fixture.create_database_application(user=user)
1563+
table = premium_data_fixture.create_database_table(database=database)
1564+
premium_data_fixture.register_fake_generate_ai_type()
1565+
premium_data_fixture.create_text_field(
1566+
table=table, order=0, name="text", primary=True
1567+
)
1568+
file_field = premium_data_fixture.create_file_field(
1569+
table=table, order=0, name="file", primary=True
1570+
)
1571+
ai_field = premium_data_fixture.create_ai_field(
1572+
table=table,
1573+
order=1,
1574+
name="ai",
1575+
ai_generative_ai_type="test_generative_ai",
1576+
ai_generative_ai_model="test_1",
1577+
ai_prompt="'What is in the file'",
1578+
ai_file_field=file_field,
1579+
)
1580+
serialized = DatabaseApplicationType().export_serialized(
1581+
database, ImportExportConfig(include_permission_data=False)
1582+
)
1583+
serialized = json.loads(json.dumps(serialized))
1584+
new_workspace = premium_data_fixture.create_workspace(user=user)
1585+
1586+
imported_database = DatabaseApplicationType().import_serialized(
1587+
new_workspace,
1588+
serialized,
1589+
ImportExportConfig(include_permission_data=True),
1590+
id_mapping={},
1591+
)
1592+
1593+
imported_table = Table.objects.get(database=imported_database)
1594+
new_ai_field = AIField.objects.get(table=imported_table)
1595+
1596+
assert new_ai_field.ai_file_field is None
1597+
1598+
14181599
@pytest.mark.django_db
14191600
@pytest.mark.field_ai
14201601
def test_import_serialized_ai_field_with_auto_update_user(premium_data_fixture):

0 commit comments

Comments
 (0)