保存するときに、フィールドが変更されたかどうかをどのように確認できますか?


293

私のモデルでは:

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エイリアスでを変更したときに新しい画像を取得するにはどうすればよいですか?次に、リモートイメージをキャッシュするより良い方法はありますか?

回答:


423

基本的に、__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

24
initを上書きする代わりに、post_init-signal docs.djangoproject.com/en/dev/ref/signals/#post-init
vikingosegundo

22
メソッドをオーバーライドするDjangoのドキュメントで推奨されていますdocs.djangoproject.com/en/dev/topics/db/models/...
大佐Sponsz

10
@callumを使用すると、オブジェクトを変更して保存した後、追加の変更を加えて再度呼び出しsave()ても、正しく機能します。
philfreo 2012年

17
@Joshはメモリ内の変更のみを追跡するため、同じデータベースに対して複数のアプリケーションサーバーが動作している場合でも問題はありません
Jens Alm

13
@lajarre、私はあなたのコメントが少し誤解を招くと思います。ドキュメントはそうするときあなたが注意することを提案します。彼らはそれをお勧めしません。
Josh、

199

私は次のミックスインを使用します:

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

注意

このソリューションは、現在のリクエストのコンテキストでのみ機能することに注意してください。したがって、主に単純なケースに適しています。複数の要求が同じモデルインスタンスを同時に操作できる並行環境では、異なるアプローチが必要です。


4
本当に完璧で、余分なクエリを実行しません。どうもありがとう !
ステファン・

28
ミックスインを使用する場合は+1。追加のDBヒットがない場合は+1。多くの便利なメソッド/プロパティの+1。複数回投票できる必要があります。
ジェイク

ええ。さらに、Mixinを使用するための1つで、追加のdbヒットはありません。
David S

2
Mixinは素晴らしいですが、このバージョンを.only()と一緒に使用すると問題が発生します。Modelに少なくとも3つのフィールドがある場合、Model.objects.only( 'id')を呼び出すと、無限再帰が発生します。これを解決するには、遅延フィールドを最初の保存から削除し、_dictプロパティを少し
gleb.pitsevich

19
Joshの回答と同様に、このコードは単一プロセスのテストサーバーで一見うまく機能しますが、任意の種類のマルチプロセッシングサーバーにデプロイすると、誤った結果になります。データベースを照会せずにデータベースの値を変更しているかどうかはわかりません。
rspeer 2015年

154

最善の方法は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

6
Joshが上で説明した方法が追加のデータベースヒットを含まない場合、これがなぜ最良の方法なのですか?
joshcartme

36
1)その方法はハックであり、信号は基本的にこのような用途向けに設計されています2)この方法はモデルに変更を加える必要がありますが、これは必要ありません3)その回答に関するコメントを読むことができるので、副作用があります問題が発生する可能性がありますが、このソリューションでは問題が発生しません
Chris Pratt、

2
この方法は、保存する直前に変更をキャッチするだけの場合に最適です。ただし、変更にすぐに対応したい場合は機能しません。後者のシナリオに何度も遭遇しました(そして、現在、そのようなインスタンスの1つに取り組んでいます)。
Josh

5
@ジョシュ:「即座に変化に反応する」とはどういう意味ですか?これはどのようにして「反応」させないのですか?
クリスプラット2012年

2
申し訳ありませんが、この質問の範囲を忘れており、まったく別の問題について言及していました。そうは言っても、信号はここに行くための良い方法だと思います(信号が利用できるようになったため)。しかし、多くの人が「ハック」を上書き保存することを検討しています。私はこれが事実であるとは思わない。この回答が示唆しているように(stackoverflow.com/questions/170337/…)、「問題のモデルに固有の」変更に取り組んでいない場合は、オーバーライドがベストプラクティスであると思います。とは言っても、私はその信念を誰かに課すつもりはありません。
Josh

138

そして今、直接的な答えとして、フィールドの値が変更されたかどうかを確認する1つの方法は、インスタンスを保存する前にデータベースから元のデータをフェッチすることです。この例を考えてみましょう:

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

24
Joshのソリューションは、よりデータベースフレンドリーです。変更内容を確認するための追加の呼び出しは高価です。
dd。

4
書き込みを行う前の1つの追加の読み取りは、それほど高価ではありません。また、複数のリクエストがある場合、変更の追跡メソッドは機能しません。ただし、フェッチと保存の間に競合状態が発生します。
ダロル2016

1
pk is not Noneたとえば、UUIDFieldを使用する場合は、チェックするように人々に告げるのは当てはまりません。これは悪いアドバイスです。
user3467349 2016年

2
@daloreでは、saveメソッドを次のように装飾することで競合状態を回避できます@transaction.atomic
Frank Pape

2
@daloreただし、トランザクション分離レベルが十分であることを確認する必要があります。postgresqlでは、デフォルトはコミットされた読み取りですが、繰り返し可能な読み取りが必要です。
フランクペープ

58

Django 1.8がリリースされて以来、from_dbクラスメソッドを使用して、remote_imageの古い値をキャッシュできます。次に、saveメソッドで、フィールドの古い値と新しい値を比較して、値が変更されたかどうかを確認できます。

@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
ありがとう-これがドキュメントへの参照です: docs.djangoproject.com/en/1.8/ref/models/instances/…。これでも、前述の問題が発生し、データベースが評価されるときと比較が行われるときの間でデータベースが変わる可能性があると思いますが、これは素晴らしい新しいオプションです。
trpt4him 2015年

1
値(値の数に基づくO(n))を検索するよりも、高速で明確な方法ではないnew._loaded_remote_image = new.remote_imageでしょうか。
dalore

1
残念ながら、以前の(現在は削除されている)コメントを元に戻す必要があります。from_dbがによって呼び出されている間refresh_from_db、インスタンスの属性(つまり、ロード済みまたは以前のもの)は更新されません。これはより優れている理由その結果、私はいかなる理由を見つけることができません__init__:あなたはまだ3例処理する必要があるとして、 __init__/ from_dbrefresh_from_db、およびをsave
クレイトンド2017


18

フォームを使用している場合は、フォームのchanged_datadocs)を使用できます。

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

これは私にとってDjango 1.8で動作します

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

4

追加のデータベース検索をせずに、django-model-changesを使用してこれを行うことができます。

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

別の遅い答えですが、新しいファイルがファイルフィールドにアップロードされたかどうかを確認するだけの場合は、これを試してください(リンクhttp://zmsmith.com/2010/05/djangoのChristopher Adamsのコメントから転載) -check-if-a-field-has-changed / zachのコメントはこちら)

更新されたリンク: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

これは、新しいファイルがアップロードされたかどうかを確認するための素晴らしいソリューションです。ファイルの名前が同じである可能性があるため、データベースに対して名前をチェックするよりもはるかに優れています。pre_saveレシーバーにもお使いいただけます。これを共有してくれてありがとう!
DataGreed

1
音声情報を読み取るためにmutagen
DataGreed

3

おそらく、最適なソリューションは、モデルインスタンスを保存する前の追加のデータベース読み取り操作も、それ以上のdjango-libraryも含まないソリューションです。これが、ラフフステの解が望ましい理由です。管理サイトのコンテキストでは、上記のSionの回答と同じように、save_model-methodをオーバーライドして、has_changedそこでフォームのメソッドを呼び出すだけです。Sionの設定例を利用changed_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

  • changed_dataフィールドの組み込みメソッド:

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


2

これは実際にはあなたの質問に答えるものではありませんが、別の方法でこれについて考えます。

remote_imageローカルコピーを正常に保存したら、フィールドをクリアするだけです。次に、saveメソッドで、がremote_image空でないときにいつでも画像を更新できます。

URLへの参照を保持したい場合は、remote_imageフィールド自体ではなく、編集不可のブールフィールドを使用してキャッシュフラグを処理できます。


2

私の解決策が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

すべてのフィールドの@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")


そして、それはまだ余分なdbクエリを作成していませんか?
andilabs 2014年

私はあなたのコードを実装しようとしていました。フィールドを編集しても問題ありません。しかし今、私は新しいものを挿入することに問題があります。クラスのFKフィールドに対してDoesNotExistを取得します。それを解決する方法についてのいくつかのヒントをいただければ幸いです。
andilabs 2014年

コードを更新したばかりですが、外部キーをスキップするため、追加のクエリ(非常に負荷が高い)でこれらのファイルをフェッチする必要がありません。オブジェクトが存在しない場合は、追加のロジックがスキップされます。
Hassek 2014年

1

@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

David Cramerのソリューションの使用についてはどうですか。

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

2
super(Mode、self).save(* args、** kwargs)を忘れた場合は、save関数を無効にするので、これをsaveメソッドに忘れずに入れてください。
最大

記事のリンクは古くなっています。これは新しいリンクです:cra.mr/2010/12/06/tracking-changes-to-fields-in-django
GoTop

1

@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

get_fields代わりに、django 1.10のパブリックメソッドを使用します。これにより、コードの将来性が高まりますが、より重要なのは、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))
    )

1

これを行う別の方法を次に示します。

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()が実行する2番目のステップは、Model.clean()を呼び出すことです。このメソッドをオーバーライドして、モデルでカスタム検証を実行する必要があります。このメソッドは、カスタムモデル検証を提供し、必要に応じてモデルの属性を変更するために使用する必要がありますたとえば、これを使用してフィールドに値を自動的に提供したり、複数のフィールドへのアクセスを必要とする検証を実行したりできます。 "


1

すべてのフィールドをキーとして、値をフィールド値として持つ属性__dict__があります。2つを比較するだけで

モデルの保存機能を以下の機能に変更するだけです

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)

使用例:

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)

変更されたフィールドのみの出力を生成します

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

1

非常にゲームに遅れているが、これはバージョンであるクリス・プラットの答え使用することにより、パフォーマンスを犠牲にしながら、競合状態から保護transactionブロックをし、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の回答の拡張として、last_updatedのモデルに日時フィールドを追加し、変更を確認する前に取得できる最大経過時間になんらかの制限を設定できます


0

@ivanlivskiのミックスインは素晴らしいです。

私はそれを拡張しました

  • Decimalフィールドで機能することを確認します。
  • プロパティを公開して使用を簡略化する

更新されたコードはこちらから入手できます:https : //github.com/sknutsonsf/python-contrib/blob/master/src/django/utils/ModelDiffMixin.py

PythonまたはDjangoを初めて使用するユーザーを支援するために、より完全な例を示します。この特定の使用法は、データプロバイダーからファイルを取得し、データベース内のレコードがファイルを確実に反映するようにすることです。

私のモデルオブジェクト:

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

ファイルをロードするクラスには、次のメソッドがあります。

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

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()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.