在我的模型中我有:

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时,我如何获取一个新的图像?其次,是否有更好的方法来缓存远程映像?


当前回答

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

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”附加当然胜过重写多个模型保存方法,但也许其他人有更好的想法。

其他回答

我扩展了@livskiy的mixin,如下所示:

class ModelDiffMixin(models.Model):
    """
    A model mixin that tracks model fields' values and provide some useful api
    to know what fields have been changed.
    """
    _dict = DictField(editable=False)
    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.
        """
        object_dict = model_to_dict(self,
               fields=[field.name for field in self._meta.fields])
        for field in object_dict:
            # for FileFields
            if issubclass(object_dict[field].__class__, FieldFile):
                try:
                    object_dict[field] = object_dict[field].path
                except :
                    object_dict[field] = object_dict[field].name

            # TODO: add other non-serializable field types
        self._dict = object_dict
        super(ModelDiffMixin, self).save(*args, **kwargs)

    class Meta:
        abstract = True

DictField为:

class DictField(models.TextField):
    __metaclass__ = models.SubfieldBase
    description = "Stores a python dict"

    def __init__(self, *args, **kwargs):
        super(DictField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        if not value:
            value = {}

        if isinstance(value, dict):
            return value

        return json.loads(value)

    def get_prep_value(self, value):
        if value is None:
            return value
        return json.dumps(value)

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

可以通过在模型中扩展它来使用它 一个_dict字段将在同步/迁移时添加,该字段将存储对象的状态

现在直接回答:检查字段的值是否已经改变的一种方法是在保存实例之前从数据库获取原始数据。想想这个例子:

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 = []

如果您对重写save方法不感兴趣,可以这样做

  model_fields = [f.name for f in YourModel._meta.get_fields()]
  valid_data = {
        key: new_data[key]
        for key in model_fields
        if key in new_data.keys()
  }

  for (key, value) in valid_data.items():
        if getattr(instance, key) != value:
           print ('Data has changed')

        setattr(instance, key, value)

 instance.save()

我对@iperelivskiy的解决方案的看法是:在大规模的情况下,为每个__init__创建_initial字典是昂贵的,而且大多数时候是不必要的。我稍微改变了mixin,这样它只在你显式地告诉它这样做(通过调用instance.track_changes)时才记录更改:

from typing import KeysView, Optional
from django.forms import model_to_dict

class TrackChangesMixin:
    _snapshot: Optional[dict] = None

    def track_changes(self):
        self._snapshot = self.as_dict

    @property
    def diff(self) -> dict:
        if self._snapshot is None:
            raise ValueError("track_changes wasn't called, can't determine diff.")
        d1 = self._snapshot
        d2 = self.as_dict
        diffs = [(k, (v, d2[k])) for k, v in d1.items() if str(v) != str(d2[k])]
        return dict(diffs)

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

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

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

虽然这实际上并没有回答你的问题,但我会以不同的方式来讨论这个问题。

成功保存本地副本后,只需清除remote_image字段。然后在保存方法中,只要remote_image不为空,就可以随时更新图像。

如果您想保留对url的引用,您可以使用一个不可编辑的布尔字段来处理缓存标志,而不是使用remote_image字段本身。