현재 이 튜토리얼을 따라 장고를 배우고 있는데, 이 책에서 구현할 기능은 여기까지지만 우리가 평소에 사용하는 게시판을 생각해보면 알듯이 더 다양한 기능들이 있다. 댓글, 카테고리, 비밀번호 찾기 및 변경, 프로필 등 이있다. 이런 것들은 나중에 따로 공부해보면 추가해보겠다. 그 전에 이 책의 저자가 더 다양한 파이보 기능들을 구현해 놓은 파이보 게시판이있다.
게시판에서 검색기능은 필수이다. 검색의 대상은 제목, 질문의 내용, 질문의 작성자, 답변의 내용, 답변의 작성자가 있다.
이런 조건으로 검색하려면 질문 목록을 조회하는 부분을 다음처럼 수정해야 한다. 일단 눈으로만 보자.
from django.db.models. import Q
kw = request.GET.get('kw', '') # 검색어
if kw: question_list = question_list.filter( Q(subject__icontains=kw) | # 제목 검색 Q(content__icontains=kw) | # 내용 검색 Q(answer__content__icontains=kw) | # 답변 내용 검색 Q(author__username__icontains=kw) | # 질문 글쓴이 검색 Q(answer__author__username__icontains=kw) # 답변 글쓴이 검색 ).distinct()
Q 함수는 OR 조건으로 데이터를 조회하기 위해 사용하는 함수이다. 제목,내용,글쓴이를 or 조건으로 검색하기위해 사용했다. 그리고 filter 함수 뒤에서 사용된 distinct 조회 결과에 중복이 있을 경우 중복을 제거하여 리턴하는 함수이다.
위 코드에서 kw 는 화면으로 부터 전달받은 검색어이다. 그리고 kw 값은 POST 가 아닌 GET 으로 읽도록 했다. GET 은 가져온다는 개념이고, POST 는 수행한다는 개념이라고 볼수있다. GET 은 글늬 내용에 대한 목록을 보여주는 경우나, 글의 내용을 보는 경우이고, POST 는 글의 내용을 저장하고, 수정할 때 사용한다.
kw = request.GET.get('kw', '') # 검색어
이처럼 작성되었기 때문에 kw 는 다음처럼 GET 방식으로 전달되어야 index 함수에서 읽을 수 있다.
http://localhost:8000/?kw=파이썬&page=1
만약 kw 를 GET이 아닌 POST 방식으로 전달하는 방법으로 한다면 page 파라미터도 POST 방식으로 전달되어야 한다. 하지만 그렇게 한다면 새로고침이나 뒤로가기 등을 했을 때 만료된 페이지라는 오류를 만난다. POST 방식은 동일한 POST 요청이 발생할 경우 오류를 발생시키기 때문이다. 그러므로 게시물 목록을 조회할 때는 GET 방식을 사용하는 것이 좋다.
이제 화면에서 kw 와 page 를 동시에 GET 방식으로 호출하는 방법에 대해서 알아보자.question_list.html 템플릿에 검색어를 입력할 수 있는 텍스트 창을 추가하자.
projects\mysite\templates\pybo\question_list.html 에 추가하자.
<table>태그 상단 우측에 검색어를 입력할 수 있는 텍스트창을 생성하였다. 맨 밑에 있던 질문 등록하기 버튼은 검색창의 좌측으로 이동했다. 그리고 자바스크림트에서 이 텍스트창에 입력된 값을 읽기 위해 다음처럼 id 속성을 추가했다.
<input type="text"
id="search_kw"
class="form-control" value="{{ kw|default_if_none:'' }}">빨간 줄로 그은 부분은 삭제해주자.
그리고 page 와 kw 를 동시에 GET 으로 요청할 수 있는 searchForm 을 다음과 같이 추가하자.projects\mysite\templates\pybo\question_list.html 에 추가하자.
GET 방식으로 요청해야 하므로 method 속성에 "get" 을 설정했다. kw 와 page 는 이전에 요청했던 값을 기억하고 있어야 하므로 value에 값을 대입했다. 이전에 요청했던 kw와 page의 값은 index 함수로부터 전달될 것이다. action 속성은 '폼이 전송되는 URL'이므로 질문 목록 URL인 {% url 'index' %} 를 지정했다.
기존 페이징 처리하는 부분도 ?page=1 처럼 직접 파라미터를 코딩하는 방식에서 값을 읽어 폼에 설정할 수 있도록 변경해야한다.
projects\mysite\templates\pybo\question_list.html 을 변경하자.
모든 페이지 링크를 href 속성에 직접 입력하는 대신 data-page 속성으로 값을 읽을 수 있도록 변경했다.
그리고 page, kw 파라미터를 동시에 요청할 수 있는 자바스크립트를 다음과 같이 추가하자.
projects\mysite\templates\pybo\question_list.html 에 추가하자.
작성한 자바스크립트 코드를 살펴보자면,
만약 다음과 같이 class 속성 값으로 'page-link' 라는 값을 가지고 있는 링크를 클릭하면
마크다운으로 작성한 문서를 HTML 문서로 변환하려면 템플릿에서 사용할 마크다운 필터를 작성해야 한다. 이전에 sub 필터를 작성했던 pybo_filter.py 파일에 다음과 같이 mark 필터를 추가하자.
projects\mysite\pybo\templatetags\pybo_filter.py 에 추가하자.
mark 함수는 markdown 모듈과 mark_safe 함수를 이용해 입력 문자열을 HTML 로 변환하는 필터 함수이다. 마크다운에는 몇 가지 확장 기능이 있는데 파이보는 위처럼 nl2br 과 fenced_code 를 사용했다. nl2br 는 줄바꿈 문자를 <br> 로 바꾸어주고 fenced_code 는 위에서 살펴본 마크다운의 소스코드 표현을 위해 필요하다.
답글을 작성하거나 수정한 후에 항상 페이지 상단으로 스크롤이 이동되어 작성한 답변을 확인하려면 다시 스크롤을 내려서 확인해야 한다. Ajax같은 비동기 통신 기술을 사용할 수 도 있지만 보다 쉬운 방법으로 이 문제를 해결해 보자. HTML 에는 URL 호출시 원하는 위치로 이동시켜 주는 앵커 태그가 있다. 예를 들어 HTML 중간에 <a id="django"></a> 라는 앵커 태그가 있다면 이 HTML 을 호출하는 URL 뒤에 #django 라고 붙여주면 해당 페이지가 호출되면서 해당 앵커로 스크롤이 이동된다. 이를 적용해 보자.
먼저 답변에 앵커기능을 추가해 보자.
projects\mysite\templates\pybo\question_detail.html 을 수정하자.
답변 작성, 수정시에이동해야할 앵커 태그를 추가했다. 답변이 반복되어 표시되는 for 문 바로 뒤에 추가했다. name 속성은 유일한 값 이어야 하므로 answer_{{ answer.id }} 같은 답변 id 를 사용했다.
이제 답변을 등록하거나 수정한 뒤 지정한 앵커 태그로 이동하도록 코드를 수정하자. 다음은 답변 등록 또는 답변 수정을 한 뒤 사용했던 기존 코드의 일부이다.
하나의 질문에 여러명이 추천할 수 있고 한명이 여러 개의 질문에 추천할 수 있으므로 다대다, N:N 관계를 의미하는 ManytoManyField 를 사용해야 한다.
projects\mysite\pybo\models.py 파일을 다음과 같이 수정하자.
voter 를 ManytoManyField 관계로 추가했다. 모델을 변경했기 때문에 makrmigrations, migrate 를 해야한다.
하지만 makemigrations 를 하면 오류가 발생한다. Question 모델에서 사용한 author와 voter 모두 User 모델과 연결되어 있기 때문에 User.question_set 처럼 User 모델을 통해서 Question 데이터에 접근하려고 할 때 author를 기준으로 할지 voter 를 기준으로 해야 할 지 명확하지 않다는 오류이다. 오류의 HINT 에서도 알 수 있듯이 related_name 인수를 추가하여 해결할 수 있다.
projects\mysite\pybbo\models.py 를 수정하자.
author 에는 related_name='author_question' 라는 인수를 지정하고 voter 에는 related_name='voter_question' 라는 인수를 지정했다. 이렇게 하면 특정 사용자가 작성한 질문을 얻기 위해서는 some_user.author_question.all() 처럼 사용할 수 있다. 마찬가지로 특정 사용자가 추천한 질문을 얻기 위해서는 some_user.voter_question.all() 처럼 사용할 수 있다. some_user 는 특정 사용자를 의미한다.
Answer 모델에도 voter 속성을 다음처럼 추가하자.
projects\mysite\pybo\models.py 를 수정하자.
Answre 모델에도 author, voter 속성에 related_name 인수를 추가했다. 다시 makemigrates 와 migrate 를 실행하자.
오류없이 잘 실행된다.
[질문 추천]
이제 질문 추천 기능을 만들어보자.
projects\mysite\templates\pybo\question_detail.html 를 수정하자.
질문의 추천 버튼을 질문의 수정 버튼 좌측에 추가했다. 그리고 버튼에는 추천수도 함께 보이도록 했다. 추천 버튼을 클릭하면 href의 속성이 javascript:void(0) 으로 되어 있기 때문에 아무런 동작도 하지 않는다. 하지만 class 속성에 recommend 를 추가하여 자바스크립트로 data-uri 에 정의된 URL이 호출되게 할 것이다. 이와 같은 방법을 사용하는 이유는 추천 버튼을 눌렀을때 확인창을 통해 사용자의 확인을 구하기 위함이다.
그 확인창을 만들어 보자.
projcets\mysite\templates\pybo\question_detail.html 을 수정하자.
오타가 안나게 잘 따라 작성하자
추천 버튼에 class="recommend" 가 적용되어 있으므로 recommend 라는 이름을 가진 클래스가 있으면 명령문이 실행되어 '정말로 추천하시겠습니까?' 라는 확인창이 나오고 확인을 누르면 data-uri 속성에 정의한 URL 이 호출된다.
question_detail.html 에 적은것 처럼 {% url 'pybo:question_vote' question.id %} URL이 추가되었으므로 pybo/urls.py 에 다음처럼 URL매핑 규칙을 추가해야 한다.
projects\mysite\pyob\urls.py 를 수정하자.
그리고 URL 매핑에 의해 실행되는 question_vote 함수도 작성해줘야 한다.
projects\mysite\pybo\views\question_views.py 에 다음과 같이 작성하자.
작성한 것을 보면 알 수 있듯이 로그인한 사용자와 추천하려는 질문의 글쓴이가 같은 유저일 경우 추천할 수 없게 했다. Question 모델의 voter 는 여러사람을 추가할 수 있는 ManyToManyField 이므로 question.voter.add(request.user) 처럼 add 함수를 사용하여 추천인을 추가한다. 동일한 사용자가 동일한 질문을 여러번 추천해도 추천수가 증가하지 않는다. 오류도 발생하지 않는다. 이는 ManyToManyField 내부에서 자체적으로 처리된다. 서버를 재시작해서 테스트를 해보자. 잘 작동되는지 확인하자.
[답변 추천]
답변 추천도 질문 추천과 동일하다. 먼저
projects\mysite\templates\pybo\question_detail.html 을 작성하자.
{% url 'pybbo:answer_vote' answer.id %} 이 추가 되어 URL 매핑규칙을 추가하자.
projects\mysite\pybo\urls.py 를 수정하자.
URL 매핑에 의해 실행되는 answer_vote 함수를 작성해준다.
projects\mysite\pybo\views\answer_views.py 에 작성해주자.