https://wikidocs.net/75418

 

3-15 파이보 추가 기능

이 책에서 구현할 파이보의 기능은 아쉽지만 여기까지이다. 함께 더 많은 기능을 추가하고 싶지만 이 책은 파이보의 완성이 아니라 파이보를 성장시키며 얻게 되는 경험을 전달하는 것을…

wikidocs.net

현재 이 튜토리얼을 따라 장고를 배우고 있는데, 이 책에서 구현할 기능은 여기까지지만 우리가 평소에 사용하는 게시판을 생각해보면 알듯이 더 다양한 기능들이 있다. 댓글, 카테고리, 비밀번호 찾기 및 변경, 프로필 등 이있다. 이런 것들은 나중에 따로 공부해보면 추가해보겠다. 그 전에 이 책의 저자가 더 다양한 파이보 기능들을 구현해 놓은 파이보 게시판이있다.

들어가서 확인해보면 완성형의 파이보 게시판을 볼 수 있다.

https://pybo.kr/pybo/question/list/qna/

 

질문과답변 - 파이보

 

pybo.kr

 

'Django > 따라하는 장고' 카테고리의 다른 글

31. 깃허브  (0) 2022.11.29
30. 깃  (0) 2022.11.29
28. 검색 기능  (0) 2022.11.24
27. 마크다운  (0) 2022.11.23
26. 앵커  (0) 2022.11.23

게시판에서 검색기능은 필수이다. 검색의 대상은 제목, 질문의 내용, 질문의 작성자, 답변의 내용, 답변의 작성자가 있다. 

 

이런 조건으로 검색하려면 질문 목록을 조회하는 부분을 다음처럼 수정해야 한다. 일단 눈으로만 보자.

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' 라는 값을 가지고 있는 링크를 클릭하면

<a class="page-link" data-page="{{ question_list.previous_page_number }}" 
href="javascript:void(0)">이전</a>

이 링크의 data-page 속성값을 읽어 searchForm 의 page 필드에 설정하여 searchForm 을 요청하도록 다음과 같은 스크립트를 추가했다.

const page_elements = document.getElementsByClassName("page-link");
Array.from(page_elements).forEach(function(element) {
    element.addEventListener('click', function() {
        document.getElementById('page').value = this.dataset.page;
        document.getElementById('searchForm').submit();
    });
});

그리고 검색버튼을 클릭하면 검색어 텍스트창에 입력된 값을 searchForm 의 kw 필드에 설정하여 searchForm 을 요청하도록 다음과 같은 스크립트를 추가했다.

const btn_search = document.getElementById("btn_search");
btn_search.addEventListener('click', function() {
    document.getElementById('kw').value = document.getElementById('search_kw').value;
    document.getElementById('page').value = 1;  // 검색버튼을 클릭할 경우 1페이지부터 조회한다.
    document.getElementById('searchForm').submit();
});

검색 버튼을 클릭하는 경우 새로운 검색에 해당되므로 page 에 항상 1을 설정하여 요청하도록 했다.

 

마지막으로 요청한 검색어가 질문 목록에 조회되도록 수정하자.

projects\mysite\pybo\views\base_views.py 의 index 함수를 수정하자.

Q 함수내에 사용된 subject__icontains=kw 의 의미는 제목에 kw 문자열이 포함되어있는지를 의미한다.

answer__author__username__icontains 는 답변을 작성한 사람의 이름이 포함되는가? 라는 의미이다. filter 함수에서 모델 속성에 접근하기 위해서는 __ (언더스크롤 두개) 를 이용하여 위 속성에 접근할 수 있다.

subject__contains=kw 대신 subject__icontains=kw 를 하면 대소문자 가리지 않고 찾아 준다.

그리고 page와 kw 를 템플릿에 전달하기 위해 context 딕셔너리에 추가했다.

테스트를 해보자.

검색어에 마크다운이라고 입력한 후 찾기를 눌러보면 마크다운 이라는 제목을 가진 질문만 나온다.

 

'Django > 따라하는 장고' 카테고리의 다른 글

30. 깃  (0) 2022.11.29
29. 파이보 추가 기능  (0) 2022.11.24
27. 마크다운  (0) 2022.11.23
26. 앵커  (0) 2022.11.23
25. 추천 기능  (0) 2022.11.23

질문이나 답변을 작성할 때 일반적인 텍스트 외에 글자를 진하게 표시하거나 링크를 추가하고 싶을 경우 '마크다운' 이라는 글쓰기 도구를 입력하면 표현할 수 있다.

먼저 마크다운의 문법을 간단하게 알아보자.

 

[마크다운 문법]

 

목록을 표시하기 위해 

* 파이썬
* 장고
* 알고리즘

이런식으로 글을 쓸 때 작성하면 위의 문자열을 마크다운 해석기가 HTML로 변환하면 실제 화면에서는 다음과 같이 보인다.

● 파이썬
● 장고
● 알고리즘

 

순서가 있는 목록을 표시하려면

1. 하나
1. 둘
1. 셋

이렇게 글을 작성하면

1. 하나
2. 둘
3. 셋

이런식으로 변환되어 작성된다.

 

작성한 글자를 강조하려면 

장고는 **파이썬**으로 만들어진 웹 프레임워크이다.

이런식으로 강조할 텍스트 양쪽에 **을 넣어주면

장고는 파이썬으로 만들어진 웹 프레임워크이다.

 

링크를 추가하려면 [링크명](링크주소) 규칙을 적용하면 된다.

파이썬 홈페이지는 [http://python.org](http://www.python.org) 입니다.

라고 작성하면

파이썬 홈페이지는 http://www.python.org 입니다.

이렇게 변환되어 결과가 나온다.

 

소스코드는 백쿼트 ` 를 3개 연이어 붙여 위아래를 감싸면 된다.

```
def add(a, b):
        return a+b
```

이렇게 작성하면

def add(a, b):
	return a+b

이런식으로 변환된다.

 

이제 마크다운 모듈을 설치 해보자.

터미널에 pip install markdown 명령어를 입력하자.

다운이 정상적으로 완료되었다.

마크다운으로 작성한 문서를 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 는 위에서 살펴본 마크다운의 소스코드 표현을 위해 필요하다.

 

이제 질문상세템플릿에 {% load pybo_filter %} 태그를 추가하고 마크다운 필터를 적용하자

projects\mysite\templates\pybo\question_detail.html 에 추가하자.

줄바꿈을 표시하기 위해 사용했던 기존의 style="white-space: pre-line;" 스타일을 삭제하고 {{ question.content|mark }} 같이 마크다운 필터를 적용했다.

 

답변 내용에도 마크다운 필터를 적용하자.

 

이제 테스트해보기 위해 

이렇게 작성을 해 본다면

마크다운 필터에 의해 자동으로 변환되어 작성된다.

'Django > 따라하는 장고' 카테고리의 다른 글

29. 파이보 추가 기능  (0) 2022.11.24
28. 검색 기능  (0) 2022.11.24
26. 앵커  (0) 2022.11.23
25. 추천 기능  (0) 2022.11.23
24. views.py 분리  (0) 2022.11.21

답글을 작성하거나 수정한 후에 항상 페이지 상단으로 스크롤이 이동되어 작성한 답변을 확인하려면 다시 스크롤을 내려서 확인해야 한다. Ajax같은 비동기 통신 기술을 사용할 수 도 있지만 보다 쉬운 방법으로 이 문제를 해결해 보자. HTML 에는 URL 호출시 원하는 위치로 이동시켜 주는 앵커 태그가 있다. 예를 들어 HTML 중간에 <a id="django"></a> 라는 앵커 태그가 있다면 이 HTML 을 호출하는 URL 뒤에 #django 라고 붙여주면 해당 페이지가 호출되면서 해당 앵커로 스크롤이 이동된다. 이를 적용해 보자.

먼저 답변에 앵커기능을 추가해 보자.

projects\mysite\templates\pybo\question_detail.html 을 수정하자.

답변 작성, 수정시에이동해야할 앵커 태그를 추가했다. 답변이 반복되어 표시되는 for 문 바로 뒤에 추가했다. name 속성은 유일한 값 이어야 하므로 answer_{{ answer.id }} 같은 답변 id 를 사용했다. 

 

이제 답변을 등록하거나 수정한 뒤 지정한 앵커 태그로 이동하도록 코드를 수정하자. 다음은 답변 등록 또는 답변 수정을 한 뒤 사용했던 기존 코드의 일부이다.

return redirect('pybo:detail', question_id=question.id)

여기에 앵커를 포함하면

return redirect('{}#answer_{}'.format{
    resolve_url('pybo:detail', question_id=question.id), answer.id))

pybo:detail URL에 #answer_2 와 같은 앵커를 추가하기 위해 resolve_url 함수를 사용했다. resolve_url 은 실제 호출되는 URL문자열을 리턴하는 장고의 함수이다.

answer_views.py의 answer_create, answer_modify, answer_vote 함수도 변경해 줘야 한다.

projcets\mysite\pybo\views\answer_views.py 를 수정하자.

테스트 해보면 답변을 작성한 후에 자동으로 스크롤이 지정한 앵커로 이동하는 것을 확인할 수 있다. 그리고 URL 을 보면 #answer_숫자 가 추가되었다.

'Django > 따라하는 장고' 카테고리의 다른 글

28. 검색 기능  (0) 2022.11.24
27. 마크다운  (0) 2022.11.23
25. 추천 기능  (0) 2022.11.23
24. views.py 분리  (0) 2022.11.21
23. 답변 수정 및 삭제  (0) 2022.11.19

먼저 Question 모델에 voter(추천인) 속성을 추가해 보자.

하나의 질문에 여러명이 추천할 수 있고 한명이 여러 개의 질문에 추천할 수 있으므로 다대다, 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 에 작성해주자.

잘 작동되는지 테스트 해보자.

 

'Django > 따라하는 장고' 카테고리의 다른 글

27. 마크다운  (0) 2022.11.23
26. 앵커  (0) 2022.11.23
24. views.py 분리  (0) 2022.11.21
23. 답변 수정 및 삭제  (0) 2022.11.19
22. 질문 수정 및 삭제  (0) 2022.11.18

+ Recent posts