Kaydederken bir alanın değişip değişmediğini nasıl kontrol edebilirsiniz?


293

Modelimde:

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

Hangi remote_imagedeğişiklikler ilk kez harika çalışıyor .

Birisi remote_imagetakma adda değişiklik yaptığında nasıl yeni bir resim alabilirim ? İkincisi, uzak bir görüntüyü önbelleğe almanın daha iyi bir yolu var mı?

Yanıtlar:


424

Esasen, orijinal değerin bir kopyasını saklamak için __init__yöntemini geçersiz kılmak istersiniz models.Model. Böylece başka bir DB arama (her zaman iyi bir şey) yapmak zorunda kalmazsınız yapar.

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

24
üzerine yazmak yerine, post_init-signal docs.djangoproject.com/en/dev/ref/signals/#post-init
vikingosegundo

22
Geçersiz kılma yöntemleri Django belgeleri tarafından önerilir: docs.djangoproject.com/en/dev/topics/db/models/…
Colonel Sponsz

10
@callum, böylece nesne üzerinde değişiklik yaparsanız, kaydedin, sonra ek değişiklikler yapın ve save()yeniden TEKRAR çağırın , yine de düzgün çalışacaktır.
philfreo

17
@Josh, aynı veritabanında çalışan ve sadece bellekteki değişiklikleri izleyen birkaç uygulama sunucunuz varsa sorun yaşamayacak
Jens Alm

13
@lajarre, Bence yorumunuz biraz yanıltıcı. Dokümanlar, bunu yaparken dikkatli olmanızı önerir. Buna karşı tavsiye etmiyorlar.
Josh

199

Aşağıdaki mixin kullanın:

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

Kullanımı:

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

Not

Lütfen bu çözümün yalnızca mevcut istek bağlamında iyi çalıştığını unutmayın. Bu nedenle öncelikle basit durumlar için uygundur. Birden fazla isteğin aynı model örneğini aynı anda değiştirebildiği eşzamanlı ortamda, kesinlikle farklı bir yaklaşıma ihtiyacınız vardır.


4
Gerçekten mükemmel ve ekstra sorgu yapmayın. Çok teşekkürler !
Stéphane

28
Kullanarak bir mixin için +1. Ekstra DB isabeti için +1. Birçok yararlı yöntem / özellik için +1. Birden fazla kez oy kullanabilmem gerekiyor.
Jake

Evet. Ayrıca Mixin'i kullanmak için bir tane ve ekstra db vuruşu yok.
David S

2
Mixin harika, ancak .only () ile birlikte kullanıldığında bu sürümde sorunlar var. Model.objects.only ('id') çağrısı, Model'in en az 3 alanı varsa sonsuz özyinelemeye yol açar. Bunu çözmek için, ertelenmiş alanları başlangıçta kaydetmekten kaldırmalı ve _dict özelliğini biraz
gleb.pitsevich

19
Josh'un cevabı gibi, bu kod tek işlemli test sunucunuzda aldatıcı bir şekilde çalışacaktır, ancak herhangi bir çok işlemcili sunucuya dağıttığınız anda yanlış sonuçlar verecektir. Veritabanını sorgulamadan veritabanındaki değeri değiştirip değiştirmediğinizi bilemezsiniz.
rspeer

154

En iyi yol bir pre_savesinyaldir. Bu soru sorulduğunda ve cevaplandığında '09'da bir seçenek olmayabilir, ancak bugün bunu gören herkes bunu şu şekilde yapmalıdır:

@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

6
Josh'un yukarıda tarif ettiği yöntem fazladan bir veritabanı isabeti içermiyorsa neden bu en iyi yoldur?
joshcartme

36
1) bu yöntem bir hack'tir, sinyaller temel olarak bu gibi kullanımlar için tasarlanmıştır 2) bu yöntem modelinizde değişiklik yapılmasını gerektirir, bu 3 değildir) bu cevabın yorumlarında okuyabileceğiniz gibi, yan etkileri vardır potansiyel olarak sorunlu olabilir, bu çözüm değil
Chris Pratt

2
Bu, yalnızca kaydetmeden hemen önce değişikliği yakalamayı önemsiyorsanız harika. Ancak, değişikliğe hemen tepki vermek istiyorsanız bu işe yaramaz. İkinci senaryoya birçok kez rastladım (ve şimdi böyle bir örnek üzerinde çalışıyorum).
Josh

5
@Josh: "Değişime hemen tepki ver" derken ne demek istiyorsun? Bu ne şekilde "tepki vermenize" izin vermiyor?
Chris Pratt

2
Maalesef, bu sorunun kapsamını unuttum ve tamamen farklı bir soruna değiniyordum. Bununla birlikte, sinyallerin buraya gitmek için iyi bir yol olduğunu düşünüyorum (şimdi mevcutlar). Ancak, birçok insanın bir "hack" kurtarmayı geçersiz kılmayı düşünüyor. Durumun bu olduğuna inanmıyorum. Bu cevabın önerdiği gibi ( stackoverflow.com/questions/170337/… ), "söz konusu modele özgü" değişiklikler üzerinde çalışmadığınız zaman geçersiz kılmanın en iyi yöntem olduğunu düşünüyorum. Bununla birlikte, bu inancı kimseye dayatmayı düşünmüyorum.
Josh

138

Ve şimdi doğrudan cevap için: alanın değerinin değişip değişmediğini kontrol etmenin bir yolu örneği kaydetmeden önce veritabanından orijinal verileri almaktır. Bu örneği düşünün:

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)

Aynı şey bir formla çalışırken de geçerlidir. Bir ModelForm'un temiz veya kaydetme yönteminde algılayabilirsiniz:

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

24
Josh'un çözümü çok daha veritabanı dostu. Nelerin değiştiğini doğrulamak için ekstra bir çağrı pahalıdır.
gg.

5
Bir yazma yapmadan önce fazladan bir okuma çok pahalı değil. Birden fazla istek varsa izleme değişiklikleri yöntemi de çalışmaz. Her ne kadar bu, getirme ve kaydetme arasında bir yarış koşulundan muzdarip olsa da.
dalore

1
İnsanlara, pk is not Noneörneğin bir UUIDField kullanıyorsanız, geçerli olmadığını kontrol etmelerini söylemeyi bırakın. Bu sadece kötü bir tavsiye.
user3467349

2
@dalore, kaydetme yöntemini dekore ederek yarış koşullarından kaçınabilirsiniz@transaction.atomic
Frank Pape

2
@dalore, işlem yalıtım düzeyinin yeterli olduğundan emin olmanız gerekir. Postgresql'de varsayılan okuma yapılır, ancak tekrarlanabilir okuma gereklidir .
Frank Pape

58

Django 1.8'in yayımlanmasından bu yana, remote_image'in eski değerini önbelleğe almak için from_db classmethod komutunu kullanabilirsiniz. Daha sonra kaydetme yönteminde, değerin değişip değişmediğini kontrol etmek için alanın eski ve yeni değerini karşılaştırabilirsiniz.

@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!

1
Teşekkürler - işte dokümanlar için bir referans: docs.djangoproject.com/en/1.8/ref/models/instances/… . Bu hala veritabanı değerlendirildiğinde ve karşılaştırma yapıldığında arasında değişebilir yukarıda belirtilen sorunla sonuçlanır inanıyorum, ama bu güzel yeni bir seçenek.
trpt4him

1
Değerler arasında arama yapmak yerine (değer sayısına bağlı olarak O (n) olan) yapmak daha hızlı ve net olmaz mıydı new._loaded_remote_image = new.remote_image?
dalore

1
Ne yazık ki önceki (şimdi silinmiş) yorumumu tersine çevirmem gerekiyor. İken from_dbtarafından çağrılan refresh_from_db, örneğinde nitelikleri (yani yüklenen veya önceki) güncellendi değildir. Bundan daha iyi olmasının bir sonucu olarak, herhangi bir neden bulamıyorum __init__: Hâlâ 3 davalarını gerektiğinden __init__/ from_db, refresh_from_dbve save.
claytond


18

Bir form kullanıyorsanız Form'un de değiştirilen_verisini ( dokümanlar ) kullanabilirsiniz:

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



5

Bu benim için Django 1.8'de çalışıyor

def clean(self):
    if self.cleaned_data['name'] != self.initial['name']:
        # Do something

4

Ek veritabanı araması olmadan bunu yapmak için django-model-change komutlarını kullanabilirsiniz :

from django.dispatch import receiver
from django_model_changes import ChangesMixin

class Alias(ChangesMixin, MyBaseModel):
   # your model

@receiver(pre_save, sender=Alias)
def do_something_if_changed(sender, instance, **kwargs):
    if 'remote_image' in instance.changes():
        # do something

4

Başka bir geç cevap, ancak sadece bir dosya alanına yeni bir dosyanın yüklenip yüklenmediğini görmeye çalışıyorsanız, şunu deneyin: (Christopher Adams'ın http://zmsmith.com/2010/05/django bağlantısındaki yorumundan uyarlanmıştır. -bölüm-eğer-a-alan-değişti / burada zach'ın yorumunda)

Güncel bağlantı: 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

Bu, yeni bir dosyanın yüklenip yüklenmediğini kontrol etmek için harika bir çözümdür. Veritabanına karşı adı kontrol etmekten çok daha iyi çünkü dosyanın adı aynı olabilir. pre_saveAlıcıda da kullanabilirsiniz . Bunu paylaştığın için teşekkürler!
DataGreed

1
Dosya ses bilgilerini okumak için mutagen kullanılarak güncellendiğinde bir veritabanındaki ses süresini güncellemek için bir örnek - gist.github.com/DataGreed/1ba46ca7387950abba2ff53baf70fec2
DataGreed

3

Optimal çözüm muhtemelen model örneğini kaydetmeden önce ek bir veritabanı okuma işlemi veya başka bir django kütüphanesi içermeyen bir çözümdür. Bu yüzden laffuste'nin çözümleri tercih edilir. Bir yönetici sitesi bağlamında, save_model-method'u geçersiz kılabilir ve has_changedyukarıdaki Sion'un cevabında olduğu gibi formun yöntemini orada çağırabilirsiniz . Sion'un örnek ayarını kullanarak ancak changed_dataolası her değişikliği almak için kullanarak böyle bir şeye ulaşırsınız :

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)
  • Geçersiz kıl save_model:

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

  • changed_dataBir alan için yerleşik yöntem:

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


2

Bu aslında sorunuza cevap vermese de, bunu farklı bir şekilde ele alırdım.

remote_imageYerel kopyayı başarıyla kaydettikten sonra alanı temizleyin . Ardından kaydetme yönteminizde remote_image, boş olmadığında görüntüyü her zaman güncelleyebilirsiniz .

URL'ye bir referans tutmak istiyorsanız, remote_imagealanın kendisi yerine önbellek bayrağını işlemek için düzenlenemeyen bir boole alanı kullanabilirsiniz .


2

Benim çözüm pre_save()hedef alan sınıfı yöntemini geçersiz kılmak için önce sadece durum
FileField örneği ile yararlı değiştirilirse çağrılacak bu durum vardı :

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

dezavantaj:
Oluşturulan nesneyi bir işte kullanmak gibi (post_save) bir işlem yapmak istiyorsanız yararlı olmaz (belirli bir alan değiştiyse)


2

tüm alanlar için @josh cevabını geliştirmek:

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)

sadece açıklığa kavuşturmak için, getattr person.namedizelerle (ör.getattr(person, "name")


Ve hala ekstra db sorguları yapmıyor?
andilabs

Kodunuzu uygulamaya çalışıyordum. Alanları düzenleyerek çalışır. Ama şimdi yeni ekleme konusunda sorunum var. Sınıftaki FK alanım için DoesNotExist alıyorum. Nasıl çözüleceğine dair bazı ipuçları takdir edilecektir.
andilabs

Ben sadece kodu güncelledik, şimdi ekstra sorguları (çok pahalı) ile bu dosyaları almak gerekmez böylece yabancı anahtarları atlar ve nesne yoksa ekstra mantığı atlayacaktır.
Hassek

1

@Livskiy karışımını aşağıdaki gibi genişlettim:

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

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

senkronize ettiğinizde / taşıdığınızda modellerinize bir _dict alanı eklenerek kullanılabilir ve bu alan nesnelerinizin durumunu depolar


1

David Cramer'in çözümünü kullanmaya ne dersiniz?

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

Bu şekilde kullanarak başarılı oldum:

@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!"

2
Süper (Mode, self) .save (* args, ** kwargs) unutursanız, kaydetme işlevini devre dışı bırakırsınız, bu yüzden bunu kaydetme yöntemine koymayı unutmayın.
max

Modası geçmiş makalenin bağlantı, bu yeni link: cra.mr/2010/12/06/tracking-changes-to-fields-in-django
GoTop

1

@ İvanperelivskiy'nin cevabında bir değişiklik:

@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

Bunun get_fieldsyerine django 1.10'un genel yöntemi kullanılır. Bu, kodu geleceğe daha fazla kanıtlar, ancak daha da önemlisi, düzenlenebilir = Yanlış olan yabancı anahtarları ve alanları da içerir.

Referans olarak, işte burada .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))
    )

1

İşte bunu yapmanın başka bir yolu.

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)

Belgelere göre: nesneleri doğrulama

"Full_clean () 'in gerçekleştirdiği ikinci adım Model.clean () yöntemini çağırmaktır. Modelinizde özel doğrulama gerçekleştirmek için bu yöntem geçersiz kılınmalıdır. Bu yöntem özel model doğrulaması sağlamak ve istenirse modelinizdeki özellikleri değiştirmek için kullanılmalıdır. Örneğin, bir alanı otomatik olarak bir değer sağlamak veya birden fazla alana erişim gerektiren doğrulama yapmak için kullanabilirsiniz: "


1

Tüm alanları anahtar ve değer olarak alan değerleri olarak içeren __dict__ özelliği vardır. Yani ikisini karşılaştırabiliriz

Sadece modelin kaydetme fonksiyonunu aşağıdaki fonksiyona değiştirin

def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
    if self.pk is not None:
        initial = A.objects.get(pk=self.pk)
        initial_json, final_json = initial.__dict__.copy(), self.__dict__.copy()
        initial_json.pop('_state'), final_json.pop('_state')
        only_changed_fields = {k: {'final_value': final_json[k], 'initial_value': initial_json[k]} for k in initial_json if final_json[k] != initial_json[k]}
        print(only_changed_fields)
    super(A, self).save(force_insert=False, force_update=False, using=None, update_fields=None)

Örnek Kullanım:

class A(models.Model):
    name = models.CharField(max_length=200, null=True, blank=True)
    senior = models.CharField(choices=choices, max_length=3)
    timestamp = models.DateTimeField(null=True, blank=True)

    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
        if self.pk is not None:
            initial = A.objects.get(pk=self.pk)
            initial_json, final_json = initial.__dict__.copy(), self.__dict__.copy()
            initial_json.pop('_state'), final_json.pop('_state')
            only_changed_fields = {k: {'final_value': final_json[k], 'initial_value': initial_json[k]} for k in initial_json if final_json[k] != initial_json[k]}
            print(only_changed_fields)
        super(A, self).save(force_insert=False, force_update=False, using=None, update_fields=None)

yalnızca değiştirilen alanlarla çıktı verir

{'name': {'initial_value': '1234515', 'final_value': 'nim'}, 'senior': {'initial_value': 'no', 'final_value': 'yes'}}

1

Oyuna çok geç, ancak bu, Chris Pratt'ın performansından ödün verirken, bir transactionblok kullanarak yarış koşullarına karşı koruyan cevabının bir versiyonudur.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

0

SmileyChris'in cevabının bir uzantısı olarak, last_updated için modele bir datetime alanı ekleyebilir ve bir değişiklik olup olmadığını kontrol etmeden önce izin vereceğiniz maksimum yaş için bir tür sınır belirleyebilirsiniz


0

@Ananlivski'den gelen karışım harika.

Genişlettim

  • Ondalık alanlarla çalıştığından emin olun.
  • Kullanımı basitleştirmek için özellikleri ortaya çıkarın

Güncellenmiş koda buradan ulaşabilirsiniz: https://github.com/sknutsonsf/python-contrib/blob/master/src/django/utils/ModelDiffMixin.py

Python veya Django'da yeni olan insanlara yardım etmek için daha eksiksiz bir örnek vereceğim. Bu özel kullanım, veri sağlayıcısından bir dosya almak ve veritabanındaki kayıtların dosyayı yansıtmasını sağlamaktır.

Model neslim:

class Station(ModelDiffMixin.ModelDiffMixin, models.Model):
    station_name = models.CharField(max_length=200)
    nearby_city = models.CharField(max_length=200)

    precipitation = models.DecimalField(max_digits=5, decimal_places=2)
    # <list of many other fields>

   def is_float_changed (self,v1, v2):
        ''' Compare two floating values to just two digit precision
        Override Default precision is 5 digits
        '''
        return abs (round (v1 - v2, 2)) > 0.01

Dosyayı yükleyen sınıf şu yöntemlere sahiptir:

class UpdateWeather (object)
    # other methods omitted

    def update_stations (self, filename):
        # read all existing data 
        all_stations = models.Station.objects.all()
        self._existing_stations = {}

        # insert into a collection for referencing while we check if data exists
        for stn in all_stations.iterator():
            self._existing_stations[stn.id] = stn

        # read the file. result is array of objects in known column order
        data = read_tabbed_file(filename)

        # iterate rows from file and insert or update where needed
        for rownum in range(sh.nrows):
            self._update_row(sh.row(rownum));

        # now anything remaining in the collection is no longer active
        # since it was not found in the newest file
        # for now, delete that record
        # there should never be any of these if the file was created properly
        for stn in self._existing_stations.values():
            stn.delete()
            self._num_deleted = self._num_deleted+1


    def _update_row (self, rowdata):
        stnid = int(rowdata[0].value) 
        name = rowdata[1].value.strip()

        # skip the blank names where data source has ids with no data today
        if len(name) < 1:
            return

        # fetch rest of fields and do sanity test
        nearby_city = rowdata[2].value.strip()
        precip = rowdata[3].value

        if stnid in self._existing_stations:
            stn = self._existing_stations[stnid]
            del self._existing_stations[stnid]
            is_update = True;
        else:
            stn = models.Station()
            is_update = False;

        # object is new or old, don't care here            
        stn.id = stnid
        stn.station_name = name;
        stn.nearby_city = nearby_city
        stn.precipitation = precip

        # many other fields updated from the file 

        if is_update == True:

            # we use a model mixin to simplify detection of changes
            # at the cost of extra memory to store the objects            
            if stn.has_changed == True:
                self._num_updated = self._num_updated + 1;
                stn.save();
        else:
            self._num_created = self._num_created + 1;
            stn.save()

0

Geçersiz kılma saveyöntemine ilgi bulamazsanız ,

  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()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.