11

में ModelChoiceField या ModelMultipleChoiceField लिए विकल्प जब एक Django रूप में ModelChoiceField या ModelMultipleChoiceField का उपयोग कर, वहाँ विकल्पों की एक कैश्ड सेट में पारित करने के लिए एक तरीका है? वर्तमान में, यदि मैं क्वेरीसेट पैरामीटर के माध्यम से विकल्पों को निर्दिष्ट करता हूं, तो इसका परिणाम डेटाबेस हिट होता है।कैशिंग क्वेरीसमूह एक Django रूप

मैं इन विकल्पों को कैश करना चाहता हूं और ऐसे क्षेत्र के साथ एक फॉर्म प्रदर्शित करते समय डेटाबेस में अनावश्यक हिट को रोकना चाहता हूं।

उत्तर

9

कारण यह है कि विशेष रूप से ModelChoiceField जब विकल्प पैदा करने एक हिट बनाता है - चाहे क्वेरीसमूह पहले से भर जाने के बाद की परवाह किए बिना - इस लाइन

for obj in self.queryset.all(): 
django.forms.models.ModelChoiceIterator में

में निहित है। Django documentation on caching of QuerySets पर प्रकाश डाला के रूप में,

callable attributes cause DB lookups every time.

तो मैं सिर्फ

for obj in self.queryset: 

उपयोग करने के लिए भले ही मैं नहीं कर रहा हूँ 100% यह सब प्रभाव के बारे में यकीन है कि पसंद करते हैं (मुझे पता है कि मैं बड़ा नहीं है बाद में क्वेरीसेट के साथ योजनाएं, इसलिए मुझे लगता है कि मैं कॉपी के बिना ठीक हूं .all() बनाता है)। मैं स्रोत कोड में बदल करने के लिए परीक्षा रहा हूँ, लेकिन जब से मैं अगले पर इसके बारे में भूल जाते हैं करने जा रहा हूँ स्थापित (और यह बुरा शैली के साथ शुरू करने के लिए है) मैं अपने कस्टम ModelChoiceField लेखन समाप्त हो गया:

class MyModelChoiceIterator(forms.models.ModelChoiceIterator): 
    """note that only line with # *** in it is actually changed""" 
    def __init__(self, field): 
     forms.models.ModelChoiceIterator.__init__(self, field) 

    def __iter__(self): 
     if self.field.empty_label is not None: 
      yield (u"", self.field.empty_label) 
     if self.field.cache_choices: 
      if self.field.choice_cache is None: 
       self.field.choice_cache = [ 
        self.choice(obj) for obj in self.queryset.all() 
       ] 
      for choice in self.field.choice_cache: 
       yield choice 
     else: 
      for obj in self.queryset: # *** 
       yield self.choice(obj) 


class MyModelChoiceField(forms.ModelChoiceField): 
    """only purpose of this class is to call another ModelChoiceIterator""" 
    def __init__(*args, **kwargs): 
     forms.ModelChoiceField.__init__(*args, **kwargs) 

    def _get_choices(self): 
     if hasattr(self, '_choices'): 
      return self._choices 

     return MyModelChoiceIterator(self) 

    choices = property(_get_choices, forms.ModelChoiceField._set_choices) 

यह डेटाबेस कैशिंग की सामान्य समस्या को हल नहीं करता है, लेकिन चूंकि आप विशेष रूप से ModelChoiceField के बारे में पूछ रहे हैं और यह वही है जो मुझे पहले कैशिंग के बारे में सोचने लगा, सोचा कि इससे मदद मिल सकती है।

+1

यह एक अच्छा समाधान है और Django 1.8 में पूरी तरह से काम करता है। दो मामूली सुझाव जो कोड को थोड़ा क्लीनर बना सकते हैं: 1) आप दोनों वर्गों से '__init __()' को हटा सकते हैं, क्योंकि वे नो-ऑप्स हैं। 2) Django 1.9 में 'cache_choices' हटा दिया गया है, ताकि आप कोड के उस पूरे टुकड़े को पट्टी कर सकें। – Chad

+0

हाय, मैं इस समय Django का उपयोग नहीं कर रहा हूं इसलिए मेरे पास कोई वर्तमान Django सेट अप नहीं है और इसलिए इसे सत्यापित करने का तरीका है। इसे कोड में बदलने के लिए अनिच्छुक मैं परीक्षण नहीं कर सकता - आपके बारे में कोड के साथ उत्तर कैसे बनाते हैं और मैं आपके उत्तर से लिंक करने के लिए नीचे इस पोस्ट को संपादित करता हूं? – Nicolas78

12

आप क्वेरीसमूह में ओवरराइड कर सकते हैं "सभी" विधि कुछ

from django.db import models 
class AllMethodCachingQueryset(models.query.QuerySet): 
    def all(self, get_from_cache=True): 
     if get_from_cache: 
      return self 
     else: 
      return self._clone() 


class AllMethodCachingManager(models.Manager): 
    def get_query_set(self): 
     return AllMethodCachingQueryset(self.model, using=self._db) 


class YourModel(models.Model): 
    foo = models.ForeignKey(AnotherModel) 

    cache_all_method = AllMethodCachingManager() 

की तरह और फिर (जब आप formsets का उपयोग exmple के लिए) का उपयोग कर प्रपत्र से पहले क्षेत्र के क्वेरीसमूह बदलने

form_class.base_fields['foo'].queryset = YourModel.cache_all_method.all() 
+0

उत्कृष्ट धन्यवाद! यह सबसे साफ समाधान है (मेरे ज्ञान के सर्वोत्तम के लिए)। विदेशी इनलाइन मॉडल के साथ तीसरे मॉडल के साथ काम करते समय बहुत उपयोगी। – ppetrid

+1

यह Django 1.8 में काम नहीं करता प्रतीत होता है। क्या कोई मदद कर सकता है? – johnny

+0

@ जॉनी, निकोलस 78 का जवाब देखें, जो मेरे लिए Django 1.8 में काम करता है। – Chad

2

मैं भी ठोकर खाई Django व्यवस्थापक में इनलाइनफॉर्मसेट का उपयोग करते समय इस समस्या पर, जिसने स्वयं दो अन्य मॉडल का संदर्भ दिया। बहुत सारे अनावश्यक प्रश्न उत्पन्न होते हैं क्योंकि Nicolas87 समझाया गया है, ModelChoiceIterator प्रत्येक बार स्क्रैच से क्वेरीसेट प्राप्त करता है।

निम्नलिखित मिक्सिन को कैश भरने के लिए आवश्यक प्रश्नों की संख्या को कम करने के लिए admin.ModelAdmin, admin.TabularInline या admin.StackedInline में जोड़ा जा सकता है। कैश Request ऑब्जेक्ट से जुड़ा हुआ है, इसलिए यह एक नए अनुरोध पर अमान्य है।

class ForeignKeyCacheMixin(object): 
    def formfield_for_foreignkey(self, db_field, request, **kwargs): 
     formfield = super(ForeignKeyCacheMixin, self).formfield_for_foreignkey(db_field, **kwargs) 
     cache = getattr(request, 'db_field_cache', {}) 
     if cache.get(db_field.name): 
      formfield.choices = cache[db_field.name] 
     else: 
      formfield.choices.field.cache_choices = True 
      formfield.choices.field.choice_cache = [ 
       formfield.choices.choice(obj) for obj in formfield.choices.queryset.all() 
      ] 
      request.db_field_cache = cache 
      request.db_field_cache[db_field.name] = formfield.choices 
     return formfield 
2

@jnns मैंने देखा है कि अपने कोड में क्वेरीसमूह दो बार (कम से कम मेरी व्यवस्थापक इनलाइन संदर्भ में), जो वैसे भी Django व्यवस्थापक के एक ओवरहेड हो रहा है यहां तक ​​कि इस mixin बिना मूल्यांकन किया जाता है,, (प्लस प्रति एक बार इनलाइन जब आपके पास यह मिश्रण नहीं है)।

इस मिश्रण के मामले में, यह इस तथ्य के कारण है कि formfield.choices में एक सेटटर है (सरलीकृत करने के लिए) ऑब्जेक्ट की क्वेरीसेट के पुनर्मूल्यांकन को ट्रिगर करता है।सभी()

मैं एक सुधार formfield.cache_choices के साथ सीधे निपटने के होते हैं जो प्रस्ताव और

formfield.choice_cache संदेश यह है:

class ForeignKeyCacheMixin(object): 

    def formfield_for_foreignkey(self, db_field, request, **kwargs): 
     formfield = super(ForeignKeyCacheMixin, self).formfield_for_foreignkey(db_field, **kwargs) 
     cache = getattr(request, 'db_field_cache', {}) 
     formfield.cache_choices = True 
     if db_field.name in cache: 
      formfield.choice_cache = cache[db_field.name] 
     else: 
      formfield.choice_cache = [ 
       formfield.choices.choice(obj) for obj in formfield.choices.queryset.all() 
      ] 
      request.db_field_cache = cache 
      request.db_field_cache[db_field.name] = formfield.choices 
     return formfield 
3

यहाँ एक छोटे से हैक मैं करने के लिए Django 1.10 के साथ उपयोग करते एक फॉर्मेट में एक क्वेरीसेट कैश करें:

qs = my_queryset 

# cache the queryset results 
cache = [p for p in qs] 

# build an iterable class to override the queryset's all() method 
class CacheQuerysetAll(object): 
    def __iter__(self): 
     return iter(cache) 
    def _prefetch_related_lookups(self): 
     return False 
qs.all = CacheQuerysetAll 

# update the forms field in the formset 
for form in formset.forms: 
    form.fields['my_field'].queryset = qs 
+0

के साथ काम करना चाहिए यह Django 1.11.4 शिकायत करने के साथ काम करना बंद कर दिया है "विशेषता त्रुटि: 'CacheQuerysetAll' ऑब्जेक्ट में कोई विशेषता नहीं है 'सब'। क्या आपको पता है कि इसे कैसे ठीक किया जाए? धन्यवाद! – hetsch

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