Hallo!
Es war so ein Vergnügen, diese Bibliothek zu benutzen! Ich bin so begeistert von all dem Komfort, den es bietet!
Danke für das!
Mein Ziel ist es im Moment, einen Endpunkt zu haben, der die Suchfähigkeit von Modellparametern und auch die Sortierung unterstützt.
Ich habe ein School
Modell mit einem berechneten Feld namens learner_enrolled_count
Die JSON-Antwort sieht in etwa so aus:
{
"schools": [
{
"id": 6,
"name": "Piano Gym Six",
"courses": [
// ...
],
"learner_enrolled_count": 0
},
{
"id": 7,
"name": "Piano Gym Seven",
"courses": [
// ...
],
"learner_enrolled_count": 5
}
]
}
Das learner_enrolled_count
ist ein berechnetes Feld.
Ich habe die Dokumentation hier gelesen:
https://django-filter.readthedocs.io/en/stable/ref/filters.html?highlight=order#orderingfilter
und hier:
https://django-filter.readthedocs.io/en/stable/ref/filters.html?highlight=order#adding -custom-filter-choices
Darauf aufbauend habe ich diesen Filtersatz hier geschrieben:
# ------------------------------------------------------------------------------
# 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"
]
Dieses Problem ist, dass es anscheinend überhaupt nicht bestellt wird! Ich habe keine Idee warum. Es ist so komisch.
Wenn ich einen Debug-Trace in der Methode filter
von SchoolOrderingFilter
ablege, sehe ich, dass values
None
. Ich bin mir nicht sicher was das sein soll.
Meine Anfrage sieht so aus
{{API_URL}}/api/v1/schools/?offset=5&limit=3&ordering=learner_enrolled_count
Und die Ansicht, die diese Anfrage erhält, sieht so aus:
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
Ich denke, es ist für mich in der Dokumentation wirklich unklar, wie man die Filterfunktion UND die berechnete Feldreihenfolge verwendet.
Was mache ich falsch? Verstehe ich diese Funktion falsch? Ich habe das Gefühl, dass ich die richtigen Schritte dafür ausführe, aber ich kann die ordering
Funktionalität dieser Bibliothek einfach nicht zum Laufen bringen!
Nochmals vielen Dank für alles!
Hallo @loganknecht. Ich muss sagen, vielen Dank für diesen wunderbaren Themenbericht. Es fängt gut an und wird immer besser. Einfach so klar. Also vielen Dank.
Zunächst einmal: Es sieht so aus, als ob Sie SchoolOrderingFilter
mit dem Feldnamen o
:
o = SchoolOrderingFilter(...)
Also wenn du sagst:
Meine Anfrage sieht so aus
{{API_URL}}/api/v1/schools/?offset=5&limit=3&ordering=learner_enrolled_count
Ich erwarte, dass der Abfragezeichenfolgenparameter auch o
ist. Funktioniert das: {{API_URL}}/api/v1/schools/?offset=5&limit=3&o=learner_enrolled_count
?
Dann (wobei ich mir in Ihrem Bericht unsicher bin) sagen Sie, es sei ein _berechnetes Feld_ -- was genau meinst du? Dh ist es ein Feld, das im Modell angezeigt wird, oder ist es eine Python-Eigenschaft, sagen wir?
Eine andere Möglichkeit, (einen Teil davon) zu fragen, ist, ob order_by()
im Abfragesatz mit diesem Feld funktioniert? dh funktioniert das:
SchoolModel.objects.filter(...).order_by(learner_enrolled_count)
@carltongibson Das ist ziemlich interessant! Ich habe nicht erwartet, dass o
der definierte Parameter ordering
ist! Das habe ich aus der Dokumentation nicht verstanden 😂
o
Interessant ist, wenn ich die Abfrage verwende
{{API_URL}}/api/v1/schools/?offset=5&limit=3&o=learner_enrolled_count
Ich bekomme diese Ergebnisse:
{
"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
}
]
}
Das Interessante daran ist, dass jetzt die eine Schule, die ich ganz oben ordnen möchte, nicht in diesem Suchergebnis enthalten ist. Das bedeutet, dass dies wahrscheinlich bestellt wurde, aber die Paginierung gleicht die Ergebnisse noch aus. Wenn ich jedoch learner_enrolled_count
es an der letzten Stelle.
{{API_URL}}/api/v1/schools/?limit=3&o=learner_enrolled_count
Also habe ich es mit dem -learner_enrolled_count
getestet (Entfernen des Offsets und Sortieren nach absteigend)
{{API_URL}}/api/v1/schools/?limit=3&o=-learner_enrolled_count
und habe diese antwort bekommen
{
"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
}
]
}
Es sieht so aus, als hätte es funktioniert!
learner_enrolled_count
existiert nicht auf dem Modell selbst. Ich habe eine Annotation verwendet, um dies zu berechnen und in die Ergebnisse einzufügen.
Da die Gefahr besteht, dass Sie zu sehr in das Datenmodell eintauchen, ist dieser Endpunkt als einzelner Einstiegspunkt zum Filtern und Ordnen von Abfragen für eine Liste von Schulen gedacht.
Ich muss diesen Endpunkt mithilfe des Modellabrufs wie folgt optimieren:
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
Das Bit calculated field
befindet sich in der Variablen query_set_to_return
. Es nimmt die count
der gesamten eingeschriebenen Lernenden und annotates
in das Feld learner_enrolled_count
. Ich habe keine Ahnung, ob das der richtige Weg ist, aber es scheint zu funktionieren 😂
Ein Vorschlag, den ich machen würde, besteht darin, dies in der Dokumentation klarzustellen, indem Sie entweder tatsächliche URL-Beispiele für Anforderungen oder einfachen Text verwenden und erklären, dass o
in den Beispielen als Parameter für die Bestellung bereitgestellt wird.
Es scheint jetzt richtig zu funktionieren, da Sie den Parameter o
geklärt haben, dem der Sortierfilter zugewiesen wird!
Wenn die querysets
ich für die filter
bereitstelle, bereits ein kommentiertes Feld enthalten - aus meinen Tests sieht es so aus, dass ich nicht verpflichtet bin, meine eigenen benutzerdefinierten OrderingFilter
.
Deshalb denke ich, dass ich stattdessen einfach ein einfaches OrderingFilter
verwenden kann!
Bitte korrigiere mich wenn ich falsch liege!
Ich glaube, das löst meine Verwirrung! Dankeschön!
Hallo @loganknecht. Super, sieht so aus, als ob es bei dir funktioniert. 💃
Wenn Sie eine Annotation verwenden, sollten Sie diese mit der Standardklasse yes filtern und sortieren können.
Ich werde die Dokumente anpassen.
Danke für deinen Beitrag.
@carltongibson und alle anderen, danke für die tolle Bibliothek!
Hilfreichster Kommentar
Hallo @loganknecht. Ich muss sagen, vielen Dank für diesen wunderbaren Themenbericht. Es fängt gut an und wird immer besser. Einfach so klar. Also vielen Dank.
Zunächst einmal: Es sieht so aus, als ob Sie
SchoolOrderingFilter
mit dem Feldnameno
:Also wenn du sagst:
Ich erwarte, dass der Abfragezeichenfolgenparameter auch
o
ist. Funktioniert das:{{API_URL}}/api/v1/schools/?offset=5&limit=3&o=learner_enrolled_count
?Dann (wobei ich mir in Ihrem Bericht unsicher bin) sagen Sie, es sei ein _berechnetes Feld_ -- was genau meinst du? Dh ist es ein Feld, das im Modell angezeigt wird, oder ist es eine Python-Eigenschaft, sagen wir?
Eine andere Möglichkeit, (einen Teil davon) zu fragen, ist, ob
order_by()
im Abfragesatz mit diesem Feld funktioniert? dh funktioniert das: