Django CBV 프로젝트 2. 세부 코딩

참고 사이트

Model.py

1
2
3
4
5
6
7
8
9
10
11
from django.db import models

class Article(models.Model):
title = models.CharField('타이틀', max_length=126, null=False)
content = models.TextField('내용', null=False)
author = models.CharField('작성자', max_length=16, null=False)
created_at = models.DateTimeField('작성일', auto_now_add=True)
created_at.editable = True

def __str__(self):
return '[{}] {}'.format(self.id, self.title)

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# bbs/views.py - CBV(Class Based View)

from django.http import HttpResponse, HttpResponseRedirect
from django.views.generic import TemplateView
from bbs.models import Article

from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.contrib import messages


class ArticleListView(TemplateView):
template_name = 'article_list.html' # 뷰 전용 템플릿 생성.
queryset = None

def get(self, request, *args, **kwargs):
print(request.GET)
ctx = {
'view': self.__class__.__name__, # 클래스의 이름
'articles': self.get_queryset()
}
return self.render_to_response(ctx)

def get_queryset(self):
if not self.queryset:
self.queryset = Article.objects.all()
return self.queryset


class ArticleDetailView(TemplateView): # 게시글 상세
template_name = 'article_detail.html'
queryset = Article.objects.all()
pk_url_kwargs = 'article_id' # 검색데이터의 primary key를 전달받을 이름

def get_object(self, queryset=None):
queryset = queryset or self.queryset # queryset 파라미터 초기화
pk = self.kwargs.get(self.pk_url_kwargs) # pk는 모델에서 정의된 pk값, 즉 모델의 id
return queryset.filter(pk=pk).first() # pk로 검색된 데이터가 있다면 그 중 첫번째 데이터 없다면 None 반환

if not article:
raise Http404('invalid pk')
return article

def get(self, request, *args, **kwargs):
article = self.get_object()

ctx = {
'view': self.__class__.__name__, # 클래스의 이름
'article': article
}
return self.render_to_response(ctx)

@method_decorator(csrf_exempt, name='dispatch') # 모든 핸들러 예외 처리
class ArticleCreateUpdateView(TemplateView): # 게시글 추가, 수정
template_name = 'article_update.html'
queryset = Article.objects.all()
pk_url_kwargs = 'article_id'

def get_object(self, queryset=None):
queryset = queryset or self.queryset
pk = self.kwargs.get(self.pk_url_kwargs)
article = queryset.filter(pk=pk).first()

if pk and not article: # 검색결과가 없으면 곧바로 에러 발생
raise Http404('invalid pk')
return article

def get(self, request, *args, **kwargs):
article = self.get_object()

ctx = {
'article': article
}
return self.render_to_response(ctx)

def post(self, request, *args, **kwargs):
action = request.POST.get('action') # request.POST 객체에서 데이터 얻기
post_data = {key: request.POST.get(key) for key in ('title', 'content', 'author')}
for key in post_data: # 세가지 데이터 모두 있어야 통과
if not post_data[key]:
messages.error(self.request, '{} 값이 존재하지 않습니다.'.format(key), extra_tags='danger') # error 레벨로 메시지 저장

if len(messages.get_messages(request)) == 0: # 메시지가 있다면 아무것도 처리하지 않음
if action == 'create': # action이 create일 경우
article = Article.objects.create(**post_data)
messages.success(self.request, '게시글이 저장되었습니다.') # success 레벨로 메시지 저장
elif action == 'update': # action이 update일 경우
article = self.get_object()
for key, value in post_data.items():
setattr(article, key, value)
article.save()
messages.success(self.request, '게시글이 성공적으로 수정되었습니다.', extra_tags='info') # info 레벨로 메시지 저장
else: # action이 없거나 create, update 중 하나가 아닐 경우
messages.error(self.request, '알 수 없는 요청입니다.', extra_tags='danger') # error 레벨로 메시지 저장

return HttpResponseRedirect('/article/') # 정상적인 저장이 완료되면 '/articles/'로 이동됨

ctx = {
'article': self.get_object() if action == 'update' else None
}
return self.render_to_response(ctx)


minitutorial/urls.py

1
2
3
4
5
6
7
8
9
10
11
12
from django.contrib import admin
from django.urls import path

from bbs.views import ArticleListView, ArticleDetailView, ArticleCreateUpdateView

urlpatterns = [
path('article/', ArticleListView.as_view()),
path('article/create/', ArticleCreateUpdateView.as_view()),
path('article/<article_id>/', ArticleDetailView.as_view()),
path('article/<article_id>/update/', ArticleCreateUpdateView.as_view()),
path('admin/', admin.site.urls),
]

프론트 (Templates)

Templates/base.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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

<!DOCTYPE html>
<html lang="ko">
<head>
{% block title %}
<title>bbs - minitutorial</title>
{% endblock title %}

{% block meta %}
{% endblock meta %}

{% block scripts %}
{% endblock scripts %}

{% block css %}
{% endblock css %}
</head>
<body>
{% block header %}
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="/article/">게시글 목록</a>
</div>
</div>
</nav>
{% if messages %} <!-- 추가된 부분 시작 -->
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible" role="alert">
{{ message }}
</div>
{% endfor %}
{% endif %} <!-- 추가된 부분 끝 -->

{% endblock header %}

{% block content %}
{% endblock content %}
</body>
</html>

Templates/article_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
27
28
{% 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">
<style type="text/css">
tbody > tr {cursor: pointer;}
</style>
{% endblock css %}

{% block content %}
<table class="table table-hover table-responsive">
<thead>
<th>번호</th><th>제목</th><th>작성자</th>
</thead>
<tbody>
{% for article in articles %}
<tr onclick="location.href='/article/{{ article.pk }}/'"> <!-- 테이블 행 click 시 url 이동 -->
<td>{{ article.pk }}</td><td>{{ article.title }}</td><td>{{ article.author }}</td>
</tr>
{% endfor %}
</tbody>
</table>

<!-- 버튼 click 시 url 이동 -->
<a href="/article/create/"><button class="btn btn-primary" type="button">새 게시글 작성</button></a>
{% endblock content %}

Templates/article_detail.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
27
28
29
30
31
32
33
34
35
36
37
<!-- bbs/templates/article_detail.html -->


{% extends 'base.html' %}

{% block title %}<title>게시글 상세 - {{ article.pk }}. {{ article.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>{{ article.pk }}</td>
</tr>
<tr>
<th>제목</th>
<td>{{ article.title }}</td>
</tr>
<tr>
<th>내용</th>
<td>{{ article.content | linebreaksbr }}</td>
</tr>
<tr>
<th>작성자</th>
<td>{{ article.author }}</td>
</tr>
<tr>
<th>작성일</th>
<td>{{ article.created_at | date:"Y-m-d H:i" }}</td>
</tr>
</table>

<a href="/article/{{ article.pk }}/update/"><button class="btn btn-primary" type="button">게시글 수정</button></a>
{% endblock content %}

Templates/article_update.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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49


{% extends 'base.html' %}

{% block title %}<title>게시글 수정 - {{ article.pk }}. {{ article.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 %}

{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible" role="alert">
{{ message }}
</div>
{% endfor %}
{% endif %}

<form action="." method="post" class="form-horizontal">
{% csrf_token %}
<input type="hidden" name="action" value="{% if article %}update{% else %}create{% endif %}">
<table class="table table-striped table-bordered">
<tr>
<th>번호</th>
<td>{{ article.pk }}</td>
</tr>
<tr>
<th>제목</th>
<td><input type="text" class="form-control" name="title" value="{{ article.title }}"></td>
</tr>
<tr>
<th>내용</th>
<td><textarea rows="10" class="form-control" name="content">{{ article.content }}</textarea></td>
</tr>
<tr>
<th>작성자</th>
<td><input type="text" class="form-control" name="author" value="{{ article.author }}"></td>
</tr>
<tr>
<th>작성일</th>
<td>{{ article.created_at | date:"Y-m-d H:i" }}</td>
</tr>
</table>

<button class="btn btn-primary" type="submit">게시글 저장</button>
</form>
{% endblock content %}

결과

수정
새로운글