Django-filter: استخدام غير واضح لـ "OrderingFilter" والحقول المحسوبة

تم إنشاؤها على ١٨ يوليو ٢٠٢٠  ·  4تعليقات  ·  مصدر: carltongibson/django-filter

يشكر

أهلا بك!

لقد كان من دواعي سروري استخدام هذه المكتبة! أنا سعيد جدًا بكل وسائل الراحة التي يوفرها!

شكرا لك على ذلك!

هدف

هدفي الآن هو أنني أرغب في الحصول على نقطة نهاية تدعم إمكانية البحث في معلمات النموذج ، والترتيب أيضًا.

نموذج البيانات

لدي نموذج School يحتوي على حقل محسوب عليه يسمى learner_enrolled_count

يبدو رد JSON كما يلي:

{
    "schools": [
        {
            "id": 6,
            "name": "Piano Gym Six",
            "courses": [
                // ...
            ],
            "learner_enrolled_count": 0
        },
        {
            "id": 7,
            "name": "Piano Gym Seven",
            "courses": [
                // ...
            ],
            "learner_enrolled_count": 5
        }
    ]
}

learner_enrolled_count هو حقل محسوب.

المشكلة

لقد قرأت الوثائق هنا:
https://django-filter.readthedocs.io/en/stable/ref/filters.html؟highlight=order#orderingfilter
و هنا:
https://django-filter.readthedocs.io/en/stable/ref/filters.html؟highlight=order#adding -custom-filter-options

بناءً على ذلك ، كتبت مجموعة التصفية هذه هنا:

# ------------------------------------------------------------------------------
# Python Standard Libraries
# ------------------------------------------------------------------------------
# N/A
# ------------------------------------------------------------------------------
# Third-party Libraries
# ------------------------------------------------------------------------------
from django_filters import CharFilter
from django_filters import OrderingFilter
from django_filters.rest_framework import FilterSet
# ------------------------------------------------------------------------------
# Custom Libraries
# ------------------------------------------------------------------------------
from piano_gym_api.versions.v1.models.school_model import SchoolModel

# See:
# https://django-filter.readthedocs.io/en/stable/ref/filters.html#adding-custom-filter-choices
class SchoolOrderingFilter(OrderingFilter):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.extra["choices"] += [
            ("learner_enrolled_count", "Learner Enrolled Count"),
            ("-learner_enrolled_count", "Learner Enrolled Count (descending)"),
        ]

    def filter(self, query_set, values):
        if(values is None):
            return super().filter(query_set, values)

        for value in values:
            if value in ['learner_enrolled_count', '-learner_enrolled_count']:
                return query_set.order_by(value)

        return super().filter(query_set, values)


class SchoolFilter(FilterSet):
    school_name = CharFilter(field_name="name",
                             lookup_expr="icontains")
    # ---
    course_name = CharFilter(field_name="school_course__name",
                             lookup_expr="icontains")
    course_description = CharFilter(field_name="school_course__description",
                                    lookup_expr="icontains")
    # ---
    lesson_name = CharFilter(field_name="school_lesson__name",
                             lookup_expr="icontains")
    lesson_description = CharFilter(field_name="school_lesson__description",
                                    lookup_expr="icontains")
    # ---
    flash_card_set_name = CharFilter(field_name="school_lesson__flash_card_set__name",
                                     lookup_expr="icontains")
    flash_card_set_description = CharFilter(field_name="school_lesson__flash_card_set__description",
                                            lookup_expr="icontains")
    # ---
    headmaster_username = CharFilter(field_name="school_board__school_board_headmaster__learner__user__username",
                                     lookup_expr="icontains")
    board_member_username = CharFilter(field_name="school_board__school_board_member__learner__user__username",
                                       lookup_expr="icontains")

    # See:
    # https://django-filter.readthedocs.io/en/stable/ref/filters.html#orderingfilter
    o = SchoolOrderingFilter(
        # tuple-mapping retains order
        fields=(
            ("learner_enrolled_count", "learner_enrolled_count"),
        ),

        # labels do not need to retain order
        field_labels={
            "learner_enrolled_count": "Total learners enrolled in school",
        }
    )

    class Meta:
        model = SchoolModel
        fields = [
            "school_name",
            # ---
            "course_name",
            "course_description",
            # ---
            "lesson_name",
            "lesson_description",
            # ---
            "headmaster_username",
            "board_member_username"
        ]

هذه المشكلة هي أنه لا يبدو أنها تطلب على الإطلاق! ليس لدي أي فكرة عن السبب. هذا شيىء غريب جدا.

إذا أسقطت تتبع تصحيح الأخطاء في طريقة filter لـ SchoolOrderingFilter أرى أن values هو None . لست متأكدًا مما يجب أن يكون عليه ذلك.

الطلب الذي أقوم بتقديمه يبدو هكذا
{{API_URL}}/api/v1/schools/?offset=5&limit=3&ordering=learner_enrolled_count

والرأي الذي يستقبل هذا الطلب يبدو كالتالي:

class SchoolViewSet(ViewSet):
    # ...

    def list(self, request):
        all_school_models = SchoolModel.objects.getBrowseSchoolsData()

        school_filter = SchoolFilter(request.GET, queryset=all_school_models)

        paginator = HeaderLinkPagination()
        current_page_results = paginator.paginate_queryset(school_filter.qs,
                                                           request)

        all_schools_serializer = SchoolSerializer(current_page_results,
                                                  many=True)
        response_data = {
            "schools": all_schools_serializer.data
        }

        response_to_return = paginator.get_paginated_response(response_data)
        return response_to_return

الأسئلة

أعتقد أنه من غير الواضح حقًا بالنسبة لي في الوثائق المتعلقة بكيفية استخدام ميزة التصفية وكيفية استخدام ترتيب الحقول المحسوب.

ما الخطأ الذي افعله؟ هل أنا أسيء فهم هذه الوظيفة؟ أشعر أنني أقوم بتنفيذ الخطوات الصحيحة لهذا ، ولكن لا يبدو أنني أحصل على وظيفة ordering لهذه المكتبة!

مرة أخرى ، شكرا على كل شيء!

التعليق الأكثر فائدة

مرحبًاloganknecht. يجب أن أقول ، شكرًا لك على تقرير المشكلة الرائع هذا. يبدأ بشكل جيد ، ويتحسن. فقط واضح جدا. لذا شكرا لك.

أولاً: يبدو أنك عرّفت SchoolOrderingFilter باسم الحقل o :

o = SchoolOrderingFilter(...)

لذلك عندما تقول:

الطلب الذي أقوم بتقديمه يبدو هكذا
{{API_URL}}/api/v1/schools/?offset=5&limit=3&ordering=learner_enrolled_count

أتوقع أن يكون معامل سلسلة الاستعلام o أيضًا. هل هذا يعمل: {{API_URL}}/api/v1/schools/?offset=5&limit=3&o=learner_enrolled_count ؟

ثم (الشيء الذي لست متأكدًا منه في تقريرك) أنت تقوله - حقل محسوب - ماذا تقصد بالضبط؟ أي هل هو حقل يظهر في النموذج ، أم أنها إحدى خصائص Python؟

هناك طريقة أخرى للسؤال (جزء من) ألا وهي هل يعمل order_by() في مجموعة طلبات البحث مع هذا الحقل؟ أي هل هذا العمل:

SchoolModel.objects.filter(...).order_by(learner_enrolled_count)

ال 4 كومينتر

مرحبًاloganknecht. يجب أن أقول ، شكرًا لك على تقرير المشكلة الرائع هذا. يبدأ بشكل جيد ، ويتحسن. فقط واضح جدا. لذا شكرا لك.

أولاً: يبدو أنك عرّفت SchoolOrderingFilter باسم الحقل o :

o = SchoolOrderingFilter(...)

لذلك عندما تقول:

الطلب الذي أقوم بتقديمه يبدو هكذا
{{API_URL}}/api/v1/schools/?offset=5&limit=3&ordering=learner_enrolled_count

أتوقع أن يكون معامل سلسلة الاستعلام o أيضًا. هل هذا يعمل: {{API_URL}}/api/v1/schools/?offset=5&limit=3&o=learner_enrolled_count ؟

ثم (الشيء الذي لست متأكدًا منه في تقريرك) أنت تقوله - حقل محسوب - ماذا تقصد بالضبط؟ أي هل هو حقل يظهر في النموذج ، أم أنها إحدى خصائص Python؟

هناك طريقة أخرى للسؤال (جزء من) ألا وهي هل يعمل order_by() في مجموعة طلبات البحث مع هذا الحقل؟ أي هل هذا العمل:

SchoolModel.objects.filter(...).order_by(learner_enrolled_count)

carltongibson هذا مثير جدا للاهتمام! لم أكن أتوقع o ليكون تعريف ordering المعلمة! لم أفهم ذلك من الوثائق 😂

استخدام المعلمة o

الشيء المثير للاهتمام هو استخدام الاستعلام
{{API_URL}}/api/v1/schools/?offset=5&limit=3&o=learner_enrolled_count

أحصل على هذه النتائج:

{
    "schools": [
        {
            "id": 6,
            "name": "Piano Gym Six",
            "courses": [
                # ...
            ],
            "learner_enrolled_count": 0
        },
        {
            "id": 8,
            "name": "Piano Gym Eight",
            "courses": [
                # ...
            ],
            "learner_enrolled_count": 0
        },
        {
            "id": 9,
            "name": "Piano Gym Nine",
            "courses": [
                # ...
            ],
            "learner_enrolled_count": 0
        }
    ]
}

الجزء المثير للاهتمام حول هذا الأمر ، الآن هو أن المدرسة الوحيدة التي أريد ترتيبها في الأعلى ليست في نتيجة البحث هذه. مما يعني أنه من المحتمل أن يكون هذا الأمر قد تم طلبه ولكن ترقيم الصفحات يوازن النتائج. ومع ذلك ، عندما استخدمت learner_enrolled_count فإنها في آخر مكان.
{{API_URL}}/api/v1/schools/?limit=3&o=learner_enrolled_count

لذلك استخدمت اختباره باستخدام -learner_enrolled_count (إزالة الإزاحة والفرز تنازليًا)
{{API_URL}}/api/v1/schools/?limit=3&o=-learner_enrolled_count
وحصلت على هذا الرد

{
    "schools": [
        {
            "id": 7,
            "name": "Piano Gym Seven",
            "courses": [
                # ...
            ],
            "learner_enrolled_count": 1
        },
        {
            "id": 1,
            "name": "Piano Gym",
            "courses": [
                # ...
            ],
            "learner_enrolled_count": 0
        },
        {
            "id": 2,
            "name": "Piano Gym Two",
            "courses": [
                # ...
            ],
            "learner_enrolled_count": 0
        }
    ]
}

يبدو أنها عملت!

المجال المحسوب

learner_enrolled_count غير موجود في النموذج نفسه. لقد استخدمت تعليقًا توضيحيًا لحساب هذا وحقنه في النتائج.

نظرًا لخطر الغوص في نموذج البيانات كثيرًا ، يُقصد بنقطة النهاية هذه أن تكون نقطة دخول فردية لتصفية طلبات البحث وترتيبها لقائمة المدارس.

لا بد لي من تحسين نقطة النهاية هذه باستخدام نموذج استرجاع مثل هذا:

class SchoolModelManager(Manager):
    def getBrowseSchoolsData(self, *args, **kwargs):
        """Return all Schools that contain lessons with flash card sets.

        Does not exclude empty sets, just requires that the school has something
        to enroll in
        """
        # WARNING: This MUST be imported here otherwise the compilation fails
        #          because of circular dependencies
        from piano_gym_api.versions.v1.models.flash_card_model import FlashCardModel
        # from piano_gym_api.versions.v1.models.flash_card_model import PlaySheetMusicFlashCardModel
        # from piano_gym_api.versions.v1.models.flash_card_model import TrueOrFalseFlashCardModel
        # from piano_gym_api.versions.v1.models.flash_card_set_model import FlashCardSetModel
        from piano_gym_api.versions.v1.models.sheet_music_model import SheetMusicModel
        # import pdb
        # pdb.set_trace()

        # --------------------
        sheet_music_query_set = (SheetMusicModel.objects.all()
                                 .select_related("school"))
        play_sheet_music_flash_card_sheet_music_prefetch = Prefetch("playsheetmusicflashcardmodel__sheet_music",
                                                                    sheet_music_query_set)
        # --------------------
        flash_card_query_set = (FlashCardModel.objects.all()
                                .select_related("flash_card_set",
                                                "playsheetmusicflashcardmodel",
                                                "school",
                                                "trueorfalseflashcardmodel")
                                .prefetch_related(play_sheet_music_flash_card_sheet_music_prefetch))
        flash_card_prefetch = Prefetch("flash_card_set__flash_card", flash_card_query_set)
        # --------------------
        school_lesson_query_set = (SchoolLessonModel.objects.all()
                                   .select_related("course",
                                                   "flash_card_set",
                                                   "school")
                                   .prefetch_related(flash_card_prefetch))
        school_lesson_prefetch = Prefetch("school_lesson", school_lesson_query_set)
        # --------------------
        school_course_query_set = (SchoolCourseModel.objects.all()
                                   .select_related("school")
                                   .prefetch_related(school_lesson_prefetch))
        school_course_prefetch = Prefetch("school_course", school_course_query_set)
        # --------------------
        query_set_to_return = (SchoolModel.objects.filter(school_lesson__flash_card_set__isnull=False)
                               .distinct()
                               # .annotate(learner_enrolled_count=Count("learner_enrolled_school", distinct=True))
                               .annotate(learner_enrolled_count=Case(
                                   When(learner_enrolled_school__learner_enrolled_course__learner_enrolled_lesson__is_enrolled=True,
                                        then=1),
                                   default=0,
                                   output_field=IntegerField())
        ).prefetch_related(school_course_prefetch))

        return query_set_to_return

calculated field bit موجود في المتغير query_set_to_return . ما يفعله هو نقل count من إجمالي المتعلمين المسجلين و annotates إلى الحقل learner_enrolled_count . ليس لدي أي فكرة عما إذا كانت هذه هي الطريقة الصحيحة لتحقيق ذلك ، ولكن يبدو أنها تعمل 😂

اقتراح

أحد الاقتراحات التي أود تقديمها هو التوضيح في الوثائق ، إما باستخدام أمثلة url الفعلية للطلبات أو نص بسيط وشرح أن o في الأمثلة هو ما يتم عرضه كمعامل للطلب.

استنتاج

يبدو أنه يعمل بشكل صحيح الآن بعد أن أوضحت أن المعلمة o هي ما يتم تعيين عامل تصفية الطلب له!

إذا كان querysets الذي أقدمه لـ filter يحتوي بالفعل على حقل مشروح به - يبدو ، من الاختبار ، أنني لست ملزمًا بعمل مخصصي OrderingFilter .

لهذا السبب أعتقد أنه يمكنني استخدام OrderingFilter بسيطًا بدلاً من ذلك!

أرجوا أن تصحح لي إذا كنت مخطئا!

أعتقد أن هذا يحل حيرتي! شكرا لك!

مرحبًاloganknecht. رائع ، يبدو أنك قد نجحت في ذلك. 💃

إذا كنت تستخدم تعليقًا توضيحيًا ، فيجب أن تكون قادرًا على تصفية ذلك وطلبه باستخدام الفصل القياسي نعم.

سأقوم بتعديل المستندات.

شكرا لمساهمتك.

carltongibson والجميع ، شكرًا على المكتبة الرائعة!

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات