질문, 답변을 달면 누가 글을 작성했는지 알려주는 작성자 항목이 필요하다. Question, Answer 모델에 글쓴이 에 해당되는 author 속성을 추가해야한다.

먼저 질문 모델에 추가하기 위해 projects\mysite\pybo\models.py

 

author 필드는 User 모델을 ForeignKey 로 적용하여 선언했다. User 모델은 django.contrib.auth 앱이 제공하는 사용자 모델로 회원 가입시 데이터 저장에 사용했던 모델이다. on_delete=models.CASCADE 는 계정이 삭제되면 이 계정이 작성한 질문을 모두 삭제하라는 의미이다.

모델을 변경한 후에는 반드시 makemigrations 와 migrate 로 데이터베이스를 변경해 주어야한다.

python manage.py makemigrations 명령어를 실행하자.

명령어를 입력하면 옵션을 선택하라는 메세지가 나온다. Question 모델에 author 를 추가하면 이미 등록되어 있던 게시물에 author 에 해당되는 값이 저장되어야하는데, author 에 어떤 값을 넣어야 하는지 모르기 때문이다. 그것에 관해 어떤 옵션을 선택하라는지 묻는것이다.

1번 옵션은 one-off default 값을 제공한다. 현재 NULL 필드(author 필드) 에 값을 제공하겠다는 의미. 2번 옵션은 실행을 취소하고 models.py 에 default 값을 추가 하겠다는 옵션이다. 우리는 1번 옵션을 선택할 것이다.

1번을 입력하면

그렇게 하면 파이썬 셸이 가동된다 기본값을 지금 입력해달라는 뜻이다. 여기서 1을 다시 입력한다.

여기서 입력한 1은 admin 계정의 id 값이다. 따라서 기존 게시물의 author 에는 admin 계정이 등록된것이다. 계정을 만들때마다 id 가 1부터 순차적으로 증가하는데 처음에 만들었던 슈퍼유저 admin 이 1 인것이다.

 

다 했다면 python manage.py migrate 로 데이터베이스에 적용하자.

 

Answer 모델에도 author 속성을 똑같이 추가하자.

그리고 저번처럼 python manage.py makemigrations 명령어로를 한후 옵션을 아까처럼 선택하자.

그 후 python manage.py migrate 명령어를 수행하자.

author 속성에 null 을 허용하려면 

author = models.ForeignKey(User, on_delete=models.CASCADE,

null=True

이렇게 하면된다. 그렇게하면 migrate 할 때 데이터베이스에 null 허용 컬럼으로 생성된다.

 

Question, Answer 모델에 author 속성이 추가되었으므로 질문과 답변 저장시에 author도 함께 저장해야 한다.

projects\mysite\pybo\views.py 파일을 다음과 같이 수정하자. 

답변과 질문의 글쓴이는 현재 로그인한 계정이므로 answer.author = request.user 로 처리했다. request.user 는 현재 로그인한 계정의 User 모델 객체이다. 이제 다시 서버를 시작하고 테스트 하면 잘 된다.

하지만 로그아웃 상태에서 질문 또는 답변을 등록하면 다음과 같은 ValueError가 발생한다.

이 오류는 request.user 가 User 객체가 아닌 AnonymousUser 객체라서 발생한 것이다. 로그인 상태일땐 User 로그아웃일땐 AnonymousUser 이다. 앞에서 author 속성을 정의할 때 User를 이용하도록 했는데 로그아웃시에는 author 에 User 가 아닌 AnonymousUser 가 대입되어 오류가 발생한 것이다.

이를 해결하기 위해선 request.user 를 사용하는 함수에 @login_required 애너테이션을 사용해야 한다. 이 애너테이션이 붙은 함수는 로그인이 필요한 함수를 의미한다.

projects\mysite\pybo\views.py 를 다음과 같이 수정하자.

질문생성과 답변생성함수는 함수내에서 request.user 를 사용하므로 로그인이 필요한 함수이기 때문에 위에 @login_required 어노테이션을 사용했다. 로그아웃상태에서 실행을 하려고 하면 로그인 화면으로 이동한다.

실제로 로그아웃상태에서 질문등록하기를 누르면 로그인 화면으로 이동이 된다. 이 때의 URL 을 확인해보자.

URL을 확인해보면 next 파라미터가 있는데 이는 로그인 성공후 next 파라미터에 있는 URL로 페이지를 이동하겠다는 의미이다. 하지만 실제로 해보면 로그인을 하면 원래 하려던 질문 등록페이지로 안가고 홈페이지로 가진다. 이를 해결하려면 로그인 템플릿에 hidden 타입의 next 항목을 추가해야한다.

projects\mysite\templates\common\login.html 을 다음과 같이 수정하자.

이렇게 하면 로그인 후 next 항목의 URL 로 이동한다.

 

로그아웃시에 질문 작성하기버튼을 눌렀을때의 오류는 해결했지만 로그아웃상태일때 답변을 달려고 할 때 가 있다. 로그아웃 상태에서도 저장하기 버튼을 누르면 로그인 화면으로 자동으로 넘어가지만 문제는 작성해놓은게 사라진다는 점이다. 작성하기 버튼을 눌렀을때 로그인 화면으로 이동하게 하는것보다 아예 로그아웃상태이라면 작성을 못하게 하는게 좋다.

projects\mysite\templates\pybo\question_detail.html 파일을 다음과 같이 수정하자.

의미를 해석하자면 로그인 상태가 아닌 경우 textates 태그에 disabled 속성을 적용하여 입력을 못하게 만들었다. 보면 알겠지만 {% if not user.is_authenticated %} 태그는 현재 사용자가 로그아웃 상태인지를 체크하는 태그이다.

로그아웃상태일때는 답변내용이 사진과 같이 사용하지 못하게 막혀있다. 이 상태에서 답변등록 버튼을 누르면 로그인 화면으로 이동한다. 그리고 로그인을 하면 오류가 발생할것이다. 로그인시 전달된 next 파라미터 때문에 로그인 후에 답변등록 URL 인 /answer/create 가 GET 방식으로 호출되기 때문이다. 하지만 답변등록시 POST 방식이 아닌경우 HttpResponseNotAllowed 오류가 발생하도록 코딩했다 그래서 오류가 발생하는것이다. 이를 해결하기위해

 projects\mysite\pybo\views.py 파일을 다음과 같이 수정하자.

from django.http import HttpResponseNotAllowed 를 삭제하고. else 부분에 form = AnswerForm() 를 추가하자.

이제 로그아웃상태에서 답변등록 버튼을 누른후 로그인을 하면 원래 화면으로 잘 돌아간다.

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

22. 질문 수정 및 삭제  (0) 2022.11.18
21. author 표시  (0) 2022.11.17
19. 회원가입  (0) 2022.11.10
18. 로그인 기능  (0) 2022.11.09
17. 템플릿 필터 및 답변 개수 표시  (0) 2022.11.07

로그인 기능을 구현했으니 이제 회원가입 기능을 구현해보자.

projects\mysite\templates\navbar.html 파일을 다음과 같이 수정하자.

 

로그인/로그아웃 링크 바로 옆에 회원가입 링크를 추가했다. 회원가입은 로그아웃 상태에서만 보일수있게 했다. {% url 'common:signup' %} 태그를 적어넣었으므로 URL매핑규칙을 추가해줘야 한다. projects\mysite\common\urls.py 에 다음과 같이 추가하자.

회원가입 링크를 누르면 views.signup 함수가 실행된다. 이제 이 함수를 만들어 줘야하지만 그 전에 일단 계정생성시 사용할 UserForm을 projects\mysite\common\forms.py 파일을 추가해 새로작성하자.

UserForm 은 djnago.contrib.auth.forms 모듈의 UserCreationForm 클래스를 상속하여 만들었다. 그리고 email 속성을 추가했다. UserForm 을 따로 만들지 않고 UserCreationForm 을 그대로 사용해도 되지만 위처럼 이메일 등의 속성을 추가하기 위해서는 UserCreationForm 클래스를 상속해서 만들어야 한다. password2 는 password1 을 제대로 입력했는지 대조하기 위한 값이다. 

 

이제 아까 만들어야했던 views.signup 함수를 작성하자. projects\mysite\common\views.py 파일에 다음과 같이 작성하자.

signup 함수는 POST 요청인 경우에는 화면에서 입력한 데이터로 사용자를 생성하고 GET 요청인 경우에는 회원가입 화면을 보여준다. form.cleaned_data.get 함수는 폼의 입력값을 개별적으로 얻고 싶은 경우에 사용하는 함수로 여기서는 인증시 사용할 사용자명과 비밀번호를 얻기 위해 사용한다. 사용자를 생성한 후에 자동 로그인 될 수 있도록 authenticated, login 함수가 사용되었다. authenticated 와 login 함수는 django.contrib.auth 모듈의 함수로 사용자 인증과 로그인을 담당한다.

 

이제 회원가입 화면을 구성하는 common/signup.html 템플릿을 작성해야한다.

projects\mysite\templates\common\signup.html 파일을 추가하고 다음과 같이 작성하자.

form 태그 밑에는 오류를 표시하기위해 form_errors.html 템플릿을 include 했다.

 

이제 테스트를 해보자.

비밀번호와 비밀번호 확인이 다를 경우 오류가 발생한다. 일단 id 는 stdiohan 비밀번호는 testpasswd 로 설정했다. 그리고 

http://localhost:8000/admin/ 페이지를 요청해보면

이런 경고 메세지가 나온다. 어드민 페이지는 슈퍼유저로 접속해야한다. 저번에 만든 아이디 admin 비밀번호 1111 로 로그인해 보면

이런 화면이 나온다 여기서 사용자(들) 버튼을 누르면

아까 만든 계정을 확인할 수 있다.

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

21. author 표시  (0) 2022.11.17
20. author 속성  (0) 2022.11.16
18. 로그인 기능  (0) 2022.11.09
17. 템플릿 필터 및 답변 개수 표시  (0) 2022.11.07
16. Paginator  (0) 2022.11.05

로그인 기능을 만들어보자. 로그인, 로그아웃을 도와주는 앱은 django.contrib.auth 이다. 이 앱은 장고 프로젝트 생성시 자동으로 추가된다. projects\mysite\config\settings.py 를 확인해보자.

 

로그인, 로그아웃기능을 구현해야한다. 로그인, 로그아웃기능은 pybo앱에 구현할수도 있지만, 하나의 웹 사이트에는 파이보와 같은 게시판 서비스 이외에도 블로그나 쇼핑몰같은 굵직한 단위의 앱들이 함께 있을 수 있기 때문에 공통으로 사용되는 기능인 로그인기능은 하나의 앱에 종속시키는게 좋다. 그렇기 때문에 common 앱에 구현하는게 좋다.

 

일단 common 앱을 다음 명령어를 입력해 새로 생성하자.

그렇게 하면 

자동으로 디렉토리와 파일들이 자동으로 생성된다.

그리고 config\settings.py 파일에 방금 생성된 common 앱을 등록하면된다.

projects\mysite\config\settings.py 에 다음과 같이 추가하자.

common 앱의 urls.py 파일을 사용하기 위해 projects\mysite\config\urls.py 파일을 다음과 같이 수정하자.

이렇게 하면 http://localhost:8000/common/ 으로 시작하는 URL 은 모두 common/urls.py 파일을 참조한다. 참조하기 위해 파일을 새로 만들자.

파일은 원하는 방법으로 알아서 만들자.

아직 common 앱에 어떤 기능도 구현하지 않았으므로 urlpatterns 는 빈상태로 놔두면 된다.

 

[로그인]

 

먼저 로그인 화면으로 진입할 수 있도록 projects\mysite\templates\navbar.html 파일의 로그인 링크를 다음처럼 수정하자.

navbar.html 파일에서 템플릿 태그로 {% url 'common:login' %} 를 작성했으므로 common/urls.py 파일에 URL 매핑규칙을 추가해야한다.

projects\mysite\common\urls.py 를 다음과 같이 수정하자.

로그인 뷰는 따로 만들 필요없이 위 코드처럼 django.contrib.auth 앱의 LoginView 를 사용하도록 설정했다.

 

여기까지 하고 파이보 페이지에서 로그인 버튼을 눌러보면 오류 페이지가 나타난다.

이 오류는 registration 디렉토리에 login.html 파일이 없다는 것을 의미한다. 앞에서 사용한 LoginView는 registration 이라는 템플릿 디렉토리에서 login.html 파일을 찾는다. 이 파일을 찾지 못해 오류가 발생한것이다. 해결하려면 registration/login.html 템플릿파일을 작성해야한다.

그런데 우리는 로그인을 common 앱에 구현할 것이므로 오류메세지에 표시한 것처럼 registraion 디렉토리에 템플릿 파일을 생성하기보다는 common 디렉토리에 템플릿을 생성하는게 좋다. 이를위해 LoginView 가 common 디렉토리의 템플릿을 참조할 수 있도록 projects\mysite\common\urls.py 파일을 다음과 같이 수정하자.

이렇게 수정하면 registrration 디렉토리가 아니라 common 디렉토리에서 login.html 파일을 참조하게 된다. 그 후 다시 로그인 버튼을 누르면

오류 내용이 수정된것을 볼 수 있다. 오류 내용을 보면 알 수 있듯이 이젠 common/login.html 이 없다는 오류다.

그렇다면 common/login.html 파일을 생성하기 위해 common 템플릿 디렉토리를 다음과 같이 생성하자. 그 후 login.html 파일을 만들자. projects\mysite\templates\common\login.html 에 다음과 같이 작성하자.

사용자ID 와 비밀번호를 입력으로 받아 로그인하는 간단한 템플릿이다. 로그인에 사용되는 사용자 ID를 의미하는 username 과 비밀번호를 의미하는 passwrod 항목이 있다. 이 2가지는 django.contrib.auth 인증시 username 과 password 이 2개의 항목은 반드시 필요하다.

그리고 {% csrf_token %} 바로 밑에 Include 태그로 포함된 form_errors.html 템플릿  파일은 다음과 같이 작성해주자.

projects\mysite\templates\form_errors.html 파일을 새로 만들고 작성하자.

form_errors.html 템플릿은 로그인 실패시 로그인이 왜 실패했는지 알려주는 역할을 한다. 폼 오류에는 다음과 같이 두 가지 종류의 오류가 있다. 필드 오류(field.errors) 와 넌필드오류(form.non_field_errors) 이다.

필드 오류는 사용자가 입력한 필드 값에 대한 오류로 값이 누락되었거나 필드의 형식이 일치하지 않는 경우에 발생하는 오류이다. 넌필드 오류는 필드의 값과는 상관없이 다른 이유로 발생하는 오류이다. form_errors.html 템플릿은 필드 오류와 넌필드 오류 모두를 표시하기 위해 삽입되는 템플릿이다. question_form.html, question_detail.html 템플릿에서 오류를 표시하기위해 추가했었던 HTML 코드를 {% include "form_errors.html" %} 로 대체해도 좋다.

 

이제 로그인 버튼을 눌러 로그인을 해보자.

아무것도 입력하지 않고 로그인 버튼을 누르면 사용자 이름과 비밀번호가 필수 항목이라고 나온다. 현재 로그인이 가능한 사용자는 초반에 만들었던 슈퍼유저 admin 하나밖에없다. 그때 만들었던 슈퍼유저를 사용해 id 에 admin 비밀번호에 1111을 입력해 로그인을 하자.

로그인에 성공하면 오류가 나온다. 오류메세지를 보면 알수있듯이 로그인에 성공해 http://localhost:8000/accounts/profile/ 로 가야하는데 아직 해당 페이지가 구성되어있지 않기 때문이다. 하지만 /accounts/profile/ 은 내가 파이보에 구성한 URL 구조와 맞지 않으므로 로그인 성공시 / 페이지로 이동할 수 있도록 projects\mysite\config\settings.py 를 다음과 같이 수정하자.

마지막 줄에 추가하면 된다.

이렇게 수정해도 다음과 같은 오류가 발생한다.

이번에는 수정해서 로그인에 성공하면 http://localhost:8000/ 페이지로 가야하지만 아직 해당페이지에 대한 URL 매핑 규칙을 작성하지 않았다. 이를 추가하기 위해 projects\mysite\config\urls.py 에 다음과 같이 수정하자.

이제 / 페이지 요청에 대해 path('', views.index, name='index') 가 작동하여 pybo/views.py 파일의 index 함수가 실행된다.

이제 로그인에 성공하면 정상적으로 작동한다. 하지만 로그인을 했음에도 로그인 버튼이 로그아웃버튼으로 바뀌지 않고 여전히 로그인 버튼으로 남아있다. 이를 해결하기위해 proejcts\mysite\templates\navbar.html 을 다음과 같이 수정하자.

{% if user.is_authenticated %} 은 현재 사용자가 로그인 되어있는지를 판별한다. 로그인되어있는지를 판별하고 로그인 되어있으면 '로그아웃' 링크를 표시하고 아니면 '로그인' 링크를 표시한다. 로그인 상태에서는 로그아웃 링크에 사용자명을 의미하는 {{ user.username }} 도 추가로 표시한다.

로그아웃 링크가 추가되었으므로 {% url 'common:logout' %}에 대응하는 URL 매핑을 추가해야한다. projects\mysite\common\urls.py 파일을 다음과 같이 수정하자.

이제 잘 되어있는지 확인해보자.

잘 적용되어있다. 로그아웃 시 이동할 위치도 projects\mysite\config\settings.py 파일에 추가하자.

그리고 이제 로그아웃을 다시 해보면 / 페이지로 이동한다.

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

20. author 속성  (0) 2022.11.16
19. 회원가입  (0) 2022.11.10
17. 템플릿 필터 및 답변 개수 표시  (0) 2022.11.07
16. Paginator  (0) 2022.11.05
15. 네비게이션 바  (0) 2022.11.04

템플릿 필터란 템플릿 태그에서 | 문자 뒤에 사용하는 필터를 말한다. 저번에 사용한 |add:-5, |add:5, |default_if_none 이러한 것들이다. 템플릿 필터를 수정해 커스텀 템플릿 필터를 만들어보자.

 

먼저 저번에 만든 페이지의 문제점을 살펴보자.

게시물의 번호를 보면 1부터 시작하는데 어느 페이지를 가도 게시물 번호가 1번부터 시작한다. 사진의 페이지는 10페이지인데 게시물번호는 1번부터 시작하는것을 볼 수 있다. 

 

만약 게시물 전체 건수가 12개 라면 첫번째 페이지 번호가 12~3 까지 역순으로 보여지고 두번째 페이지에서 2~1까지 보여져야한다. 최근에 작성한 게시물이 맨 앞으로 와야하기 때문에 역순으로 정렬되어야한다. 그렇게 하러면 

번호 = 전체건수 - 시작인덱스 - 현재인덱스 + 1

이러한 공식이 적용되어야 한다. 시작 인덱스는 페이지당 시작되는 게시물의 시작 번호이다. 예를 들어 페이지당 게시물을 10건씩 보여준다면 1페이지의 시작 인덱스는 1, 2페이지의 시작 인덱스는 11이 된다. 현재 인덱스는 페이지에 보여지는 게시물 개수만큼 0부터 1씩 증가되는 번호이다. 따라서 전체 게시물 개수가 12개이고 페이지당 10건씩 게시물을 보여 준다면 공식에 의해 1페이지의 번호는 12 - 1 (0~9 반복) + 1 이 되어 12~3까지 표시되고 2페이지는 12 - 11 - (0~1 반복) + 1 이 되어 2~1 이 표시된다.

템플릿에서 이 공식을 적용하려면 빼기 기능이 필요하다. 앞에서 사용한 더하기 필터 |add:5 처럼 빼기 필터 |sub:3 를 쓰면 될것같지만 빼기필터는 존재하지 않는다. 그래서 빼기 필터를 직접 만들어야 한다.

|add:-3 같이 숫자를 직접입력하여 사용할 수 는 있지만, 지금은 단순한 숫자가 아닌 변수를 적용할 것이기 때문에 저 방법을 사용할 수 없다. add 필터는 인수로 숫자만 가능하기 때문이다.

 

[템플릿 필터 작성]

 

먼저 템플릿 필터 파일을 저장한 templatetags 폴더를 projects\mysite\pybo\templatetags 에 만들자.

디렉토리와 파일을 만드는것은 터미널에서 명령어를 사용하거나 파이참에서 마우스 오른쪽클릭으로 만들거나 편한방식으로 하자.

 

위처럼 sub 함수에 @register.filter 에너테이션을 적용하면 템플릿에서 해당 함수를 필터로 사용할 수 있게 된다. sub 함수는 기존 값 value 에서 입력으로 받은 값 arg 를 빼서 리턴하는 함수이다. 에너테이션은 @ 기호를 붙여 사용하는데 주석이라는 의미를 가지는데 컴파일러에게 코드 작성 문법 에러를 체크하도록 정보를 제공하고, 소프트웨어 개발툴이 빌드나 배치시 코드를 자동으로 생성할 수 있도록 정보를 제공하고, 실행시 특정 기능을 실행하도록 정보를 제공한다.

 

작성한 템플릿 필터를 사용하기 위해서는 {% load pybo_filter %} 로 sub 필터를 저장한 파일을 먼저 로드해야한다.

템플릿 상단에 extends 문이 있을경우 load 문은 extends 문 다음에 위치해야한다.

projects\mysite\templates\pybo\question_list.html 파일을 다음과 같이 수정하자.

{% load pybo_filter %} 를 상단에 적어주고 밑에 '번호 = 전체건수-시작인덱스-현재인덱스+1' 에 해당하는 것을 작성했는데 어렵게 생겼지만 단어 하나하나 살펴본다면 간단하다.

question_list.paginator.count 는 전체건수를 의미하고,

question_list.start_index 는 시작인덱스를 의미하고

forloop.counter0 는 현재인덱스를 의미한다.

이것들을 템플릿필터 sub 와 add 를 이용해 작성했다.

결론적으로 보면,

전체건수 - 시작인덱스 - 현재인덱스 + 1 은

question_list.paginator.count|sub:question_list.start_index|sub:forloop.counter0|add:1 과 같은 의미가 된다.

 

이제 서버를 재시작한 후 잘 적용되었는지 확인해보면

잘 적용된것을 확인할 수 있다.

 

[답변 개수 표시]

 

질문 목록에 해당 질문에 달린 답변의 개수를 표시하는 기능을 추가해보자.

projects\mysite\templates\pybo\question_list.html 을 다음과 같이 수정하자.

{% if question.answer_set.count > 0 %} 로 답변이 있는 경우를 검사하고, {{ question.answer_set.count }} 로 답변 개수를 표시했다. 잘 적용되었는지 서버를 시작해서 확인해보자.

 

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

19. 회원가입  (0) 2022.11.10
18. 로그인 기능  (0) 2022.11.09
16. Paginator  (0) 2022.11.05
15. 네비게이션 바  (0) 2022.11.04
14. 폼  (1) 2022.11.03

질문 목록 페이지는 아직 페이징 처리가 안되어 있기 때문에 만약 300개의 게시물을 작성하면 한 페이지에 300개의 게시물이 모두 한꺼번에 표시된다. 이 문제를 해결해보자.

일단 테스트를 할 수 있도록 테스트데이터를 만들자. 다음 명령어로  장고 쉘을 실행하자.

여기서 질문 데이터를 생성하기 위한 모듈을 import 한후 for 문을 이용해 300개의 테스트 데이터를 생성하자.

그리고 장고셸을 종료하고 테스트를 확인해보자.

 

300개 이상의 질문들이 한 페이지에 전부 보여진다. 반드시 페이징이 필요한 이유이다.

 

[Paginator]

 

장고에서 페이징을 위한 클래스는 Paginator 이다. 이 클래스를 이용해 index 함수에 페이징 기능을 적용하자.

projects\mysite\pybo\views.py 파일을 다음과 같이 수정하자.

page = request.GET.get('page', '1') 은 http://localhost:8000/pybo/?page=1 처럼 GET 방식으로 호출된 URL 에서 page 값을 가져올 때 사용한다. 만약 http://localhost:8000/pybo/ 처럼 page 값 없이 호출된 경우에는 디폴트로 1이라는 값을 설정한다.

 

paginator = Pagiantor(question_list, 10) 에서 첫 번째 파라미터 question_list 는 게시물 전체를 의미하는 데이터이고 두번째 파라미터 10은 페이지당 보여줄 게시물의 수이다.

 

page_obj = paginator.get_page(page) 의 의미는 paginator 를 이용해 요청된 페이지에 해당되는 페이지 객체(page_obj)를 생성했다. 이렇게 하면 장고 내부적으로는 데이터 전체를 조회하지 않고 해당 페이지의 데이터만 조회하도록 쿼리가 변경된다. 쿼리는 웹 서버에 특정한 정보를 보여달라는 웹 클라이언트 요청에 의한 처리이다.

 

페이징 객체 page_obj 에는 다양한 속성들이 있다.

paginator.count 전체 게시물 개수
paginator.per_page 페이지당 보여줄 게시물 개수
paginator.page_range 페이지 범위
number 현재 페이지 번호
previous_page_number 이전 페이지 번호
next_page_number 다음 페이지 번호
has_previous 이전 페이지 유무
has_next 다음 페이지 유무
start_index 현재 페이지 시작 인덱스(1부터 시작)
end_index 현재 페이지의 끝 인덱스(1부터 시작)

이 속성들은 템플리에서 페이징을 처리할 때 사용된다.

 

index 하수에서 pybo/question_list.html 에 전달한 데이터(context)는 다음과 같다.

따라서 질문 목록 템플릿에 전달된 페이징 객체는 question_list 이다.

 

이제 템플릿을 페이징에 적용하기위해 

projects\mysite\templates\pybo\question_list.html 파일을 다음과 같이 수정하자. </table> 태그 바로 밑에 추가하자.

양은 많지만 하나하나 살펴보면 어렵지 않다. 문단별로 살펴보면,

 

이전 페이지가 있는 경우에는 '이전' 링크가 활성화 되고, 이전 페이지가 없는 경우에는 '이전' 링크가 비활성화 된다.

페이지 리스트를 루프 돌면서 해당 페이지로 이동할 수 있는 링크를 생성하였다. 이 때 현재 페이지와 같을 경우에는 active 클래스를 적용해 강조표시를 했다.

다음 페이지가 있는 경우에는 '다음' 링크가 활성화 되고, 다음 페이지가 없는 경우에는 '다음' 링크가 비활성화 된다.

 

위 템플릿에 사용된 주요 페이징 기능을 살펴보자면

기능 코드
이전 페이지가 있는지 체크 {% if question_list.has_previous %}
이전 페이지 번호 {{ question_list.previous_page_number }}
다음 페이지가 있는지 체크 {% if question_list.has_next %}
다음 페이지 번호 {{ question_list.next_page_number }}
페이지 리스트 루프 {% for page_number in question_list.paginator.page_range %}
현제 페이지와 같은지 체크 {% if page_number == question_list.number %}

그리고 페이지 리스트를 보기 좋게 표시하기 위해 부트스트랩의 pagination 컴포넌트를 이용했다. 템플릿에 사용한 pagination, page-item, page-link 등이 부트스트랩의 pagiantion 컴포넌트의 클래스이다. 컴포넌트란 여러 개의 프로그램 하수들을 모아 하나의 특정한 기능을 수행할 수 있도록 구성한 작은 단위를 말한다. 컴포넌트를 이용하면 소프트웨어 개발을 마치 레고블록을 쌓듯이 조립식으로 쉽게 할 수 있다. 모듈 이라고도 한다.

 

이제 잘 적용되었는지 확인해보자.

페이징처리는 잘 되었지만 한 페이지에 모든 페이지가 표시되어서 오히려 보기 안좋아 졌다. 해결하기 위해 

projects\mysite\templates\pybo\question_list.html 을 다음과 같이 수정하자.

위 코드에서 |add:-5, |add:5 는 템플릿 필터로 각각 -5만큼 빼고 5만큼 더하라는 의미이다.

위 코드는 페이지 르스트가 현재 페이지 기준으로 좌우 5개씩만 보이도록 만든다. 현재 페이지를 의미하는 question_list.number 보다 5만큼 크거나 작은 값만 표시되는것이다.

확인해보면 이제 페이지 번호들이 깔끔하게 나오는것을 확인할 수 있다.

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

18. 로그인 기능  (0) 2022.11.09
17. 템플릿 필터 및 답변 개수 표시  (0) 2022.11.07
15. 네비게이션 바  (0) 2022.11.04
14. 폼  (1) 2022.11.03
13. 표준 HTML & 템플릿 상속  (0) 2022.10.31

네비게이션 바 는 모든화면 위쪽에 고정되어 있는 부트스트랩 컴포넌트이다. 이를 이용해 메인페이지(Home)으로 돌아갈수있는 장치를 만들 수 있다. 네비게이션바는 모든 페이지에서 공통적으로 보여져야 하므로 projects\mysite\templates\base.html 을 다음과 같이 수정하자.

pybo:index 페이지로 이동해 주는 Pybo 로고를 가장 왼쪽에 배치하고 그 오른쪽에 로그인 링크를 추가했다. 로그인 기능은 추후에 설정한다.

좌상단에 Pybo 아이콘과 로그인 아이콘이 생겼다. 아무 페이지에서 Pybo 를 누르면 메인 페이지로 돌아간다. 로그인 버튼은 아직 설정을 안해서 아무런 일도 발생하지 않는다.

웹 브라우저의 크기를 작게 조절하면 로그인버튼이 사라지고 우상단에 메뉴버튼이 생긴다. 부트스트랩은 크기가 작아지면 네이게이션바에 있는 링크들은 작은 메뉴버튼으로 들어간다. 부트스트랩의 반응형 웹 기능이다. 메뉴버튼안에는 로그인 버튼이 들어있지만 아직은 설정을 다 끝내지 않아서 아무런 반응이 없다. 부트스트랩의 자바스크립트 파일(bootstrap.min.js)이 base.html 파일에 포함되지 않았다.

저번에 다운받은 파일 bootstrap-5.1.3-dist 파일에 있는bootstrap.min.js 파일을 옮겨주자.

bootstrap-5.1.3-dist\js\bootstrap.min.js 파일을 projects\mysite\static\bootstrap.min.js 로 옮겨주자.

이렇게 복사해주자. 그리고 projects\mysite\templates\base.html 파일을 열어 bootstrap.min.js 파일을 사용할 수 있도록 다음과 같이 수정하자.

이렇게 수정하면 메뉴 버튼  클릭시 숨겨진 링크가 나온다.

[include 태그]

 

직접 눌러서 확인해보면 알수있듯이 우상단의 버튼을 클릭하면 숨겨진 메뉴였던 로그인 버튼이 나온다.

 

장고에는 템플릿의 특정 위치에 다른 템플릿을 삽입할 수 있는 Include 태그가 있다. include 태그는 보통 템플릿에서 특정 영역이 반복적으로 사용될 경우 중복을 없애기 위해 사용된다. include 를 이용해 네비게이션바를 base.html 템플릿에 포함시키자.

일단 base.html 에 포함시킬 navbar.html 을 만들자.

projects\mysite\templates\navbar.html 위치에 파일을 만들고 다음과 같이 작성하자.

그리고 projects\mysite\templates\base.html 파일을 열어보자.

navbar.html 파일을 작성하면서 눈치 챘겠지만  base.html 의 빨간네모친 부분과 똑같다. 똑같은 저 부분을 다 지우고 Include 태그를 이용해 navbar.html 을 base.html 로 포함시키는 작업을 하려는것이다. 저 부분을 다 지우고 다음과 같이 수정하자.

이렇게 한다면 navbar.html 템플릿을 include 로 포함시킨것이다.

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

17. 템플릿 필터 및 답변 개수 표시  (0) 2022.11.07
16. Paginator  (0) 2022.11.05
14. 폼  (1) 2022.11.03
13. 표준 HTML & 템플릿 상속  (0) 2022.10.31
12. 부트스트랩  (0) 2022.10.27

[질문등록]

 

질문 등록을 하기위해 '질문 등록하기' 버튼을 만들어야 한다. question_list.html 파일을 수정해 '질문 등록하기' 버튼을 생성하자.

<a href="...">과 같은 링크이지만 부트스트랩의 btn btn-primary 클래스를 적용하면 버튼으로 보인다. 버튼을 클릭하면 pybo:question_create 별칭에 해당되는 URL 이 호출된다.

이제 pybo:question_create 별칭에 해당하는 URL 매핑규칙을 추가해야한다.

projects\mysite\pybo\urls.py 파일을 수정하자.

views.question_create 함수를 호출하도록 매핑했다.

 

[Form]

 

이제 views.question_create 함수를 작성해야 한다. 그 전에 Form 에 대해서 먼저 알아보면, 폼은 페이지 요청시 전달되는 파라미터들을 쉽게 관리하기 위해 사용하는 클래스이다. 폼은 필수 파라미터의 값이 누락되지 않았는지, 파라미터의 형식은 적절한지 등을 검증할 목적으로 사용한다. 이 외에 HTML을 자동으로 생성하거나 폼에 연결된 모델을 이용하여 데이터를 저장하는 기능도 있다.

새롭게 projects\mysite\pybo\forms.py 파일을 새로 만들어 작성하자.

QuestionForm 은 모델 폼(forms.ModelForm)을 상속했다. 장고의 폼은 일반 폼(forms.Form)과 모델 폼(forms.ModelForm)이 있는데 모델 폼은 모델과 연결된 폼으로 폼을 저장하면 연결된 모델의 데이터를 저장할 수 있는 폼이다. 모델 폼은 이너 클래스인 Meta 클래스가 반드시 필요하다. Meta 클래스에는 사용할 모델과 모델의 속성을 적어야 한다.

즉, QuestionForm 은 Quiestion 모델과 연결된 폼이고 속성으로 Question 모델의 subjects, contnet 를 사용한다고 정의한 것이다.

 

이제 다시 아까 필요했던 함수 views.question_create 함수를 작성해보자.

projects\mysite\pybo\views.py 파일을 수정하자.

question_create 함수는 위에서 작성한 QuestionForm 을 사용했다. render 함수에 전달한 {'form':form} 은 템플릿에서 질문 등록시 사용할 폼 엘리먼트를 생성할 때 쓰인다.

 

이제 템플릿을 작성해야한다. 방금 작성한 pybo/question_form.html 파일을 만들어 줘야한다.

projects\mysite\template\pybo\question_form.html 파일을 만들고 다음과 같이 작성하자.

템플릿에서 사용한 {{ form.as_p }} 의 폼은 question_create  함수에서 전달한 QuestionForm 의 객체이다. {{ form.as_p }} 는 폼에 정의한 subject, content 속성에 해당하는 HTML 코드를 자동으로 생성한다.

보통 form 태그에는 항상 action 속성을 지정하여 submit 실행시 action 에 정의된 URL로 폼을 전송해야한다. 하지먼 방금 작성한  폼 태그 <form method="post"> 에서는 특별하게 action 속성을 지정하지 않았다. 폼 태그에 액션 속성을 지정하지 않으면 현재 페이지의 URL 이 디폴트action 으로 설정 된다.

<form method="post" action="{% url 'pybo:question_create' %}"> 처럼 action 속성을 명확하게 지정해도 된다.

하지만 이렇게 작성하면 question_form.html 템플릿은 "질문 등록" 에서만 사용 가능하다. 이후에 사용할 '질문 수정' 템플릿에서는 사용할 수가 없다. 그 이유는 질문 수정일 경우에는 action 값을 다르게 해야하기 때문이다. 동일한 템플릿을 여러기능에서 함께 사용할 경우에는 이와같이 폼의 액션 속성을 비워드는 경우도 종종 있다.

 

이제 수정한 페이지를 다시 요청해보면

질문 등록하기 버튼이 추가 된 것을 확인할 수 있다. 질문 등록하기 버튼을 누르면

질문을 등록하는 페이지가 나온다. 하지만 subject 와 content 에 아무거나 입력하고 저장하기 버튼을 눌러도 아무 반응이 없다. 우리는 아직 question_create 함수에 데이터를 저장하는 코드를 작성하지 않았기 때문이다. 

 

question_create 함수 수정을 위해

projects\mysite\pybo\views.py 파일을 수정하자.

동일한 URL 요청을 POST, GET 요청 방식에 따라 다르게 처리했다. 질문 목록 화면에서 '질문 등록하기' 버튼을 클릭했을 때는 /pybo/question/create/ 페이지가 GET 방식으로 요청되어 question_create 함수가 실행된다. 그 이유는 <a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a> 와 같은 링크를 통해 페이지를 요청할 경우에는 무조건 GET 방식이 사용되기 때문이다. 따라서 이 경우에는 request.method 값이 GET이 되어 if ... else ... 구문에서 else 구문을 타게 되어 질문을 등록하는 화면을 렌더링한다.

그리고 질문 등록 화면에서 subject, content 항목에 값을 기입하고 '저장하기' 버튼을 누르면 이번에는 /pybo/question/create/ 페이지를 POST 방식으로 요청한다. form 태그에 action 속성이 저장되지 않으면 현재 페이지가 디폴트 action 으로 설정되기 때문이다.

따라서 '저장하기' 버튼을 클릭하면 question_create 함수가 싫애되고 request.method 값은 POST가 되어 if 문이 실행된다.

 

if문을 살펴보자면,

 

GET 방식에서는 form = QuestionForm() 처럼 QuestionForm을 인수없이 생성했지만 POST 방식에서는 form = QuestionForm(request.POST) 처럼 request.POST 를 인수로 생성했다. request.POST 를 인수로 QuestionForm 을 생성할 경우에는 request.POST 에 담긴 subject, content 값이 QuestionForm 의 subject, content 속성에 자동으로 저장되어 객체가 생성된다.

 

폼이 유효하다면 if form.is_valid(): 이후의 문장이 수행되어 질문 데이터가 생성된다. question = form.save(commit=False) 는 form 에 저장된 데이터로 Question 데이터를 저장하기 위한 코드이다. QeustionForm 이 Question 모델과 연결된 모델 폼이기 때문에 이와같이 사용할 수 있다. commit=False 는 임시저장을 의미한다. 즉, 실제 데이터는 아직 데이터베이스에 저장되지 않은 상태이다. 여기서 form.save(commit=False) 대신 form.save() 를 수행하면 Question 모델의 create_date 값이 없다는 오류가 발생한다. QuestionForm 에 subject, content 속성만 정의되어 있고 create_date 속성이 없기 때문이다. 그렇기 때문에 임시 저장을하여 question 객체를 리턴받고 create_date 값을 성정한 후 question.save() 로 실제 데이터를 저장하는 것이다.

 

이제 작성한 것들이 잘 작동되는지 확인해보자.

질문 등록하기 버튼을 누르면 GET 요청방식(if ... else ... 문에서 else 문)을 따라 질문등록 페이지가 렌더링 되고, subject, content 를 기입하고 저장하기 버튼을 누르면 POST 방식으로 요청한다.

저장하기 버튼을 누르면 POST 방식으로 요청해(if 문을 수행한다) 제목과 내용을 저장하고 작성일시를 설정하고 저장된다.

 

[폼 위젯]

 

이제 화면을 꾸며보자. 부트스트랩을 적용해 꾸미면 되지만 {{ form.as_p }} 태그는 HTML 코드를 자동으로 생성하기 때문에 부트스트랩을 적용할 수 없다. QuestionForm 을 수정하면 어느정도 해결이 가능하다. projects\mysite\pybo\forms.py 를 수정하자.

이와 같이 widgets 속성을 지정하면 subject, content 입력 필드에 form-control 과 같은 부트스트랩 클래스를 추가할 수 있다. 추가환하면을 확인해보면 이렇다.

projects\mysite\pybo\forms.py 의 label 속성을 지정해주면 subject, content 를 한글로 표시할 수 있다.

이렇게 수정후 확인해보면 

한글로 수정되어 있다.

 

[수동으로 폼 작성]

 

{{ form.as_p }} 을 사용하면 빠르게 템플릿을 만들 수 있지만 HTML 코드가 자동으로 생성되므로 디자인측면에서 많은 제한이 생긴다. 예를 들어 엘리먼트 내에 특정 태그를 추가하거나 필요한 클르스를 추가하는 작업에 제한이 생긴다. 또 디자인 영역과 서버 프로그램 영역이 혼재되어 웹 디자이너와 개발자의 역할을 분리하기도 모호해질수있다.

이번엔 폼을 이용해 자동으로 HTML 코드를 생성하지 않고 직접 HTML 코드를 작성해보자. 일단 수작업시 필요없는 widget 속성을 제거하자.

projects\mysite\pybo\forms.py 파일을열어 수정하자

widget= { ... } 부분을 삭제했다. 그후 projects\mysite\templates\pybo\question_form.html 파일을 다음과 같이 수정하자.

{{ form.as_p }}로 자동 생성되는 HTML 대신 제목과 내용에 해당되는 HTML코드를 직접 작성했다. 그리고 question_create 함수에서 form.is_valid() 가 실패할 경우에 발생하는 오류의 내용을 표시하기 위해 요류를 표시하는 영역을 추가했다.

subject 항목의 value 에는 {{ form.subject|default_if-none:'' }} 처럼 값을 대입해 주었는데 이것은 오류가 발생했을 경우 기존에 입력했던값을 유지하기 위함이다. |default_if_none:'' 의 의미는 폼 데이터 ( form.subject.value )에 값이 없을 경우 None 이라는 문자열이 표시되는데 None 대신 공백으로 표시하라는 의미의 템플릿 필터이다.

장고의 템플릿 필터는 |default_if_none:'' 처럼 | 기호와 함께 사용된다.

여기까지 했으면 테스트를해보자. '질문등록'화면에서 제목에 TEST 라고 입력하고 '내용'은 비워둔채 '저장하기'를 누르면

'내용'에 아무런 값도 입력하지 않아내용을 입력하라는 오류메세지를 볼수있다. 그리고 '제목'에 입력했던 TEST 는 사라지지 않고 유지되는것도 확인할 수 있다.

 

[답변 등록]

 

이번엔 답변등록도 폼에 적용해보자. projects\mysite\pybo\forms.py 를 다음과 같이 수정하자.

 

그리고 answer_create 함수도 수정해야한다. projects\mysite\pybo\views.py 

question_create 와 같은 방법으로 AnswerForm 을 이용하도록 변경했다. 답변등록때는 POST 방식만 사용되기 때문에 GET 방식으로 요청할 경우에는 HttpResponseNotAllowed 오류가 발생하도록 수정했다.

마지막으로 projects\mysite\templates\pybo\question_detail.html 을 다음과 같이 수정해 오류를 표시하기 위한 영역을 추가하자.

이제 테스트를 해보자.

답변 내용을 아무것도 작성하지 않고 답변등록 버튼을 누르면 답변내용은 필수항목이라는 오류메세지가 표시된다.

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

16. Paginator  (0) 2022.11.05
15. 네비게이션 바  (0) 2022.11.04
13. 표준 HTML & 템플릿 상속  (0) 2022.10.31
12. 부트스트랩  (0) 2022.10.27
11. 스태틱 디렉토리  (0) 2022.10.25

[표준 HTML]

 

지금까지 작성한 질문 목록, 질문 상세 템플릿은 표준 HTML 구조가 아니다. 어떤 웹 브라우저를 사용하더라도 웹 페이지가 동일하게 보이고 정상적으로 작동하게 하려면 반드시 웹 표준을 지키는 HTML 문서를 작성해야한다.

 

표준 HTML 문서의 구조는 html, head, body 엘리먼트가 있어야하고, CSS 파일 링크는 head 엘리먼트 안에 있어야 한다. meta, title 엘리먼트 또한 head 엘리먼트 등이 포함되어야 한다. 

 

[템플릿 상속]

 

앞에서 작성한 질문 목록, 질문 상세 템플릿을 표준 HTML 구조가 되도록 수정하자. 템플릿 파일들을 모두 표준 HTML 구조로 변경한다면 body 엘리먼트의 바깥 부분은 모두 같은 내용으로 중복될 것이다. 그러면 CSS 파일 이름이 변경되거나 새로운 CSS 파일이 추가될 때 마다 모든 템플릿 파일을 하나하나 수정해야 한다.

 

장고는 이러한 불편함을 해소하기 위해 템플릿 상속기능을 제공한다. 템플릿 상속은 기본 틀이 되는 템플릿을 먼저 작성하고 다른 템플릿에서 그 템플릿을 상속해 사용하는 방법이다.

 

먼저 projects/mysite/templates/base.html 을 만들고 작성하자.

파이참 커뮤니티 버전은 <!doctype html>이 오류표시가 나오는데 기능상의 문제는 없다

base.html 템플릿은 모든 템플릿이 상속해야 하는 템플릿으로 표준 HTML 문서의 기본 틀이 된다. body 엘리먼트 안의 {% block content %} 와 {% endblock %} 템플릿 태그는 base.html을 상속한 템플릿에서 개별적으로 구현해야 하는 영역이다.

 

이제 projects/mysite/templates/pybo/question_list.html 템플릿은 다음과 같이 수정하자.

<div> 엘리먼트 안의 내용은 변경이없다. 파이참 기능으로 잠시 축소 시켜놓은 상태이다.

원래 적혀있던

{% load static %}

<link rel="stylesheet" type="text/css" href="{% static bbootstrap.min.css' %}">

이 두줄은 base.html 에 이미 있는 내용이라 삭제했다.

base.html 템플릿을 상속하기 위해 extends(상속) 템플릿 문법을 사용했다. {% block content %} 와 {% endblock %} 사이에 question_list.html 에서만 사용하는 내용을 작성했다. 이렇게 하면 question_list.html 은 base.html 템플릿을 상속받아 표준 HTML 문서로 바뀐다.

 

question_detail.html 파일도 손봐야한다.

projects/mysite/templates/pybo/question_deatail.html 파일을 수정하자.

<div> 엘리먼트의 변경점은 없다.

이전과 마찬가지로 원래있던 맨위에 2줄은 base.html 에 이미 있는 내용이라 삭제했고, {% extends 'base.html' %} 로 base.html 을 상속한 후 기존 내용 위 아래로 {% block content %} 와 {% endblock %} 를 작성했다.

 

이제 http://localhost:8000/pybo 페이지를 열어보면 화면에 보여지는 것은 동일하지만 표준 HTML 로 작성되어있다. 이 사실을 확인하려면 맥os 사파리 기준으로 

 

[개발자용 - 페이지 소스 보기] 버튼을 클릭하면 된다. 개발자용 버튼을 생성하는 법은 인터넷에 검색하면 나온다.

버튼을 누르면 이렇게 표준 HTML 구조로 작성된 화면을 확인할 수 있다.

 

[style.css]

 

부트스트랩을 적용했기 때문에 style.css 의 내용은 필교아 없어졌으므로 기존에 있던 내용을 모두 삭제하자. 이 파일은 이후에 부트스트랩으로 표현할 수 없는 스타일을 위해 사용할 이기 때문에 파일 자차를 삭제하지는 말고 내용만 삭제하자.

 

projects\mysite\static\style.css 파일의 내용을 모두 삭제하자.

내용을 모두 삭제한 모습이다

 

 

 

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

15. 네비게이션 바  (0) 2022.11.04
14. 폼  (1) 2022.11.03
12. 부트스트랩  (0) 2022.10.27
11. 스태틱 디렉토리  (0) 2022.10.25
10. 답변 등록하기  (0) 2022.10.22

+ Recent posts