장고에서 모델을 만들때, on_delete=models.CASCADE라고 설정하면 외래 키의 주인 테이블이 drop 되거나, 행이 삭제되거나 했을때 자동으로 삭제되는걸로 생각한다.
아래의 예제를 보면 Product가 삭제되면 그 Product를 참조하고 있는 ProductItem도 같이 삭제될것이다.
class Product(models.Model):
title = models.CharField(max_length=40)
repr_price = models.IntegerField()
status = models.CharField(max_length=20)
def __str__(self):
return f'{self.title}({self.repr_price})'
class ProductItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, null=False)
name = models.CharField(max_length=50)
price = models.IntegerField()
def __str__(self):
return f'{self.name}({self.price})'
하지만 실제 DB 레벨에 CASCADE 설정이 반영되는것은 아니다.
직접 table의 제약 조건을 확인해봐도 on_delete에 No Action인 것을 확인할 수 있다.
실제로 생쿼리로 부모 테이블의 row를 삭제해보면 외래키 에러가 뜬다. DB에서 cascade 설정이 되는건 아닌것이다.
장고에서는 어떻게 삭제 처리할까?
shell_plus —print-sql으로 확인해보았더니 자식 테이블에서 IN절을 사용해 먼저 지운 다음, 부모 테이블에서 row를 지워주고 있었다.
DELETE
FROM "product_item"
WHERE "product_item"."product_id" IN (1)
DELETE
FROM "product"
WHERE "product"."id" IN (1)
실제로 Queryset에서 delete 하는 과정을 관찰해보자
class QuerySet(AltersData):
def delete(self):
"""Delete the records in the current QuerySet."""
...
collector = Collector(using=del_query.db, origin=self)
collector.collect(del_query)
deleted, _rows_count = collector.delete()
# Clear the result cache, in case this QuerySet gets reused.
self._result_cache = None
return deleted, _rows_count
delete 할때 Collection이라는 클래스에서 collect 하는 과정을 거치는데, 여기서 new_objs로 연관된 모델을 같이 불러온다. 여기서 같이 삭제하는것이다. 설명을 보면 삭제할 개체 컬렉션에 부모와 연관된 개체들을 추가한다고 되어있다.
def collect(
self,
objs,
source=None,
nullable=False,
collect_related=True,
source_attr=None,
reverse_dependency=False,
keep_parents=False,
fail_on_restricted=True,
):
"""
Add 'objs' to the collection of objects to be deleted as well as all
parent instances. 'objs' must be a homogeneous iterable collection of
model instances (e.g. a QuerySet). If 'collect_related' is True,
related objects will be handled by their respective on_delete handler.
If the call is the result of a cascade, 'source' should be the model
that caused it and 'nullable' should be set to True, if the relation
can be null.
...
대부분의 경우 쿼리셋을 통해 delete하므로 신경쓸 일 없지만, 어쩌다 로우 쿼리로 삭제하는 경우에는 주의해야한다.
'Django,Python' 카테고리의 다른 글
[Python] Pickle (1) | 2024.04.06 |
---|---|
[Django] Custom Command 만들기 (0) | 2024.04.06 |
[Python] 파이썬의 typing (2) | 2024.03.09 |
[Django] Template script에서 view데이터 사용하기 (1) | 2024.02.24 |
MAMP로 Python CGI 테스트 해보기 (1) | 2024.01.12 |