(2024.07.02. 수정됨.)
주요 파일 경로
blog
|- entry
| |- models.py
| |- forms.py
| |- views.py
| |- urls.py
|- tempaltes
|- detail.html
|- secret.html
이번에는 비밀 글과 비공개 글 설정을 만들어볼 겁니다. 비공개 글을 위한 설정까지는 간단한데, 비밀 글은 그렇지 않습니다.
비밀번호 입력을 요구하기 위해 class view를 사용하기 때문인데, class view의 사용법이 적응하기 어렵습니다.
비밀 글과 비공개 글 설정 만들기
models.py에 비밀 글의 조회를 위한 password field와 비공개 글 설정을 위한 is_private field를 추가합니다.
비밀번호는 max_length arg를 통해 최대 9글자까지 입력할 수 있도록 설정했습니다.
# entry/models.py
from django.core.validators import MinLengthValidator # 추가
from django.db import models
from django.shortcuts import resolve_url
class Post(models.Model):
is_private = models.BooleanField(default=False,) # 추가
password = models.CharField(max_length=9, blank=True, validators=[MinLengthValidator(3),],) # 추가
title = models.CharField(max_length=30,)
content = models.TextField(max_length=999, default='',)
date_post = models.DateTimeField(auto_now_add=True,)
date_edit = models.DateTimeField(auto_now=True,)
def get_absolute_url(self): return resolve_url('entry:detail', self.pk)
MinLengthValidator
CharField에서 입력된 문자열의 최소 길이를 확인하는 Validator입니다.
이것을 통해 비밀번호가 3글자 미만인 경우 에러를 발생합니다.
마이그레이트
새로운 field가 추가되었으니 마이그레이트하여 db 테이블을 수정합니다.
(python3.10) C:\seolpyo\python3.10\Django\blog>python manage.py makemigrations
Migrations for 'entry':
entry\migrations\0004_post_is_private_post_password.py
- Add field is_private to post
- Add field password to post
(python3.10) C:\seolpyo\python3.10\Django\blog>python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, django_summernote, entry, sessions
Running migrations:
Applying entry.0004_post_is_private_post_password... OK
(python3.10) C:\seolpyo\python3.10\Django\blog>
폼 수정하기
is_private와 password에 해당하는 label을 추가합니다.
모델을 그대로 상속받아 사용하기 때문에 따로 변경해야하는 부분은 없습니다.
# etnry/forms.py
from django import forms
from .models import Post
class Form(forms.ModelForm):
class Meta:
model = Post
fields = '__all__'
...
labels = {
'title': '', 'content': '',
'is_private': '비공개로 설정',
'password': '비밀번호',
}
템플릿 수정하기
비밀 글과 비밀번호 글을 구분할 수 있도록, 템플릿에서 비밀 글일 때는 "[비밀]", 비공개 글일 때는 "[비공개]"라는 문구를 제목 앞에 붙이도록 수정합니다.
# templates/index.html
...
<article>
{% for object in object_list %}
<p>
<a href="{{ object.get_absolute_url }}">
{% if object.is_private %}[비공개] {% endif %}
{% if object.password %}[비밀] {% endif %}
{{ object.title }}
</a>
</p>
{% empty %}
<p>작성된 글이 없습니다.</p>
{% endfor %}
</article>
...
# templats/detail.html
...
{% block title %}
{% if object.is_private %}[비공개] {% endif %}
{% if object.password %}[비밀] {% endif %}
{{ object.title }}
...
뷰 수정하기
관리자가 아닌 이용자가 비공개 글을 조회하려고 하면 403 에러 페이지를,
관리자가 아닌 이용자가 비밀 글을 조회하려고 하면 비밀번호 입력 페이지를 조회할 수 있도록 class View를 작성합니다.
겸사겸사 개별 글 조회 페이지에서도 검색을 수행할 수 있도록 context에 검색폼을 추가했습니다.
class View는 적응하면 그럭저럭 쓸만하긴 한데, 우리가 사용하는 최종 class를 만들기 위해 어마어마한 분량의 코드가 작성되고 상속되어있습니다.
그렇기 때문에 처음 보는 입장에서는 이게 대체 뭐하는 코드인지 감도 잡히지 않을 수 있습니다.
...
from django.core.exceptions import PermissionDenied # 추가
from django.shortcuts import render, redirect, get_object_or_404
from django.utils.decorators import method_decorator # 추가
from django.views.decorators.csrf import csrf_protect # 추가
from django.views.generic import DetailView # 추가
from .models import Post
from .forms import Form, Search
...
# 추가
class Detail(DetailView):
model = Post
template_name = 'detail.html'
def get_object(self, queryset=None) -> Post:
obj: Post = super().get_object(queryset)
if obj.is_private and not self.request.user.has_perm('is_admin'): raise PermissionDenied
return obj
def get_context_data(self, **kwargs: any) -> dict[str, any]:
context = super().get_context_data(**kwargs)
context.update({
'search': Search(),
})
return context
def render_to_response(self, context, **response_kwargs):
self.object: Post
if (
self.object.password
and not self.request.user.has_perm('is_admin')
and self.request.POST.get('password') != self.object.password
): self.template_name = 'secret.html'
return super().render_to_response(context, **response_kwargs)
@method_decorator(csrf_protect)
def post(self, request, *args, **kwargs): return self.get(request, *args, **kwargs)
...
DetailView
장고에서 개별 글 조회 페이지를 구현하기 위해 제공하는 class View입니다.
이외에도 ListView, EditView, CreateView, DeleteView 등 다양한 class View가 존재합니다.
model variable
detail view에서 가져올 object의 model class입니다.
template_name variable
detail view에서 보여줄 페이지에 사용할 템플릿명입니다.
get_object method
detail view에서 object를 가져올 때 사용하는 method입니다.
get_context_data method
detail view에서 context를 가져올 때 사용하는 method입니다.
render_to_response method
detial view에서 페이지를 랜딩할 때 사용하는 method입니다.
post method
class View에서 POST 요청을 받았을 때 사용되는 코드입니다.
마찬가지로 GET 요청을 받으면 get method가 동작합니다.
method_decorator
함수 위에 데코레이터를 적어놓기만 했던 function view와는 달리, class view에서는 method_decorator를 사용해야 데코레이터를 이용할 수 있습니다.
url 변경하기
detail 이름으로 선언한 url에서 사용할 view를 views.detail에서 views.Detail로 변경합니다.
함수의 이름만 적어넣으면 됐던 function view와는 달리 class view의 경우 ".as_view()"를 붙여주어야 합니다.
# entry/url.py
from django.urls import path
from . import views
app_name = 'entry'
urlpatterns = [
...
# path('<int:pk>/', views.detail, name='detail'), # 제거
path('<int:pk>/', views.Detail.as_view(), name='detail'), # 추가
...
]
템플릿 작성하기
이용자가 비밀 글에 접근했을 때, 비밀번호를 입력할 html을 작성합니다.
# templates/secret.html
{% extends 'base.html' %}
{% block title %}
{% if object.is_private %}[비공개] {% endif %}
{% if object.password %}[비밀] {% endif %}
{{ object.title }}
{% endblock %}
{% block main %}
<div>
<a href="{% url 'entry:index' %}">글 목록</a>
</div>
<hr>
<article>
<form method="post" style="display: flex;">
<label for="id_password">비밀번호 입력</label>
{% csrf_token %}
<input name="password" type="password" id="id_password">
<input type="submit" value="비밀번호 제출">
</form>
</article>
{% endblock %}
확인해보기
이제 관리자가 아닌 이용자가 조회할 수 없는 글을 만들고 관리할 수 있게 되었습니다.

이 글의 댓글 기능은 일부러 막아놓았습니다. 궁금한 내용이 있다면 게시판을 이용해주세요!