[django] 저장시 필드가 변경되었는지 어떻게 확인할 수 있습니까?

내 모델에는 다음이 있습니다.

class Alias(MyBaseModel):
    remote_image = models.URLField(max_length=500, null=True, help_text="A URL that is downloaded and cached for the image. Only
 used when the alias is made")
    image = models.ImageField(upload_to='alias', default='alias-default.png', help_text="An image representing the alias")


    def save(self, *args, **kw):
        if (not self.image or self.image.name == 'alias-default.png') and self.remote_image :
            try :
                data = utils.fetch(self.remote_image)
                image = StringIO.StringIO(data)
                image = Image.open(image)
                buf = StringIO.StringIO()
                image.save(buf, format='PNG')
                self.image.save(hashlib.md5(self.string_id).hexdigest() + ".png", ContentFile(buf.getvalue()))
            except IOError :
                pass

처음으로 remote_image변경 사항이 훌륭합니다 .

누군가가 remote_image별칭을 수정했을 때 새 이미지를 가져 오려면 어떻게 해야합니까? 둘째, 원격 이미지를 캐시하는 더 좋은 방법이 있습니까?



답변

기본적으로 원래 값의 사본을 유지하도록 __init__메소드 를 대체하려고 models.Model합니다. 따라서 다른 DB 조회를 수행 할 필요가 없습니다 (항상 좋은 것임).

class Person(models.Model):
    name = models.CharField()

    __original_name = None

    def __init__(self, *args, **kwargs):
        super(Person, self).__init__(*args, **kwargs)
        self.__original_name = self.name

    def save(self, force_insert=False, force_update=False, *args, **kwargs):
        if self.name != self.__original_name:
            # name changed - do something here

        super(Person, self).save(force_insert, force_update, *args, **kwargs)
        self.__original_name = self.name


답변

다음 믹스 인을 사용합니다.

from django.forms.models import model_to_dict


class ModelDiffMixin(object):
    """
    A model mixin that tracks model fields' values and provide some useful api
    to know what fields have been changed.
    """

    def __init__(self, *args, **kwargs):
        super(ModelDiffMixin, self).__init__(*args, **kwargs)
        self.__initial = self._dict

    @property
    def diff(self):
        d1 = self.__initial
        d2 = self._dict
        diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]]
        return dict(diffs)

    @property
    def has_changed(self):
        return bool(self.diff)

    @property
    def changed_fields(self):
        return self.diff.keys()

    def get_field_diff(self, field_name):
        """
        Returns a diff for field if it's changed and None otherwise.
        """
        return self.diff.get(field_name, None)

    def save(self, *args, **kwargs):
        """
        Saves model and set initial state.
        """
        super(ModelDiffMixin, self).save(*args, **kwargs)
        self.__initial = self._dict

    @property
    def _dict(self):
        return model_to_dict(self, fields=[field.name for field in
                             self._meta.fields])

용법:

>>> p = Place()
>>> p.has_changed
False
>>> p.changed_fields
[]
>>> p.rank = 42
>>> p.has_changed
True
>>> p.changed_fields
['rank']
>>> p.diff
{'rank': (0, 42)}
>>> p.categories = [1, 3, 5]
>>> p.diff
{'categories': (None, [1, 3, 5]), 'rank': (0, 42)}
>>> p.get_field_diff('categories')
(None, [1, 3, 5])
>>> p.get_field_diff('rank')
(0, 42)
>>>

노트

이 솔루션은 현재 요청의 상황에서만 잘 작동합니다. 따라서 주로 간단한 경우에 적합합니다. 여러 요청이 동일한 모델 인스턴스를 동시에 조작 할 수있는 동시 환경에서는 반드시 다른 접근 방식이 필요합니다.


답변

가장 좋은 방법은 pre_save신호입니다. 이 질문에 대한 답을 받았을 때 ’09 년에 옵션이되지 않았을 수도 있지만, 오늘 이것을 보는 사람은 다음과 같이해야합니다.

@receiver(pre_save, sender=MyModel)
def do_something_if_changed(sender, instance, **kwargs):
    try:
        obj = sender.objects.get(pk=instance.pk)
    except sender.DoesNotExist:
        pass # Object is new, so field hasn't technically changed, but you may want to do something else here.
    else:
        if not obj.some_field == instance.some_field: # Field has changed
            # do something


답변

이제 직접 답하십시오. 필드 값이 변경되었는지 확인하는 한 가지 방법은 인스턴스를 저장하기 전에 데이터베이스에서 원본 데이터를 가져 오는 것입니다. 이 예제를 고려하십시오.

class MyModel(models.Model):
    f1 = models.CharField(max_length=1)

    def save(self, *args, **kw):
        if self.pk is not None:
            orig = MyModel.objects.get(pk=self.pk)
            if orig.f1 != self.f1:
                print 'f1 changed'
        super(MyModel, self).save(*args, **kw)

양식을 사용할 때도 마찬가지입니다. ModelForm의 clean 또는 save 메소드에서이를 감지 할 수 있습니다.

class MyModelForm(forms.ModelForm):

    def clean(self):
        cleaned_data = super(ProjectForm, self).clean()
        #if self.has_changed():  # new instance or existing updated (form has data to save)
        if self.instance.pk is not None:  # new instance only
            if self.instance.f1 != cleaned_data['f1']:
                print 'f1 changed'
        return cleaned_data

    class Meta:
        model = MyModel
        exclude = []


답변

Django 1.8이 릴리스 되었으므로 from_db 클래스 메소드 를 사용 하여 remote_image의 이전 값을 캐시 할 수 있습니다 . 그런 다음 저장 방법 에서 이전 및 새 필드 값을 비교하여 값이 변경되었는지 확인할 수 있습니다.

@classmethod
def from_db(cls, db, field_names, values):
    new = super(Alias, cls).from_db(db, field_names, values)
    # cache value went from the base
    new._loaded_remote_image = values[field_names.index('remote_image')]
    return new

def save(self, force_insert=False, force_update=False, using=None,
         update_fields=None):
    if (self._state.adding and self.remote_image) or \
        (not self._state.adding and self._loaded_remote_image != self.remote_image):
        # If it is first save and there is no cached remote_image but there is new one, 
        # or the value of remote_image has changed - do your stuff!


답변

필드 변경 내용 추적은 django-model-utils에서 사용할 수 있습니다.

https://django-model-utils.readthedocs.org/en/latest/index.html


답변

양식을 사용하는 경우 양식의 changed_data ( docs )를 사용할 수 있습니다 .

class AliasForm(ModelForm):

    def save(self, commit=True):
        if 'remote_image' in self.changed_data:
            # do things
            remote_image = self.cleaned_data['remote_image']
            do_things(remote_image)
        super(AliasForm, self).save(commit)

    class Meta:
        model = Alias