2025, Sep 29 19:31

FilterSet की qs override से with_parents को अंतिम चरण में लागू करें

django-filter में FilterSet की qs override से with_parents/ancestors टॉगल को आख़िर में चलाएँ, archived_flag फ़िल्टर पहले लगें, फिर जरूरत पर विस्तार करें.

जब django-filter में बनाया गया कोई कस्टम फ़िल्टर सभी मानक फ़ील्ड्स के बाद चलना ज़रूरी हो, तो सामान्य method-आधारित hook काम नहीं आता। एक सामान्य उदाहरण include-ancestors टॉगल है, जिसे केवल तब जाँचना चाहिए जब आधार परिणाम-सेट स्पष्ट हो जाए—मसलन is_archived बाधा लागू करने के बाद। नीचे बताया गया है कि FilterSet को कैसे व्यवस्थित करें ताकि ancestors वाला चरण सबसे अंत में चले और केवल तब, जब इसकी मांग की गई हो।

समस्या

मकसद यह है कि include_ancestors से जुड़ी लॉजिक को फ़िल्टरिंग के बिल्कुल अंत में धकेला जाए, क्योंकि यह इस बात पर निर्भर करती है कि बाकी सभी फ़िल्टर लागू होने के बाद कौन-से रिकॉर्ड बचे।

import django_filters
class NodeFilters(django_filters.FilterSet):
    model = CatalogItem
    fields = {
        "archived_flag": ("exact",),
    }
    with_parents = django_filters.BooleanFilter(method="with_parents_hook")
    def with_parents_hook(self, queryset, name, value):
        pass

सवाल यह है कि with_parents फ़िल्टर को archived_flag (और अन्य फ़ील्ड फ़िल्टरों) के बाद कैसे चलाया जाए, ताकि वह अंतिम परिणाम-सेट पर काम कर सके।

ऐसा क्यों होता है

Method-backed फ़िल्टर भी अन्य फ़ील्ड की तरह उसी फ़िल्टरिंग पाइपलाइन का हिस्सा होते हैं। यदि ancestors वाला चरण पहले से फ़िल्टर हुए परिणाम से अवगत रहना चाहिए, तो उसे उसी पाइपलाइन में साधारण फ़ील्ड फ़िल्टर की तरह नहीं चलाना चाहिए। इसके बजाय, उसे आधार queryset तैयार होने के बाद लागू किया जाना चाहिए।

समाधान

टॉगल को बिना method के एक सामान्य BooleanFilter के रूप में घोषित करें। फिर qs प्रॉपर्टी को override करें: पहले पूरी तरह फ़िल्टर हुआ queryset लें, और उसके बाद ही, अगर टॉगल मौजूद हो, तो उसी अंतिम queryset पर ancestors की लॉजिक लागू करें।

import django_filters
class NodeFilters(django_filters.FilterSet):
    with_parents = django_filters.BooleanFilter(required=False)
    class Meta:
        model = CatalogItem
        fields = {"archived_flag": ["exact"]}
    @property
    def qs(self):
        base_qs = super().qs
        if self.form.cleaned_data.get("with_parents"):
            base_qs = self._expand_with_parents(base_qs)
        return base_qs
    def _expand_with_parents(self, queryset):
        pass

इस तरह super().qs के जरिए मानक फ़िल्टर पहले चलते हैं। उसके बाद ही, अगर सबमिट किए गए पैरामीटर्स में with_parents सच हो, तो परिणाम को _expand_with_parents से बढ़ाया जाता है।

यह क्यों मायने रखता है

जब किसी फ़िल्टर का अर्थ अन्य फ़िल्टरों के नतीजों पर निर्भर करता है, तो क्रम निर्णायक होता है। ancestors चरण को आख़िर में लगाने से वह सही, अंतिम चयन पर काम करता है—ठीक वही चाहिए जब शामिल करना इस बात पर टिका हो कि किन children पहले लगाए गए प्रतिबंधों के बाद बचे रहे।

मुख्य निष्कर्ष

टॉगल के लिए non-method BooleanFilter का उपयोग करें और सशर्त लॉजिक को qs प्रॉपर्टी में ले जाएँ। पहले super().qs से पूरा फ़िल्टर हुआ queryset निकालें, फिर केवल अनुरोध होने पर ancestors का विस्तार लागू करें। इससे फ़िल्टरिंग पूर्वानुमेय रहती है और अंतिम चरण वास्तविक परिणाम-सेट पर निर्भर कर पाता है।

यह लेख StackOverflow पर प्रश्न (लेखक: Thomas) और Exprator के उत्तर पर आधारित है।