Django: podczas zapisywania, jak możesz sprawdzić, czy Pole się zmieniło?

W moim modelu mam:

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

Który działa świetnie po raz pierwszy remote_image zmiany.

Jak mogę pobrać nowy obrazek, gdy ktoś zmodyfikował remote_image na aliasie? A po drugie, czy istnieje lepszy sposób na buforowanie zdalnego obrazu?

Author: Paul Tarjan, 2009-08-31

22 answers

Choć jest trochę późno, pozwól, że wyrzucę To rozwiązanie dla innych, którzy natkną się na ten post. Zasadniczo, chcesz nadpisać metodę __init__ z models.Model, aby zachować kopię oryginalnej wartości. To sprawia, że nie musisz wykonywać kolejnego wyszukiwania DB (co zawsze jest dobre).

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
 353
Author: Josh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-19 21:51:16

Używam następującej mixin:

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

Użycie:

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

Uwaga

Należy pamiętać, że to rozwiązanie działa dobrze tylko w kontekście bieżącego żądania. Dlatego nadaje się przede wszystkim do prostych przypadków. W środowisku równoległym, w którym wiele żądań może manipulować tą samą instancją modelu w tym samym czasie, zdecydowanie potrzebujesz innego podejścia.

 160
Author: iperelivskiy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-17 04:37:43

A teraz odpowiedź bezpośrednia: jednym ze sposobów sprawdzenia, czy wartość pola się zmieniła, jest pobranie oryginalnych danych z bazy danych przed zapisaniem instancji. Rozważ ten przykład:

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)

To samo dotyczy pracy z formularzem. Można go wykryć za pomocą metody clean lub save Modelu:

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 = []
 128
Author: zgoda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-09 22:20:07

Najlepszym sposobem jest sygnał pre_save. Może nie było opcji w 2009, kiedy to pytanie zostało zadane i odpowiedzi, ale każdy, kto dziś to zobaczy, powinien zrobić to w ten sposób: {]}

@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
 126
Author: Chris Pratt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-11 14:51:58

Od wydania Django 1.8, możesz użyć from_db classmethod do buforowania starej wartości remote_image. Następnie w metodzie save można porównać starą i nową wartość pola, aby sprawdzić, czy wartość się zmienił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!
 47
Author: Serge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-09 01:06:43

Zauważ, że śledzenie zmian pól jest dostępne w django-model-utils.

Https://django-model-utils.readthedocs.org/en/latest/index.html

 12
Author: Lee Hinde,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-24 21:14:25

Jeśli używasz formularza, możesz użyć formularza changed_data (docs):

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
 10
Author: laffuste,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-02 02:10:53

Od Django 1.8 istnieje metoda from_db, jak wspomina Serge. W rzeczywistości dokumenty Django zawierają ten konkretny przypadek użycia jako przykład:

Https://docs.djangoproject.com/en/dev/ref/models/instances/#customizing-model-loading

Poniżej znajduje się przykład pokazujący jak zapisywać początkowe wartości pól załadowanych z bazy danych

 5
Author: Amichai Schreiber,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-16 11:07:36

Trochę się spóźniłem na imprezę, ale znalazłem też takie rozwiązanie: Django Dirty Fields

 5
Author: Fred Campos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-19 07:37:01

Możesz użyć django-model-changes aby to zrobić bez dodatkowego przeszukiwania bazy danych:

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
 3
Author: Robert Kajic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-14 14:50:58

Optymalnym rozwiązaniem jest prawdopodobnie takie, które nie zawiera dodatkowej operacji odczytu bazy danych przed zapisaniem instancji modelu, ani żadnej innej biblioteki django. Dlatego preferowane są rozwiązania laffuste. W kontekście strony administratora, można po prostu nadpisać metodę save_model i wywołać tam metodę has_changed formularza, tak jak w odpowiedzi Sion powyżej. Dochodzimy do czegoś takiego, czerpiąc z przykładowego ustawienia Sion, ale używając "changed_data", aby uzyskać wszystkie możliwe zmiana:

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)
  • Zastąp save_model:

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

  • Wbudowana metoda changed_data dla pola:

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

 3
Author: user3061675,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-04 10:36:50

Chociaż to nie odpowiada na twoje pytanie, potraktowałbym to inaczej.

Po pomyślnym zapisaniu kopii lokalnej wyczyść pole remote_image. Następnie w swojej metodzie save możesz zawsze zaktualizować obrazek, gdy remote_image nie jest pusty.

Jeśli chcesz zachować odniesienie do adresu url, możesz użyć nieedytowalnego pola logicznego do obsługi znacznika buforowania, a nie samego pola remote_image.

 2
Author: SmileyChris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-30 23:21:51

Miałem taką sytuację, zanim moim rozwiązaniem było nadpisanie metody pre_save() klasy pola docelowego, która zostanie wywołana tylko wtedy, gdy pole zostało zmienione
przydatne z FileField przykład:

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


nie jest to przydatne, jeśli chcesz wykonać dowolną operację (post_save), jak użycie utworzonego obiektu w jakimś zadaniu (jeśli pewne pole się zmieniło)

 2
Author: MYaser,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-07 12:41:35

Poprawa @josh odpowiedzi dla wszystkich pól:

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)

Dla wyjaśnienia, getattr działa, aby uzyskać pola takie jak {[1] } z łańcuchami (np. getattr(person, "name")

 2
Author: Hassek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-31 16:11:14

To działa dla mnie w Django 1.8

def clean(self):
    if self.cleaned_data['name'] != self.initial['name']:
        # Do something
 2
Author: jhrs21,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-05 15:54:33

Kolejna późna odpowiedź, ale jeśli próbujesz sprawdzić, czy nowy plik został przesłany do pola Pliku, spróbuj tego: (zaadaptowane z komentarza Christophera Adamsa na link http://zmsmith.com/2010/05/django-check-if-a-field-has-changed/ in zach ' s comment here)

Zaktualizowany link: 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
Author: Aaron McMillin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-12 14:19:34

Rozszerzyłem mixin @livskiy w następujący sposób:

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

A Dyktafon to:

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)

Można go używać rozszerzając go w swoich modelach pole _dict zostanie dodane podczas synchronizacji/migracji i to pole będzie przechowywać stan Twoich obiektów

 1
Author: MYaser,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-10 10:47:32

Jako rozszerzenie odpowiedzi SmileyChris, możesz dodać pole datetime do modelu dla last_updated i ustawić jakiś limit dla maksymalnego wieku, na który pozwoli Ci się dostać przed sprawdzeniem zmiany

 0
Author: Jiaaro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-31 14:56:16

Mixin od @ ivanlivski jest świetny.

Rozszerzyłem go na

  • upewnij się, że działa z polami dziesiętnymi.
  • Expose właściwości w celu uproszczenia użytkowania

Zaktualizowany kod jest dostępny tutaj: https://github.com/sknutsonsf/python-contrib/blob/master/src/django/utils/ModelDiffMixin.py

Aby pomóc ludziom nowym w Pythonie lub Django, podam bardziej kompletny przykład. Tym szczególnym zastosowaniem jest pobranie pliku od dostawcy danych i zapewnienie rekordy w bazie danych odzwierciedlają plik.

Mój obiekt modelu:

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

Klasa, która ładuje plik ma następujące metody:

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
Author: sknutsonsf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-30 18:20:07

A co powiesz na użycie rozwiązania Davida Cramer ' a:

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

Odniosłem sukces używając go w ten sposób:

@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
Author: Sion,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-22 16:57:37

Modyfikacja odpowiedzi @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

Używa zamiast tego publicznej metody django 1.10 get_fields. To sprawia, że kod jest bardziej przyszłościowy, ale co ważniejsze zawiera również klucze obce i pola, w których editable=False.

Dla odniesienia, oto implementacja .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))
    )
 0
Author: theicfire,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-06 21:56:22

Oto inny sposób na zrobienie tego.

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)                    

Zgodnie z dokumentacją: Walidacja obiektów

" drugim krokiem full_clean () jest wywołanie modelu.clean (). Ta metoda powinna zostać nadpisana, aby wykonać walidację niestandardową w modelu. Ta metoda powinna być używana do walidacji modelu niestandardowego i w razie potrzeby do modyfikowania atrybutów modelu. Można na przykład użyć go do automatycznego podania wartości pola lub do walidacji wymagającej dostępu do więcej niż jedno pole: "

 0
Author: Gonzalo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-24 20:18:55