在我的模型中我有:

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__方法。模型,以便保留原始值的副本。这使得您不必再执行另一个DB查找(这总是一件好事)。

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

        __original_name = None

        def __init__(self, *args, **kwargs):
            super().__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().save(force_insert, force_update, *args, **kwargs)
            self.__original_name = self.name

其他回答

改进所有领域的@josh答案:

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

def __init__(self, *args, **kwargs):
    super(Person, self).__init__(*args, **kwargs)
    self._original_fields = dict([(field.attname, getattr(self, field.attname))
        for field in self._meta.local_fields if not isinstance(field, models.ForeignKey)])

def save(self, *args, **kwargs):
  if self.id:
    for field in self._meta.local_fields:
      if not isinstance(field, models.ForeignKey) and\
        self._original_fields[field.name] != getattr(self, field.name):
        # Do Something    
  super(Person, self).save(*args, **kwargs)

澄清一下,getattr的工作是获取像person.name这样带字符串的字段(即getattr(person, "name"))

这是Chris Pratt的答案的一个版本,通过使用事务块和select_for_update()来防止竞争条件,同时牺牲性能。

@receiver(pre_save, sender=MyModel)
@transaction.atomic
def do_something_if_changed(sender, instance, **kwargs):
    try:
        obj = sender.objects.select_for_update().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

我使用以下mixin:

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)
>>>

Note

请注意,此解决方案仅在当前请求的上下文中工作良好。因此,它主要适用于简单的情况。在并发环境中,多个请求可以同时操作同一个模型实例,您肯定需要一种不同的方法。

有时我想检查多个共享这些字段的模型上相同特定字段的更改,因此我定义了这些字段的列表并使用一个信号。在这种情况下,只有当某些东西发生了变化,或者条目是新的时,地理编码才会寻址:

from django.db.models.signals import pre_save
from django.dispatch import receiver

@receiver(pre_save, sender=SomeUserProfileModel)
@receiver(pre_save, sender=SomePlaceModel)
@receiver(pre_save, sender=SomeOrganizationModel)
@receiver(pre_save, sender=SomeContactInfoModel)
def geocode_address(sender, instance, *args, **kwargs):

    input_fields = ['address_line', 'address_line_2', 'city', 'state', 'postal_code', 'country']

    try:
        orig = sender.objects.get(id=instance.id)
        if orig:
            changes = 0
            for field in input_fields:
                if not (getattr(instance, field)) == (getattr(orig, field)):
                    changes += 1
            if changes > 0:
                # do something here because at least one field changed...
                my_geocoder_function(instance)
    except:
        # do something here because there is no original, or pass.
        my_geocoder_function(instance)

只编写一次并使用“@receiver”附加当然胜过重写多个模型保存方法,但也许其他人有更好的想法。

注意,字段更改跟踪在django-model-utils中是可用的。

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