간단한 접속 페이지 만들기 [응용]
예를 들어 특정 게시글의 내용을 볼때는 /view/1 과 같이 번호를 url에 입력하여 접속하기도 하니 말이다.
1. url 경로에 키워드 인자를 받도록 수정
•
수정 전
from django.contrib import admin
from django.urls import path
# /bbs/views.py 의 home 핸들러 참조
from bbs.views import home
urlpatterns = [
# /home에 접속하면 views.py에 정의된 home 핸들러가 실행되도록 한다.
path('home/', home),
path('admin/', admin.site.urls),
]
Python
복사
path('home/', home) 에 키워드 인자를 받을 수 있도록 아래와 같이 코드를 수정해준다.
•
수정 후
from django.contrib import admin
from django.urls import path
# /bbs/views.py 의 home 핸들러 참조
from bbs.views import home
urlpatterns = [
# /home에 접속하면 views.py에 정의된 home 핸들러가 실행되도록 한다.
# 또한 <str> 이라는 키워드 인자를 입력받아 핸들러에 전달해줄 수 있다.
path('home/<str>', home),
path('admin/', admin.site.urls),
]
Python
복사
2. 핸들러에서 키워드 인자를 받도록 수정
•
수정 전
from django.shortcuts import render
from django.http import HttpResponse
# home 핸들러를 선언합니다.
def home(request):
# HttpResponse 함수를 사용하여 메세지를 반환합니다.
return HttpResponse('THIS IS HOMEPAGE!')
Python
복사
view.py 에 작성된 home 핸들러의 인자에서 urls.py 에서 추가한 str 키워드를 인자로 받도록 수정한다.
또한 str 값이 반환되는 메세지에 보이도록 적용해준다.
•
수정 후
from django.shortcuts import render
from django.http import HttpResponse
# home 핸들러를 선언합니다.
def home(request, str):
# HttpResponse 함수를 사용하여 메세지를 반환합니다.
# urls.py에서 적용한 str 키워드 값이 home 핸들러의 인자로 사용되며, 해당 값을 반환메세지에 적용
return HttpResponse('THIS IS {}!'.format(str))
Python
복사
3. 서버 실행 후 변경된 내용 확인해보기
이번에는 str 키워드 확인을 위해 /home/MYSTR 이라는 경로로 접속해보았다.
정상적으로 코드가 적용됬다면 아래와 같이 입력한 키워드를 확인할 수 있다.
URL에 인자값을 받고 사용하는 간편한 방법
urls.py 에서 아래와 같이 값의 형식만 지정해줘도 되지만, 추가적으로 입력한 값의 이름도 설정할 수 있다.
아래 예시와 같이 <형식:param값 이름> 으로 데이터 형식과 이름을 같이 설정해줄 수 있다.
...
urlpatterns = [
path('home/<str:name>', TestMethod.as_view())
]
...
Python
복사
위의 방법으로 입력받은 param 값은 view에서 아래와 같이 간편하게 인자 값으로 사용 가능하다.
if is not <param이름> 형태의 조건문을 같이 사용한다면 예외 처리도 가능하다.
RestAPI 형태로 작성된 예시 내용이다.
...
# Create your views here.
class TestMethod(APIView):
def get(self, request, name):
msg = {'status': "success", "msg": name}
return Response(msg, status=status.HTTP_200_OK)
...
Python
복사
/home/teststr 경로에 접속하면 아래와 같은 값을 반환 받는다.
{
"status": "success",
"msg": "teststr"
}
Python
복사
게시판 사이트 만들기 [ MODEL 설정 ]
이제는 실제 데이터를 다루는 예시 사이트, 게시판 사이트를 만들어본다.
게시판 사이트는 글에 대한 데이터를 다루기 때문에, Model을 만들어 적용해볼 수 있다.
게시판 기능 목록
•
게시글 목록 확인
•
게시글 상세 내용 확인
•
게시글 작성
•
게시글 수정
•
게시글 삭제
1. 모델(Model) 생성
게시글에는 제목(title), 게시글 내용(content), 작성자(author), 작성 일시(created) 등 여러 데이터를 담을 것이다.
/bbs/model.py 경로에 모델을 정의한다.
처음 model.py 를 확인하면 아래와 같은 내용이며, 모델 정의 시 사용할 models 모듈을 확인할 수 있다.
•
수정 전
from django.db import models
# Create your models here.
Python
복사
Django에서 모델 정의 시, 클래스 형태로 정의하며 models.Model 클래스를 상속하여 사용한다.
모델의 이름은 Board 로 정하였으며, 제목(title), 게시글 내용(content), 작성자(author), 작성 일시(created) 4가지에 대한 데이터를 정의한다.
•
수정 후
from django.db import models
class Board(models.Model):
title = models.CharField('제목', max_length=126, null=False)
content = models.TextField('내용', null=False)
author = models.CharField('작성자', max_length=16, null=False)
created = models.DateTimeField('작성일', auto_now_add=True)
Python
복사
model 모듈의 여러 필드를 이용하여, 각 데이터에 적절한 필드로 정의해준다.
1.
CharField : SQL에서 varchar 자료형으로 변환되며 글자수 제한있는 문자열 데이터를 저장하는 필드
•
max_length 인자를 이용하여 최대 글자수를 정의한다.
2.
TextField : SQL에서 text 자료형으로 변환되며 길이 수 제한없는 문자열 데이터를 저장하는 필드
3.
DateTimeField : SQL에서 datetime 자료형으로 변환되며 날짜와 시간이 utc 시간으로 저장하는 필드
•
auto_now_add 인자를 사용하여 값 미 설정 시, 자동으로 현재 시간을 등록하도록 설정
해당 타임존은 settings.py 에서 변경이 가능하다.
2. 앱 등록과 DB 설정 변경
/toyproject/settings.py 에서 사용할 DB의 설정과 우리가 생성한 게시판 앱(bbs)를 등록해줘야 한다.
1.
bbs 앱 등록
INSTALLED_APPS 에 게시판 앱 이름(bbs)을 추가해준다.
... (생략)
# Application definition
INSTALLED_APPS = [
'bbs',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
... (생략)
Python
복사
2.
DB 설정
만약 sqllite3 를 사용하며, 루트 디렉터리(BASE_DIR) 위치에 존재하는 db.sqlite3 파일(자동 생성)에 데이터를 저장한다면 굳이 건들 필요가 없다.
... (생략)
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
... (생략)
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
... (생략)
Python
복사
공식 문서에 Django Settings DATABASES 에 관한 내용이 있다.
해당 내용에는 sqlite3가 아닌 다른 DB에 연결하는 법을 확인할 수 있다.
→ Django Settings DATABASES in Django Docs
... (생략)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydatabase',
'USER': 'mydatabaseuser',
'PASSWORD': 'mypassword',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
... (생략)
Python
복사
3.
타임존 설정
기본적으로 UTC 시간대를 사용하기 때문에, 이에 관한 설정을 변경해줘야한다.
아무 설정을 변경하지 않은 기본 상태는 다음과 같다.
... (생략)
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
... (생략)
Python
복사
USE_TZ 는 False로 변경하여 UTC를 사용하지 않는 옵션으로 변경하고, TIME_ZONE 설정은 한국 시간으로 변경해준다.
... (생략)
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Asia/Seoul'
USE_I18N = True
USE_L10N = True
USE_TZ = False
... (생략)
Python
복사
3. 모델 생성 내용 적용하기
manage.py 파일과 makimigrations 를 사용하여 모델의 변경사항을 기록해준다.
실제로 DB에 저장하지는 않으며, 실제 저장을 위해서는 manage.py 파일과 migrate 를 사용해야한다.
만약 모델에 대한 수정 사항 발생 시(생성, 변경, 삭제 등), 아래 두 과정을 순서대로 실행하여 DB에 적용해야 한다.
•
모델 변경 사항 기록
정상적으로 설정이 되었다면, 아래 이미지와 같이 Create model Board 라는 메세지를 확인할 수 있다.
또한, /bbs/migrations 폴더에 변경사항을 기록한 내용을 확인할 수 있다.
./manage.py makemigrations
# 특정 앱 makemigrations
./manage.py makemigrations [app_name]
Bash
복사
변경사항 기록 내용 (0001_initial.py)
◦
모델 변경 사항 기록 확인 후 실제 DB에 저장
정상적으로 DB에 데이터 필드가 설정되었다면, 아래 이미지와 같이 Apply all migrations 라는 메세지를 확인할 수 있다.
만약 이미 존재하는 테이블일 때, migrate 명령어로 새로 만들면 에러가 발생한다.
이럴 땐 --fake-initial 옵션을 추가하여 이미 이전에 마이그레이션 한 상태처럼 진행한다.
./manage.py migrate
# 특정 앱 migrate
./manage.py migrate [app_name]
# 이미 존재하는 테이블로 migrate
./manage.py migrate [app_name] --fake-initial
Bash
복사
잘못된 Migration 복원하기
만약 잘못된 정보로 makemigrations 을 하였을 때 복원하는 방법이다.
1.
이전 마이그레이션 초기화
python manage.py migrate --fake [app_name] zero
Bash
복사
2.
가장 최근의 migrations/xxxx_initial.py 파일 삭제
3.
models.py 수정 후 다시 작업
게시판 사이트 만들기 [ VIEW 생성 ]
우리는 위에서 정의한 게시판의 구조에 맞춰 View 를 생성할 것이다.
게시글 리스트 확인, 상세 확인, 게시글 생성, 게시글 수정, 게시글 삭제 총 5가지이다.
1. View 파일 수정
/bbs/view.py 파일에 우리가 원하는 기능의 View를 작성해준다.
아래 파일은 CBV(Class Based View) 즉, 클래스를 기반으로 만들어진 뷰로 만들어주었다.
FBV(Function Based View), 함수를 기반으로 만드는 방법도 있지만 중복 코드 감소 등 CBV가 FBV보다 더 적절하다고 생각했다. (사실 참고한 블로그 분이 CBV 기반으로 작성해주셨다! ㅋㅋㅋ)
물론 CBV가 FBV보다 무조건 좋은건 아니다. 만드는 프로젝트에 따라 더 적합한 방법을 사용하면 된다.
역시 이러한 차이에 대한 궁금증은 잘 정리해주신 분들의 내용을 구글검색으로 쉽게 찾을 수 있다!
→ CBV vs FBV & 장점과 단점
CBV를 기반으로 작성된 게시글의 View 내용이다.
우선 각 기능에 대해 template의 base.html (아래 과정에서 만들어 줄꺼임) 가 출력되도록 만들어주고, 상세한 기능은 설정하지 않았다.
... (생략)
from django.views.generic import TemplateView
... (생략)
# 게시글 리스트 확인 페이지에서 사용할 클래스
class BoardListView(TemplateView):
template_name = 'base.html'
def get(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
# 게시글 상세 페이지에서 사용할 클래스
class BoardDetailView(TemplateView):
template_name = 'base.html'
def get(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
# 게시글 생성에서 사용할 클래스
class BoardCreateView(TemplateView):
template_name = 'base.html'
def get(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
# 게시글 수정에서 사용할 클래스
class BoardUpdateView(TemplateView):
template_name = 'base.html'
def get(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
# 게시글 삭제에서 사용할 클래스
class BoardDeleteView(TemplateView):
template_name = 'base.html'
def get(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
Python
복사
아직은 상세 기능이 없어 다 비슷한 내용이니, 이해를 위해 먼저 수정 부분의 코드만 설명하겠다.
... (생략)
from django.views.generic import TemplateView
... (생략)
# 게시글 수정에서 사용할 클래스
class BoardUpdateView(TemplateView):
template_name = 'base.html'
def get(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
... (생략)
Python
복사
•
TemplateView : 우선 장고에서 기본적으로 제공하는 제네릭 클래스 기반 뷰 중 하나이다.
이 프로젝트에서는 TemplateView 를 사용한다.
제네릭 클래스 기반 뷰는 django.views.generic 모듈에 의해 구현되어 있는 기능 중 하나이다.
장고 프레임워크에 구현되어 있는 기능 사용하는 것으로, 목적에 맞게 원하는 뷰를 골라 사용할 수 있다.
→ 제너릭 뷰 목록과 상속 내용
•
template_name : 해당 뷰의 템플릿 페이지 이름을 작성한다. 이 파일은 [app_name]/templates/ 위치에 있어야 한다.
•
def get(self, request, *args, **kwargs) : 만약 해당 뷰에서 GET 요청을 받았을 경우 실행되는 함수이다.
지금은 아무 내용이 없지만, return self.render_to_response(context) 메소드를 이용하여 템플릿에 원하는 값을 넘길 수 있다.
render_to_response 는 제네릭 뷰에서 제공하는 함수로 템플릿을 자동적으로 기본 템플릿 엔진을 이용해서 html로 변환해주는 함수이다.
만약 템플릿 내부에서 변수를 사용해야 한다면, 인자로 dict 형태의 데이터를 사용하여 템플릿에서 사용할 수 있다.
•
def post(self, request, *args, **kwargs) : def get() 경우와 마찬가지 기능이지만, 이름이 post 이기 때문에, POST 요청을 받았을 경우 실행되는 함수이다.
2. url 매핑 파일 수정
/toyproject/urls.py 파일에 View 에서 정의한 기능들에 접속할 수 있도록 url을 정의해준다.
가장 처음 view 파일의 클래스를 import 후, url을 작성한다.
상세보기, 게시글 수정, 게시글 삭제 기능은 게시글의 ID(board_id) 필요하다.
또한, 숫자형(int형)만 인식하도록 <int:~> 형태로 작성한다.
... (생략)
# view의 클래스들 import
from bbs.views import BoardListView, BoardDetailView, BoardCreateView, BoardUpdateView, BoardDeleteView
urlpatterns = [
... (생략)
# url 설정
path('board/', BoardListView.as_view()),
path('board/<int:board_id>/', BoardDetailView.as_view()),
path('board/create/', BoardCreateView.as_view()),
path('board/<int:board_id>/update/', BoardUpdateView.as_view()),
path('board/<int:board_id>/delete/', BoardDeleteView.as_view()),
]
Python
복사
3. Templates 폴더에 base.html 만들기
위 과정에서 작성한 base.html 을 작성할 것 이다.
가장 먼저 /bbs/templates 폴더 속에 존재해야 하기 때문에, templates 폴더를 생성해준다.
templates 폴더에 base.html 파일을 생성하며, 내용은 아래와 같다.
임시 파일이기 때문에 본인 맘대로 해도된다.
<html lang="ko">
<head>
<title>base</title>
</head>
<body>
THIS IS BASE PAGE
</body>
</html>
HTML
복사
4. 테스트를 위해 서버 실행
./manage.py runserver 명령어로 서버를 실행 후 확인해본다.
정상적으로 설정되었다면, 모든 기능에 접속하였을 때 base.html 의 내용이 나올 것이다.
테스트 결과
게시글 데이터 적용 [ MODEL + VIEW ]
위 과정에서 만든 View에 데이터를 다룰 수 있도록 적용하는 작업이다.
1. 전체 게시글 목록 설정
/toyproject/bbs/views.py 소스코드 중 전체 게시글을 확인할 수 있는 부분을 수정합니다.
•
수정 전 상태
# 게시글 리스트 확인 페이지에서 사용할 클래스
class BoardListView(TemplateView):
template_name = 'base.html'
def get(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
Python
복사
•
수정 후
...(생략)
from bbs.models import Board
...(생략)
# 게시글 리스트 확인 페이지에서 사용할 클래스
class BoardListView(TemplateView):
template_name = 'base.html'
# 해당 모델의 전체 데이터 얻어오기
queryset = Board.objects.all()
def get(self, request, *args, **kwargs):
# dict 자료형
context = {
'view': self.__class__.__name__,
'data': self.queryset
}
return self.render_to_response(context)
class BoardListView(TemplateView):
template_name = 'board_list.html'
# 최초 1회
queryset = Board.objects.all()
def get_object(self, queryset=None):
if queryset is None:
queryset = self.queryset
queryset = Board.objects.all()
return queryset
def get(self, request, *args, **kwargs):
# 해당 모델의 전체 데이터 얻어오기 (접속 시 마다)
boards = self.get_object()
# dict 자료형
context = {
'boards': boards
}
return self.render_to_response(context)
Python
복사
•
변경 부분 설명
◦
~ import Board : 모델에 정의한 Board 모델을 import 한다.
◦
queryset : Board 모델의 전체 데이터(Model.objects.all())를 가져온다.
처음 queryset = Board.objects.all() 는 최초 1회만 받아온다.
하지만 get_object() 메소드를 재 정의하여 GET 메소드로 접속 시 DB의 정보를 다시 받아온다.
◦
context { ~ } : 템플릿에 전달 할 변수의 값을 담고 있으며, view 와 data 라는 변수를 전달한다.
▪
view : 해당 클래스의 이름 (전체 글 확인 클래스의 경우 BoardListView)
▪
data : queryset 값을 전달 (오브젝트 단위로 통째로 전달)
◦
boards : Board 모델의 전체 데이터( GET 메소드 요청 시 마다 )의 오브젝트 데이터
2. 템플릿에 전체 게시글 출력 테스트
Admin 페이지를 사용하여 이미 Board 모델에 데이터를 하나 넣어둔 상태이다.
이전에 테스트를 위해 사용했던 /toyproject/bbs/templates/base.html 파일에 전달받은 context 값을 적용하도록 코드를 변경할 것이다.
•
수정 전
<html lang="ko">
<head>
<title>base</title>
</head>
<body>
THIS IS BASE PAGE
</body>
</html>
HTML
복사
•
수정 후
<html lang="ko">
<head>
<title>context test base.html</title>
</head>
<body>
view: {{ view }}
<br>
data: {{ data }}
</body>
</html>
HTML
복사
•
변경 부분 설명
◦
{{ view }} : 이전에 response 된 context 변수의 view 값이다. ( context['view'] )
◦
{{ data }} : 이전에 response 된 context 변수의 data 값이다. ( context['data'] )
이제 서버를 다시 실행 후 정상적으로 적용 시, 아래 그림처럼 확인이 가능하다.
3. 게시글 생성 view 변경
<board_id> 를 사용하지 않는 게시글 생성(BoardCreateView 클래스)를 먼저 설명하겠다.
•
수정 전
# 게시글 생성에서 사용할 클래스
class BoardCreateView(TemplateView):
template_name = 'base.html'
def get(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
Python
복사
•
수정 후
...(생략)
from django.http import Http404
from bbs.models import Board
...(생략)
# 게시글 생성에서 사용할 클래스
class BoardCreateView(TemplateView):
template_name = 'base.html'
def get(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
post_data = {key: request.POST.get(key) for key in ('title', 'content', 'author')}
for key in post_data:
if not post_data[key]:
raise Http404('No Data For {}'.format(key))
board = Board.objects.create(title=title, content=content, author=author)
context = {
'view': self.__class__.__name__,
'data': board
}
return self.render_to_response(context)
...(생략)
Python
복사
•
변경 부분 설명
•
post_data : request에 담긴 POST 데이터를 dict 형태로 저장한 변수
•
raise Http404 : 만약 특정 키 값에서 데이터가 존재하지 않은 상태로 POST 요청 시 404 에러
◦
board : Model.objects.create() 함수를 사용하여 저장한다.
Model.objects.create() vs Model()
Model.objects.create() : 단일 객체 생성 시 사용하는 메소드
Model() : 모델의 생성자를 사용하여 객체를 생성하며, DB 저장 시 save() 라는 추가 명령어가 필요하다.
정상적으로 설정 되었다면 아래 명령어로 create API를 동작해볼 수 있다.
Insomnia / postman 같은 프로그램을 써도 무방하다.
curl -X POST http://127.0.0.1:8000/board/create/ -d "title='TEST TITLE'&content='TEST CONTENT'&author='TEST AUTHOR'"
Bash
복사
하지만, CSRF verification failed. Request aborted. 라는 메세지와 함께 403 에러가 발생한다.
이는 CSRF 공격 방지를 위한 CSRF Token이 Body에 설정되어 있지 않기 때문이다.
이는 아래 과정의 Template 설정 시 Token을 전달하도록 설정 할 것이기 때문에, 우선 나머지 기능을 구현하자.
4. 게시글의 나머지 기능 구현
이제 <board_id> 를 사용하는 나머지 기능(상세보기, 수정하기, 삭제하기)을 구현한다.
게시글 상세보기
•
수정 전
# 게시글 상세 페이지에서 사용할 클래스
class BoardDetailView(TemplateView):
template_name = 'base.html'
def get(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
Python
복사
•
수정 후
... (생략)
from django.http import Http404
from bbs.models import Board
... (생략)
# 게시글 상세 페이지에서 사용할 클래스
class BoardDetailView(TemplateView):
template_name = 'base.html'
queryset = Board.objects.all()
pk_url_kwargs = 'board_id'
def get_object(self, queryset=None):
if queryset is None:
queryset = self.queryset
pk = self.kwargs.get(self.pk_url_kwargs)
return queryset.filter(pk=pk).first()
def get(self, request, *args, **kwargs):
board = self.get_object()
if not board:
raise Http404('invalid board_id')
context = {
'view': self.__class__.__name__,
'data': board
}
return self.render_to_response(context)
... (생략)
Python
복사
•
변경 부분 설명
◦
queryset : 먼저 Model의 전체 데이터를 받아온 후, queryset.filter() 메소드를 사용하여 원하는 board_id 의 데이터만 사용한다.
게시글 수정하기
•
수정 전
# 게시글 수정에서 사용할 클래스
class BoardUpdateView(TemplateView):
template_name = 'base.html'
def get(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
Python
복사
•
수정 후
게시글 추가와 게시글 상세보기를 섞었다고 생각하면 편하다.
get_object() 메소드 재 정의 + request.POST.get() 메소드를 사용한 POST 전달 데이터 사용
... (생략)
from django.http import Http404
from bbs.models import Board
... (생략)
# 게시글 수정에서 사용할 클래스
class BoardUpdateView(TemplateView):
template_name = 'base.html'
queryset = Board.objects.all()
pk_url_kwargs = 'board_id'
def get_object(self, queryset=None):
if queryset is None:
queryset = self.queryset
pk = self.kwargs.get(self.pk_url_kwargs)
return queryset.filter(pk=pk).first()
def get(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
post_data = {key: request.POST.get(key) for key in ('title', 'content', 'author')}
for key in post_data:
if not post_data[key]:
raise Http404('No Data For {}'.format(key))
board = self.get_object()
if not board:
raise Http404('invalid board_id')
for key, value in post_data.items():
setattr(board, key, value)
board.save()
context = {
'view': self.__class__.__name__,
'data': board
}
return self.render_to_response(context)
... (생략)
Python
복사
•
변경 부분 설명
•
post_data : request에 담긴 POST 데이터를 dict 형태로 저장한 변수
•
raise Http404 : 만약 특정 키 값에서 데이터가 존재하지 않은 상태로 POST 요청 시 404 에러
◦
setattr(board, key, value) : 기존 board 오브젝트의 데이터를 setattr 메소드를 사용하여 새롭게 얻어온 POST 데이터(post_data) 값으로 변경
◦
board.save() : Model.objects.create() 와 같은 명령어로 값을 설정하지 않았기 때문에, 오브젝트의 변경사항을 변경해준다.
게시글 삭제하기
•
수정 전
# 게시글 삭제에서 사용할 클래스
class BoardDeleteView(TemplateView):
template_name = 'base.html'
def get(self, request, *args, **kwargs):
context = {}
return self.render_to_response(context)
Python
복사
•
수정 후
게시글 상세보기와 거의 동일하다.
차이점은 게시글 상세보기 기능은 반환된 Object를 보여주는 것이고, 삭제 기능은 그 Object를 삭제하는 것이다.
... (생략)
from django.http import HttpResponse, HttpResponseRedirect
from django.http import Http404
from bbs.models import Board
... (생략)
# 게시글 삭제에서 사용할 클래스
class BoardDeleteView(TemplateView):
template_name = 'base.html'
queryset = Board.objects.all()
pk_url_kwargs = 'board_id'
def get_object(self, queryset=None):
if queryset is None:
queryset = self.queryset
pk = self.kwargs.get(self.pk_url_kwargs)
return queryset.filter(pk=pk).first()
def get(self, request, *args, **kwargs):
board = self.get_object()
if not board:
raise Http404('invalid board_id')
board.delete()
return HttpResponseRedirect("/board/")
... (생략)
Python
복사
•
변경 사항 설명
◦
board.delete() : URL 에서 입력한 <board_id>를 가진 게시글의 Object 데이터를 삭제한다.
◦
HttpResponseRedirect("/board/") : 삭제 완료 후, /board/ 페이지로 이동한다.
5. 게시글 View 테스트 결과
현재는 CSRF 관련 에러로 인해 완전히 테스트가 어렵기 때문에, 템플릿을 만든 후 테스트 사진을 첨부하겠다.
정상적으로 설정되었을 때 상태를 간략하게 글로 정리하면 아래와 같다.
•
게시글 수정 기능 : 게시글 생성 기능과 같이 CSRF 관련 에러가 출력되어야한다.
•
게시글 상세 기능 : 만약 존재하지 않는 <board_id>의 게시글일 경우 404 반환, 존재하는 id라면 해당 게시글의 오브젝트 내용이 나와야한다.
•
게시글 삭제 기능 : 정상적으로 삭제되며, 다시 게시글 전체 리스트 사이트(/board/ 페이지)로 리다이렉트 되어야 한다.
템플릿을 생성하여 연결하기 [ MODEL + VIEW + TEMPLATE ]
이제 마지막 작업이다. API 로 구현은 끝났으니 웹 사이트 형태로 보기 위해 Template를 만들어 연결해준다.
템플릿도 잘 설계해야 한다. 어떤 영역의 코드가 어떤 데이터를 보여주는지, 실제 눈에 보이는 코드인지 아니면 CSS와 같은 설정 코드인 건지 말이다.
<html lang="ko">
<head>
{% block title %} <!-- 페이지별 타이틀 공간 -->
<title>bbs - minitutorial</title>
{% endblock title %}
{% block meta %} <!-- 페이지별 메타 데이터 공간 -->
{% endblock meta %}
{% block scripts %} <!-- 페이지별 스크립트 공간 -->
{% endblock scripts %}
{% block css %} <!-- 페이지별 css -->
{% endblock css %}
</head>
<body>
{% block content %}
view: {{ view }} <!-- context['view'] -->
<br>
data: {{ data }} <!-- context['data'] -->
{% endblock content %}
</body>
</html>
HTML
복사
이전처럼 base.html 로만 사용하지 않고 각 기능에 맞는 템플릿을 만들고 이름도 바꿀 것이기 때문에 view.py 코드도 수정해줘야 한다.
•
기능별 템플릿 파일 이름 (/toyproject/bbs/templates/ 내부에 저장)
◦
전체 게시글 확인 : board_list.html
◦
게시글 상세 확인 : board_detail.html
◦
게시글 생성 기능 : board_create.html
◦
게시글 수정 기능 : board_update.html
◦
게시글 삭제 기능 : 미 존재 (기능 완료 후 /board/ 페이지로 리다이렉트)
1. 전체 게시글 확인 템플릿 생성
전체 게시글의 목록을 확인할 수 있는 /templates/board_list.html 파일의 소스이다.
<tbody> 태그에서 템플릿으로 넘어온 boards 변수의 값을 반복문으로 출력한다.
그 외에 추가적으로 아래 3가지 기능을 적용한다.
1.
BootStrap CSS 적용
2.
행 클릭 시 상세 보기(/board_detail.html/)로 이동
3.
가장 하단에는 새 게시글 작성 버튼 생성
{% extends 'base.html' %}
{% block css %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
{% endblock css %}
{% block title %}<title>게시글 목록</title>{% endblock title %}
{% block content %}
<table class="table table-hover table-responsive">
<thead>
<th>Board ID</th><th>제목</th><th>작성자</th>
</thead>
<tbody>
{% for item in boards %}
<tr onclick="location.href='/board/{{ board.pk }}/'">
<td>{{ item.pk }}</td><td>{{ item.title }}</td><td>{{ item.author }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="/board/create/"><button class="btn btn-primary" type="button">새 게시글 작성</button></a>
{% endblock content %}
HTML
복사
view.py 파일에서 해당 기능의 template_name 값과 템플릿으로 전송되는 변수의 이름을 변경한다.
...(생략)
# 게시글 리스트 확인 페이지에서 사용할 클래스
class BoardListView(TemplateView):
template_name = 'board_list.html'
# 해당 모델의 전체 데이터 얻어오기
queryset = Board.objects.all()
def get(self, request, *args, **kwargs):
# dict 자료형
context = {
'boards': self.queryset
}
return self.render_to_response(context)
...(생략)
Python
복사
정상적으로 설정되었다면 아래 그림처럼 확인할 수 있다.
2. 게시글 상세 확인 템플릿 생성
특정 게시글의 내용을 확인할 수 있는 /templates/board_detail.html 파일의 소스이다.
<table> 태그에서 템플릿으로 넘어온 board 변수의 값을 출력한다.
가장 하단에는 게시글 수정과 게시글 삭제 버튼을 생성한다.
{% extends 'base.html' %}
{% block title %}<title>게시글 상세 - {{ board.pk }}. {{ board.title }}</title>{% endblock title %}
{% block css %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
{% endblock css %}
{% block content %}
<table class="table table-striped table-bordered">
<tr>
<th>번호</th>
<td>{{ board.pk }}</td>
</tr>
<tr>
<th>제목</th>
<td>{{ board.title }}</td>
</tr>
<tr>
<th>내용</th>
<td>{{ board.content | linebreaksbr }}</td>
</tr>
<tr>
<th>작성자</th>
<td>{{ board.author }}</td>
</tr>
<tr>
<th>작성일</th>
<td>{{ board.created | date:"Y-m-d H:i" }}</td>
</tr>
</table>
<a href="/board/{{ board.pk }}/update/"><button class="btn btn-primary" type="button">게시글 수정</button></a>
<br><br>
<a href="/board/{{ board.pk }}/delete/"><button class="btn btn-primary" type="button">게시글 삭제</button></a>
{% endblock content %}
HTML
복사
view.py 파일에서 해당 기능의 template_name 값과 템플릿으로 전송되는 변수의 이름을 변경한다.
...(생략)
# 게시글 상세 페이지에서 사용할 클래스
class BoardDetailView(TemplateView):
template_name = 'board_detail.html'
...(생략)
def get(self, request, *args, **kwargs):
board = self.get_object()
if not board:
raise Http404('invalid board_id')
context = {
'board': board
}
return self.render_to_response(context)
...(생략)
Python
복사
정상적으로 설정되었다면 아래 그림처럼 확인할 수 있다.
3. 게시글 생성 기능 템플릿 생성
게시글을 작성할 수 있는 /templates/board_create.html 파일의 소스이다.
이 템플릿은 GET 메소드로 /board/create/ 페이지에 접속하였을 때 확인 가능한 페이지이다.
{% extends 'base.html' %}
{% block title %}<title>게시글 생성</title>{% endblock title %}
{% block css %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
{% endblock css %}
{% block content %}
<form action="." method="post" class="form-horizontal">
{% csrf_token %}
<table class="table table-striped table-bordered">
<tr>
<th>제목</th>
<td><input type="text" class="form-control" name="title"></td>
</tr>
<tr>
<th>내용</th>
<td><textarea rows="10" class="form-control" name="content"></textarea></td>
</tr>
<tr>
<th>작성자</th>
<td><input type="text" class="form-control" name="author"></td>
</tr>
</table>
<button class="btn btn-primary" type="submit">게시글 저장</button>
</form>
<br>
<a href="/board/"><button class="btn btn-primary" type="button">게시글 목록</button></a>
{% endblock content %}
HTML
복사
view.py 파일에서 해당 기능의 template_name 값과 템플릿으로 전송되는 변수의 이름을 변경한다.
...(생략)
# 게시글 생성에서 사용할 클래스
class BoardCreateView(TemplateView):
template_name = 'board_create.html'
...(생략)
board = Board.objects.create(title=post_data['title'], content=post_data['content'], author=post_data['author'])
context = {
'board': board
}
return self.render_to_response(context)
...(생략)
Python
복사
정상적으로 설정되었다면 아래 그림처럼 확인할 수 있다.
4. 게시글 수정 기능 템플릿 생성
특정 게시글의 내용을 업데이트 할 수 있는 /templates/board_update.html 파일의 소스이다.
{% extends 'base.html' %}
{% block title %}<title>{{ board.pk }}번 게시글 수정</title>{% endblock title %}
{% block css %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
{% endblock css %}
{% block content %}
<form action="." method="post" class="form-horizontal">
{% csrf_token %}
<table class="table table-striped table-bordered">
<tr>
<th>Board ID</th>
<td><input type="text" class="form-control" name="title" value="{{ board.pk }}" disabled></td>
</tr>
<tr>
<th>제목</th>
<td><input type="text" class="form-control" name="title" value="{{ board.title }}"></td>
</tr>
<tr>
<th>내용</th>
<td><textarea rows="10" class="form-control" name="content">{{ board.content }}</textarea></td>
</tr>
<tr>
<th>작성자</th>
<td><input type="text" class="form-control" name="author" value="{{ board.author }}"></td>
</tr>
</table>
<button class="btn btn-primary" type="submit">게시글 수정</button>
</form>
<br>
<a href="/board/"><button class="btn btn-primary" type="button">게시글 목록</button></a>
{% endblock content %}
HTML
복사
view.py 파일에서 해당 기능의 template_name 값과 템플릿으로 전송되는 변수의 이름을 변경한다.
...(생략)
# 게시글 수정에서 사용할 클래스
class BoardUpdateView(TemplateView):
template_name = 'board_update.html'
queryset = Board.objects.all()
pk_url_kwargs = 'board_id'
...(생략)
board.save()
context = {
'board': board
}
return self.render_to_response(context)
...(생략)
Python
복사
정상적으로 설정되었다면 아래 그림처럼 확인할 수 있다.
•
수정 전
•
수정 작업
•
수정 완료