Skip to content

Commit c43ef8e

Browse files
fix: allow enterprise field rules import without a license (baserow#5161)
* fix: allow enterprise field rules import without a license * fix: address copilot feedback * fix: docstring Co-authored-by: dimmur-brw <przemyslaw+gh@baserow.io> --------- Co-authored-by: dimmur-brw <przemyslaw+gh@baserow.io>
1 parent 7e03df6 commit c43ef8e

5 files changed

Lines changed: 83 additions & 4 deletions

File tree

backend/src/baserow/contrib/database/field_rules/handlers.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,28 @@ def create_rule(
180180
self, rule_type_name: str, in_data: dict, primary_key_value: int | None = None
181181
) -> FieldRule:
182182
"""
183-
Creates a rule of a given type.
183+
Creates a rule of a given type after checking rule-type preconditions.
184+
185+
Delegates to `can_create_rule` on the rule type (which may raise if
186+
the required feature or license is unavailable) and then to
187+
`force_create_rule`.
188+
189+
:param rule_type_name: registered rule type name.
190+
:param in_data: a dictionary with all rule params.
191+
:param primary_key_value: (optional) the primary key value for the rule (if
192+
the instance is being restored).
193+
:return: rule instance.
194+
"""
195+
196+
rule_type = self.get_type_handler(rule_type_name)
197+
rule_type.can_create_rule(self.table)
198+
return self.force_create_rule(rule_type_name, in_data, primary_key_value)
199+
200+
def force_create_rule(
201+
self, rule_type_name: str, in_data: dict, primary_key_value: int | None = None
202+
) -> FieldRule:
203+
"""
204+
Creates a rule of a given type without checking rule-type preconditions.
184205
185206
This method creates an instance of a field rule. Field rule type is provided
186207
in `rule_type_name` param. Each field rule type should validate additional
@@ -190,7 +211,7 @@ def create_rule(
190211
This is used in undo/redo operations, because we want to preserve
191212
rule identification.
192213
193-
:param rule_type_name: registered rule type name .
214+
:param rule_type_name: registered rule type name.
194215
:param in_data: a dictionary with all rule params.
195216
:param primary_key_value: (optional) the primary key value for the rule (if
196217
the instance is being restored).
@@ -597,4 +618,4 @@ def import_rule(self, rule_data: dict, id_mapping: dict) -> FieldRule:
597618
rule_type = self.get_type_handler(rule_type_name)
598619

599620
prepared_values = rule_type.prepare_values_for_import(rule_data, id_mapping)
600-
return self.create_rule(rule_type_name, prepared_values)
621+
return self.force_create_rule(rule_type_name, prepared_values)

backend/src/baserow/contrib/database/field_rules/registries.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,16 @@ def prepare_values_for_import(self, rule_data: dict, id_mapping: dict) -> dict:
154154

155155
return rule_data
156156

157+
def can_create_rule(self, table: Table) -> None:
158+
"""
159+
Called before creating a new rule to check if the rule can be created.
160+
161+
Raises an exception if the rule cannot be created (e.g. missing license).
162+
Does nothing by default.
163+
164+
:param table: the table for which the rule is being created
165+
"""
166+
157167
def prepare_values_for_create(self, table, in_data: dict) -> dict:
158168
"""
159169
Called before creating a new rule. Resulting dict should contain
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 template sync failing when importing enterprise field rules without a license.",
4+
"issue_origin": "github",
5+
"issue_number": null,
6+
"domain": "database",
7+
"bullet_points": [],
8+
"created_at": "2026-04-09"
9+
}

enterprise/backend/src/baserow_enterprise/date_dependency/field_rule_types.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,14 +288,16 @@ def _validate_data(self, table: Table, in_data: dict) -> DateDepenencyDict:
288288
)
289289

290290
# lifecycle hooks
291+
def can_create_rule(self, table: Table) -> None:
292+
self.check_license(table)
293+
291294
def prepare_values_for_create(
292295
self, table: Table, in_data: dict
293296
) -> DateDepenencyDict:
294297
"""
295298
Returns a dictionary with values needed to create a new rule.
296299
"""
297300

298-
self.check_license(table)
299301
return self._validate_data(table, in_data)
300302

301303
def prepare_values_for_update(

enterprise/backend/tests/baserow_enterprise_tests/date_dependency/test_date_dependency_handler.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,43 @@ def test_date_dependency_handler_create_rule_serializer(
8888
assert serializer.is_valid(raise_exception=False)
8989

9090

91+
@pytest.mark.django_db
92+
def test_date_dependency_import_rule_without_license(data_fixture):
93+
user = data_fixture.create_user()
94+
table = data_fixture.create_database_table(user=user)
95+
96+
start_date_field = data_fixture.create_date_field(
97+
table=table, name="start_date_field"
98+
)
99+
end_date_field = data_fixture.create_date_field(table=table, name="end_date_field")
100+
duration_field = data_fixture.create_duration_field(
101+
table=table, name="duration_field", duration_format="d h"
102+
)
103+
104+
serialized_rule = {
105+
"type": "date_dependency",
106+
"is_active": True,
107+
"start_date_field_id": start_date_field.id,
108+
"end_date_field_id": end_date_field.id,
109+
"duration_field_id": duration_field.id,
110+
"dependency_linkrow_field_id": None,
111+
"dependency_linkrow_role": "predecessors",
112+
"dependency_connection_type": "end-to-start",
113+
"dependency_buffer_type": "fixed",
114+
"dependency_buffer": 0,
115+
}
116+
id_mapping = {
117+
f.id: f.id for f in [start_date_field, end_date_field, duration_field]
118+
}
119+
120+
handler = FieldRuleHandler(table, user)
121+
rule = handler.import_rule(serialized_rule, id_mapping)
122+
123+
assert rule is not None
124+
assert rule.table == table
125+
assert DateDependency.objects.filter(pk=rule.pk).exists()
126+
127+
91128
@pytest.mark.django_db
92129
def test_date_dependency_handler_create_rule_no_license(data_fixture):
93130
user = data_fixture.create_user()

0 commit comments

Comments
 (0)