검색¶
QuerySet 검색¶
Wagtail 검색은 Django의 QuerySet API를 기반으로 합니다. 모델과 필터링되는 필드가 검색 색인에 추가된 경우 모든 Django QuerySet을 검색할 수 있어야 합니다.
페이지 검색¶
Wagtail은 페이지 검색을 위한 단축키인 .search() QuerySet 메서드를 제공합니다. 모든 PageQuerySet 에서 이 메서드를 호출할 수 있습니다. 예를 들어:
# 미래 EventPage 검색
>>> from wagtail.models import EventPage
>>> EventPage.objects.filter(date__gt=timezone.now()).search("Hello world!")
PageQuerySet 의 다른 모든 메서드는 search() 와 함께 사용할 수 있습니다. 예를 들어:
# 이벤트 색인 아래에 있는 모든 라이브 EventPage 검색
>>> EventPage.objects.live().descendant_of(events_index).search("Event")
[<EventPage: 이벤트 1>, <EventPage: 이벤트 2>]
참고
search() 메서드는 QuerySet 을 Wagtail의 SearchResults 클래스 인스턴스 중 하나로 변환합니다(백엔드에 따라 다름). 즉, search() 를 호출하기 전에 필터링을 수행해야 합니다.
search 메서드의 표준 동작은 완전한 단어에 대해서만 일치하는 항목을 반환하는 것입니다. 예를 들어, “hel”을 검색하면 “hello”라는 단어가 포함된 결과는 반환되지 않습니다. 예외는 대체 데이터베이스 검색 백엔드입니다. 이 백엔드는 데이터베이스에 전체 텍스트 검색 확장이 없거나 대체 백엔드가 지정되지 않은 경우 사용됩니다. 이는 기본 부분 문자열 일치를 수행하며 모든 단어 경계를 무시하고 검색어가 포함된 결과를 반환합니다.
자동 완성 검색¶
Wagtail은 특정 자동 완성 필드에서 부분 일치를 수행하는 별도의 메서드를 제공합니다. 이는 사용자가 쿼리를 입력할 때 실시간으로 페이지를 제안하는 데 주로 유용합니다. 일반 검색에는 권장되지 않습니다. 자동 완성 기능이 검색되는 특정 용어 외에 원치 않는 결과를 추가하는 경향이 있기 때문입니다.
>>> EventPage.objects.live().autocomplete("Eve")
[<EventPage: 이벤트 1>, <EventPage: 이벤트 2>]
이미지, 문서 및 사용자 지정 모델 검색¶
Wagtail의 문서 및 이미지 모델은 페이지와 마찬가지로 QuerySet에 search 메서드를 제공합니다.
>>> from wagtail.images.models import Image
>>> Image.objects.filter(uploaded_by_user=user).search("Hello")
[<Image: 안녕하세요>, <Image: 안녕하세요 세계!>]
사용자 지정 모델은 검색 백엔드에서 직접 search 메서드를 사용하여 검색할 수 있습니다.
>>> from myapp.models import Book
>>> from wagtail.search.backends import get_search_backend
# 책 검색
>>> s = get_search_backend()
>>> s.search("Great", Book)
[<Book: 위대한 유산>, <Book: 위대한 개츠비>]
QuerySet을 search 메서드에 전달할 수도 있으며, 이를 통해 검색 결과에 필터를 추가할 수 있습니다.
>>> from myapp.models import Book
>>> from wagtail.search.backends import get_search_backend
# 책 검색
>>> s = get_search_backend()
>>> s.search("Great", Book.objects.filter(published_date__year__lt=1900))
[<Book: 위대한 유산>]
검색할 필드 지정¶
기본적으로 Wagtail은 index.SearchField 를 사용하여 색인화된 모든 필드를 검색합니다.
fields 키워드 인수를 사용하여 특정 필드 집합으로 제한할 수 있습니다.
# 제목 필드만 검색
>>> EventPage.objects.search("Event", fields=["title"])
[<EventPage: 이벤트 1>, <EventPage: 이벤트 2>]
패싯 검색¶
Wagtail은 분류 필드(예: 카테고리 또는 페이지 유형)를 기반으로 하는 필터링의 일종인 패싯 검색을 지원합니다.
.facet(field_name) 메서드는 OrderedDict 를 반환합니다. 키는 지정된 필드에서 참조된 관련 객체의 ID이고, 값은 각 ID에 대해 발견된 참조 수입니다. 결과는 참조 수 내림차순으로 정렬됩니다.
예를 들어, 검색 결과에서 가장 일반적인 페이지 유형을 찾으려면:
>>> Page.objects.search("Test").facet("content_type_id")
# 참고: 키는 ContentType 객체의 ID에 해당합니다. 값은 해당 유형에 대해 반환된 페이지 수입니다.
OrderedDict([
('2', 4), # 4개 페이지에 content_type_id == 2
('1', 2), # 2개 페이지에 content_type_id == 1
])
검색 동작 변경¶
검색 연산자¶
검색 연산자는 사용자가 여러 검색어를 입력했을 때 검색이 어떻게 동작해야 하는지 지정합니다. 두 가지 가능한 값이 있습니다.
“or” - 결과는 하나 이상의 용어와 일치해야 합니다(Elasticsearch의 기본값).
“and” - 결과는 모든 용어와 일치해야 합니다(데이터베이스 검색의 기본값).
두 연산자 모두 장단점이 있습니다. “or” 연산자는 훨씬 더 많은 결과를 반환하지만 관련 없는 결과가 많이 포함될 수 있습니다. “and” 연산자는 모든 검색어가 포함된 결과만 반환하지만 사용자가 쿼리에 더 정확해야 합니다.
관련성별로 정렬할 때는 “or” 연산자를 사용하고, 다른 것으로 정렬할 때는 “and” 연산자를 사용하는 것이 좋습니다.
다음은 operator 키워드 인수를 사용하는 예시입니다.
# 데이터베이스에는 다음 항목이 있는 "Thing" 모델이 포함되어 있습니다.
# - 안녕하세요 세계
# - 안녕하세요
# - 세계
# "or" 연산자로 검색
>>> s = get_search_backend()
>>> s.search("Hello world", Things, operator="or")
# 모두 "hello" 또는 "world"를 포함하므로 모든 레코드가 반환됩니다.
[<Thing: 안녕하세요 세계>, <Thing: 안녕하세요>, <Thing: 세계>]
# "and" 연산자로 검색
>>> s = get_search_backend()
>>> s.search("Hello world", Things, operator="and")
# 두 용어를 모두 포함하는 유일한 항목이므로 "hello world"만 반환됩니다.
[<Thing: 안녕하세요 세계>]
페이지, 이미지 및 문서 모델의 경우 operator 키워드 인수는 QuerySet의 search 메서드에서도 지원됩니다.
>>> Page.objects.search("Hello world", operator="or")
# "hello" 또는 "world"를 포함하는 모든 페이지가 반환됩니다.
[<Page: 안녕하세요 세계>, <Page: 안녕하세요>, <Page: 세계>]
구문 검색¶
구문 검색은 개별 용어 대신 전체 문장 또는 구문을 찾는 데 사용됩니다. 용어는 함께 나타나야 하며 동일한 순서여야 합니다.
예를 들어:
>>> from wagtail.search.query import Phrase
>>> Page.objects.search(Phrase("Hello world"))
[<Page: 안녕하세요 세계>]
>>> Page.objects.search(Phrase("World hello"))
[<Page: 세계 안녕하세요 날>]
이중 따옴표 구문을 사용하여 구문 쿼리를 구현하려는 경우 쿼리 문자열 구문 분석를 참조하십시오.
퍼지 일치¶
퍼지 일치는 레벤슈타인 편집 거리로 측정된 검색어와 유사한 용어가 포함된 문서를 반환합니다.
3~5자 용어의 경우 최대 1회 편집(전치, 삽입 또는 문자 제거)이 허용되고, 더 긴 용어의 경우 2회 편집이 허용되며, 더 짧은 용어는 정확히 일치해야 합니다.
예를 들어:
>>> from wagtail.search.query import Fuzzy
>>> Page.objects.search(Fuzzy("Hallo"))
[<Page: 안녕하세요 세계>, <Page: 안녕하세요>]
퍼지 일치는 Elasticsearch 검색 백엔드에서만 지원됩니다.
operator 키워드 인수는 Fuzzy 객체에서도 지원되며, 기본값은 “or”입니다.
>>> Page.objects.search(Fuzzy("Hallo wurld", operator="and"))
[<Page: 안녕하세요 세계>]
복잡한 검색 쿼리¶
검색 쿼리 클래스를 사용하여 Wagtail은 다른 검색 쿼리로 래핑하고 결합할 수 있는 Python 객체로 검색 쿼리를 구축하는 것도 지원합니다. 다음 클래스를 사용할 수 있습니다.
PlainText(query_string, operator=None, boost=1.0)
이 클래스는 별도의 용어 문자열을 래핑합니다. 이는 쿼리 클래스 없이 검색하는 것과 동일합니다.
쿼리 문자열, 연산자 및 부스트를 사용합니다.
예를 들어:
>>> from wagtail.search.query import PlainText, Boost
>>> Page.objects.search(PlainText("Hello world"))
# 이 예시는 "hello world"와 "Hello earth"를 모두 일치시킵니다.
>>> Page.objects.search(PlainText("Hello") & (PlainText("world") | PlainText("earth")))
Phrase(query_string)
이 클래스는 구문을 포함하는 문자열을 래핑합니다. 작동 방식은 이전 섹션을 참조하십시오.
예를 들어:
# 이 예시는 "hello world"와 "Hello earth" 구문을 모두 일치시킵니다.
>>> Page.objects.search(Phrase("Hello world") | Phrase("Hello earth"))
Boost(query, boost)
이 클래스는 다른 쿼리의 점수를 높입니다.
예를 들어:
>>> from wagtail.search.query import PlainText, Boost
# 이 예시는 "hello world"와 "Hello earth" 구문을 모두 일치시키지만 "hello world"에 대한 일치는 더 높은 순위로 지정됩니다.
>>> Page.objects.search(Boost(Phrase("Hello world"), 10.0) | Phrase("Hello earth"))
이는 PostgreSQL 또는 데이터베이스 검색 백엔드에서는 지원되지 않습니다.
쿼리 문자열 구문 분석¶
이전 섹션에서는 구문 검색 쿼리를 수동으로 구성하는 방법을 보여주지만, 많은 검색 엔진(Wagtail 관리자 포함, 시도해보세요!)은 구문을 큰따옴표로 묶어 구문 쿼리를 작성하는 것을 지원합니다. 구문 외에도 사용자가 콜론 구문(hello world published:yes)을 사용하여 쿼리에 필터를 추가할 수 있도록 할 수도 있습니다.
이 두 가지 기능은 parse_query_string 유틸리티 함수를 사용하여 구현할 수 있습니다. 이 함수는 사용자가 입력한 쿼리 문자열을 받아 쿼리 객체와 필터의 QueryDict를 반환합니다.
예를 들어:
>>> from wagtail.search.utils import parse_query_string
>>> filters, query = parse_query_string('my query string "this is a phrase" this_is_a:filter key:value1 key:value2', operator='and')
# 또는..
# filters, query = parse_query_string("my query string 'this is a phrase' this_is_a:filter key:test1 key:test2", operator='and')
>>> filters
<QueryDict: {'this_is_a': ['filter'], 'key': ['value1', 'value2']}> >
# getlist 메서드를 사용하여 특정 키와 연결된 값 목록 가져오기
>>> filters.getlist('key')
['value1', 'value2']
# dict 메서드를 사용하여 dict 표현 가져오기
# 참고: dict 메서드는 특정 키에 대한 여러 값을 마지막으로 할당된 값으로 줄입니다.
>>> filters.dict()
{
'this_is_a': 'filter',
'key': 'value2'
}
>>> query
And([
PlainText("my query string", operator='and'),
Phrase("this is a phrase"),
])
다음은 이 함수를 검색 뷰에서 사용하는 방법의 예시입니다.
from wagtail.search.utils import parse_query_string
def search(request):
query_string = request.GET['query']
# 쿼리 구문 분석
filters, query = parse_query_string(query_string, operator='and')
# 게시된 필터
# `published:yes` 또는 `published:no` 를 허용하고 페이지를 그에 따라 필터링하는 예시 필터
published_filter = filters.get('published')
published_filter = published_filter and published_filter.lower()
if published_filter in ['yes', 'true']:
pages = pages.filter(live=True)
elif published_filter in ['no', 'false']:
pages = pages.filter(live=False)
# 검색
pages = pages.search(query)
return render(request, 'search_results.html', {'pages': pages})
사용자 지정 정렬¶
기본적으로 검색 결과는 백엔드가 지원하는 경우 관련성별로 정렬됩니다. QuerySet의 기존 정렬을 유지하려면 search() 메서드에서 order_by_relevance 키워드 인수를 False 로 설정해야 합니다.
예를 들어:
# 날짜별로 정렬된 이벤트 목록 가져오기
>>> EventPage.objects.order_by('date').search("Event", order_by_relevance=False)
# 날짜별로 정렬된 이벤트
[<EventPage: 부활절>, <EventPage: 할로윈>, <EventPage: 크리스마스>]
점수로 결과 주석 달기¶
일치하는 각 결과에 대해 Elasticsearch는 “점수”를 계산합니다. 이는 사용자 쿼리를 기반으로 결과가 얼마나 관련성이 있는지 나타내는 숫자입니다. 결과는 일반적으로 점수를 기반으로 정렬됩니다.
점수에 액세스하는 것이 유용한 경우가 있습니다(예: 다른 모델에 대한 두 쿼리를
프로그래밍 방식으로 결합하는 경우). SearchQuerySet 에서
.annotate_score(field) 메서드를 호출하여 각 결과에 점수를 추가할 수 있습니다.
예를 들어:
>>> events = EventPage.objects.search("Event").annotate_score("_score")
>>> for event in events:
... print(event.title, event._score)
...
("부활절", 2.5),
("할로윈", 1.7),
("크리스마스", 1.5),
점수 자체는 임의적이며 동일한 쿼리에 대한 결과 비교에만 유용합니다.
페이지 검색 뷰 예시¶
다음은 사이트에 “검색” 페이지를 추가하는 데 사용할 수 있는 Django 뷰 예시입니다.
# views.py
from django.shortcuts import render
from wagtail.models import Page
from wagtail.contrib.search_promotions.models import Query
def search(request):
# 검색
search_query = request.GET.get('query', None)
if search_query:
search_results = Page.objects.live().search(search_query)
# Wagtail이 홍보된 결과를 제안할 수 있도록 쿼리 로깅
Query.get(search_query).add_hit()
else:
search_results = Page.objects.none()
# 템플릿 렌더링
return render(request, 'search_results.html', {
'search_query': search_query,
'search_results': search_results,
})
다음은 함께 사용할 템플릿입니다.
{% extends "base.html" %}
{% load wagtailcore_tags %}
{% block title %}검색{% endblock %}
{% block content %}
<form action="{% url 'search' %}" method="get">
<input type="text" name="query" value="{{ search_query }}">
<input type="submit" value="검색">
</form>
{% if search_results %}
<ul>
{% for result in search_results %}
<li>
<h4><a href="{% pageurl result %}">{{ result }}</a></h4>
{% if result.search_description %}
{{ result.search_description|safe }}
{% endif %}
</li>
{% endfor %}
</ul>
{% elif search_query %}
결과를 찾을 수 없습니다.
{% else %}
검색 상자에 무언가를 입력하십시오.
{% endif %}
{% endblock %}
홍보된 검색 결과¶
“홍보된 검색 결과”를 통해 편집자는 관련 콘텐츠를 검색어에 명시적으로 연결할 수 있으므로 결과 페이지에는 검색 엔진의 결과 외에 선별된 콘텐츠가 포함될 수 있습니다.
이 기능은 search_promotions contrib 모듈에서 제공합니다.