μ¬λ³΄μΈμ! μ΄ λμκ΄μ μ λ§ κ°μ¬ν©λλ€! νμ¬ APIλ‘ ν μ€νΈν μ μμ΄μ μ λ§ κΈ°λ»€μ΅λλ€!
School
μ SchoolCourse
μ λ κ°μ§ λͺ¨λΈμ΄ μμ΅λλ€. SchoolCourse
μλ School
μ λν λ€λμΌ κ΄κ³κ° λ°λμ
λλ€.
SchoolCourse
μ name
λ₯Ό κΈ°λ°μΌλ‘ School
κ°μ²΄λ₯Ό νν°λ§ν μ μκΈ°λ₯Ό μν©λλ€.
λν νμ₯ λͺ©νλ‘ κ²°κ΅ SchoolLesson
λ‘λ νν°λ§ν μ μκΈ°λ₯Ό λ°λλλ€.
class SchoolModel(Model):
name = CharField(max_length=24, unique=True)
REQUIRED_FIELDS = ["name"]
class Meta:
ordering = ("id",)
def get_courses(self):
school_courses = SchoolCourseModel.objects.filter(school=self)
return school_courses
class SchoolCourseModel(Model):
description = CharField(default="", max_length=200)
name = CharField(max_length=50)
school = ForeignKey(SchoolModel,
related_name="school_course",
on_delete=CASCADE)
REQUIRED_FIELDS = ["school", "name"]
class Meta:
ordering = ("id",)
unique_together = ("school", "name",)
def get_lessons(self):
school_lessons = SchoolLessonModel.objects.filter(course=self)
return school_lessons
μ΄λ κ² - λλ²κ·Έ 보기μμ μ΄λ¬ν λͺ¨λΈλ‘ νλ μ΄νλ©΄ λ€μμ λ³Ό μ μμ΅λλ€.
all_school_models[50].school_course
(Pdb++) <django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager object at 0x10a8177d0>
all_school_models[50].school_course.all()
(Pdb++) <QuerySet [<SchoolCourseModel: SchoolCourseModel object (151)>, <SchoolCourseModel: SchoolCourseModel object (152)>, <SchoolCourseModel: SchoolCourseModel object (153)>]>
all_school_models[50].school_course.all()[0]
(Pdb++) <SchoolCourseModel: SchoolCourseModel object (151)>
all_school_models[50].school_course.all()[0].name
(Pdb++) 'Nihongo Course One'
λ΄κ° λ§λ νν°λ μλΉν μ§κ΄μ μΈ κ² κ°μ΅λλ€.
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")
class Meta:
model = SchoolModel
fields = [
"school_name",
# ---
"course_name",
"course_description"
]
κ·Έλ¬λ μ΄κ²μ μλνμ§ μμ΅λλ€. λ΄κ° μ΄ μμ²μ νλ©΄:
{{API_URL}}/api/v1/schools/?course_name=Nihongo
λλ μ΄κ²μ μλ΅μΌλ‘ λλλ € λ°λλ€.
{
"schools": []
}
λ΄κ° μ¬κΈ°μ λ μλͺ»νκ³ μλ?
λ΄κ° μ₯κ³ νν°λ₯Ό μλͺ» ν΄μνκ³ μμ΅λκΉ? λ°μ΄ν° λͺ¨λΈμ μ°κ²°νλ λ°©λ²μ μλͺ» ν΄μνκ³ μμ΅λκΉ?
λͺ¨λ μ§μΉ¨μ μ£Όμλ©΄ κ°μ¬νκ² μ΅λλ€!
μλ
νμΈμ @loganknechtμ
λλ€. μ¨μ μ± κ²μ¬μ λ§μ°¬κ°μ§λ‘ URLμ΄ μ¬λ°λ₯Έμ§ νμΈν μ μμ΅λκΉ? μμ URLμμ 쿼리 λ¬Έμμ΄μ $ ?
/
λ‘ μμν©λλ€.
μ @rpkilby - μ£μ‘ν©λλ€. Postman
μμ 볡μ¬νμ¬ λΆμ¬λ£λ κ²μ μ μ€μμμ΅λλ€. urlμ μ¬μ€ {{API_URL}}/api/v1/schools/?offset=49&course_name=Nihongo
μΈλ° ν·κ°λ¦¬μ§ μκ² offset
λ₯Ό μμ κ³ λ°λλ‘ ν΄μ μ±κ³΅νμ΅λλ€ π
κ·νμ μλ λμκ² λͺ
λ°±ν λ¬Έμ λ₯Ό μ κΈ°νμ§ μμ΅λλ€. λ΄ μΆμΈ‘μΌλ‘λ API 보기μ λ¬Έμ κ° μλ€λ κ²μ
λλ€. DjangoFilterBackend
μ μ€μ /보기μ filter_backends
μΆκ°νμ
¨μ΅λκΉ? κ·Έλ¦¬κ³ filterset_class = SchoolFilter
(μ΄μ μλ filter_class
)λ₯Ό μ€μ νμ
¨μ΅λκΉ?
μλ νμΈμ @rpkilby
λ΄κ° κ°μ§κ³ μλ보기 μ½λλ λ€μκ³Ό κ°μ΅λλ€.
class SchoolViewSet(ViewSet):
http_method_names = ["get", "post"]
def list(self, request):
all_school_models = SchoolModel.objects.all()
filtered_school_models = SchoolFilter(request.GET, queryset=all_school_models)
# import pdb
# pdb.set_trace()
paginator = HeaderLinkPagination()
current_page_results = paginator.paginate_queryset(filtered_school_models.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
pdb.set_trace()
λ λ΄κ° μμ μ¬λ¬Έμ κ²μν κ³³μ
λλ€.
DjangoFilterBackend
λ μ μ ꡬμ±μ΄λΌκ³ κ°μ νκΈ° λλ¬Έμ μ€μ νμ§ μμκ³ λ¨μΌ λμ μμλ§ ν
μ€νΈνκ³ μμ΅λλ€.
λν κ°μ μ΄μ λ‘ ν΄λμ€μ λν΄μλ μ΄κ²μ μ€μ νμ§ μμμ΅λλ€.
SchoolModel
μ΄λ¦μ μ΄ νν°λ₯Ό μ¬μ©ν μ μμ§λ§ μλ°©ν₯ μΈλ ν€ school_course
λ₯Ό νν°λ§νλ©΄ μλνμ§ μλλ€λ κ²μ μλ κ²μ΄ μ€μν©λλ€.
Gotcha - λ·°μμ μ§μ νν° μΈνΈλ₯Ό μμ±νκ³ μμ΅λλ€. μ΄ κ²½μ° νν° λ°±μλ/filterset ν΄λμ€λ₯Ό μ€μ ν νμκ° μμ΅λλ€.
λ°μ΄ν°κ° μ¬λ°λ₯Έμ§, μ€λ₯κ° μλμ§ λλ SQL μΏΌλ¦¬κ° μ¬λ°λ₯΄κ² ꡬμ±λμλμ§ νμΈν μ μμ΅λλ€. λ Έλ ₯νλ€:
filtered_school_models = SchoolFilter(request.GET, queryset=all_school_models)
print(filtered_school_models.data)
print(filtered_school_models.errors)
print(filtered_school_models.qs)
print(str(filtered_school_models.qs.query))
@rpkilby μ¬κΈ° λ΄κ° 보λ κ²μ΄ μμ΅λλ€
<QueryDict: {'offset': ['49'], 'course_name': ['Nihongo']}>
<QuerySet [<SchoolModel: SchoolModel object (51)>]>
SELECT "piano_gym_api_schoolmodel"."id", "piano_gym_api_schoolmodel"."name", "piano_gym_api_schoolmodel"."school_board_id" FROM "piano_gym_api_schoolmodel" INNER JOIN "piano_gym_api_schoolcoursemodel" ON ("piano_gym_api_schoolmodel"."id" = "piano_gym_api_schoolcoursemodel"."school_id") WHERE UPPER("piano_gym_api_schoolcoursemodel"."name"::text) LIKE UPPER(%Nihongo%) ORDER BY "piano_gym_api_schoolmodel"."id" ASC
@rpkilby μ΄κ²μ λ€μκ³Ό κ°μ λ΄ νμ΄μ§ λ€μ΄ν° ꡬνμ λ¬Έμ κ° μλ κ² κ°μ΅λλ€.
class HeaderLinkPagination(LimitOffsetPagination):
default_limit = settings.DEFAULT_LIMIT
max_limit = settings.DEFAULT_MAX_LIMIT
min_limit = settings.DEFAULT_MIN_LIMIT
min_offset = settings.DEFAULT_MIN_OFFSET
max_offset = settings.DEFAULT_MAX_OFFSET
def get_paginated_response(self, data):
next_url = self.get_next_link()
previous_url = self.get_previous_link()
links = []
header_data = (
(previous_url, "prev"),
(next_url, "next"),
)
for url, label in header_data:
if url is not None:
links.append("<{}>; rel=\"{}\"".format(url, label))
headers = {"Link": ", ".join(links)} if links else {}
return Response(data, headers=headers)
def paginate_queryset(self, queryset, request, view=None):
limit = request.query_params.get("limit")
offset = request.query_params.get("offset")
if limit is None:
limit = settings.DEFAULT_LIMIT
if offset is None:
offset = settings.DEFAULT_OFFSET
limit = int(limit)
if limit > self.max_limit:
error_message = ("Limit should be less than or equal to {0}"
).format(self.max_limit)
errors = {"limit": [error_message]}
raise ValidationError(errors)
elif limit < self.min_limit:
error_message = ("Limit should be greater than or equal to {0}"
).format(self.min_limit)
errors = {"limit": [error_message]}
raise ValidationError(errors)
offset = int(offset)
if offset > self.max_offset:
error_message = ("Offset should be less than or equal to {0}"
).format(self.max_offset)
errors = {"offset": [error_message]}
raise ValidationError(errors)
elif offset < self.min_offset:
error_message = ("Offset should be greater than or equal to {0}"
).format(self.min_offset)
errors = {"offset": [error_message]}
raise ValidationError(errors)
import pdb
pdb.set_trace()
return super(self.__class__, self).paginate_queryset(queryset, request, view)
ν΄κ²°μ± μ΄ λ³΄μ΄λ©΄ μλ €μ£ΌμΈμ. λ΄κ° μ΄κ²μ λ¨μ³λ΄λ €κ³ νλ λμ κ³μ μ§μΌλ΄ μ£Όμμμ€.
λ΄ μκ°μ μ΄κ²μ μ£Όμ΄μ§ μ€νμ κ³Ό κ΄λ ¨μ΄ μμ΅λλ€. νν°λ§λ 쿼리 μΈνΈμλ κ°μ²΄κ° 1κ°λΏμ΄μ§λ§ μμ²μ νμ΄μ§λ€μ΄ν°μκ² μ²μ 49κ° νλͺ©μ 건λλ°λλ‘ μ§μν©λλ€. νμΈνμ ¨λ€λ©΄
print(filtered_school_models[49:])
<QuerySet []>
μ μ»μ κ²μ΄λΌκ³ κ°μ ν©λλ€.
λ«κΈ°, μ΄κ²μ΄ νμ΄μ§ λ§€κΉ λ¬Έμ μΈ κ²μ²λΌ 보μ λλ€.
μλ νμΈμ @rpkilby
μ΄ μ΄μΌκΈ°λ₯Ό ν΄μ£Όμ
μ μ λ§ κ°μ¬νλ€λ λ§μμ λλ¦¬κ³ μΆμ΅λλ€. django-filter
λ¬Έμ λ μλμμ΅λλ€. μ λ κ·Έλ₯ λ©κ΅¬μ€μμ΅λλ€. λΉμ μ λλ₯Ό κ³ λ¬΄ λνΉ μνΌ μνΌ κ°μ¬ν©λλ€.
λΉμ μ νλ₯ν©λλ€. λ€μ ν λ² μ΄ λλΌμ΄ λΌμ΄λΈλ¬λ¦¬μ κ°μ¬λ립λλ€!
κ±±μ λ§. λμ μ€ μμμ΄μ κΈ°λ»!
κ°μ₯ μ μ©ν λκΈ
λ΄ μκ°μ μ΄κ²μ μ£Όμ΄μ§ μ€νμ κ³Ό κ΄λ ¨μ΄ μμ΅λλ€. νν°λ§λ 쿼리 μΈνΈμλ κ°μ²΄κ° 1κ°λΏμ΄μ§λ§ μμ²μ νμ΄μ§λ€μ΄ν°μκ² μ²μ 49κ° νλͺ©μ 건λλ°λλ‘ μ§μν©λλ€. νμΈνμ ¨λ€λ©΄
<QuerySet []>
μ μ»μ κ²μ΄λΌκ³ κ°μ ν©λλ€.λ«κΈ°, μ΄κ²μ΄ νμ΄μ§ λ§€κΉ λ¬Έμ μΈ κ²μ²λΌ 보μ λλ€.