
boards\models.py 확인
class Post(models.Model):
(... 생략 ...)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
created_at은 어드민에서 인스턴스가 새로 생성된 경우 당시의 시간이 저장 되어야 한다.
이 경우 모델에서 필드를 만들 때 auto_now_add=True 옵션을 주면 새로 생성한 경우 생성 시간이 자동으로 저장 된다.
updated_at은 업데이트, 즉 어드민에서 저장될 때마다 당시의 시간으로 업데이트 되어야 한다.
이 경우 모델에서 필드를 만들 때 auto_now=True 옵션을 주면 어드민에서 값을 저장할 때 마다 당시의 시간이 업데이트 된다.
boards\admin.py 을 아래와 같이 수정
#admin.site.register(Post)
class PostAdmin(admin.ModelAdmin):
search_fields = ['title','content']
list_display = ('id', 'user','title', 'created_at','updated_at')
admin.site.register(Post, PostAdmin)
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ('id', 'user','created_at','updated_at')
#admin.site.register(Comment)
admin 으로 들어 간후 아래와 같이 아무거나 수정후 updated_at 시간확인

작성한 게시글을 수정하려면 게시글 화면에서 "수정" 버튼을 클릭하여 수정 화면으로 진입해야 한다.
게시글 수정 버튼
수정 버튼은 로그인한 사용자와 글쓴이가 동일한 경우에만 노출되도록 수정
templates\boards\post_detail.html
<!-- 부모글 -->
<h2 class="border-bottom py-2">{{ post.title }}</h2>
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;">{{ post.content }}</div>
<div class="d-flex justify-content-end">
<div class="badge bg-light text-dark p-2 text-start">
<div class="mb-2">{{ post.user.username }}</div>
<div>{{ post.created_at }}</div>
</div>
</div>
<div class="my-3">
{% if request.user == post.user %}
<a href="{% url ''boards:post_modify' post.id %}"
class="btn btn-sm btn-outline-secondary">수정</a>
{% endif %}
</div>
</div>
</div>
수정 버튼은 로그인한 사용자와 글쓴이가 동일한 경우에만 노출되도록 {% if request.user == post.user %}을 적용하였다.로그인한 사용자와 글쓴이가 다르다면 수정 버튼은 보이지 않을 것이다.
boards\urls.py
아래와 같이 수정
(... 생략 ...)
urlpatterns = [
(... 생략 ...)
path('post/modify/<int:post_id>/', views.post_modify, name='post_modify'),
]
boards\views.py
@login_required(login_url='accounts:login')
def post_modify(request, post_id):
post = get_object_or_404(Post, pk=post_id)
if request.user != post.user:
messages.error(request, '수정권한이 없습니다')
return redirect('boards:detail', post_id=post.id)
if request.method == "POST":
form = PostForm(request.POST, instance=post)
if form.is_valid():
post = form.save(commit=False)
#post.modify_date = timezone.now() # 수정일시 저장
post.save()
return redirect('boards:detail', post_id=post.id)
else:
form = PostForm(instance=post)
context = {'form': form}
return render(request, 'boards/post_form.html', context)
post_modify 함수는 로그인한 사용자(request.user)와 수정하려는 질문의 글쓴이(post.user)가 다를 경우에는 "수정권한이 없습니다"라는 오류를 발생시킨다. 이 오류를 발생시키기 위해 messages 모듈을 이용하였다. messages는 장고가 제공하는 모듈로 넌필드 오류(non-field error)를 발생시킬 경우에 사용한다.
form_error.html 템플릿에서 필드오류와 넌필드오류에 대해서 출력했던 부분을 떠올려 보자.
게시글 상세 화면에서 "수정" 버튼을 클릭하면 http://127.0.0.1:8000/boards/post/modify/204/ 페이지가 GET 방식으로 호출되어 질문수정 화면이 보여진다. 게시글 수정화면에서 사용한 템플릿은 게시글 등록시 사용했던 boards/post_form.html 파일과 동일하다. 그리고 질문 수정 화면에서 "저장하기" 버튼을 클릭하면 http://127.0.0.1:8000/boards/post/modify/204/ 페이지가 POST 방식으로 호출되어 데이터가 수정된다. 왜냐하면 form 태그에 action 속성이 없는 경우 디폴트 action은 현재 페이지가 되기 때문이다.
GET 요청인 경우 질문수정 화면에 조회된 게시글의 제목과 내용이 반영될 수 있도록 다음과 같이 폼을 생성해야 한다.
form = PostForm(instance=post)
폼 생성시 이처럼 instance 값을 지정하면 폼의 속성 값이 instance의 값으로 채워진다. 따라서 게시글을 수정하는 화면에서 제목과 내용이 채워진 채로 보일 것이다.
POST 요청인 경우 수정된 내용을 반영해야 하는 케이스이므로 다음처럼 폼을 생성해야 한다.
form = PostForm(request.POST, instance=post)
위 코드의 의미는 instance를 기준으로 PostForm을 생성하지만 request.POST의 값으로 덮어쓰라는 의미이다. 따라서 게시글 수정화면에서 제목 또는 내용을 변경하여 POST 요청하면 변경된 내용이 PostForm에 저장될 것이다.
그리고 messages 모듈에 의해 발생되는 "수정권한이 없습니다"라는 오류가 표시될수 있도록 게시글 상세 화면 위쪽에 오류 영역을 추가하자.
templates\boards\post_detail.html
<div class="container my-3">
<!-- message 표시 -->
{% if messages %}
<div class="alert alert-danger my-3" role="alert">
{% for message in messages %}
<strong>{{ message.tags }}</strong>
<ul><li>{{ message.message }}</li></ul>
{% endfor %}
</div>
{% endif %}
물론 수정은 로그인 한 사용자와 글 작성자가 동일한 경우에만 가능하기 때문에 이 오류가 표시될 일은 없을 것이다. 하지만 비 정상적인 방법으로 질문을 수정할 경우 오류를 보여주어야 하므로 필요한 부분이다.
제 로그인 사용자와 글쓴이가 같으면 게시글 상세 화면에 <수정> 버튼이 보일 것이다.

작성한 게시글을 삭제하려면 게시글 상세 화면에서 "삭제" 버튼을 클릭하여 삭제해야 한다
templates\boards\post_detail.html
{% if request.user == post.user %}
<a href="{% url 'boards:post_modify' post.id %}"
class="btn btn-sm btn-outline-secondary">수정</a>
<a href="javascript:void(0)" class="delete btn btn-sm btn-outline-secondary"
data-uri="{% url 'boards:post_delete' post.id %}">삭제</a>
{% endif %}
<삭제> 버튼은 <수정> 버튼과 달리 href 속성값을 javascript:void(0)로 설정했다.
href 속성값을 javascript:void(0)로 설정하면 해당 링크를 클릭해도 아무런 동작도 하지 않는다.
그리고 삭제를 실행할 URL을 얻기 위해 data-uri 속성을 추가하고, <삭제> 버튼이 눌리는 이벤트를 확인할 수 있도록 class 속성에 "delete" 항목을 추가했다.
data-uri 속성은 자바스크립트에서 클릭 이벤트 발생시 this.dataset.uri와 같이 사용하여 그 값을 얻을 수 있다.
href에 삭제 URL을 직접 사용하지 않고 이러한 방식을 사용하는 이유는 삭제 버튼을 클릭했을때 "정말로 삭제하시겠습니까?" 와 같은 확인창이 필요하기 때문이다.
삭제 버튼을 눌렀을때 확인창을 호출하기 위해서는 다음과 같은 자바스크립트 코드가 필요하다.
아래 코드를 아직 추가하지 말자. 지금은 눈으로만 보자.
<script type='text/javascript'>
const delete_elements = document.getElementsByClassName("delete");
Array.from(delete_elements).forEach(function(element) {
element.addEventListener('click', function() {
if(confirm("정말로 삭제하시겠습니까?")) {
location.href = this.dataset.uri;
};
});
});
</script>
자바스크립트는 HTML 구조에서 다음과 같이 태그 바로 위에 삽입하는 것을 추천한다.
왜냐하면 이렇게 해야 화면 렌더링이 완료된 후에 자바스크립트가 실행되기 때문이다.
화면 렌더링이 완료되지 않은 상태에서 자바스크립트를 실행하면 화면의 값을 읽지 못하는 오류가 발생할수도 있고 화면 로딩이 지연되는 문제가 발생할 수도 있다. 따라서 자바스크립트는 태그 바로 위에 삽입하는 것이 좋다.
templates\layout\base.html
<body>
<!-- 네비게이션바 -->
{% include "layout/navbar.html" %}
<!-- 기본 템플릿 안에 삽입될 내용 Start -->
{% block content %}
{% endblock %}
<!-- 기본 템플릿 안에 삽입될 내용 End -->
<!-- 자바스크립트 Start -->
{% block script %}
{% endblock %}
<!-- 자바스크립트 End -->
</body>
</html>
templates\boards\post_detail.html
(... 생략 ...)
{% block script %}
<script type='text/javascript'>
const delete_elements = document.getElementsByClassName("delete");
Array.from(delete_elements).forEach(function(element) {
element.addEventListener('click', function() {
if(confirm("정말로 삭제하시겠습니까?")) {
location.href = this.dataset.uri;
};
});
});
</script>
{% endblock %}
태그 바로 위에 {% block script %}{% endblock %} 블록을 추가했다. 이렇게 하면 이제 base.html을 상속하는 템플릿은 자바스크립트의 삽입 위치를 신경쓸 필요없이 스크립트 블록을 사용하여 자바스크립트를 작성하면 된다.
{% block script %}과 {% endblock %} 사이에 게시글을 삭제할 수 있는 자바스크립트를 작성하였다.
boards\urls.py
(... 생략 ...)
path('post/delete/<int:post_id>/', views.post_delete, name='post_delete'),
그리고 위에서 정의한 views.post_delete 함수를 다음처럼 작성하자.
@login_required(login_url='accounts:login')
def post_delete(request, post_id):
post = get_object_or_404(Post, pk=post_id)
if request.user != post.user:
messages.error(request, '삭제권한이 없습니다')
return redirect('boards:detail', post_id=post.id)
post.delete()
return redirect('boards:index')
post_delete 함수 역시 로그인이 필요하므로 @login_required 애너테이션을 적용하고 로그인한 사용자와 글쓴이가 동일한 경우에만 삭제할 수 있도록 했다.
게시글을 작성한 사용자와 로그인한 사용자가 동일할 경우 다음처럼 상세조회 화면에 "삭제" 버튼이 노출될 것이다.
