Um exemplo de uso seria a classificação por um objeto relacionado aninhado.
Representação aninhada:
{
'username': 'george',
'email': '[email protected]'
'stats': {
'facebook_friends': 560,
'twitter_followers': 4043,
...
},
},
{
'username': 'michael',
'email': '[email protected]'
'stats': {
'facebook_friends': 256,
'twitter_followers': 120,
...
},
},
...
Uma opção é suportar a notação de sublinhado duplo django orm __ para modelos relacionados.
Ex. ?ordering=stats__facebook_friends
classificaria por facebook_friends.
Atualmente, o pedido funciona apenas para campos do modelo específico especificado no queryset.
https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/filters.py#L125
Eu consideraria puxar solicitações para isso, mas não é algo que eu teria tempo de fazer sozinho.
Qualquer pessoa que queira implementar isso precisa garantir um comportamento sensato quando nomes de campo incorretos são usados na filtragem. No mínimo, os resultados ainda devem retornar bem. (Idealmente, quaisquer nomes de campo incorretos devem ser ignorados, mas os outros campos devem ser deixados.)
Pensando bem, vou encerrar isso. Se alguém emitir uma solicitação pull e testar que lida com isso, então eu devo reconsiderar, mas implementações mais completas de cada uma das classes básicas de filtragem é realmente algo que alguém poderia resolver muito bem em um pacote de terceiros, que poderia então ser mantido separadamente, e vinculado a partir dos documentos principais.
Sem solicitação pull porque não tive tempo de escrever testes ou documentos, mas caso isso ajude outras pessoas a encontrar esse problema, estou usando:
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 sua versão não permite ordenação reversa de relação, apenas fk's diretos aqui são a versão corrigida
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('-'))]
O snippet de pySilver funcionou bem para mim, podemos colocar isso no DRF por acaso?
De onde você importa RelatedObject
? Não parece estar disponível para mim
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, parece que foi removido do 1.8, consulte https://code.djangoproject.com/ticket/21414
Obrigado pela dica: +1:. O link permite a correção. Isso funciona
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('-'))]
Não teve problemas com a última versão do django rest framework :)
Não se esqueça de especificar corretamente o seu ordering_fields
Apenas uma nota - no Django> 1.10 alguns métodos mudaram
Especificamente, você terá que alterar o seguinte:
field, parent_model, direct, m2m = model._meta.get_field_by_name(components[0])
para
field = model._meta.get_field(components[0])
e a assinatura de remove_invalid_fields
mudou para:
def remove_invalid_fields(self, queryset, ordering, view, request):
O que resulta na versão final de trabalho para 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('-'))]
O código acima terá problemas com OneToOneField, adicionando uma correção para isso:
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 e field.rel.to irão gerar um aviso de depreciação no Django> = 1.10. Eles são agora respectivamente:
@tomchristie, considerando que este patch foi persistente (e constantemente atualizado) nesta edição por 4 anos (temos usado isso em produção desde o ano passado), poderia ser adequado para um PR e incorporado em DRF (sim, apoiado por um conjunto adequado de testes de unidade)?
Apenas meus 0,2 centavos
Este patch funciona para mim, podemos mesclá-lo com as versões de lançamento?
Como dito anteriormente, torne-o um terceiro (preferencial) ou ele precisa de um RP adequado com testes e documentação.
@filiperinaldi Acho que em Django> = 1,10 field.rel.to
se torna field.related_model
. Esta é a versão mais recente do patch que passou em nossos testes de unidade.
nb Estamos usando Django 1.11 então 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('-'))]
Ok pessoal, para o 4º aniversário desta edição, decidi tentar e hackear juntos um pacote externo para ser instalado junto com o DRF, a fim de oferecer suporte a esta ordem útil:
https://github.com/apiraino/djangorestframework_custom_filters_ordering
Estarei trabalhando nisso nos próximos dias para terminar o trabalho (ou seja, preciso empacotar e fatorar o código corretamente, refinar os testes e garantir o suporte para versões do Django com suporte ativo).
As contribuições são bem-vindas, é claro!
Felicidades
ref # 5533.
Estou muito confuso. Isso parece funcionar por padrão?
Eu cheguei a esse problema, li todos os comentários e continuei a implementar a solução por @apiraino , mas então descobri que simplesmente havia digitado errado o nome do meu campo relacionado.
No entanto, agora estou usando ?ordering=job__customer__company
para um relacionamento aninhado duplo para ordenar os resultados da API e está funcionando bem.
@halfnibble - acredito que isso foi corrigido no # 5533.
cc @carltongibson
@halfnibble , qual versão você usou? Estou no 3.8.2 e não está funcionando para mim. Estas são minhas versões:
"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"
]
Desculpe, agora eu entendo. Por padrão, ele só funcionará se o parâmetro de pedido relacionado estiver incluído em ordering_fields
. Mas para uma solução mais geral, o patch ainda é necessário.
Olá, sou relativamente novo no DRF e estou procurando uma maneira de adicionar pedidos aos meus campos de pedidos sem expor a estrutura do nosso banco de dados. Por exemplo, se eu quisesse procurar z, precisaria escrever x__y__z e passar isso para o ponto de extremidade como um parâmetro expõe a estrutura. É isso que estou procurando se eu só quiser dizer z e fazer com que a função verifique se é um campo relacionado no ORM?
Você pode tentar substituir OrderingFilter.get_ordering
. Algo como...
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 Muito obrigado! Isso é exatamente o que eu estava procurando. Agradeço a ajuda.
Aqui está uma solução que criei:
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__'
Comentários muito úteis
Obrigado pela dica: +1:. O link permite a correção. Isso funciona