সংরক্ষণ করার সময়, কোনও ক্ষেত্র পরিবর্তিত হয়েছে কিনা তা আপনি কীভাবে পরীক্ষা করতে পারেন?


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যাতে আপনি মূল মানের একটি অনুলিপি রাখেন। এটি এটিকে এমন করে তোলে যাতে আপনাকে আর একটি ডিবি লুকআপ করতে না হয় (যা সর্বদা ভাল জিনিস)।

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-সংকেত ব্যবহার করতে চাই docs.djangoproject.com/en/dev/ref/signals/#post-init
vikingosegundo

22
জ্যাঙ্গো ডকুমেন্টেশন দ্বারা ওভাররাইডিং
কর্নেল

10
@ ক্যালাম যাতে আপনি যদি বস্তুটিতে পরিবর্তন করেন তবে এটি সংরক্ষণ করুন, তারপরে অতিরিক্ত পরিবর্তন save()করে এটিকে আবার কল করুন, এটি এখনও সঠিকভাবে কাজ করবে।
ফিলাফ্রেও

17
@ জোসে কোনও সমস্যা হবে না যদি আপনার কাছে একই অ্যাপ্লিকেশন সার্ভার একই ডাটাবেসের বিরুদ্ধে কাজ করে যেহেতু এটি কেবল স্মৃতিতে পরিবর্তনগুলি ট্র্যাক করে
জেনস আলম

13
@ লাজেরে, আমি মনে করি আপনার মন্তব্যটি কিছুটা বিভ্রান্তিকর। দস্তাবেজগুলি পরামর্শ দেয় যে আপনি যখন এটি করবেন তখন আপনার যত্ন নেওয়া উচিত। তারা এর বিরুদ্ধে প্রস্তাব দেয় না।
জোশ

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। অতিরিক্ত ডিবি হিট করার জন্য +1। প্রচুর দরকারী পদ্ধতি / বৈশিষ্ট্যের জন্য +1 আমি একাধিকবার upvote সক্ষম হতে হবে।
জ্যাক

হাঁ। মিক্সিন ব্যবহারের জন্য প্লাস ওয়ান এবং কোনও অতিরিক্ত ডিবি হিট নেই।
ডেভিড এস

2
মিক্সিন দুর্দান্ত, তবে .only () এর সাথে একসাথে ব্যবহার করার সময় এই সংস্করণটিতে সমস্যা রয়েছে। Model.objects.only ('id') এ কল করলে অসীম পুনরাবৃত্তি হতে পারে মডেলটির কমপক্ষে 3 টি ক্ষেত্র থাকলে। এটি সমাধানের জন্য, আমাদের প্রারম্ভিক সঞ্চয় থেকে স্থগিত ক্ষেত্রগুলি সরিয়ে নেওয়া উচিত এবং সম্পত্তিটি কিছুটা
gleb.pitsevich

19
জোশের উত্তরের মতো, এই কোডটি আপনার সিঙ্গল-প্রসেস টেস্টিং সার্ভারে ছদ্মবেশপূর্ণভাবে কাজ করবে, তবে আপনি যে কোনও ধরণের মাল্টি-প্রসেসিং সার্ভারে এটি স্থাপন করবেন সেই মুহুর্তটি এটি ভুল ফলাফল দেবে। আপনি জানতে পারবেন না যে আপনি ডাটাবেসকে জিজ্ঞাসা না করে ডাটাবেসে মান পরিবর্তন করছেন কিনা।
আরএসপিয়ার

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
জোশ উপরে বর্ণিত পদ্ধতিটিতে অতিরিক্ত ডাটাবেস হিট জড়িত না কেন কেন এটি সেরা উপায়?
joshcartme

36
1) সেই পদ্ধতিটি একটি হ্যাক, সংকেতগুলি মূলত এর মতো ব্যবহারের জন্য তৈরি করা হয় 2) যে পদ্ধতিটি আপনার মডেলটিতে পরিবর্তন আনতে হবে, এটি 3 টি নয়) আপনি উত্তরটিতে মন্তব্যগুলিতে পড়তে পারেন, এর পার্শ্ব-প্রতিক্রিয়া রয়েছে যা সম্ভাব্য সমস্যা হতে পারে, এই সমাধানটি করে না
ক্রিস প্র্যাট

2
এই উপায়টি দুর্দান্ত যদি আপনি কেবল সংরক্ষণের ঠিক আগে পরিবর্তনটি ধরার বিষয়ে চিন্তা করেন। তবে আপনি যদি তাত্ক্ষণিক পরিবর্তনে প্রতিক্রিয়া জানাতে চান তবে এটি কাজ করবে না। আমি পরবর্তী সময়ে অনেকবার এসেছি (এবং আমি এখন এরকম একটি উদাহরণ নিয়ে কাজ করছি)।
জোশ

5
@ জোশ: "তাত্ক্ষণিকভাবে পরিবর্তনের প্রতি প্রতিক্রিয়া জানান" এর অর্থ কী? কীভাবে এটি আপনাকে "প্রতিক্রিয়া" দেয় না?
ক্রিস প্র্যাট

2
দুঃখিত, আমি এই প্রশ্নের ক্ষেত্রটি ভুলে গিয়েছিলাম এবং সম্পূর্ণ আলাদা সমস্যার উল্লেখ করছি। এটি বলেছিল, আমি মনে করি সিগন্যালগুলি এখানে যাওয়ার ভাল উপায় (এখন যেগুলি উপলভ্য রয়েছে)। তবে, আমি অনেক লোককে "হ্যাক" সংরক্ষণ করে ওভাররাইড বিবেচনা করে দেখছি। আমি বিশ্বাস করি না এটিই কেস। এই উত্তরটি যেমনটি প্রমাণ করে ( স্ট্যাকওভারফ্লো.com / প্রশ্নস / ১70০৩77/২ ), আপনি মনে করেন যে "প্রশ্নে থাকা মডেলের সাথে সুনির্দিষ্ট" যে পরিবর্তনগুলির উপরে আপনি কাজ করছেন না তখন ওভাররাইডিংই সেরা অভ্যাস। এটি বলেছিল, আমি এই বিশ্বাস কারও উপর চাপিয়ে দেওয়ার ইচ্ছা করি না।
জোশ

138

এবং এখন সরাসরি উত্তরের জন্য: ক্ষেত্রের মান পরিবর্তন হয়েছে কিনা তা যাচাই করার একটি উপায় হ'ল উদাহরণ সংরক্ষণের আগে ডাটাবেস থেকে আসল ডেটা আনা। এই উদাহরণ বিবেচনা করুন:

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)

ফর্মের সাথে কাজ করার সময় একই জিনিসটি প্রযোজ্য। আপনি এটি মডেলফর্মের পরিষ্কার বা সংরক্ষণ পদ্ধতিতে সনাক্ত করতে পারেন:

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
জোশের সমাধান অনেক বেশি ডাটাবেস বান্ধব। যা পরিবর্তিত হয়েছে তা যাচাই করার জন্য একটি অতিরিক্ত কল ব্যয়বহুল।
ডিডি।

4
আপনার লেখার আগে আরও একটি অতিরিক্ত পড়া এত ব্যয়বহুল নয়। এছাড়াও একাধিক অনুরোধ থাকলে ট্র্যাকিং পরিবর্তন পদ্ধতি কাজ করে না। যদিও এটি আনয়ন এবং সংরক্ষণের মধ্যে একটি রেসের শর্তে ভুগবে।
দোলার

1
লোকেরা এটি পরীক্ষা করতে বলা বন্ধ করুন pk is not Noneউদাহরণস্বরূপ যদি কোনও ইউইউডিফিল্ড ব্যবহার করে তবে এটি প্রযোজ্য নয়। এটি কেবল খারাপ পরামর্শ।
ব্যবহারকারী 3467349

2
@ ডলোর আপনি সংরক্ষণের পদ্ধতিটি সাজাতে সাহায্যের মাধ্যমে দৌড়ের পরিস্থিতি এড়াতে পারবেন@transaction.atomic
ফ্র্যাঙ্ক পেপ ২ '

2
@ডলোর যদিও আপনার লেনদেনের বিচ্ছিন্নতা স্তর পর্যাপ্ত কিনা তা নিশ্চিত করতে হবে। Postgresql এ, ডিফল্ট পড়ার জন্য প্রতিশ্রুতিবদ্ধ হয়, তবে পুনরাবৃত্তিযোগ্য পড়া প্রয়োজনীয়
ফ্রাঙ্ক পেপ

58

জ্যাঙ্গো ১.৮ প্রকাশিত হওয়ার পরে, আপনি রিমোট_আইমেজের পুরানো মানটি ক্যাশে করতে_ডিবি ক্লাসমেড ব্যবহার করতে পারেন । তারপরে সংরক্ষণের পদ্ধতিতে আপনি ক্ষেত্রের পুরানো এবং নতুন মানের তুলনা করতে পারেন মানটি পরিবর্তন হয়েছে কিনা তা পরীক্ষা করে।

@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
ধন্যবাদ - দস্তাবেজের একটি রেফারেন্স এখানে: ডকস.ডজ্যাঙ্গোপ্রজেক্ট / এএন / ১.৮ / রিফ / মডেলস / ইন্সট্যান্টস / … আমি বিশ্বাস করি এটি এখনও পূর্বোক্ত ইস্যুতে ফলাফল দেয় যেখানে এটি যখন মূল্যায়ন করা হয় এবং যখন তুলনা করা হয় তখন এর মধ্যে ডাটাবেস পরিবর্তিত হতে পারে তবে এটি একটি দুর্দান্ত নতুন বিকল্প।
trpt4him

1
পরিবর্তে মানগুলির মাধ্যমে অনুসন্ধান করা (যা মান (সংখ্যার ভিত্তিতে ও (এন)) এটি করা আরও দ্রুত এবং পরিষ্কার হবে না new._loaded_remote_image = new.remote_image?
aloreলোর

1
দুর্ভাগ্যক্রমে আমাকে আমার আগের (এখন মুছে ফেলা) মন্তব্যটি বিপরীত করতে হবে। যদিও from_dbনামাঙ্কিত refresh_from_db, উদাহরণস্বরূপ উপর বৈশিষ্ট্যাবলী (অর্থাত লোড অথবা পূর্ববর্তী) আপডেট করা হয় না। ফলস্বরূপ, আমি কোন কারণে কেন এই বেশী ভালো খুঁজে পাচ্ছি না __init__: যেমন আপনি যদি এখনও 3 মামলা পরিচালনা করতে প্রয়োজন __init__/ from_db, refresh_from_dbএবং save
ক্লাটিটন্ড

21

নোট করুন যে ফিল্ড চেঞ্জ ট্র্যাকিং জাঙ্গো-মডেল-ইউটিসে উপলব্ধ।

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


2
FieldTracker জ্যাঙ্গো মডেল-utils থেকে সত্যিই ভাল কাজ বলে মনে হয়, আপনাকে ধন্যবাদ!
গ্রেগ সাদেটস্কি

18

আপনি যদি কোনও ফর্ম ব্যবহার করছেন তবে আপনি ফর্মের পরিবর্তিত_ডাটা ( ডক্স ) ব্যবহার করতে পারেন :

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

জ্যাঙ্গো ১.৮ অনুসারে, from_dbপদ্ধতিটি রয়েছে, যেমন সার্জ উল্লেখ করেছেন। আসলে, জ্যাঙ্গো ডক্স একটি নির্দিষ্ট উদাহরণ হিসাবে এই নির্দিষ্ট ব্যবহারের ক্ষেত্রে অন্তর্ভুক্ত করেছে:

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

নীচে ডাটাবেস থেকে লোড হওয়া ক্ষেত্রগুলির প্রাথমিক মানগুলি কীভাবে রেকর্ড করা যায় তা দেখানো উদাহরণ রয়েছে


5

এটি আমার পক্ষে জাজানো ১.৮ এ কাজ করে

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

4

অতিরিক্ত ডাটাবেস অনুসন্ধান ছাড়া এটি করতে আপনি জ্যাঙ্গো-মডেল-পরিবর্তনগুলি ব্যবহার করতে পারেন :

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 লিঙ্কটিতে ক্রিস্টোফার অ্যাডামসের মন্তব্য থেকে অভিযোজিত -চেক-যদি-একটি-ফিল্ড-বদল হয়েছে / জাচের মন্তব্য এখানে)

আপডেট লিঙ্ক: 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রিসিভারেও ব্যবহার করতে পারেন । এটি ভাগ করে নেওয়ার জন্য ধন্যবাদ!
ডেটাগ্রিড

1
অডিও তথ্য পড়ার জন্য ফাইলটি যখন মুটিজেন
ডেটাগ্রিড

3

সর্বোত্তম সমাধানটি সম্ভবত এমন একটি যা মডেল উদাহরণটি সংরক্ষণ করার পূর্বে কোনও অতিরিক্ত ডাটাবেস রিড অপারেশনকে অন্তর্ভুক্ত করে না বা আরও কোনও জ্যাঞ্জো-লাইব্রেরি অন্তর্ভুক্ত করে না। এ কারণেই ল্যাফুস্টের সমাধানগুলি বেশি পছন্দনীয়। অ্যাডমিন সাইটের প্রসঙ্গে, উপরের সায়নের উত্তরের মত , কেউ কেবল- save_modelআদর্শকে ওভাররাইড করতে পারে এবং ফর্মের has_changedপদ্ধতিটি সেখানে অনুরোধ করতে পারে। আপনি সায়ন এর উদাহরণ সেটিং আঁকেন তবে 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সাফল্যের সাথে স্থানীয় অনুলিপি সংরক্ষণের পরে কেবল ক্ষেত্রটি সাফ করুন । তারপরে আপনার সংরক্ষণ পদ্ধতিতে আপনি যখনই remote_imageখালি না হন তখন সর্বদা চিত্র আপডেট করতে পারেন ।

আপনি যদি url- তে একটি রেফারেন্স রাখতে চান তবে আপনি নিজের remote_imageক্ষেত্রের চেয়ে ক্যাচিং পতাকাটি হ্যান্ডেল করার জন্য একটি অ সম্পাদনীয় বুলিয়ান ক্ষেত্রটি ব্যবহার করতে পারেন ।


2

আমার সমাধান pre_save()টার্গেট ফিল্ড ক্লাসের পদ্ধতিটি ওভাররাইড করার আগে আমার এই পরিস্থিতি ছিল কেবল তখনই বলা হবে যদি
ফাইলফিল্ডের সাথে ক্ষেত্রটি দরকারী পরিবর্তিত হয়েছে :

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

অসুবিধা:
আপনি যদি কোনও (পোস্ট_সেভ) ক্রিয়াকলাপ তৈরি করতে চান যেমন কোনও কাজ (যেমন নির্দিষ্ট ক্ষেত্র পরিবর্তিত হয়েছে) ব্যবহার করতে চান তবে দরকারী নয়


2

সমস্ত ক্ষেত্রের জন্য @ জোশ উত্তরের উন্নতি:

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


এবং এটি এখনও অতিরিক্ত ডিবি অনুসন্ধান করে না?
andilabs

আমি আপনার কোডটি প্রয়োগ করার চেষ্টা করছিলাম। ক্ষেত্রগুলি সম্পাদনা করে এটি ঠিক আছে। তবে এখন নতুন tingোকাতে আমার সমস্যা হচ্ছে। আমি ক্লাসে আমার এফকে ক্ষেত্রের জন্য করণীয় নিক্ষিপ্ত পেয়েছি। এটি সমাধান করার জন্য কিছু ইঙ্গিত প্রশংসা করা হবে।
andilabs

আমি সবেমাত্র কোডটি আপডেট করেছি, এটি এখন বিদেশী কীগুলি এড়িয়ে যায় যাতে আপনার অতিরিক্ত প্রশ্নগুলির (খুব ব্যয়বহুল) ফাইলগুলি আনার দরকার নেই এবং যদি অবজেক্টটি বিদ্যমান না থাকে তবে এটি অতিরিক্ত যুক্তি এড়িয়ে যাবে।
হাসেসেক 31'14

1

আমি @ লিভস্কির মিশ্রণটি নিম্নরূপ প্রসারিত করেছি:

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

এবং ডিক্টফিল্ডটি হ'ল:

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)

এটি আপনার মডেলগুলিতে প্রসারিত করে ব্যবহার করা যেতে পারে আপনি যখন সিঙ্ক / মাইগ্রেট করবেন তখন একটি ডিক্ট ফিল্ড যুক্ত হবে এবং সেই ক্ষেত্রটি আপনার অবজেক্টের স্থিতি সংরক্ষণ করবে


1

ডেভিড ক্র্যামারের সমাধানটি কীভাবে ব্যবহার করবেন:

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
যদি আপনি সুপার (মোড, স্ব) s সেভ (* আরগস, ** কোয়ার্গস) ভুলে যান তবে আপনি সেভ ফাংশনটি অক্ষম করছেন তাই এটি সংরক্ষণ পদ্ধতিতে মনে রাখবেন।
সর্বাধিক

নিবন্ধটির লিঙ্কটি পুরানো, এটি নতুন লিঙ্ক: ক্র.এম.আর
12

1

@ আইভানপেরেলিভস্কি এর উত্তরে একটি পরিবর্তন:

@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 এর সর্বজনীন পদ্ধতি ব্যবহার করে। এটি কোডটিকে আরও ভবিষ্যতের প্রমাণ করে তোলে, তবে আরও গুরুত্বপূর্ণভাবে বিদেশী কী এবং ক্ষেত্রগুলিকে অন্তর্ভুক্ত করা হয় যেখানে সম্পাদনযোগ্য = মিথ্যা।

রেফারেন্সের জন্য, এখানে বাস্তবায়ন .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)

ডকুমেন্টেশন অনুসারে: বৈধতা অবজেক্টস

"দ্বিতীয় ধাপে পূর্ণ_ক্লান () সম্পাদন করে তা হল মডেল.ক্লিয়েন () কল করা। এই পদ্ধতিটি আপনার মডেলটিতে কাস্টম বৈধকরণ সম্পাদন করতে হবে method উদাহরণস্বরূপ, আপনি এটিকে কোনও ক্ষেত্রের জন্য স্বয়ংক্রিয়ভাবে একটি মূল্য সরবরাহ করতে বা একক ক্ষেত্রের চেয়ে বেশি অ্যাক্সেসের প্রয়োজন হয় এমন বৈধকরণ করতে ব্যবহার করতে পারেন: "


1

__Dict__- এর একটি বৈশিষ্ট্য রয়েছে যার কীগুলি হিসাবে সমস্ত ক্ষেত্র এবং ক্ষেত্রের মান হিসাবে মান। সুতরাং আমরা কেবল তাদের দুটি তুলনা করতে পারি

কেবলমাত্র মডেলটির সেভ ফাংশনটি নীচের ফাংশনে পরিবর্তন করুন

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

স্মাইলি ক্রিসের উত্তরের সম্প্রসারণ হিসাবে, আপনি শেষ_পুষ্টিত মডেলটিতে একটি ডেটটাইম ফিল্ড যুক্ত করতে পারেন এবং পরিবর্তনের জন্য যাচাই করার আগে আপনি এটি সর্বাধিক বয়সের জন্য সীমাবদ্ধ করতে পারেন you'll


0

@Ivanlivski এর মিশ্রণটি দুর্দান্ত।

আমি এটি প্রসারিত করেছি

  • এটি দশমিক ক্ষেত্রের সাথে কাজ করে তা নিশ্চিত করুন।
  • ব্যবহারকে সহজ করার জন্য বৈশিষ্ট্যগুলি উদ্ভাসিত করুন

আপডেট কোডটি এখানে উপলভ্য: https://github.com/sknutsonsf/python-contrib/blob/master/src/django/utils/ModelDiffMixin.py

পাইথন বা জ্যাঙ্গোতে নতুন লোকদের সহায়তা করতে আমি আরও একটি সম্পূর্ণ উদাহরণ দেব give এই বিশেষ ব্যবহারটি কোনও ডেটা সরবরাহকারীর কাছ থেকে একটি ফাইল নেওয়া এবং ডাটাবেসে থাকা রেকর্ডগুলি ফাইলটি প্রতিবিম্বিত করে তা নিশ্চিত করে।

আমার মডেল অবজেক্ট:

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.