Khi lưu, làm thế nào bạn có thể kiểm tra nếu một trường đã thay đổi?


293

Trong mô hình của tôi, tôi có:

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

Mà hoạt động tuyệt vời cho lần đầu tiên những remote_imagethay đổi.

Làm cách nào tôi có thể tìm nạp hình ảnh mới khi ai đó đã sửa đổi remote_imagebí danh? Và thứ hai, có cách nào tốt hơn để lưu trữ hình ảnh từ xa không?

Câu trả lời:


423

Về cơ bản, bạn muốn ghi đè __init__phương thức models.Modelđể bạn giữ một bản sao của giá trị ban đầu. Điều này làm cho nó để bạn không phải thực hiện tra cứu DB khác (luôn luôn là một điều tốt).

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
thay vì ghi đè init, tôi sử dụng tài liệu post_init-signal docs.djangoproject.com/en/dev/ref/signals/#post-init
vikingosegundo

22
Các phương thức ghi đè được khuyến nghị bởi tài liệu Django: docs.djangoproject.com/en/dev/topics/db/models/ Kẻ
Đại tá Sponsz

10
@callum để nếu bạn thay đổi đối tượng, hãy lưu nó, sau đó thực hiện các thay đổi bổ sung và gọi save()nó LẠI, nó vẫn hoạt động chính xác.
philfreo

17
@Josh sẽ không có vấn đề gì nếu bạn có một số máy chủ ứng dụng hoạt động với cùng một cơ sở dữ liệu vì nó chỉ theo dõi các thay đổi trong bộ nhớ
Jens Alm

13
@lajarre, tôi nghĩ nhận xét của bạn hơi sai lệch. Các tài liệu đề nghị bạn nên cẩn thận khi bạn làm như vậy. Họ không khuyên bạn nên chống lại nó.
Josh

199

Tôi sử dụng mixin sau đây:

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

Sử dụng:

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

Ghi chú

Xin lưu ý rằng giải pháp này chỉ hoạt động tốt trong bối cảnh yêu cầu hiện tại. Do đó, nó phù hợp chủ yếu cho các trường hợp đơn giản. Trong môi trường đồng thời, nơi nhiều yêu cầu có thể thao tác cùng một thể hiện mô hình cùng một lúc, bạn chắc chắn cần một cách tiếp cận khác.


4
Thực sự hoàn hảo và không thực hiện truy vấn thêm. Cảm ơn rất nhiều !
Stéphane

28
+1 cho việc sử dụng mixin. +1 không có thêm DB hit. +1 cho rất nhiều phương thức / thuộc tính hữu ích. Tôi cần để có thể upvote nhiều lần.
Jake

vâng Thêm một để sử dụng Mixin và không có thêm db hit.
David S

2
Mixin rất tuyệt, nhưng phiên bản này có vấn đề khi được sử dụng cùng với .only (). Cuộc gọi đến Model.objects.only ('id') sẽ dẫn đến đệ quy vô hạn nếu Model có ít nhất 3 trường. Để giải quyết vấn đề này, chúng ta nên xóa các trường bị trì hoãn khỏi việc lưu trong tài sản ban đầu và thay đổi thuộc tính _dict một chút
gleb.pitsevich

19
Giống như câu trả lời của Josh, mã này sẽ hoạt động tốt trên máy chủ thử nghiệm một quy trình của bạn, nhưng ngay khi bạn triển khai nó tới bất kỳ loại máy chủ đa xử lý nào, nó sẽ cho kết quả không chính xác. Bạn không thể biết nếu bạn thay đổi giá trị trong cơ sở dữ liệu mà không truy vấn cơ sở dữ liệu.
rspeer

154

Cách tốt nhất là với một pre_savetín hiệu. Có thể không phải là một lựa chọn trở lại trong '09 khi câu hỏi này được hỏi và trả lời, nhưng bất cứ ai nhìn thấy điều này ngày hôm nay nên làm theo cách này:

@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
Tại sao đây là cách tốt nhất nếu phương pháp mà Josh mô tả ở trên không liên quan đến một cơ sở dữ liệu bổ sung?
joshcartme

36
1) phương pháp đó là hack, các tín hiệu được thiết kế cơ bản cho các mục đích sử dụng như thế này 2) phương pháp đó yêu cầu thay đổi mô hình của bạn, phương pháp này không 3) như bạn có thể đọc trong các nhận xét về câu trả lời đó, nó có tác dụng phụ có thể có khả năng có vấn đề, giải pháp này không
Chris Pratt

2
Cách này rất tuyệt nếu bạn chỉ quan tâm đến việc nắm bắt thay đổi ngay trước khi lưu. Tuy nhiên, điều này sẽ không hoạt động nếu bạn muốn phản ứng với sự thay đổi ngay lập tức. Tôi đã bắt gặp kịch bản sau nhiều lần (và bây giờ tôi đang làm việc với một ví dụ như vậy).
Josh

5
@Josh: Ý bạn là gì khi "phản ứng với sự thay đổi ngay lập tức"? Bằng cách nào điều này không cho phép bạn "phản ứng"?
Chris Pratt

2
Xin lỗi, tôi đã quên phạm vi của câu hỏi này và đang đề cập đến một vấn đề hoàn toàn khác. Điều đó nói rằng, tôi nghĩ rằng tín hiệu là một cách tốt để đi đến đây (bây giờ chúng có sẵn). Tuy nhiên, tôi thấy nhiều người cân nhắc việc ghi đè lưu "hack". Tôi không tin đây là trường hợp. Như câu trả lời này cho thấy ( stackoverflow.com/questions/170337/ ,), tôi nghĩ ghi đè là cách tốt nhất khi bạn không làm việc với các thay đổi "cụ thể cho mô hình đang đề cập". Điều đó nói rằng, tôi không có ý định áp đặt niềm tin đó lên bất cứ ai.
Josh

138

Và bây giờ để trả lời trực tiếp: một cách để kiểm tra xem giá trị của trường đã thay đổi hay chưa là tìm nạp dữ liệu gốc từ cơ sở dữ liệu trước khi lưu ví dụ. Xem xét ví dụ này:

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)

Điều tương tự áp dụng khi làm việc với một hình thức. Bạn có thể phát hiện nó tại phương thức xóa hoặc lưu của ModelForm:

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
Giải pháp của Josh thân thiện với cơ sở dữ liệu hơn nhiều. Một cuộc gọi thêm để xác minh những gì đã thay đổi là tốn kém.
đ.

4
Một lần đọc thêm trước khi bạn viết không quá tốn kém. Ngoài ra phương pháp theo dõi thay đổi không hoạt động nếu có nhiều yêu cầu. Mặc dù điều này sẽ phải chịu một điều kiện cuộc đua ở giữa tìm nạp và tiết kiệm.
dinois 24/2/2016

1
Ngừng nói với mọi người để kiểm tra xem pk is not Nonenó không áp dụng ví dụ nếu sử dụng UUIDField. Đây chỉ là lời khuyên tồi.
dùng3467349

2
@datio bạn có thể tránh điều kiện cuộc đua bằng cách trang trí phương thức lưu với@transaction.atomic
Frank Pape

2
@dalore mặc dù bạn cần đảm bảo mức cô lập giao dịch là đủ. Trong postgresql, mặc định được đọc cam kết, nhưng đọc lặp lại là cần thiết .
Frank Pape

58

Kể từ khi Django 1.8 được phát hành, bạn có thể sử dụng from_db classmethod để lưu trữ giá trị cũ của remote_image. Sau đó, trong phương thức lưu, bạn có thể so sánh giá trị cũ và mới của trường để kiểm tra xem giá trị đã thay đổi chưa.

@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
Cảm ơn - đây là một tài liệu tham khảo cho các tài liệu: docs.djangoproject.com/en/1.8/ref/models/instances/ ,. Tôi tin rằng điều này vẫn dẫn đến vấn đề đã nói ở trên, nơi cơ sở dữ liệu có thể thay đổi giữa khi điều này được đánh giá và khi so sánh được thực hiện, nhưng đây là một tùy chọn mới tuyệt vời.
trpt4him

1
Thay vì tìm kiếm thông qua các giá trị (là O (n) dựa trên số lượng giá trị) sẽ không nhanh hơn và rõ ràng hơn để làm gì new._loaded_remote_image = new.remote_image?
dinois

1
Thật không may, tôi phải đảo ngược nhận xét trước đây (hiện đã bị xóa). Trong khi from_dbđược gọi bởi refresh_from_db, các thuộc tính trên thể hiện (tức là đã tải hoặc trước đó) không được cập nhật. Kết quả là, tôi không thể tìm thấy bất cứ lý do tại sao điều này là tốt hơn so với __init__khi bạn vẫn cần phải xử lý 3 trường hợp: __init__/ from_db, refresh_from_dbsave.
claytond


18

Nếu bạn đang sử dụng một biểu mẫu, bạn có thể sử dụng Change_data ( docs ) của Form :

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

Điều này làm việc cho tôi trong Django 1.8

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

4

Bạn có thể sử dụng django-model-thay đổi để thực hiện việc này mà không cần tra cứu cơ sở dữ liệu bổ sung:

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

Một câu trả lời muộn khác, nhưng nếu bạn chỉ đang cố gắng xem liệu một tệp mới đã được tải lên một trường tệp hay chưa, hãy thử điều này: (phỏng theo nhận xét của Christopher Adams về liên kết http://zmsmith.com/2010/05/django -check-if-a-field-has-đã thay đổi / trong nhận xét của zach tại đây)

Liên kết được cập nhật: 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

Đó là một giải pháp tuyệt vời để kiểm tra nếu một tập tin mới được tải lên. Tốt hơn nhiều so với việc kiểm tra tên dựa trên cơ sở dữ liệu vì tên của tệp có thể giống nhau. Bạn cũng có thể sử dụng nó trong pre_savemáy thu. Cảm ơn đã chia sẻ điều này!
DataGreed

1
Đây là một ví dụ để cập nhật thời lượng âm thanh trong cơ sở dữ liệu khi tệp được cập nhật bằng mutagen để đọc thông tin âm thanh - gist.github.com/DataGreed/1ba46ca7387950abba2ff53baf70fec2
DataGreed

3

Giải pháp tối ưu có lẽ là một giải pháp không bao gồm thao tác đọc cơ sở dữ liệu bổ sung trước khi lưu phiên bản mô hình, cũng như bất kỳ thư viện django nào khác. Đây là lý do tại sao các giải pháp của laffuste là thích hợp hơn. Trong ngữ cảnh của một trang quản trị, người ta có thể chỉ cần ghi đè save_model-method và gọi biểu mẫuhas_changed ở đó, giống như trong câu trả lời của Sion ở trên. Bạn đến một cái gì đó như thế này, dựa trên cài đặt ví dụ của Sion nhưng sử dụng changed_datađể có được mọi thay đổi có thể:

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)
  • Ghi đè save_model :

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

  • Được xây dựng trong changed_data đối xứng cho một lĩnh vực:

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


2

Trong khi điều này không thực sự trả lời câu hỏi của bạn, tôi sẽ giải quyết vấn đề này theo một cách khác.

Đơn giản chỉ cần xóa remote_imagetrường sau khi lưu thành công bản sao cục bộ. Sau đó, trong phương thức lưu của bạn, bạn luôn có thể cập nhật hình ảnh bất cứ khi nào remote_imagekhông trống.

Nếu bạn muốn giữ một tham chiếu đến url, bạn có thể sử dụng trường boolean không thể chỉnh sửa để xử lý cờ lưu trữ thay vì remote_imagechính trường đó.


2

Tôi đã gặp tình huống này trước khi giải pháp của tôi là ghi đè pre_save()phương thức của lớp trường đích, nó sẽ chỉ được gọi nếu trường được thay đổi
hữu ích với ví dụ 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

Nhược điểm:
không hữu ích nếu bạn muốn thực hiện bất kỳ thao tác (post_save) nào như sử dụng đối tượng đã tạo trong một số công việc (nếu trường nhất định đã thay đổi)


2

cải thiện câu trả lời @josh cho tất cả các lĩnh vực:

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)

chỉ cần làm rõ, getattr hoạt động để có được các trường như person.namevới chuỗi (nghĩa làgetattr(person, "name")


Và nó vẫn không tạo thêm truy vấn db?
andilabs

Tôi đã cố gắng để thực hiện mã của bạn. Nó hoạt động ok bằng cách chỉnh sửa các lĩnh vực. Nhưng bây giờ tôi có vấn đề với việc chèn mới. Tôi nhận được DoesNotExist cho trường FK của mình trong lớp. Một số gợi ý làm thế nào để giải quyết nó sẽ được đánh giá cao.
andilabs

Tôi vừa cập nhật mã, bây giờ nó bỏ qua các khóa ngoại nên bạn không cần tìm nạp các tệp đó bằng các truy vấn bổ sung (rất tốn kém) và nếu đối tượng không tồn tại, nó sẽ bỏ qua logic bổ sung.
Hassek

1

Tôi đã mở rộng mixin của @livskiy như sau:

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

và DictField là:

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)

nó có thể được sử dụng bằng cách mở rộng nó trong các mô hình của bạn, trường _dict sẽ được thêm vào khi bạn đồng bộ hóa / di chuyển và trường đó sẽ lưu trữ trạng thái của các đối tượng của bạn


1

Cách sử dụng giải pháp của David Cramer:

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

Tôi đã thành công khi sử dụng nó như thế này:

@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
Nếu bạn quên siêu (Chế độ, tự) .save (* args, ** kwargs) thì bạn đang vô hiệu hóa chức năng lưu, vì vậy hãy nhớ đưa phương thức này vào phương thức lưu.
tối đa

Liên kết của bài viết đã lỗi thời, đây là liên kết mới: cra.mr/2010/12/06/tracking-changes-to-fields-in-django
GoTop

1

Một sửa đổi cho câu trả lời của @ 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

Điều này sử dụng phương pháp công khai của django 1.10 get_fieldsthay thế. Điều này làm cho mã bằng chứng trong tương lai nhiều hơn, nhưng quan trọng hơn là cũng bao gồm các khóa và trường nước ngoài có thể chỉnh sửa = Sai.

Để tham khảo, đây là việc thực hiện .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

Đây là một cách khác để làm điều đó.

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)

Theo tài liệu: các đối tượng xác nhận

"Bước thứ hai full_clean () thực hiện là gọi Model.clean (). Phương thức này nên được ghi đè để thực hiện xác thực tùy chỉnh trên mô hình của bạn. Phương thức này nên được sử dụng để cung cấp xác thực mô hình tùy chỉnh và sửa đổi các thuộc tính trên mô hình của bạn nếu muốn Ví dụ: bạn có thể sử dụng nó để tự động cung cấp giá trị cho một trường hoặc để xác thực yêu cầu quyền truy cập vào nhiều hơn một trường: "


1

Có một thuộc tính __dict__ có tất cả các trường là khóa và giá trị làm giá trị trường. Vì vậy, chúng ta chỉ có thể so sánh hai trong số họ

Chỉ cần thay đổi chức năng lưu của mô hình thành chức năng dưới đây

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)

Cách sử dụng ví dụ:

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)

mang lại sản lượng chỉ với những lĩnh vực đã được thay đổi

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

1

Rất muộn với trò chơi, nhưng đây là phiên bản câu trả lời của Chris Pratt bảo vệ chống lại các điều kiện cuộc đua trong khi hy sinh hiệu suất, bằng cách sử dụng một transactionkhối và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

như một phần mở rộng của câu trả lời của SmileyChris, bạn có thể thêm trường datetime vào mô hình cho last_updated và đặt một số giới hạn cho độ tuổi tối đa bạn sẽ cho phép trước khi kiểm tra thay đổi


0

Mixin từ @ivanlivski là tuyệt vời.

Tôi đã mở rộng nó thành

  • Đảm bảo nó hoạt động với các trường thập phân.
  • Các thuộc tính phơi bày để đơn giản hóa việc sử dụng

Mã cập nhật có sẵn tại đây: https://github.com/sknutsonsf/python-contrib/blob/master/src/django/utils/ModelDiffMixin.py

Để giúp mọi người mới sử dụng Python hoặc Django, tôi sẽ đưa ra một ví dụ đầy đủ hơn. Cách sử dụng cụ thể này là lấy một tệp từ nhà cung cấp dữ liệu và đảm bảo các bản ghi trong cơ sở dữ liệu phản ánh tệp.

Đối tượng mô hình của tôi:

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

Lớp tải tệp có các phương thức sau:

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

Nếu bạn không tìm thấy hứng thú với savephương pháp ghi đè , bạn có thể làm

  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()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.