2012-09-17 14 views
12

मान लें कि मेरे पास Box एक GenericForeignKey है जो Apple उदाहरण या Chocolate उदाहरण के लिए इंगित करता है। Apple और Chocolate बदले में, क्रमशः Farm और Factory पर विदेशीकेज़ हैं। मैं Box es की एक सूची प्रदर्शित करना चाहता हूं, जिसके लिए मुझे Farm और Factory तक पहुंचने की आवश्यकता है। मैं इसे जितना संभव हो उतने डीबी प्रश्नों में कर सकता हूं?django: जेनेरिक फोरिनेकी

मिनिमल उदाहराणदर्शक उदाहरण:

class Farm(Model): 
    ... 

class Apple(Model): 
    farm = ForeignKey(Farm) 
    ... 

class Factory(Model): 
    ... 

class Chocolate(Model): 
    factory = ForeignKey(Factory) 
    ... 

class Box(Model) 
    content_type = ForeignKey(ContentType) 
    object_id = PositiveIntegerField() 
    content_object = GenericForeignKey('content_type', 'object_id') 
    ... 

    def __unicode__(self): 
     if self.content_type == ContentType.objects.get_for_model(Apple): 
      apple = self.content_object 
      return "Apple {} from Farm {}".format(apple, apple.farm) 
     elif self.content_type == ContentType.objects.get_for_model(Chocolate): 
      chocolate = self.content_object 
      return "Chocolate {} from Factory {}".format(chocolate, chocolate.factory) 

यहाँ कुछ चीजें मैंने कोशिश कर रहे हैं। इन सभी उदाहरणों में, एन बॉक्स की संख्या है। क्वेरी गिनती मानती है कि ContentType एस Apple और Chocolate के लिए पहले से ही कैश किया गया है, इसलिए get_for_model() कॉल डीबी को हिट नहीं करते हैं।

1) अनुभवहीन:

print [box for box in Box.objects.all()]

यह (बक्से लाने करता है) + एन (Apple या प्रत्येक बॉक्स के लिए चॉकलेट) + एन (प्रत्येक एप्पल के लिए फार्म लाने और लाने प्रत्येक चॉकलेट के लिए फैक्टरी) प्रश्न।

2) select_related यहां सहायता नहीं करता है, क्योंकि Box.content_objectGenericForeignKey है।

3) django 1.4, prefetch_related के रूप में GenericForeignKey एस प्राप्त कर सकते हैं।

print [box for box in Box.objects.prefetch_related('content_object').all()]

यह (बक्से लाने) करता + (सभी बक्से के लिए सेब और चॉकलेट लाने) + एन (लाने प्रत्येक एप्पल और फैक्टरी प्रत्येक चॉकलेट के लिए के लिए फार्म) प्रश्नों।

4) स्पष्ट रूप से prefetch_related जेनेरिक फॉर्निनेकीज़ के विदेशी क्षेत्रों का पालन करने के लिए पर्याप्त स्मार्ट नहीं है। अगर मैं कोशिश:

print [box for box in Box.objects.prefetch_related( 'content_object__farm', 'content_object__factory').all()]

यह हक शिकायत है कि Chocolate वस्तुओं एक farm क्षेत्र नहीं है, और इसके विपरीत।

5) मैं कर सकता:

apple_ctype = ContentType.objects.get_for_model(Apple) 
chocolate_ctype = ContentType.objects.get_for_model(Chocolate) 
boxes_with_apples = Box.objects.filter(content_type=apple_ctype).prefetch_related('content_object__farm') 
boxes_with_chocolates = Box.objects.filter(content_type=chocolate_ctype).prefetch_related('content_object__factory') 

यह है (बक्से लाने) + (सभी बक्से के लिए सेब और चॉकलेट) + (सभी सेब के लिए फार्म लाने लाने और सभी चॉकलेट के लिए कारखानों) प्रश्न। नकारात्मकता यह है कि मुझे मैन्युअल रूप से दो क्वेरीसेट्स (boxes_with_apples, boxes_with_chocolates) को मर्ज और सॉर्ट करना होगा। मेरे असली एप्लिकेशन में, मैं इन बॉक्सों को एक पेजिनेटेड मॉडलएडमिन में प्रदर्शित कर रहा हूं। यह स्पष्ट नहीं है कि इस समाधान को कैसे एकीकृत किया जाए। शायद मैं इस कैशिंग को पारदर्शी रूप से करने के लिए एक कस्टम पेजिनेटर लिख सकता हूं?

6) मैं this पर आधारित कुछ जोड़ सकता हूं जो ओ (1) प्रश्न भी करता है। लेकिन अगर मैं इससे बच सकता हूं तो मैं आंतरिक (_content_object_cache) के साथ गड़बड़ नहीं करूंगा।

सारांश में: एक बॉक्स को प्रिंट करने के लिए जेनेरिक फॉर्निनेकी के विदेशी क्षेत्रों तक पहुंच की आवश्यकता होती है। मैं ओ (1) प्रश्नों में एन बॉक्स कैसे मुद्रित कर सकता हूं? है (5) सबसे अच्छा मैं कर सकता हूं, या क्या कोई आसान समाधान है?

बोनस अंक: इस तरह के प्रश्नों को आसान बनाने के लिए आप इस डीबी स्कीमा को कैसे प्रतिक्रिया देंगे?

+0

का उपयोग आप 'कुछ साधारण नाम के नाम बदलते हैं farm' /' factory' काम prefetch_related होगा 'creator', जैसे,? – Igor

+0

दरअसल, 'prefetch_related (' content_object__creator ') आपके सुझाए गए नाम के बाद काम करता है। दुर्भाग्यवश, नाम आपके द्वारा ऐप्पल/फार्म और चॉकलेट/फैक्ट्री के स्थान पर मौजूद वास्तविक मॉडल के आधार पर समझ सकता है या नहीं। – cberzan

उत्तर

8

आप मैन्युअल रूप से prefetch_selected जैसे कुछ कार्यान्वित कर सकते हैं और Django की select_related विधि का उपयोग कर सकते हैं, जो डेटाबेस क्वेरी में शामिल हो जाएगा।

apple_ctype = ContentType.objects.get_for_model(Apple) 
chocolate_ctype = ContentType.objects.get_for_model(Chocolate) 
boxes = Box.objects.all() 
content_objects = {} 
# apples 
content_objects[apple_ctype.id] = Apple.objects.select_related(
    'farm').in_bulk(
     [b.object_id for b in boxes if b.content_type == apple_ctype] 
    ) 
# chocolates 
content_objects[chocolate_ctype.id] = Chocolate.objects.select_related(
    'factory').in_bulk(
     [b.object_id for b in boxes if b.content_type == chocolate_ctype] 
    ) 

यह केवल 3 प्रश्नों बनाना चाहिए (get_for_model प्रश्नों छोड़े गए हैं)। in_bulk विधि आपको प्रारूप में एक नियम देता है {id: model}। तो अपने content_object प्राप्त करने के लिए आप की तरह एक कोड की जरूरत है:

content_obj = content_objects[box.content_type_id][box.object_id] 

हालांकि मुझे यकीन है कि अगर यह कोड तो जल्दी हो जाएगा नहीं कर रहा हूँ अपने हे (5) के रूप में समाधान बक्से से अधिक अतिरिक्त यात्रा की आवश्यकता है क्वेरीसमूह और भी WHERE id IN (...) कथन

के साथ क्वेरी उत्पन्न करता है लेकिन यदि आप बॉक्स मॉडल से केवल फ़ील्ड द्वारा बॉक्स को सॉर्ट करते हैं तो आप पेजिंग के बाद content_objects dict भर सकते हैं। लेकिन तुम पास करनी होगी content_objects__unicode__ को किसी भी तरह

कैसे आप इस डीबी स्कीमा इस तरह के प्रश्नों को आसान बनाने के refactor होगा?

हमारे पास समान संरचना है। हम Box में स्टोर करते हैं, लेकिन object_id और content_object के बजाय हम ForeignKey(Box)Apple और Chocolate में उपयोग करते हैं। Box में हमारे पास ऐप्पल या चॉकलेट मॉडल को वापस करने के लिए get_object विधि है। इस मामले में हम select_related का उपयोग कर सकते हैं, लेकिन हमारे अधिकांश उपयोग-मामलों में हम content_type द्वारा बॉक्स को फ़िल्टर करते हैं। तो हमारे पास आपके 5 वें विकल्प की तरह ही समस्याएं हैं। लेकिन हमने Django 1.2 पर प्रोजेक्ट शुरू किया जब कोई prefetch_selected नहीं था।

यदि आप कुछ आम नामों के लिए खेत/फैक्ट्री का नाम बदलते हैं, जैसे निर्माता, prefetch_related काम करेगा?

बारे में अपने विकल्प 6

मैं _content_object_cache भरने के खिलाफ कुछ भी कह सकते हैं। आप internals से निपटने के लिए पसंद नहीं है यदि आप कस्टम गुण भर सकते हैं और उसके बाद

apple = getattr(self, 'my_custop_prop', None) 
if apple is None: 
    apple = self.content_object 
+0

बस ध्यान दिया कि मेरा जवाब आपके * विकल्प 6 * के बहुत करीब है लेकिन कम automatization के साथ। मैंने पहले कभी उस लेख को नहीं पढ़ा है।यह भी ओ (1) की तरह नहीं दिखता है, यह ओ (2 + number_of_unique_ctypes) है – Igor

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