Примером использования может быть сортировка по вложенному связанному объекту.
Вложенное представление:
{
'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
Я бы рассмотрел запросы на вытягивание для этого, но у меня не было бы времени делать это сам.
Любой, кто хочет реализовать это, должен обеспечить разумное поведение при использовании неправильных имен полей при фильтрации. По крайней мере, результаты все равно должны быть нормальными. (В идеале любые неправильные имена полей следует игнорировать, но другие поля следует оставить.)
Обдумывая, я закрою это. Если кто-то выдаст пул-реквест и тесты, которые имеют дело с этим, я мог бы пересмотреть, но более полные реализации каждого из основных классов фильтрации - это действительно то, что кто-то может действительно хорошо решить в стороннем пакете, который затем может поддерживаться отдельно, и связаны с основными документами.
Нет запроса на перенос, потому что у меня не было времени писать тесты или документы, но если это помогает другим людям найти эту проблему, я использую:
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 framework :)
Не забудьте правильно указать свои 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, учитывая, что этот патч задерживается (и постоянно обновляется) в этом выпуске в течение 4 лет (мы используем его в производстве с прошлого года), может ли он быть подходящим для PR и объединен с DRF (да, при поддержке правильный набор юнит-тестов)?
Только мои 0,2 цента
Этот патч мне подходит, можем ли мы объединить его с релизными версиями?
Как было сказано ранее, либо сделайте его сторонним (предпочтительным), либо ему потребуется надлежащий PR с тестами и документацией.
@filiperinaldi Я думаю, что в Django> = 1.10 field.rel.to
становится field.related_model
. Вот последняя версия патча, прошедшая наши модульные тесты.
nb Мы используем 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('-'))]
Хорошо, люди, к 4-му дню рождения этого вопроса я решил попробовать и собрать внешний пакет, который будет установлен вместе с 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__'
Самый полезный комментарий
Спасибо за подсказку: +1:. Ссылка пусть на исправление. Это работает