Django-rest-framework: دعم الطلب من خلال الحقول المتداخلة ذات الصلة في OrderingFilter

تم إنشاؤها على ٢٥ يوليو ٢٠١٣  ·  28تعليقات  ·  مصدر: encode/django-rest-framework

مثال على الاستخدام سيكون الفرز حسب كائن متداخل ذي صلة.

تمثيل متداخل:

{
    'username': 'george',
    'email': '[email protected]'
    'stats': {
        'facebook_friends': 560,
        'twitter_followers': 4043,
        ...
    },
},
{
    'username': 'michael',
    'email': '[email protected]'
    'stats': {
        'facebook_friends': 256,
        'twitter_followers': 120,
        ...
    },
},
...

أحد الخيارات هو دعم علامة django orm المزدوجة للشرطة السفلية __ للنماذج ذات الصلة.

السابق. ?ordering=stats__facebook_friends يمكن الترتيب حسب facebook_friends.

يعمل الطلب حاليًا فقط مع الحقول الخاصة بالنموذج المحدد المحدد في مجموعة الاستعلام.

https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/filters.py#L125

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

شكرا للنصيحة: +1:. الرابط دعنا إلى الإصلاح. هذا يعمل

from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields.related import ForeignObjectRel
from rest_framework.filters import OrderingFilter


class RelatedOrderingFilter(OrderingFilter):
    """
    Extends OrderingFilter to support ordering by fields in related models
    using the Django ORM __ notation
    """
    def is_valid_field(self, model, field):
        """
        Return true if the field exists within the model (or in the related
        model specified using the Django ORM __ notation)
        """
        components = field.split('__', 1)
        try:
            field, parent_model, direct, m2m = \
                model._meta.get_field_by_name(components[0])

            # reverse relation
            if isinstance(field, ForeignObjectRel):
                return self.is_valid_field(field.model, components[1])

            # foreign key
            if field.rel and len(components) == 2:
                return self.is_valid_field(field.rel.to, components[1])
            return True
        except FieldDoesNotExist:
            return False

    def remove_invalid_fields(self, queryset, ordering, view):
        return [term for term in ordering
                if self.is_valid_field(queryset.model, term.lstrip('-'))]

ال 28 كومينتر

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

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

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

لا يوجد طلب سحب لأنه لم يكن لدي الوقت لكتابة الاختبارات أو المستندات ، ولكن في حال كان هذا يساعد الأشخاص الآخرين في العثور على هذه المشكلة ، فأنا أستخدم:

from django.db.models.fields import FieldDoesNotExist 
from rest_framework.filters import OrderingFilter

class RelatedOrderingFilter(OrderingFilter):
    """ 
    Extends OrderingFilter to support ordering by fields in related models
    using the Django ORM __ notation
    """
    def is_valid_field(self, model, field):
        """
        Return true if the field exists within the model (or in the related 
        model specified using the Django ORM __ notation)
        """
        components = field.split('__', 1)
        try:
            field, parent_model, direct, m2m = model._meta.get_field_by_name(components[0])
            if field.rel and len(components) == 2:
                return self.is_valid_field(field.rel.to, components[1])
            return True
        except FieldDoesNotExist:
            return False

    def remove_invalid_fields(self, queryset, ordering):
        return [term for term in ordering if self.is_valid_field(queryset.model, term.lstrip('-'))]

rhunwicks إصدارك لا يسمح بفرز العلاقة العكسية ، فقط نسخة fk المباشرة هنا هي نسخة مصححة

class RelatedOrderingFilter(OrderingFilter):
    """
    Extends OrderingFilter to support ordering by fields in related models
    using the Django ORM __ notation
    """
    def is_valid_field(self, model, field):
        """
        Return true if the field exists within the model (or in the related
        model specified using the Django ORM __ notation)
        """
        components = field.split('__', 1)
        try:
            field, parent_model, direct, m2m = \
                model._meta.get_field_by_name(components[0])

            # reverse relation
            if isinstance(field, RelatedObject):
                return self.is_valid_field(field.model, components[1])

            # foreign key
            if field.rel and len(components) == 2:
                return self.is_valid_field(field.rel.to, components[1])
            return True
        except FieldDoesNotExist:
            return False

    def remove_invalid_fields(self, queryset, ordering, view):
        return [term for term in ordering
                if self.is_valid_field(queryset.model, term.lstrip('-'))]

نجح مقتطف pySilver بشكل جيد بالنسبة لي ، فهل يمكننا إدخال هذا في DRF بأي فرصة؟

من أين تستورد RelatedObject من؟ لا يبدو أنه متاح لي

Python 3.4.3
Django==1.8.5
djangorestframework==3.2.3

eldamir django.db.models.related.RelatedObject

ImportError: No module named 'django.db.models.related'

آه ، يبدو أنه تمت إزالته من 1.8 ، راجع https://code.djangoproject.com/ticket/21414

شكرا للنصيحة: +1:. الرابط دعنا إلى الإصلاح. هذا يعمل

from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields.related import ForeignObjectRel
from rest_framework.filters import OrderingFilter


class RelatedOrderingFilter(OrderingFilter):
    """
    Extends OrderingFilter to support ordering by fields in related models
    using the Django ORM __ notation
    """
    def is_valid_field(self, model, field):
        """
        Return true if the field exists within the model (or in the related
        model specified using the Django ORM __ notation)
        """
        components = field.split('__', 1)
        try:
            field, parent_model, direct, m2m = \
                model._meta.get_field_by_name(components[0])

            # reverse relation
            if isinstance(field, ForeignObjectRel):
                return self.is_valid_field(field.model, components[1])

            # foreign key
            if field.rel and len(components) == 2:
                return self.is_valid_field(field.rel.to, components[1])
            return True
        except FieldDoesNotExist:
            return False

    def remove_invalid_fields(self, queryset, ordering, view):
        return [term for term in ordering
                if self.is_valid_field(queryset.model, term.lstrip('-'))]

لم تواجه مشكلة مع الإصدار الأخير من إطار عمل django rest :)

لا تنس تحديد ordering_fields بشكل صحيح

مجرد ملاحظة - في Django> 1.10 تغيرت طريقتان

على وجه التحديد ، سيتعين عليك تغيير ما يلي:

field, parent_model, direct, m2m = model._meta.get_field_by_name(components[0])
ل
field = model._meta.get_field(components[0])

وتغير توقيع remove_invalid_fields إلى:

def remove_invalid_fields(self, queryset, ordering, view, request):

والذي ينتج عنه إصدار العمل النهائي لـ Django> 1.10:

class RelatedOrderingFilter(OrderingFilter):
    """
    Extends OrderingFilter to support ordering by fields in related models
    using the Django ORM __ notation
    """
    def is_valid_field(self, model, field):
        """
        Return true if the field exists within the model (or in the related
        model specified using the Django ORM __ notation)
        """
        components = field.split('__', 1)
        try:

            field = model._meta.get_field(components[0])

            # reverse relation
            if isinstance(field, ForeignObjectRel):
                return self.is_valid_field(field.model, components[1])

            # foreign key
            if field.rel and len(components) == 2:
                return self.is_valid_field(field.rel.to, components[1])
            return True
        except FieldDoesNotExist:
            return False

    def remove_invalid_fields(self, queryset, fields, view, request):
        return [term for term in fields if self.is_valid_field(queryset.model, term.lstrip('-'))]

سيواجه الرمز أعلاه مشكلة مع OneToOneField ، مع إضافة إصلاح لذلك:

from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields.reverse_related import ForeignObjectRel, OneToOneRel

from rest_framework.filters import OrderingFilter


class RelatedOrderingFilter(OrderingFilter):
    """
    Extends OrderingFilter to support ordering by fields in related models.
    """    

    def is_valid_field(self, model, field):
        """
        Return true if the field exists within the model (or in the related
        model specified using the Django ORM __ notation)
        """
        components = field.split('__', 1)
        try:

            field = model._meta.get_field(components[0])

            if isinstance(field, OneToOneRel):
                return self.is_valid_field(field.related_model, components[1])

            # reverse relation
            if isinstance(field, ForeignObjectRel):
                return self.is_valid_field(field.model, components[1])

            # foreign key
            if field.rel and len(components) == 2:
                return self.is_valid_field(field.rel.to, components[1])
            return True
        except FieldDoesNotExist:
            return False

    def remove_invalid_fields(self, queryset, fields, view):
        return [term for term in fields
                if self.is_valid_field(queryset.model, term.lstrip('-'))]        

سيقوم field.rel و field.rel.to برفع تحذير الإهلاك على Django> = 1.10. هم الآن على التوالي:

  • الحقل
  • المجال

tomchristie معتبرا أن هذا التصحيح

فقط سنتان

هذا التصحيح يعمل معي ، هل يمكننا دمجه في إصدارات الإصدار؟

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

filiperinaldi أعتقد أنه في Django> = 1.10 field.rel.to يصبح field.related_model . ها هو أحدث إصدار من التصحيح الذي نجح في اجتياز اختباراتنا.

ملحوظة نحن نستخدم Django 1.11 لذا ymmw

class RelatedOrderingFilter(filters.OrderingFilter):
    """

    See: https://github.com/tomchristie/django-rest-framework/issues/1005

    Extends OrderingFilter to support ordering by fields in related models
    using the Django ORM __ notation
    """
    def is_valid_field(self, model, field):
        """
        Return true if the field exists within the model (or in the related
        model specified using the Django ORM __ notation)
        """
        components = field.split('__', 1)
        try:
            field = model._meta.get_field(components[0])

            if isinstance(field, OneToOneRel):
                return self.is_valid_field(field.related_model, components[1])

            # reverse relation
            if isinstance(field, ForeignObjectRel):
                return self.is_valid_field(field.model, components[1])

            # foreign key
            if field.remote_field and len(components) == 2:
                return self.is_valid_field(field.related_model, components[1])
            return True
        except FieldDoesNotExist:
            return False

    def remove_invalid_fields(self, queryset, fields, ordering, view):
        return [term for term in fields
                if self.is_valid_field(queryset.model, term.lstrip('-'))]

حسنًا ، في عيد الميلاد الرابع لهذه المشكلة ، قررت أن أجربها واختراق حزمة خارجية معًا ليتم تثبيتها جنبًا إلى جنب مع DRF لدعم هذا الطلب المفيد:

https://github.com/apiraino/djangorestframework_custom_filters_ordering

سأعمل على ذلك في الأيام التالية لإنهاء المهمة (أي أحتاج إلى حزم الكود ومعاملته بشكل صحيح ، وتحسين الاختبار وضمان الدعم لإصدارات Django المدعومة بشكل فعال).

نرحب بالمساهمات بالطبع!

هتافات

المرجع # 5533.

انا محتار جدا. يبدو أن هذا يعمل بشكل افتراضي؟

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

ومع ذلك ، أستخدم الآن ?ordering=job__customer__company لعلاقة مزدوجة متداخلة لطلب نتائج API وهي تعمل بشكل جيد.

halfnibble - أعتقد أن هذا تم إصلاحه في # 5533.

cc carltongibson

halfnibble ، ما هو الإصدار الذي

    "install_requires": [
        "django==2.0.3",
        "coreapi==2.3.3",
        "django-filter==1.1.0",
        "djangorestframework-filters==0.10.2.post0",
        "djangorestframework-queryfields==1.0.0",
        "djangorestframework==3.8.2",
        "django-bulk-update==2.2.0",
        "django-cors-headers==2.4.0",
        "django-rest-auth[with_social]==0.9.2",
        "drf-yasg==1.6.0",
        "django-taggit==0.22.2",
        "google-api-python-client==1.6.2",
        "markdown==2.6.11",
        "pygments==2.2.0",
        "xlrd==1.1.0",
        "xlsxwriter==0.9.8",
        "factory-boy==2.10.0",
        "psycopg2-binary==2.7.4",
        "django-admin-tools==0.8.1"
    ]

آسف ، فهمت الآن. بشكل افتراضي ، لن يعمل إلا إذا تم تضمين معلمة الطلب ذات الصلة في ordering_fields . ولكن للحصول على حل أكثر عمومية ، لا يزال التصحيح مطلوبًا.

مرحبًا ، أنا جديد نسبيًا على DRF وكنت أبحث عن طريقة لإضافة الطلب إلى حقول طلبي دون الكشف عن بنية قاعدة البيانات الخاصة بنا. على سبيل المثال ، إذا أردت البحث عن z ، فسأحتاج إلى كتابة x__y__z ، وتمرير ذلك في نقطة النهاية كمعامل يعرض البنية. هل هذا ما أبحث عنه إذا أردت فقط أن أقول z ولدي فحص الوظيفة لمعرفة ما إذا كان حقلًا مرتبطًا في ORM؟

يمكنك محاولة تجاوز OrderingFilter.get_ordering . شيء مثل...

class CustomOrderingFilter(OrderingFilter):
    def get_ordering(self, request, queryset, view):
        ordering = super().get_ordering(request, queryset, view)
        field_map = {
            'z': 'x__y__z',
        }
        return [field_map.get(o, o) for o in ordering]

rpkilby شكرا جزيلا لك! هذا هو بالضبط ما كنت أبحث عنه. أنا أقدر المساعدة.

إليك الحل الذي أعددته:

class RelatedOrderingFilter(filters.OrderingFilter):
    _max_related_depth = 3

    <strong i="6">@staticmethod</strong>
    def _get_verbose_name(field: models.Field, non_verbose_name: str) -> str:
        return field.verbose_name if hasattr(field, 'verbose_name') else non_verbose_name.replace('_', ' ')

    def _retrieve_all_related_fields(
            self,
            fields: Tuple[models.Field],
            model: models.Model,
            depth: int = 0
    ) -> List[tuple]:
        valid_fields = []
        if depth > self._max_related_depth:
            return valid_fields
        for field in fields:
            if field.related_model and field.related_model != model:
                rel_fields = self._retrieve_all_related_fields(
                    field.related_model._meta.get_fields(),
                    field.related_model,
                    depth + 1
                )
                for rel_field in rel_fields:
                    valid_fields.append((
                        f'{field.name}__{rel_field[0]}',
                        self._get_verbose_name(field, rel_field[1])
                    ))
            else:
                valid_fields.append((
                    field.name,
                    self._get_verbose_name(field, field.name),
                ))
        return valid_fields

    def get_valid_fields(self, queryset: models.QuerySet, view, context: dict = None) -> List[tuple]:
        valid_fields = getattr(view, 'ordering_fields', self.ordering_fields)
        if not valid_fields == '__all_related__':
            if not context:
                context = {}
            valid_fields = super().get_valid_fields(queryset, view, context)
        else:
            valid_fields = [
                *self._retrieve_all_related_fields(queryset.model._meta.get_fields(), queryset.model),
                *[(key, key.title().split('__')) for key in queryset.query.annotations]
            ]
        return valid_fields
````

Then I add this to wherever I want to be able to order by all related fields:
```python
filter_backends = (RelatedOrderingFilter,)
ordering_fields = '__all_related__'
هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات