| 2479 | @skipUnlessDBFeature( |
| 2480 | "supports_column_check_constraints", "can_introspect_check_constraints" |
| 2481 | ) |
| 2482 | @isolate_apps("schema") |
| 2483 | def test_field_custom_constraint_detected_in_alter_field(self): |
| 2484 | class CharChoiceField(CharField): |
| 2485 | """ |
| 2486 | A custom CharField that automatically creates a db constraint to guarante |
| 2487 | that the stored value respects the field's `choices`. |
| 2488 | """ |
| 2489 | @property |
| 2490 | def non_db_attrs(self): |
| 2491 | # Remove `choices` from non_db_attrs so that migrations that only change |
| 2492 | # choices still trigger a db operation and drop/create the constraint. |
| 2493 | attrs = super().non_db_attrs |
| 2494 | return tuple({*attrs} - {"choices"}) |
| 2495 | |
| 2496 | def db_check(self, connection): |
| 2497 | if not self.choices: |
| 2498 | return None |
| 2499 | constraint = CheckConstraint( |
| 2500 | condition=Q(**{f"{self.name}__in": dict(self.choices)}), |
| 2501 | name="", # doesn't matter, Django will reassign one anyway |
| 2502 | ) |
| 2503 | with connection.schema_editor() as schema_editor: |
| 2504 | return constraint._get_check_sql(self.model, schema_editor) |
| 2505 | |
| 2506 | class ModelWithCustomField(Model): |
| 2507 | f = CharChoiceField(choices=[]) |
| 2508 | |
| 2509 | class Meta: |
| 2510 | app_label = "schema" |
| 2511 | |
| 2512 | self.isolated_local_models = [ModelWithCustomField] |
| 2513 | with connection.schema_editor() as editor: |
| 2514 | editor.create_model(ModelWithCustomField) |
| 2515 | |
| 2516 | constraints = self.get_constraints(ModelWithCustomField._meta.db_table) |
| 2517 | self.assertEqual( |
| 2518 | len(constraints), |
| 2519 | 1, # just the pk constraint |
| 2520 | ) |
| 2521 | |
| 2522 | old_field = ModelWithCustomField._meta.get_field("f") |
| 2523 | new_field = CharChoiceField(choices=[("a", "a")]) |
| 2524 | new_field.contribute_to_class(ModelWithCustomField, "f") |
| 2525 | with connection.schema_editor() as editor: |
| 2526 | editor.alter_field(ModelWithCustomField, old_field, new_field, strict=True) |
| 2527 | |
| 2528 | constraints = self.get_constraints(ModelWithCustomField._meta.db_table) |
| 2529 | self.assertEqual( |
| 2530 | len(constraints), |
| 2531 | 2, # pk + custom constraint |
| 2532 | ) |
| 2533 | |