μλ νμΈμ!
μ΄ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νλ κ²μ μ λ§ μ¦κ±°μ μ΅λλ€! λλ κ·Έκ²μ΄ μ 곡νλ λͺ¨λ νΈμμ λν΄ λ§€μ° κΈ°μ©λλ€!
κ°μ¬ν©λλ€!
μ§κΈ μ λͺ©νλ λͺ¨λΈ 맀κ°λ³μμ λν κ²μ κΈ°λ₯κ³Ό μ£Όλ¬Έ κΈ°λ₯μ μ§μνλ μλν¬μΈνΈλ₯Ό κ°κ³ μΆμ΅λλ€.
learner_enrolled_count
λΌλ κ³μ°λ νλκ° μλ School
λͺ¨λΈμ΄ μμ΅λλ€.
JSON μλ΅μ λ€μκ³Ό κ°μ΅λλ€.
{
"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
λ κ³μ°λ νλμ
λλ€.
μ¬κΈ°μμ λ¬Έμλ₯Ό μ½μμ΅λλ€.
https://django-filter.readthedocs.io/en/stable/ref/filters.html?highlight=order#orderingfilter
κ·Έλ¦¬κ³ μ¬κΈ°:
https://django-filter.readthedocs.io/en/stable/ref/filters.html?highlight=order#adding -custom-filter-choices
κ·Έλμ μ¬κΈ°μ κΈ°λ°νμ¬ μ΄ νν° μΈνΈλ₯Ό μμ±νμ΅λλ€.
# ------------------------------------------------------------------------------
# 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"
]
μ΄ λ¬Έμ λ μ£Όλ¬Έμ΄ μ ν λμ§ μλ κ² κ°μ΅λλ€! μ΄μ λ₯Ό λͺ¨λ₯΄κ² μ΅λλ€. λ무 μ΄μν΄
SchoolOrderingFilter
μ filter
λ©μλμ λλ²κ·Έ μΆμ μ λμΌλ©΄ values
κ° None
μμ μ μ μμ΅λλ€. κ·Έκ² λ¬΄μμΈμ§ μ λͺ¨λ₯΄κ² μ΅λλ€.
λ΄κ° λ§λλ μμ²μ λ€μκ³Ό κ°μ΅λλ€
{{API_URL}}/api/v1/schools/?offset=5&limit=3&ordering=learner_enrolled_count
κ·Έλ¦¬κ³ μ΄ μμ²μ λ°λ λ·°λ λ€μκ³Ό κ°μ΅λλ€.
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
νν°λ§ κΈ°λ₯μ μ¬μ©νλ λ°©λ²κ³Ό κ³μ°λ νλ μμλ₯Ό μ¬μ©νλ λ°©λ²μ λν μ€λͺ μμμ μ μκ²λ μ λ§ λΆλΆλͺ νλ€κ³ μκ°ν©λλ€.
λ΄κ° 무μμ μλͺ»νκ³ μμ§? μ΄ κΈ°λ₯μ μλͺ» μ΄ν΄νκ³ μμ΅λκΉ? μ¬λ°λ₯Έ λ¨κ³λ₯Ό μννκ³ μλ κ² κ°μ§λ§ μ΄ λΌμ΄λΈλ¬λ¦¬μ ordering
κΈ°λ₯μ΄ μλνμ§ μλ κ² κ°μ΅λλ€!
λ€μ ν λ², λͺ¨λ κ²μ κ°μ¬λ립λλ€!
μλ νμΈμ @loganknechtμ λλ€. μ΄λ κ² λ©μ§ λ¬Έμ λ³΄κ³ μμ κ°μ¬λ립λλ€. κ·Έκ²μ μ μμνκ³ μ μ μ’μμ§λλ€. λ무 λͺ νν©λλ€. κ°μ¬ν©λλ€.
λ¨Όμ SchoolOrderingFilter
νλ μ΄λ¦μ o
μ μν κ² κ°μ΅λλ€.
o = SchoolOrderingFilter(...)
κ·Έλμ λΉμ μ΄ λ§ν λ :
λ΄κ° λ§λλ μμ²μ λ€μκ³Ό κ°μ΅λλ€
{{API_URL}}/api/v1/schools/?offset=5&limit=3&ordering=learner_enrolled_count
쿼리 λ¬Έμμ΄ λ§€κ°λ³μλ o
κ²μΌλ‘ μμν©λλ€. μ΄κ²μ΄ μλν©λκΉ: {{API_URL}}/api/v1/schools/?offset=5&limit=3&o=learner_enrolled_count
?
κ·Έλ° λ€μ (λΉμ μ λ³΄κ³ μμμ λ΄κ° νμ ν μ μλ λΆλΆ) λΉμ μ κ·Έκ²μ΄ _κ³μ°λ νλ_λΌκ³ λ§ν©λλ€ -- μ νν 무μμ μλ―Έν©λκΉ? μ¦, λͺ¨λΈμ λνλλ νλμ λκΉ, μλλ©΄ Python μμ±μ΄ λ§νλ κ²μ λκΉ?
쿼리 μΈνΈμ order_by()
κ° μ΄ νλμμ μλνλμ§ λ¬»λ (μΌλΆ) λ€λ₯Έ λ°©λ²μ 무μμ
λκΉ? μ¦, μ΄ μμ
μ μνν©λλ€.
SchoolModel.objects.filter(...).order_by(learner_enrolled_count)
@carltongibson κ½€ ν₯λ―Έ o
μ΄ μ μλ ordering
맀κ°λ³μκ° λ κ²μ΄λΌκ³ μμνμ§ λͺ»νμ΅λλ€! λλ λ¬Έμμμ κ·Έκ²μ μ΄ν΄νμ§ λͺ»νμ΅λλ€ π
o
맀κ°λ³μ μ¬μ©ν₯λ―Έλ‘μ΄ μ μ 쿼리λ₯Ό μ¬μ©νλ©΄
{{API_URL}}/api/v1/schools/?offset=5&limit=3&o=learner_enrolled_count
λλ λ€μκ³Ό κ°μ κ²°κ³Όλ₯Ό μ»μ΅λλ€.
{
"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
}
]
}
μ΄κ²μ λν ν₯λ―Έλ‘μ΄ λΆλΆμ μ΄μ λ΄κ° 맨 μμ μ£Όλ¬Ένκ³ μΆμ νκ΅κ° μ΄ κ²μ κ²°κ³Όμ μλ€λ κ²μ
λλ€. μ΄κ²μ μ΄κ²μ΄ μ£Όλ¬Έλμμ κ°λ₯μ±μ΄ μμ§λ§ νμ΄μ§ 맀κΉμ΄ κ²°κ³Όλ₯Ό μ¬μ ν μμνκ³ μμμ μλ―Έν©λλ€. κ·Έλ¬λ learner_enrolled_count
μ¬μ©νμ λ λ§μ§λ§ μ§μ μ μμ΅λλ€.
{{API_URL}}/api/v1/schools/?limit=3&o=learner_enrolled_count
κ·Έλμ -learner_enrolled_count
(μ€νμ
μ κ±° λ° λ΄λ¦Όμ°¨μ μ λ ¬)λ₯Ό μ¬μ©νμ¬ ν
μ€νΈνμ΅λλ€.
{{API_URL}}/api/v1/schools/?limit=3&o=-learner_enrolled_count
κ·Έλ¦¬κ³ μ΄ μλ΅μ λ°μμ΅λλ€
{
"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
}
]
}
ν¨κ³Όκ° μμλ κ² κ°μ΅λλ€!
learner_enrolled_count
μ(λ) λͺ¨λΈ μ체μ μ‘΄μ¬νμ§ μμ΅λλ€. μ£Όμμ μ¬μ©νμ¬ μ΄κ²μ κ³μ°νκ³ κ²°κ³Όμ μ½μ
νμ΅λλ€.
λ°μ΄ν° λͺ¨λΈμ λ무 λ§μ΄ λ€λ£° μνμ΄ μλ μ΄ λμ μ νκ΅ λͺ©λ‘μ λν 쿼리λ₯Ό νν°λ§νκ³ μ λ ¬νκΈ° μν λ¨μΌ μ§μ μ μ λλ€.
λ€μκ³Ό κ°μ λͺ¨λΈ κ²μμ μ¬μ©νμ¬ μ΄ λμ μ μ΅μ νν΄μΌ ν©λλ€.
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
calculated field
λΉνΈλ query_set_to_return
λ³μμ μμ΅λλ€. κ·Έκ²μ΄ νλ μΌμ λ±λ‘λ μ΄ νμ΅μ μ€ count
λ₯Ό annotates
learner_enrolled_count
νλλ‘ κ°μ Έμ€λ κ²μ
λλ€. μ΄κ²μ΄ μ΄κ²μ λ¬μ±νλ μ¬λ°λ₯Έ λ°©λ²μΈμ§ λͺ¨λ₯΄κ² μ§λ§ μλνλ κ² κ°μ΅λλ€ π
λ΄κ° μ μνλ ν κ°μ§ μ μμ μμ²μ λν μ€μ URL μμ λ κ°λ¨ν ν
μ€νΈλ₯Ό μ¬μ©νμ¬ λ¬Έμμμ λͺ
νν νκ³ μμ μ o
κ° μ£Όλ¬Έμ μν 맀κ°λ³μλ‘ λ
ΈμΆλλ κ²μμ μ€λͺ
νλ κ²μ
λλ€.
o
맀κ°λ³μκ° μμ μ§μ νν°κ° ν λΉλλ 맀κ°λ³μμμ λͺ
νν νκΈ° λλ¬Έμ μ΄μ μ¬λ°λ₯΄κ² μλνλ κ²μΌλ‘ 보μ
λλ€!
querysets
λν΄ μ 곡νλ filter
μ΄λ―Έ μ£Όμ νλκ° μλ κ²½μ° - ν
μ€νΈ κ²°κ³Ό, λλ§μ μ¬μ©μ μ μ OrderingFilter
λ₯Ό λ§λ€ μλ¬΄κ° μλ κ²μΌλ‘ 보μ
λλ€
κ·Έλμ μ λ λμ κ°λ¨ν OrderingFilter
λ₯Ό μ¬μ©ν μ μλ€κ³ μκ°ν©λλ€!
λ΄κ° νλ Έλ€λ©΄ μ§μ ν΄μ£ΌμΈμ!
λλ κ·Έκ²μ΄ λ΄ νΌλμ ν΄κ²°νλ€κ³ λ―Ώμ΅λλ€! κ°μ¬ν©λλ€!
μλ νμΈμ @loganknechtμ λλ€. μνΌ, μλνλ κ² κ°μ΅λλ€. π
μ£Όμμ μ¬μ©νλ κ²½μ° νμ€ ν΄λμ€ yesλ₯Ό μ¬μ©νμ¬ μ£Όμμ νν°λ§νκ³ μ λ ¬ν μ μμ΄μΌ ν©λλ€.
λ¬Έμλ₯Ό μμ νκ² μ΅λλ€.
μ λ ₯ν΄ μ£Όμ μ κ°μ¬ν©λλ€.
@carltongibson κ³Ό λ€λ₯Έ λͺ¨λ μ¬λλ€, λλΌμ΄ λΌμ΄λΈλ¬λ¦¬μ κ°μ¬λ립λλ€!
κ°μ₯ μ μ©ν λκΈ
μλ νμΈμ @loganknechtμ λλ€. μ΄λ κ² λ©μ§ λ¬Έμ λ³΄κ³ μμ κ°μ¬λ립λλ€. κ·Έκ²μ μ μμνκ³ μ μ μ’μμ§λλ€. λ무 λͺ νν©λλ€. κ°μ¬ν©λλ€.
λ¨Όμ
SchoolOrderingFilter
νλ μ΄λ¦μo
μ μν κ² κ°μ΅λλ€.κ·Έλμ λΉμ μ΄ λ§ν λ :
쿼리 λ¬Έμμ΄ λ§€κ°λ³μλ
o
κ²μΌλ‘ μμν©λλ€. μ΄κ²μ΄ μλν©λκΉ:{{API_URL}}/api/v1/schools/?offset=5&limit=3&o=learner_enrolled_count
?κ·Έλ° λ€μ (λΉμ μ λ³΄κ³ μμμ λ΄κ° νμ ν μ μλ λΆλΆ) λΉμ μ κ·Έκ²μ΄ _κ³μ°λ νλ_λΌκ³ λ§ν©λλ€ -- μ νν 무μμ μλ―Έν©λκΉ? μ¦, λͺ¨λΈμ λνλλ νλμ λκΉ, μλλ©΄ Python μμ±μ΄ λ§νλ κ²μ λκΉ?
쿼리 μΈνΈμ
order_by()
κ° μ΄ νλμμ μλνλμ§ λ¬»λ (μΌλΆ) λ€λ₯Έ λ°©λ²μ 무μμ λκΉ? μ¦, μ΄ μμ μ μνν©λλ€.