Django: При сохранении, как вы можете проверить, изменилось ли поле? - PullRequest
250 голосов
/ 31 августа 2009

В моей модели у меня есть:

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 на псевдониме? А во-вторых, есть ли лучший способ кэширования удаленного изображения?

Ответы [ 23 ]

3 голосов
/ 05 ноября 2015

Это работает для меня в Django 1.8

def clean(self):
    if self.cleaned_data['name'] != self.initial['name']:
        # Do something
3 голосов
/ 04 ноября 2016

Оптимальным решением, вероятно, является решение, которое не включает в себя дополнительную операцию чтения базы данных перед сохранением экземпляра модели или какую-либо дополнительную библиотеку django. Вот почему решения Лаффуста предпочтительнее. В контексте сайта администратора можно просто переопределить метод save_model и вызвать там метод формы has_changed, как и в ответе Сиона выше. Вы получаете что-то вроде этого, опираясь на пример настройки Sion, но используя «updated_data», чтобы получить все возможные изменения:

class ModelAdmin(admin.ModelAdmin):
   fields=['name','mode']
   def save_model(self, request, obj, form, change):
     form.changed_data #output could be ['name']
     #do somethin the changed name value...
     #call the super method
     super(self,ModelAdmin).save_model(request, obj, form, change)
  • Перезаписать save_model:

https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_model

  • Встроенный метод updated_data для поля:

https://docs.djangoproject.com/en/1.10/ref/forms/api/#django.forms.Form.changed_data

3 голосов
/ 17 января 2014

Еще один поздний ответ, но если вы просто пытаетесь увидеть, был ли загружен новый файл в файловое поле, попробуйте это: (адаптировано из комментария Кристофера Адамса по ссылке http://zmsmith.com/2010/05/django-check-if-a-field-has-changed/ в комментарии Заха здесь )

Обновлена ​​ссылка: https://web.archive.org/web/20130101010327/http://zmsmith.com:80/2010/05/django-check-if-a-field-has-changed/

def save(self, *args, **kw):
    from django.core.files.uploadedfile import UploadedFile
    if hasattr(self.image, 'file') and isinstance(self.image.file, UploadedFile) :
        # Handle FileFields as special cases, because the uploaded filename could be
        # the same as the filename that's already there even though there may
        # be different file contents.

        # if a file was just uploaded, the storage model with be UploadedFile
        # Do new file stuff here
        pass
2 голосов
/ 12 сентября 2013

улучшение ответа @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")

2 голосов
/ 07 марта 2013

У меня была такая ситуация до того, как я решил переопределить метод pre_save() класса целевого поля, он будет вызываться только в том случае, если поле было изменено
полезно с FileField пример:

class PDFField(FileField):
    def pre_save(self, model_instance, add):
        # do some operations on your file 
        # if and only if you have changed the filefield

недостаток:
бесполезно, если вы хотите выполнить какую-либо (post_save) операцию, например, использовать созданный объект в каком-либо задании (если определенное поле изменилось)

2 голосов
/ 31 августа 2009

Хотя это на самом деле не отвечает на ваш вопрос, я бы пошел по-другому.

Просто очистите поле remote_image после успешного сохранения локальной копии. Тогда в вашем методе сохранения вы всегда можете обновить изображение, когда remote_image не пусто.

Если вы хотите сохранить ссылку на URL, вы можете использовать не редактируемое логическое поле для обработки флага кэширования, а не само поле remote_image.

1 голос
/ 10 марта 2013

Миксин @livskiy я расширил следующим образом:

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 будет добавлено при синхронизации / миграции, и в этом поле будет храниться состояние ваших объектов

1 голос
/ 24 апреля 2018

Вот еще один способ сделать это.

class Parameter(models.Model):

    def __init__(self, *args, **kwargs):
        super(Parameter, self).__init__(*args, **kwargs)
        self.__original_value = self.value

    def clean(self,*args,**kwargs):
        if self.__original_value == self.value:
            print("igual")
        else:
            print("distinto")

    def save(self,*args,**kwargs):
        self.full_clean()
        return super(Parameter, self).save(*args, **kwargs)
        self.__original_value = self.value

    key = models.CharField(max_length=24, db_index=True, unique=True)
    value = models.CharField(max_length=128)

Согласно документации: проверка объектов

"Второй шаг, который выполняет full_clean (), - это вызов Model.clean (). Этот метод должен быть переопределен для выполнения пользовательской проверки вашей модели. Этот метод должен использоваться для обеспечения пользовательской проверки модели и для изменения атрибутов вашей модели, если это необходимо. Например, вы можете использовать его для автоматического предоставления значения для поля или для проверки, которая требует доступа к более чем одному полю: "

1 голос
/ 22 января 2015

Как насчет использования решения Дэвида Крамера:

http://cramer.io/2010/12/06/tracking-changes-to-fields-in-django/

Я успешно использовал это, как это:

@track_data('name')
class Mode(models.Model):
    name = models.CharField(max_length=5)
    mode = models.CharField(max_length=5)

    def save(self, *args, **kwargs):
        if self.has_changed('name'):
            print 'name changed'

    # OR #

    @classmethod
    def post_save(cls, sender, instance, created, **kwargs):
        if instance.has_changed('name'):
            print "Hooray!"
0 голосов
/ 07 февраля 2017

Модификация ответа @ ivanperelivskiy:

@property
def _dict(self):
    ret = {}
    for field in self._meta.get_fields():
        if isinstance(field, ForeignObjectRel):
            # foreign objects might not have corresponding objects in the database.
            if hasattr(self, field.get_accessor_name()):
                ret[field.get_accessor_name()] = getattr(self, field.get_accessor_name())
            else:
                ret[field.get_accessor_name()] = None
        else:
            ret[field.attname] = getattr(self, field.attname)
    return ret

Вместо этого используется открытый метод django 1.10 get_fields. Это делает код более перспективным для будущего, но, что более важно, также включает внешние ключи и поля, где editable = False.

Для справки, вот реализация .fields

@cached_property
def fields(self):
    """
    Returns a list of all forward fields on the model and its parents,
    excluding ManyToManyFields.

    Private API intended only to be used by Django itself; get_fields()
    combined with filtering of field properties is the public API for
    obtaining this field list.
    """
    # For legacy reasons, the fields property should only contain forward
    # fields that are not private or with a m2m cardinality. Therefore we
    # pass these three filters as filters to the generator.
    # The third lambda is a longwinded way of checking f.related_model - we don't
    # use that property directly because related_model is a cached property,
    # and all the models may not have been loaded yet; we don't want to cache
    # the string reference to the related_model.
    def is_not_an_m2m_field(f):
        return not (f.is_relation and f.many_to_many)

    def is_not_a_generic_relation(f):
        return not (f.is_relation and f.one_to_many)

    def is_not_a_generic_foreign_key(f):
        return not (
            f.is_relation and f.many_to_one and not (hasattr(f.remote_field, 'model') and f.remote_field.model)
        )

    return make_immutable_fields_list(
        "fields",
        (f for f in self._get_fields(reverse=False)
         if is_not_an_m2m_field(f) and is_not_a_generic_relation(f) and is_not_a_generic_foreign_key(f))
    )
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...