바로 어제 포스팅에서 django-pgtrigger
는 어째서 필드별 업데이트 제한 기능을 넣지 않았는가를 설파한지 하루만에
슬프게도
document에 처음부터 떡하니 나와있는 read_only 기능을 사용하면 된다는 것을 깨닫게 되었다.
그리고 pgtrigger는 첫 설치시에만 makemigration이 필요하다.
이후 trigger를 추가할 때에는 그때마다 바로 migrate를 하면 된다.
참고 : https://django-pgtrigger.readthedocs.io/en/latest/cookbook.html#read-only-models-and-fields
이걸 좀 빨리 알았으면 참 좋았을텐데
나는 이미 먼 길을 돌아가서
custom한 class를 만들고 pr까지 날린 뒤였다.
class ProtectUpdateFields(pgtrigger.Protect):
"""
특정 Model field의 update를 막는 trigger
Attributes:
fields: update를 막을 필드들 (tuple)
name: 해당 trigger의 이름 (str)
"""
def __init__(self, *, name=None, fields=None):
self.fields = fields
self.name = name or self.name
self.operation = pgtrigger.Update
if not self.fields:
raise ValueError('Must provide "fields" for ProtectUpdateFields')
super().__init__(name=name, operation=self.operation)
def get_func(self, model):
validation_uri = self._distinct_validation_uri(model)
exception_message = self._exception_message(model)
sql = f'''
IF ({validation_uri}) THEN
RAISE EXCEPTION
'{exception_message}',
TG_TABLE_NAME;
ELSE
RETURN NEW;
END IF;
'''
return sql
def _distinct_validation_uri(self, model):
cols = []
for field in self.fields:
cols.append(model._meta.get_field(field).column)
separator = ' OR '
conditions = []
for col in cols:
conditions.append(f'OLD.{col} IS DISTINCT FROM NEW.{col}')
return separator.join(conditions)
def _exception_message(self, model):
cols = []
for field in self.fields:
cols.append(model._meta.get_field(field).column)
val = []
for field in self.fields:
val.append(field)
val = ', '.join(val)
return f'pgtrigger: Cannot Update {val} from %'
기껏 만들었는데 쓰이지 않은 게 아쉬워 여기에라도 올린다.
사용방법은 아래와 같다.
class MyModel(models.Model):
test_field1 = models.CharField(_('테스트1'), max_length=50)
test_field2 = models.CharField(_('테스트2'), max_length=50)
class Meta:
triggers = [
ProtectFieldsUpdate(
name='protect_update_in_post_fields',
fields=['test_field1', 'test_field2']
)
]
위에서 말했듯 저렇게 커스텀할 필요도 없이,
그냥 django-pgtrigger에서 자체적으로 제공해주는 기능인 read-only trigger를 아래처럼 사용하면 된다.
class TimestampedModel(models.Model):
"""Ensure created_at timestamp is read only"""
created_at = models.DateTimeField(auto_now_add=True)
editable_value = models.TextField()
class Meta:
triggers = [
pgtrigger.ReadOnly(
name="read_only_created_at",
fields=["created_at"]
)
]
- fields: A list of read-only fields.
- exclude: Fields to exclude. All other fields will be read-only.
paramter는 둘 중 하나를 선택해서
만약 특정 몇 개의 필드만 read-only로 만들고 싶으면 fields를 정의하고,
대부분의 필드를 read-only로 만들고 싶으면 exclude를 정의해서 exclude에 넣은 필드를 제외한 나머지 필드들은 모두 read-only로 만들면 된다.
사실 document를 보면서 read-only 기능이 있다는 건 알고있었다.
근데 왜 쓸 생각을 못했느냐...
정말 바보같게도 django-read-only 라이브러리를 사용했던 걸 trigger로 시도했던 걸로 착각했던 것이었다.
django-read-only를 사용하면 기본적으로 생성시에도 필드를 직접 지정할 수 없게 막는다.
생성시에도 작동하게 하려면 enable_writes()
를 호출하여 사용해야하는데 아무튼 뭐.. 많이 다르다.
아마 이후로 read-only쪽은 거들떠도 안 본 것 같은데
pgtrigger.ReadOnly는 operation도 core.Update로 되어있으니 너무 당연하게도 수정될 때만 작동한다.
슬프다.
document를 꼼꼼하게 읽자