¡Hola!
¡Ha sido un placer utilizar esta biblioteca! ¡Estoy tan emocionado por toda la comodidad que ofrece!
¡Gracias por eso!
Mi objetivo en este momento es tener un punto final que admita la capacidad de búsqueda en los parámetros del modelo y también en los pedidos.
Tengo un modelo School
que tiene un campo calculado llamado learner_enrolled_count
La respuesta JSON se parece a esto:
{
"schools": [
{
"id": 6,
"name": "Piano Gym Six",
"courses": [
// ...
],
"learner_enrolled_count": 0
},
{
"id": 7,
"name": "Piano Gym Seven",
"courses": [
// ...
],
"learner_enrolled_count": 5
}
]
}
learner_enrolled_count
es un campo calculado.
He leído la documentación aquí:
https://django-filter.readthedocs.io/en/stable/ref/filters.html?highlight=order#orderingfilter
y aquí:
https://django-filter.readthedocs.io/en/stable/ref/filters.html?highlight=order#adding -custom-filter-options
Entonces, en base a eso, escribí este conjunto de filtros aquí:
# ------------------------------------------------------------------------------
# Python Standard Libraries
# ------------------------------------------------------------------------------
# N/A
# ------------------------------------------------------------------------------
# Third-party Libraries
# ------------------------------------------------------------------------------
from django_filters import CharFilter
from django_filters import OrderingFilter
from django_filters.rest_framework import FilterSet
# ------------------------------------------------------------------------------
# Custom Libraries
# ------------------------------------------------------------------------------
from piano_gym_api.versions.v1.models.school_model import SchoolModel
# See:
# https://django-filter.readthedocs.io/en/stable/ref/filters.html#adding-custom-filter-choices
class SchoolOrderingFilter(OrderingFilter):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.extra["choices"] += [
("learner_enrolled_count", "Learner Enrolled Count"),
("-learner_enrolled_count", "Learner Enrolled Count (descending)"),
]
def filter(self, query_set, values):
if(values is None):
return super().filter(query_set, values)
for value in values:
if value in ['learner_enrolled_count', '-learner_enrolled_count']:
return query_set.order_by(value)
return super().filter(query_set, values)
class SchoolFilter(FilterSet):
school_name = CharFilter(field_name="name",
lookup_expr="icontains")
# ---
course_name = CharFilter(field_name="school_course__name",
lookup_expr="icontains")
course_description = CharFilter(field_name="school_course__description",
lookup_expr="icontains")
# ---
lesson_name = CharFilter(field_name="school_lesson__name",
lookup_expr="icontains")
lesson_description = CharFilter(field_name="school_lesson__description",
lookup_expr="icontains")
# ---
flash_card_set_name = CharFilter(field_name="school_lesson__flash_card_set__name",
lookup_expr="icontains")
flash_card_set_description = CharFilter(field_name="school_lesson__flash_card_set__description",
lookup_expr="icontains")
# ---
headmaster_username = CharFilter(field_name="school_board__school_board_headmaster__learner__user__username",
lookup_expr="icontains")
board_member_username = CharFilter(field_name="school_board__school_board_member__learner__user__username",
lookup_expr="icontains")
# See:
# https://django-filter.readthedocs.io/en/stable/ref/filters.html#orderingfilter
o = SchoolOrderingFilter(
# tuple-mapping retains order
fields=(
("learner_enrolled_count", "learner_enrolled_count"),
),
# labels do not need to retain order
field_labels={
"learner_enrolled_count": "Total learners enrolled in school",
}
)
class Meta:
model = SchoolModel
fields = [
"school_name",
# ---
"course_name",
"course_description",
# ---
"lesson_name",
"lesson_description",
# ---
"headmaster_username",
"board_member_username"
]
¡Este problema es que no parece estar ordenando nada! No tengo ni idea de porqué. Es tan extraño.
Si coloco un seguimiento de depuración en el método filter
de SchoolOrderingFilter
, veo que values
es None
. No estoy seguro de qué debería ser.
La solicitud que estoy haciendo se parece a esto
{{API_URL}}/api/v1/schools/?offset=5&limit=3&ordering=learner_enrolled_count
Y la vista que recibe esta solicitud se ve así:
class SchoolViewSet(ViewSet):
# ...
def list(self, request):
all_school_models = SchoolModel.objects.getBrowseSchoolsData()
school_filter = SchoolFilter(request.GET, queryset=all_school_models)
paginator = HeaderLinkPagination()
current_page_results = paginator.paginate_queryset(school_filter.qs,
request)
all_schools_serializer = SchoolSerializer(current_page_results,
many=True)
response_data = {
"schools": all_schools_serializer.data
}
response_to_return = paginator.get_paginated_response(response_data)
return response_to_return
Creo que no está muy claro para mí en la documentación sobre cómo usar la función de filtrado Y cómo usar el orden de campo calculado.
¿Qué estoy haciendo mal? ¿Estoy entendiendo mal esta funcionalidad? ¡Siento que estoy siguiendo los pasos correctos para esto, pero parece que no puedo hacer que funcione la funcionalidad ordering
de esta biblioteca!
Nuevamente, gracias por todo!
Hola @loganknecht. Debo decirle, gracias por tan maravilloso informe temático. Empieza bien y simplemente mejora. Tan claro. Así que gracias.
En primer lugar: parece que definió SchoolOrderingFilter
con el nombre de campo o
:
o = SchoolOrderingFilter(...)
Entonces, cuando dices:
La solicitud que estoy haciendo se parece a esto
{{API_URL}}/api/v1/schools/?offset=5&limit=3&ordering=learner_enrolled_count
Espero que el parámetro de cadena de consulta sea o
también. ¿Funciona esto: {{API_URL}}/api/v1/schools/?offset=5&limit=3&o=learner_enrolled_count
?
Entonces (la parte de la que no estoy seguro en su informe) dice que es un _campo calculado_. ¿Qué quiere decir exactamente? es decir, ¿es un campo que aparece en el modelo, o es una propiedad de Python, por ejemplo?
Otra forma de preguntar (parte de) eso es, ¿funciona order_by()
en el conjunto de consultas con este campo? es decir, ¿esto funciona?
SchoolModel.objects.filter(...).order_by(learner_enrolled_count)
@carltongibson ¡ Eso es bastante interesante! ¡No esperaba que o
fuera el parámetro ordering
definido! No entendí eso de la documentación 😂
o
Lo interesante es si uso la consulta
{{API_URL}}/api/v1/schools/?offset=5&limit=3&o=learner_enrolled_count
Obtengo estos resultados:
{
"schools": [
{
"id": 6,
"name": "Piano Gym Six",
"courses": [
# ...
],
"learner_enrolled_count": 0
},
{
"id": 8,
"name": "Piano Gym Eight",
"courses": [
# ...
],
"learner_enrolled_count": 0
},
{
"id": 9,
"name": "Piano Gym Nine",
"courses": [
# ...
],
"learner_enrolled_count": 0
}
]
}
La parte interesante de esto es que ahora la única escuela que quiero que se ordene en la parte superior no está en este resultado de búsqueda. Lo que significa que probablemente se ordenó, pero la paginación aún está compensando los resultados. Sin embargo, cuando usé learner_enrolled_count
está en el último lugar.
{{API_URL}}/api/v1/schools/?limit=3&o=learner_enrolled_count
Así que lo probé usando -learner_enrolled_count
(eliminando el desplazamiento y ordenando descendiendo)
{{API_URL}}/api/v1/schools/?limit=3&o=-learner_enrolled_count
y obtuve esta respuesta
{
"schools": [
{
"id": 7,
"name": "Piano Gym Seven",
"courses": [
# ...
],
"learner_enrolled_count": 1
},
{
"id": 1,
"name": "Piano Gym",
"courses": [
# ...
],
"learner_enrolled_count": 0
},
{
"id": 2,
"name": "Piano Gym Two",
"courses": [
# ...
],
"learner_enrolled_count": 0
}
]
}
¡Parece que funcionó!
learner_enrolled_count
no existe en el modelo en sí. Usé una anotación para calcular esto e inyectarlo en los resultados.
Con el riesgo de sumergirse demasiado en el modelo de datos, este punto final está destinado a ser un punto de entrada singular para filtrar y ordenar consultas para una lista de escuelas.
Tengo que optimizar este punto final usando la recuperación de modelos como este:
class SchoolModelManager(Manager):
def getBrowseSchoolsData(self, *args, **kwargs):
"""Return all Schools that contain lessons with flash card sets.
Does not exclude empty sets, just requires that the school has something
to enroll in
"""
# WARNING: This MUST be imported here otherwise the compilation fails
# because of circular dependencies
from piano_gym_api.versions.v1.models.flash_card_model import FlashCardModel
# from piano_gym_api.versions.v1.models.flash_card_model import PlaySheetMusicFlashCardModel
# from piano_gym_api.versions.v1.models.flash_card_model import TrueOrFalseFlashCardModel
# from piano_gym_api.versions.v1.models.flash_card_set_model import FlashCardSetModel
from piano_gym_api.versions.v1.models.sheet_music_model import SheetMusicModel
# import pdb
# pdb.set_trace()
# --------------------
sheet_music_query_set = (SheetMusicModel.objects.all()
.select_related("school"))
play_sheet_music_flash_card_sheet_music_prefetch = Prefetch("playsheetmusicflashcardmodel__sheet_music",
sheet_music_query_set)
# --------------------
flash_card_query_set = (FlashCardModel.objects.all()
.select_related("flash_card_set",
"playsheetmusicflashcardmodel",
"school",
"trueorfalseflashcardmodel")
.prefetch_related(play_sheet_music_flash_card_sheet_music_prefetch))
flash_card_prefetch = Prefetch("flash_card_set__flash_card", flash_card_query_set)
# --------------------
school_lesson_query_set = (SchoolLessonModel.objects.all()
.select_related("course",
"flash_card_set",
"school")
.prefetch_related(flash_card_prefetch))
school_lesson_prefetch = Prefetch("school_lesson", school_lesson_query_set)
# --------------------
school_course_query_set = (SchoolCourseModel.objects.all()
.select_related("school")
.prefetch_related(school_lesson_prefetch))
school_course_prefetch = Prefetch("school_course", school_course_query_set)
# --------------------
query_set_to_return = (SchoolModel.objects.filter(school_lesson__flash_card_set__isnull=False)
.distinct()
# .annotate(learner_enrolled_count=Count("learner_enrolled_school", distinct=True))
.annotate(learner_enrolled_count=Case(
When(learner_enrolled_school__learner_enrolled_course__learner_enrolled_lesson__is_enrolled=True,
then=1),
default=0,
output_field=IntegerField())
).prefetch_related(school_course_prefetch))
return query_set_to_return
El bit calculated field
está en la variable query_set_to_return
. Lo que hace es llevar los count
del total de estudiantes inscritos y annotates
al campo learner_enrolled_count
. No tengo idea de si esta es la forma correcta de lograr esto, pero parece funcionar 😂
Una sugerencia que haría es aclarar en la documentación, ya sea usando ejemplos de URL reales para solicitudes o texto simple y explicar que o
en los ejemplos es lo que se expone como parámetro para ordenar.
¡Parece estar funcionando correctamente ahora que aclaró que el parámetro o
es a lo que se asigna el filtro de orden!
Si los querysets
que estoy proporcionando para los filter
tienen un campo anotado, parece, según mis pruebas, que no estoy obligado a hacer mi propio OrderingFilter
.
¡Por eso creo que puedo usar un simple OrderingFilter
lugar!
¡Por favor corrígeme si estoy equivocado!
¡Creo que eso resuelve mi confusión! ¡Gracias!
Hola @loganknecht. Super, parece que lo tienes funcionando. 💃
Si está utilizando una anotación, debería poder filtrar y ordenar en ella con la clase estándar sí.
Haré unos retoques en los documentos.
Gracias por tu contribución.
@carltongibson y todos los demás, ¡gracias por la increíble biblioteca!
Comentario más útil
Hola @loganknecht. Debo decirle, gracias por tan maravilloso informe temático. Empieza bien y simplemente mejora. Tan claro. Así que gracias.
En primer lugar: parece que definió
SchoolOrderingFilter
con el nombre de campoo
:Entonces, cuando dices:
Espero que el parámetro de cadena de consulta sea
o
también. ¿Funciona esto:{{API_URL}}/api/v1/schools/?offset=5&limit=3&o=learner_enrolled_count
?Entonces (la parte de la que no estoy seguro en su informe) dice que es un _campo calculado_. ¿Qué quiere decir exactamente? es decir, ¿es un campo que aparece en el modelo, o es una propiedad de Python, por ejemplo?
Otra forma de preguntar (parte de) eso es, ¿funciona
order_by()
en el conjunto de consultas con este campo? es decir, ¿esto funciona?