장고 - Q 객체를 이용한 키워드 검색 기능 학습

  • 이번에는 게시판 기능 중, 사용자가 글제목/작성자/글내용에 포함되어 있는 특정 키워드를 검색 시 해당 키워드를 가진 객체를 필터링해주는 방법에 대해 알아볼 것이다.

유저가 게시판에서 특정 키워드로 검색 시, 해당 키워드를 가진 글 목록 제공하기

1. 모델 작성

  • 경로 : board_project > board(앱) > models.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class Document(models.Model):
    category = models.ForeignKey(Category, on_delete=models.SET_NULL,
    null=True, blank=True, related_name='documents')
    author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE,
    related_name='documents')
    # on_delete=models.SET_NULL : 참조하고 있는 Category 객체가 지워져도 삭제되지 않음
    title = models.CharField(max_length=100)
    # db_index=True : DB에 인덱싱 가능
    slug = models.SlugField(max_length=120, db_index=True, unique= True,
    allow_unicode=True, blank=True)
    text = models.TextField()
    image = models.ImageField(upload_to='board_images/%Y/%m/%d')
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    def __str__(self):
    return self.title

    def save(self, *args, **kwargs):
    self.slug = slugify(self.title, allow_unicode=True)
    super(Document, self).save(*args, **kwargs)

    def get_absolute_url(self):
    return reverse('board:detail', args=[self.id])

2. 경로 설정

  • 경로 : board_project > board > urls.py
    1
    2
    3
    4
    5
    6
    7
    8
    from django.urls import path
    from .views import *

    app_name = 'board'

    urlpatterns = [
    path('', document_list, name='list'),
    ...

3. 뷰 작성

  • 경로 : board_project > board > templates > board > document_list.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <form action="" method="get" id="search_form" class="">
    {%csrf_token%}
    <div class="form-row align-items-center justify-content-center">
    <div class="form-check form-check-inline">
    <input class="form-check-input" type="checkbox" id="inlineCheckbox1" value="username" name="search_type">
    <label class="form-check-label" for="inlineCheckbox1">작성자</label>
    </div>
    <div class="form-check form-check-inline">
    <input class="form-check-input" type="checkbox" id="inlineCheckbox2" value="title" name="search_type" checked>
    <label class="form-check-label" for="inlineCheckbox2">제목</label>
    </div>
    <div class="form-check form-check-inline">
    <input class="form-check-input" type="checkbox" id="inlineCheckbox3" value="text" name="search_type">
    <label class="form-check-label" for="inlineCheckbox3">본문</label>
    </div>
    </div>
    <div class="form-row align-items-center justify-content-center">
    <div class="col-sm-3 my-1">
    <input type="text" class="form-control" placeholder="Search Keyword" name="search_key">
    </div>

    <div class="col-auto my-1">
    <button type="submit" class="btn btn-primary">Search</button>
    </div>
    </div>
    </form>
  • 경로 : board_project > board > views.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    from django.shortcuts import render
    from django.db.models import Q
    from .models import Document

    def document_list(request):
    # search_type = ['username', 'title', 'text']
    search_type = reqeust.GET.getlist('search_type', None)
    search_q = None
    # search_key = 유저가 입력한 키워드(문자열 형태로 수신)
    search_key = request.GET.get('search_key', None)

    # 유저가 검색한 키워드가 있는 경우
    if search_key:
    # 유저가 '제목' checkbox 선택한 경우
    if 'title' in search_type:
    # Document 객체 title 필드에 search_key를 포함하는 키워드가 있는 경우 그 객체들을 temp_q 변수가 참조한다.
    temp_q = Q(title__icontaions=search_key)
    # search_q가 있다면, (search_q 또는 temp_q)를 search_q 변수가 참조하게 하고,
    # search_q가 없다면, temp_q가 참조하고 있는 객체를 search_q 변수가 참조하게 한다.
    search_q = search_q | temp_q if search_q else temp_q
    # 유저가 '본문' checkbox 선택한 경우
    if 'text' in search_type:
    # Document 객체의 text 필드에 search_key 포함하는 키워드가 있는 경우, 그 객체들을 temp_q 변수가 참조한다.
    temp_q = Q(text__icontains=search_key)
    search_q = search_q | temp_q if search_q else temp_q
    # 유저가 '작성자' checkbox 선택한 경우
    if 'username' in search_type:
    # Document 객체의 author__username 데이터에 search_key 포함하는 키워드가 있는 경우, 그 객체들을 temp_q 변수가 참조한다.
    temp_q = Q(author__username__icontaions=search_key)
    search_q = search_q | temp_q if search_q else temp_q
    # Document 에서 search_q 가 참조하고 있는 객체들을 documents 변수가 참조하게 한다.
    documents = get_list_or_404(Document, search_q)

    # 유저가 검색한 키워드가 없는 경우
    else:
    # 모든 Document 객체들을 documents 변수가 참조하게 한다.
    documents = Document.objects.all()

    # context data로 documents 변수를 'object_list' 변수가 참조하게 하여 board/document_list.html 페이지에 보낸다.
    return render(request, 'board/document_list.html', {'object_list':documents})