Skip to content

Commit d486b36

Browse files
committed
feat: deprecate implicit on_delete default in FK/O2O/M2M fields
Add a DeprecationWarning when `on_delete` is not explicitly passed to `ForeignKeyField`, `OneToOneField`, or `ManyToManyField`. The current silent default of `CASCADE` is dangerous as it can cause unintended cascade deletes. Users should pass `on_delete` explicitly. A sentinel `_UNSET` is used to distinguish "not passed" from a real `CASCADE` value, so existing code continues to work but emits a warning. Fixes #1801
1 parent 9f42589 commit d486b36

1 file changed

Lines changed: 24 additions & 6 deletions

File tree

tortoise/fields/relational.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
MODEL = TypeVar("MODEL", bound="Model")
1818

19+
_UNSET: object = object() # sentinel for detecting omitted on_delete
20+
1921

2022
class _NoneAwaitable:
2123
__slots__ = ()
@@ -317,13 +319,21 @@ def __init__(
317319
self,
318320
model_name: type[Model] | str,
319321
related_name: str | None | Literal[False] = None,
320-
on_delete: OnDelete = CASCADE,
322+
on_delete: OnDelete = _UNSET, # type: ignore[assignment]
321323
**kwargs: Any,
322324
) -> None:
323325
super().__init__(None, **kwargs) # type:ignore[arg-type]
324326
self.validate_model_name(model_name)
325327
self.model_name = model_name
326328
self.related_name = related_name
329+
if on_delete is _UNSET:
330+
warnings.warn(
331+
"Not passing `on_delete` to ForeignKeyField is deprecated and will be an error "
332+
"in a future release. Pass `on_delete` explicitly (e.g. on_delete=fields.CASCADE).",
333+
DeprecationWarning,
334+
stacklevel=3,
335+
)
336+
on_delete = CASCADE
327337
if on_delete not in set(OnDelete):
328338
raise ConfigurationError(
329339
"on_delete can only be CASCADE, RESTRICT, SET_NULL, SET_DEFAULT or NO_ACTION"
@@ -360,7 +370,7 @@ def __init__(
360370
self,
361371
model_name: type[MODEL] | str,
362372
related_name: str | None | Literal[False] = None,
363-
on_delete: OnDelete = CASCADE,
373+
on_delete: OnDelete = _UNSET, # type: ignore[assignment]
364374
**kwargs: Any,
365375
) -> None:
366376
super().__init__(model_name, related_name, on_delete, unique=True, **kwargs)
@@ -386,7 +396,7 @@ def __init__(
386396
forward_key: str | None = None,
387397
backward_key: str = "",
388398
related_name: str = "",
389-
on_delete: OnDelete = CASCADE,
399+
on_delete: OnDelete = _UNSET, # type: ignore[assignment]
390400
field_type: type[MODEL] = None, # type: ignore
391401
unique: bool = True,
392402
**kwargs: Any,
@@ -400,6 +410,14 @@ def __init__(
400410
stacklevel=2,
401411
)
402412
unique = kwargs.pop("create_unique_index")
413+
if on_delete is _UNSET:
414+
warnings.warn(
415+
"Not passing `on_delete` to ManyToManyField is deprecated and will be an error "
416+
"in a future release. Pass `on_delete` explicitly (e.g. on_delete=fields.CASCADE).",
417+
DeprecationWarning,
418+
stacklevel=3,
419+
)
420+
on_delete = CASCADE
403421
super().__init__(field_type, unique=unique, **kwargs)
404422
self.validate_model_name(model_name)
405423
self.model_name = model_name
@@ -459,7 +477,7 @@ def OneToOneField(
459477
def OneToOneField(
460478
to: type[MODEL] | str,
461479
related_name: str | None | Literal[False] = None,
462-
on_delete: OnDelete = CASCADE,
480+
on_delete: OnDelete = _UNSET, # type: ignore[assignment]
463481
db_constraint: bool = True,
464482
null: bool = False,
465483
**kwargs: Any,
@@ -534,7 +552,7 @@ def ForeignKeyField(
534552
def ForeignKeyField(
535553
to: type[MODEL] | str,
536554
related_name: str | None | Literal[False] = None,
537-
on_delete: OnDelete = CASCADE,
555+
on_delete: OnDelete = _UNSET, # type: ignore[assignment]
538556
db_constraint: bool = True,
539557
null: bool = False,
540558
**kwargs: Any,
@@ -589,7 +607,7 @@ def ManyToManyField(
589607
forward_key: str | None = None,
590608
backward_key: str = "",
591609
related_name: str = "",
592-
on_delete: OnDelete = CASCADE,
610+
on_delete: OnDelete = _UNSET, # type: ignore[assignment]
593611
db_constraint: bool = True,
594612
unique: bool = True,
595613
**kwargs: Any,

0 commit comments

Comments
 (0)