Un ejemplo de uso sería ordenar por un objeto relacionado anidado.
Representación anidada:
{
'username': 'george',
'email': '[email protected]'
'stats': {
'facebook_friends': 560,
'twitter_followers': 4043,
...
},
},
{
'username': 'michael',
'email': '[email protected]'
'stats': {
'facebook_friends': 256,
'twitter_followers': 120,
...
},
},
...
Una opción es admitir la notación de subrayado doble de django orm __ para modelos relacionados.
Ex. ?ordering=stats__facebook_friends
ordenaría por facebook_friends.
Actualmente, ordenar solo funciona para campos del modelo particular especificado en queryset.
https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/filters.py#L125
Consideraría solicitudes de extracción para esto, pero no es algo para lo que tenga tiempo de hacer yo mismo.
Cualquiera que desee implementar esto debe garantizar un comportamiento sensato cuando se utilizan nombres de campo incorrectos en el filtrado. Como mínimo, los resultados deberían volver bien. (Lo ideal es que se ignoren los nombres de campo incorrectos, pero se deben dejar los demás campos).
Considerándolo, voy a cerrar esto. Si alguien emite una solicitud de extracción y prueba que se ocupa de ello, entonces podría reconsiderarlo, pero las implementaciones más completas de cada una de las clases de filtrado básicas son realmente algo que alguien podría abordar muy bien en un paquete de terceros, que luego podría mantenerse por separado. y vinculado a desde los documentos principales.
Sin solicitud de extracción porque no he tenido tiempo de escribir pruebas o documentos, pero en caso de que esto ayude a otras personas a encontrar este problema, estoy 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 su versión no permite la clasificación de relación inversa, solo la versión directa de fk aquí está parcheada
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('-'))]
El fragmento de pySilver funcionó bien para mí, ¿podemos poner esto en DRF por casualidad?
¿De dónde importas RelatedObject
? No parece estar disponible para mi
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 se ha eliminado de 1.8, consulte https://code.djangoproject.com/ticket/21414
Gracias por el consejo: +1:. El enlace deja la solución. Esto 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('-'))]
No tuve el problema con la última versión de django rest framework :)
No olvide especificar correctamente su ordering_fields
Solo una nota: en Django> 1.10, un par de métodos han cambiado
Específicamente, tendrás que cambiar lo siguiente:
field, parent_model, direct, m2m = model._meta.get_field_by_name(components[0])
a
field = model._meta.get_field(components[0])
y la firma de remove_invalid_fields
ha cambiado a:
def remove_invalid_fields(self, queryset, ordering, view, request):
Lo que da como resultado la versión de trabajo final 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('-'))]
El código anterior tendrá problemas con OneToOneField, agregando una solución para eso:
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 y field.rel.to generarán una advertencia de obsolescencia en Django> = 1.10. Ahora son respectivamente:
@tomchristie teniendo en cuenta que este parche se ha mantenido (y se ha actualizado constantemente) en este número durante 4 años (lo hemos estado usando en producción desde el año pasado), ¿podría ser adecuado para un PR y se fusionó con DRF (sí, respaldado por un conjunto adecuado de pruebas unitarias)?
Solo mis .2 centavos
Este parche me funciona, ¿podemos fusionarlo con las versiones de lanzamiento?
Como se dijo anteriormente, conviértalo en un tercero (preferido) o necesita un PR adecuado con pruebas y documentación.
@filiperinaldi Creo que en Django> = 1.10 field.rel.to
convierte en field.related_model
. Aquí está la última versión del parche que pasa nuestras pruebas unitarias.
nb Estamos usando Django 1.11 así que mmm
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('-'))]
Bien, gente, para el cuarto cumpleaños de este número, decidí intentarlo y hackear un paquete externo para instalarlo junto con DRF para admitir este pedido útil:
https://github.com/apiraino/djangorestframework_custom_filters_ordering
Trabajaré en él en los próximos días para terminar el trabajo (es decir, necesito empaquetar y factorizar correctamente el código, refinar las pruebas y garantizar el soporte para las versiones de Django con soporte activo).
¡Las contribuciones son bienvenidas, por supuesto!
Salud
ref # 5533.
Estoy muy confundido. ¿Parece que esto funciona de forma predeterminada?
Llegué a este problema, leí todos los comentarios y procedí a implementar la solución de @apiraino , pero luego descubrí que simplemente había escrito mal el nombre de mi campo relacionado.
Sin embargo, ahora estoy usando ?ordering=job__customer__company
para una relación anidada doble para ordenar los resultados de la API y está funcionando bien.
@halfnibble : creo que esto se solucionó en el n. ° 5533.
cc @carltongibson
@halfnibble , ¿qué versión
"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"
]
Lo siento, ahora lo entiendo. De forma predeterminada, solo funcionará si el parámetro de pedido relacionado se incluye en ordering_fields
. Pero para una solución más general, todavía se requiere el parche.
Hola, soy relativamente nuevo en DRF y estaba buscando una manera de agregar pedidos a los campos de mi pedido sin exponer la estructura de nuestra base de datos. Por ejemplo, si quisiera buscar z, necesitaría escribir x__y__z, y pasar eso al punto final como parámetro expone la estructura. ¿Es esto lo que estoy buscando si solo quisiera decir zy que la función verifique si es un campo relacionado en el ORM?
Podría intentar anular 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 ¡ Muchas gracias! Esto es exactamente lo que estaba buscando. Agradezco la ayuda
Aquí hay una solución que armé:
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__'
Comentario más útil
Gracias por el consejo: +1:. El enlace deja la solución. Esto funciona