2008-09-21 21 views
27

मेरे ऐप में मुझे मॉडल सहेजे जाने पर बदले गए मूल्यों (पुराने और नए) को सहेजने की आवश्यकता है। कोई उदाहरण या काम कोड?django में गंदे फ़ील्ड

मुझे सामग्री के प्रीोडोरेशन के लिए इसकी आवश्यकता है। उदाहरण के लिए, यदि उपयोगकर्ता मॉडल में कुछ बदलता है, तो व्यवस्थापक अलग-अलग तालिका में सभी बदलाव देख सकता है और फिर उन्हें लागू करने का निर्णय ले सकता है या नहीं।

+0

मैंने गंदे क्षेत्रों के लिए समान प्रश्न देखे हैं, लेकिन यह वही समस्या है; व्यवस्थापक को बदलने के लिए क्या बदल गया है, आपको सबसे पहले यह पहचानने की आवश्यकता है कि क्या बदल गया ... – dnozay

उत्तर

13

आपने अपने विशिष्ट उपयोग के मामले या ज़रूरतों के बारे में बहुत कुछ नहीं कहा है। विशेष रूप से, यह जानना उपयोगी होगा कि आपको परिवर्तन जानकारी के साथ क्या करना है (आपको इसे कब तक स्टोर करने की आवश्यकता है?)। यदि आपको केवल क्षणिक उद्देश्यों के लिए इसे स्टोर करने की आवश्यकता है, तो @ एसएलॉट का सत्र समाधान सबसे अच्छा हो सकता है। यदि आप डीबी में संग्रहीत वस्तुओं में सभी परिवर्तनों का पूर्ण लेखा परीक्षा निशान चाहते हैं, तो यह AuditTrail solution आज़माएं।

अद्यतन: हालांकि यह कुछ सीमाएं (ManyToMany क्षेत्रों के लिए बिल्कुल काम नहीं करता है) है AuditTrail कोड मैं ऊपर से जुड़ा हुआ, निकटतम मैं एक पूर्ण समाधान है कि आपके मामले के लिए काम करेगा करने के लिए देखा है है। यह डीबी में आपकी ऑब्जेक्ट्स के पिछले सभी संस्करणों को स्टोर करेगा, इसलिए व्यवस्थापक किसी भी पिछले संस्करण पर वापस रोल कर सकता है। यदि आप स्वीकृति तक परिवर्तन प्रभावी नहीं करना चाहते हैं तो आपको इसके साथ थोड़ा सा काम करना होगा।

आप @Armin Ronacher's DiffingMixin जैसे कुछ पर आधारित कस्टम समाधान भी बना सकते हैं। आप बाद में समीक्षा करने के लिए व्यवस्थापक के लिए diff शब्दकोश (शायद मसालेदार?) स्टोर करेंगे और वांछित होने पर आवेदन करें (आपको diff शब्दकोश लेने के लिए कोड लिखना होगा और इसे किसी उदाहरण पर लागू करना होगा)।

+0

हाँ, आप सही हैं, मैंने और सटीक विवरण जोड़ा है। –

1

यदि आप अपने लेनदेन का उपयोग कर रहे हैं (डिफ़ॉल्ट व्यवस्थापक एप्लिकेशन नहीं), तो आप अपनी ऑब्जेक्ट के पहले और बाद के संस्करणों को सहेज सकते हैं। आप सत्र में पहले संस्करण को सहेज सकते हैं, या आप इसे फॉर्म में "छिपा" फ़ील्ड में डाल सकते हैं। छिपे हुए क्षेत्र एक सुरक्षा दुःस्वप्न है। इसलिए, इस उपयोगकर्ता के साथ क्या हो रहा है के इतिहास को बनाए रखने के लिए सत्र का उपयोग करें।

इसके अतिरिक्त, आपको पिछली वस्तु को लाने की ज़रूरत है ताकि आप इसमें बदलाव कर सकें। इसलिए मतभेदों की निगरानी करने के आपके पास कई तरीके हैं।

def updateSomething(request, object_id): 
    object= Model.objects.get(id=object_id) 
    if request.method == "GET": 
     request.session['before']= object 
     form= SomethingForm(instance=object) 
    else request.method == "POST" 
     form= SomethingForm(request.POST) 
     if form.is_valid(): 
      # You have before in the session 
      # You have the old object 
      # You have after in the form.cleaned_data 
      # Log the changes 
      # Apply the changes to the object 
      object.save() 
10

Django वर्तमान में डेटाबेस में सभी कॉलम भेज रहा है, भले ही आपने अभी एक बदल दिया हो। इसे बदलने के लिए, डेटाबेस सिस्टम में कुछ बदलाव आवश्यक होंगे। जब आप __set__ कॉलम मान प्रत्येक बार मॉडल में गंदे फ़ील्ड का एक सेट जोड़कर और कॉलम नाम जोड़कर मौजूदा कोड पर आसानी से कार्यान्वित किया जा सकता है।

यदि आपको उस सुविधा की आवश्यकता है, तो मैं सुझाव दूंगा कि आप Django ORM को देखें, इसे कार्यान्वित करें और Django Trac में पैच डालें। इसे जोड़ना बहुत आसान होना चाहिए और इससे अन्य उपयोगकर्ताओं की भी मदद मिलेगी। जब आप ऐसा करते हैं, तो एक हुक जोड़ें जिसे प्रत्येक बार कॉलम सेट किया जाता है।

यदि आप Django पर ही हैक करना नहीं चाहते हैं, तो आप ऑब्जेक्ट सृजन पर निर्देश की प्रतिलिपि बना सकते हैं और इसे अलग कर सकते हैं।

हो सकता है कि इस तरह की एक mixin साथ

:

class DiffingMixin(object): 

    def __init__(self, *args, **kwargs): 
     super(DiffingMixin, self).__init__(*args, **kwargs) 
     self._original_state = dict(self.__dict__) 

    def get_changed_columns(self): 
     missing = object() 
     result = {} 
     for key, value in self._original_state.iteritems(): 
      if key != self.__dict__.get(key, missing): 
       result[key] = value 
     return result 

class MyModel(DiffingMixin, models.Model): 
    pass 

इस कोड अपरीक्षित है, लेकिन काम करना चाहिए। जब आप model.get_changed_columns() पर कॉल करते हैं तो आपको सभी बदले गए मानों का एक नियम मिलता है। यह निश्चित रूप से कॉलम में म्यूटेबल ऑब्जेक्ट्स के लिए काम नहीं करेगा क्योंकि मूल स्थिति dict की एक सपाट प्रति है।

+4

यह शायद लंबे समय से अतिदेय है, लेकिन यह होना चाहिए 'यदि मूल्य! = स्वयं .__ dict __। प्राप्त करें (कुंजी, गायब) ' – tghw

+0

क्या आप' __set__' दृष्टिकोण पर विस्तृत कर सकते हैं? ऐसा लगता है कि यह मेरी वर्तमान जरूरतों के अनुरूप होगा, लेकिन मैं इसके साथ कोई प्रगति करने में असमर्थ था। – kasperd

19

मुझे आर्मीन का विचार बहुत उपयोगी पाया गया है। यहां मेरी विविधता है;

class DirtyFieldsMixin(object): 
    def __init__(self, *args, **kwargs): 
     super(DirtyFieldsMixin, self).__init__(*args, **kwargs) 
     self._original_state = self._as_dict() 

    def _as_dict(self): 
     return dict([(f.name, getattr(self, f.name)) for f in self._meta.local_fields if not f.rel]) 

    def get_dirty_fields(self): 
     new_state = self._as_dict() 
     return dict([(key, value) for key, value in self._original_state.iteritems() if value != new_state[key]]) 

संपादित करें: मैंने इस बीटीडब्ल्यू का परीक्षण किया है।

लंबी लाइनों के बारे में खेद है। अंतर है (नामों से अलग) यह केवल स्थानीय गैर-संबंध फ़ील्ड को कैश करता है। दूसरे शब्दों में यह मौजूद होने पर माता-पिता मॉडल के फ़ील्ड कैश नहीं करता है।

और एक और बात है; सहेजने के बाद आपको _original_state dict को रीसेट करने की आवश्यकता है। लेकिन मैं बचत() विधि को ओवरराइट नहीं करना चाहता था क्योंकि अधिकांश बार हम सहेजने के बाद मॉडल उदाहरणों को त्याग देते हैं।

def save(self, *args, **kwargs): 
    super(Klass, self).save(*args, **kwargs) 
    self._original_state = self._as_dict() 
+0

[django-dirtyfields] (https://github.com/smn/django-dirtyfields) ऐप एक ही तरह का मिश्रण प्रदान करता है। – dnozay

+7

@ डीनोज़े, आश्चर्यजनक नहीं है क्योंकि django-dirtyfields "क्रेडिट" के तहत कहता है कि यह इस स्टैक ओवरफ्लो प्रश्न –

-1

सभी की जानकारी के लिए, muhuk के समाधान python2.6 के तहत विफल रहता है के रूप में यह बताते हुए एक अपवाद को जन्म देती है 'वस्तु .__ init __()' कोई तर्क स्वीकार करता है ...

संपादित करें: हो! स्पष्ट रूप से यह शायद मुझे मिश्रण का दुरुपयोग कर रहा हो ... मैंने ध्यान नहीं दिया और इसे अंतिम माता-पिता के रूप में घोषित किया और इसके कारण init को अगले माता-पिता की बजाय ऑब्जेक्ट पेरेंट में समाप्त कर दिया गया क्योंकि यह न ही हीरे आरेख विरासत के साथ!

from django.db.models.signals import post_save 

class DirtyFieldsMixin(object): 
    def __init__(self, *args, **kwargs): 
     super(DirtyFieldsMixin, self).__init__(*args, **kwargs) 
     post_save.connect(self._reset_state, sender=self.__class__, 
          dispatch_uid='%s-DirtyFieldsMixin-sweeper' % self.__class__.__name__) 
     self._reset_state() 

    def _reset_state(self, *args, **kwargs): 
     self._original_state = self._as_dict() 

    def _as_dict(self): 
     return dict([(f.name, getattr(self, f.name)) for f in self._meta.local_fields if not f.rel]) 

    def get_dirty_fields(self): 
     new_state = self._as_dict() 
     return dict([(key, value) for key, value in self._original_state.iteritems() if value != new_state[key]]) 

कौन सा एक बार मूल राज्य साफ होगा: तो मेरी टिप्पणी :)

3

Muhuk के सुझाव पर जारी & Django के संकेतों को जोड़ने और एक अद्वितीय dispatch_uid आप को बचाने() अधिभावी बिना बचाने पर स्थिति को रीसेट कर सकता है पर ध्यान न दें सहेजने के लिए ओवरराइड किए बिना सहेजा गया()। कोड काम करता है लेकिन यकीन नहीं क्या प्रदर्शन दंड मैं muhuk और smn के समाधान के लिए बढ़ाया अंतर विदेशी कुंजी और एक-से-एक क्षेत्र के लिए प्राथमिक कुंजी पर जाँच शामिल करने के लिए __init__

3

में जोड़ने संकेतों के है:

from django.db.models.signals import post_save 

class DirtyFieldsMixin(object): 
    def __init__(self, *args, **kwargs): 
     super(DirtyFieldsMixin, self).__init__(*args, **kwargs) 
     post_save.connect(self._reset_state, sender=self.__class__, 
          dispatch_uid='%s-DirtyFieldsMixin-sweeper' % self.__class__.__name__) 
     self._reset_state() 

    def _reset_state(self, *args, **kwargs): 
     self._original_state = self._as_dict() 

    def _as_dict(self): 
     return dict([(f.attname, getattr(self, f.attname)) for f in self._meta.local_fields]) 

    def get_dirty_fields(self): 
     new_state = self._as_dict() 
     return dict([(key, value) for key, value in self._original_state.iteritems() if value != new_state[key]]) 

फर्क सिर्फ इतना है _as_dict में है मैं

return dict([ 
    (f.name, getattr(self, f.name)) for f in self._meta.local_fields 
    if not f.rel 
]) 

से

return dict([ 
    (f.attname, getattr(self, f.attname)) for f in self._meta.local_fields 
]) 
के अंतिम पंक्ति बदल

यह mixin, ऊपर वाले की तरह है, इसलिए की तरह इस्तेमाल किया जा सकता है:

class MyModel(DirtyFieldsMixin, models.Model): 
    .... 
6

मैं ट्रे Hunner के समाधान के लिए बढ़ाया M2M रिश्तों का समर्थन करने के। उम्मीद है कि यह दूसरों को एक समान समाधान की तलाश करने में मदद करेगा।

from django.db.models.signals import post_save 

DirtyFieldsMixin(object): 
    def __init__(self, *args, **kwargs): 
     super(DirtyFieldsMixin, self).__init__(*args, **kwargs) 
     post_save.connect(self._reset_state, sender=self.__class__, 
      dispatch_uid='%s._reset_state' % self.__class__.__name__) 
     self._reset_state() 

    def _as_dict(self): 
     fields = dict([ 
      (f.attname, getattr(self, f.attname)) 
      for f in self._meta.local_fields 
     ]) 
     m2m_fields = dict([ 
      (f.attname, set([ 
       obj.id for obj in getattr(self, f.attname).all() 
      ])) 
      for f in self._meta.local_many_to_many 
     ]) 
     return fields, m2m_fields 

    def _reset_state(self, *args, **kwargs): 
     self._original_state, self._original_m2m_state = self._as_dict() 

    def get_dirty_fields(self): 
     new_state, new_m2m_state = self._as_dict() 
     changed_fields = dict([ 
      (key, value) 
      for key, value in self._original_state.iteritems() 
      if value != new_state[key] 
     ]) 
     changed_m2m_fields = dict([ 
      (key, value) 
      for key, value in self._original_m2m_state.iteritems() 
      if sorted(value) != sorted(new_m2m_state[key]) 
     ]) 
     return changed_fields, changed_m2m_fields 

कोई भी दो फ़ील्ड सूचियों को मर्ज करना चाहता है। उसके लिए, M2M समर्थन (अद्यतन का उपयोग dirtyfields और नए _meta API और कुछ बग फिक्स), @Trey पर और @ टोनी ऊपर आधारित के साथ एक अद्यतन समाधान

changed_fields.update(changed_m2m_fields) 
return changed_fields 
+0

@trey से पैदा हुआ है, यह एम 2 एम के लिए अच्छा लगता है। क्या आपने परीक्षण किया है कि यह काम करता है? साथ ही, क्या यह अद्यतन _meta एपीआई का उपयोग कर नवीनतम Django के लिए अद्यतन किया गया है? – Neil

+0

मैंने इस धागे – Neil

+0

@Neil पर एक अद्यतन संस्करण लिखा है और साझा किया है: लेखापरीक्षा कोड साल पहले djnago-simple-history में लपेटा गया था। मैंने आज सुझाए गए समाधानों को ध्यान में रखते हुए एक दूसरा जवाब जोड़ा। मूल उत्तर देने के बाद से बहुत कुछ बदल गया है। इसे पुनर्जीवित करने के लिए धन्यवाद! –

0

साथ अंतिम पंक्ति

return changed_fields, changed_m2m_fields 

बदलें। इसने मेरे लिए कुछ बुनियादी प्रकाश परीक्षण पास कर दिया है।

from dirtyfields import DirtyFieldsMixin 
class M2MDirtyFieldsMixin(DirtyFieldsMixin): 
    def __init__(self, *args, **kwargs): 
     super(M2MDirtyFieldsMixin, self).__init__(*args, **kwargs) 
     post_save.connect(
      reset_state, sender=self.__class__, 
      dispatch_uid='{name}-DirtyFieldsMixin-sweeper'.format(
       name=self.__class__.__name__)) 
     reset_state(sender=self.__class__, instance=self) 

    def _as_dict_m2m(self): 
     if self.pk: 
      m2m_fields = dict([ 
       (f.attname, set([ 
        obj.id for obj in getattr(self, f.attname).all() 
       ])) 
       for f,model in self._meta.get_m2m_with_model() 
      ]) 
      return m2m_fields 
     return {} 

    def get_dirty_fields(self, check_relationship=False): 
     changed_fields = super(M2MDirtyFieldsMixin, self).get_dirty_fields(check_relationship) 
     new_m2m_state = self._as_dict_m2m() 
     changed_m2m_fields = dict([ 
      (key, value) 
      for key, value in self._original_m2m_state.iteritems() 
      if sorted(value) != sorted(new_m2m_state[key]) 
     ]) 
     changed_fields.update(changed_m2m_fields) 
     return changed_fields 

def reset_state(sender, instance, **kwargs): 
    # original state should hold all possible dirty fields to avoid 
    # getting a `KeyError` when checking if a field is dirty or not 
    instance._original_state = instance._as_dict(check_relationship=True) 
    instance._original_m2m_state = instance._as_dict_m2m() 
4

एक दूसरे सवाल का जवाब क्योंकि एक बहुत समय इस सवाल मूल रूप पोस्ट किया गया था के बाद से बदल गया है जोड़ना।

Django दुनिया में कई ऐप्स हैं जो इस समस्या को हल करते हैं। आप Django पैकेज साइट पर एक पूर्ण list of model auditing and history apps पा सकते हैं।

मैंने इन ऐप्स में से कुछ की तुलना में a blog post लिखा था। यह पोस्ट अब 4 साल पुराना है और यह थोड़ा दिनांकित है। हालांकि इस समस्या को हल करने के लिए अलग-अलग दृष्टिकोण समान हैं।

दृष्टिकोण:

  1. स्टोर एक धारावाहिक प्रारूप में सभी ऐतिहासिक परिवर्तन (JSON?) एक एकल तालिका
  2. स्टोर में एक तालिका में सभी ऐतिहासिक परिवर्तन एक मॉडल
  3. स्टोर सभी के लिए मूल मिरर मूल मॉडल के रूप में ही तालिका में ऐतिहासिक परिवर्तन (मैं इस अनुशंसा नहीं करते हैं)

django-reversion पैकेज अभी भी सबसे लोकप्रिय रों हो रहा है इस समस्या के लिए olution। यह पहला दृष्टिकोण लेता है: तालिकाओं को प्रतिबिंबित करने के बजाय परिवर्तनों को क्रमबद्ध करें।

मैंने कुछ साल पहले django-simple-history को पुनर्जीवित किया था। यह दूसरा दृष्टिकोण लेता है: प्रत्येक तालिका दर्पण।

तो मैं इस समस्या को हल करने के लिए एक ऐप का उपयोग करके की सिफारिश करूंगा। कुछ लोकप्रिय लोग हैं जो इस बिंदु पर बहुत अच्छी तरह से काम करते हैं।

ओह और यदि आप केवल गंदे क्षेत्र की जांच कर रहे हैं और सभी ऐतिहासिक परिवर्तनों को संग्रहीत नहीं कर रहे हैं, तो FieldTracker from django-model-utils देखें।

संबंधित मुद्दे