Django-rest-framework: Dukungan untuk memesan berdasarkan bidang terkait bersarang di OrderingFilter

Dibuat pada 25 Jul 2013  ·  28Komentar  ·  Sumber: encode/django-rest-framework

Contoh penggunaan akan diurutkan berdasarkan objek terkait bersarang.

Representasi bersarang:

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

Salah satu opsi adalah mendukung notasi garis bawah ganda Django orm __ untuk model terkait.

Mantan. ?ordering=stats__facebook_friends akan mengurutkan berdasarkan facebook_friends.

Saat ini pemesanan hanya berfungsi untuk bidang model tertentu yang ditentukan dalam queryset.

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

Komentar yang paling membantu

Terima kasih atas tipnya :+1: . Link biarkan untuk memperbaiki. Ini bekerja

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('-'))]

Semua 28 komentar

Saya akan mempertimbangkan permintaan tarik untuk ini, tetapi itu bukan sesuatu yang saya punya waktu untuk melakukannya sendiri.

Siapa pun yang ingin menerapkan ini perlu memastikan perilaku yang masuk akal ketika nama bidang yang salah digunakan dalam pemfilteran. Minimal hasilnya harus tetap kembali oke. (Idealnya, nama bidang yang salah harus diabaikan, tetapi bidang lain harus dibiarkan.)

Dengan pertimbangan saya akan menutup ini. Jika seseorang mengeluarkan permintaan tarik dan tes yang menanganinya maka saya mungkin mempertimbangkan kembali, tetapi implementasi yang lebih lengkap dari masing-masing kelas penyaringan dasar benar-benar sesuatu yang dapat ditangani dengan sangat baik oleh seseorang dalam paket pihak ketiga, yang kemudian dapat dipertahankan secara terpisah, dan ditautkan ke dari dokumen utama.

Tidak ada permintaan tarik karena saya tidak punya waktu untuk menulis tes atau dokumen, tetapi jika ini membantu orang lain menemukan masalah ini, saya menggunakan:

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 versi Anda tidak mengizinkan penyortiran hubungan terbalik, hanya fk langsung di sini adalah versi yang ditambal

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('-'))]

cuplikan pySilver bekerja dengan baik untuk saya, bisakah kita memasukkan ini ke DRF?

Dari mana Anda mengimpor RelatedObject ? Sepertinya tidak tersedia untuk saya

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'

ah, sepertinya sudah dihapus dari 1,8, lihat https://code.djangoproject.com/ticket/21414

Terima kasih atas tipnya :+1: . Link biarkan untuk memperbaiki. Ini bekerja

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('-'))]

Tidak memiliki masalah dengan versi terakhir dari kerangka istirahat Django :)

Jangan lupa untuk menentukan ordering_fields

Sekedar catatan - di Django > 1.10 beberapa metode telah berubah

Secara khusus Anda harus mengubah yang berikut:

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

dan tanda tangan untuk remove_invalid_fields telah berubah menjadi:

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

Yang menghasilkan versi kerja final untuk 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('-'))]

Kode di atas akan memiliki masalah dengan OneToOneField, menambahkan perbaikan untuk itu:

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 dan field.rel.to akan memunculkan peringatan penghentian pada Django >= 1.10. Mereka sekarang masing-masing:

  • field.remote_field
  • bidang.model

@tomchristie mengingat tambalan ini telah berlama-lama (dan terus diperbarui) dalam masalah ini selama 4 tahun (kami telah menggunakan ini dalam produksi sejak tahun lalu), apakah itu cocok untuk PR dan digabung menjadi DRF (ya, didukung oleh satu set yang tepat dari unittests)?

Hanya .2 sen saya

Patch ini berfungsi untuk saya, dapatkah kami menggabungkannya ke versi rilis?

Seperti yang dikatakan sebelumnya, buatlah menjadi pihak ke-3 (lebih disukai) atau perlu PR yang tepat dengan tes dan dokumentasi.

@filiperinaldi Saya pikir di Django >= 1.10 field.rel.to menjadi field.related_model . Ini adalah versi terbaru dari patch yang lolos uji unit kami.

nb Kami menggunakan Django 1.11 jadi 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('-'))]

Oke teman-teman, untuk ulang tahun ke-4 masalah ini, saya memutuskan untuk mencobanya dan meretas bersama paket eksternal untuk dipasang bersama DRF untuk mendukung pemesanan yang bermanfaat ini:

https://github.com/apiraino/djangorestframework_custom_filters_ordering

Saya akan mengerjakannya di hari-hari berikutnya untuk menyelesaikan pekerjaan (yaitu saya perlu mengemas dan memfaktorkan kode dengan benar, menyempurnakan pengujian dan memastikan dukungan untuk versi Django yang didukung secara aktif).

Kontribusi dipersilahkan, tentu saja!

Bersulang

ref #5533.

Saya sangat kebingungan. Ini tampaknya berfungsi secara default?

Saya sampai pada masalah ini, membaca semua komentar, dan melanjutkan untuk mengimplementasikan solusi oleh @apiraino , tetapi kemudian saya menemukan bahwa saya hanya salah mengetik nama bidang terkait saya.

Namun, saya sekarang menggunakan ?ordering=job__customer__company untuk hubungan bersarang ganda untuk memesan hasil API dan berfungsi dengan baik.

@halfnibble - Saya yakin ini telah diperbaiki di #5533.

cc @carltongibson

@halfnibble , versi apa yang kamu gunakan? Saya menggunakan 3.8.2 dan itu tidak berfungsi untuk saya. Ini adalah versi saya:

    "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"
    ]

Maaf, sekarang saya mengerti. Secara default ini hanya akan berfungsi jika parameter pemesanan terkait disertakan dalam ordering_fields . Tetapi untuk solusi yang lebih umum, tambalan masih diperlukan.

Hai, saya relatif baru di DRF dan sedang mencari cara untuk menambahkan pemesanan ke bidang pesanan saya tanpa memaparkan struktur database kami. Misalnya, jika saya ingin mencari z, saya perlu menulis x__y__z, dan meneruskannya ke titik akhir sebagai parameter memperlihatkan struktur. Apakah ini yang saya cari jika saya hanya ingin mengatakan z dan memeriksa fungsi untuk melihat apakah itu bidang terkait di ORM?

Anda dapat mencoba mengganti OrderingFilter.get_ordering . Sesuatu seperti...

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 Terima kasih banyak! Inilah yang saya cari. Saya menghargai bantuannya.

Inilah solusi yang saya kumpulkan:

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__'
Apakah halaman ini membantu?
0 / 5 - 0 peringkat