[django] AI_ENTRY 웹페이지 프로젝트 -(6)
이번 포스팅에서는 블로그 글 수정, 삭제 기능을 구현해보도록 하겠다.
그 전에 css로 페이지를 좀 그럴싸하게 꾸며보려고 한다.
웹페이지 디자인은 gpt에 부탁했다.
진심 내 개인적인 사견으로는 프론트엔드는 몇몇 인재들 제외하곤 종말할것 같다.
요즘 생성형 AI가 너무 잘나온다 진짜로 ... 결국에는 인간의 창의성이 관건이어서, 단순 코더들은 다 사라질듯
ch99 > static > index.html @font-face {
font-family: 'NanumSquareRoundEB';
src: url('/static/fonts/NanumSquareRoundEB.ttf') format('truetype'); /* 폰트 파일 경로 및 형식 */
}
/* 전체 페이지 스타일 */
body {
font-family: 'NanumSquareRoundEB';
background-color: #f0f0f0;
margin: 0;
padding: 0;
}
/* 제목 스타일 */
h1 {
text-align: center;
background-color: #007bff;
color: #fff;
padding: 20px;
}
/* 테이블 스타일 */
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
table th {
background-color: #333;
color: #fff;
text-align: left;
padding: 10px;
}
table th, table td {
border: 1px solid #ccc;
padding: 10px;
}
/* "글쓰기" 버튼 스타일 */
.btn_post {
text-align: center;
margin: 20px 0;
}
.btn_post a {
display: inline-block;
padding: 10px 20px;
background-color: #007bff;
color: #fff;
text-decoration: none;
border-radius: 5px;
}
.btn_post a:hover {
background-color: #0056b3;
}
|
ch99 > template > base.html : index.html이 base.html을 상속 받아서, 여기에 index.css를 연결해주었다. <!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" type="text/css" href="{% static '/css/index.css' %}">
<title>소프트웨어야 놀자</title>
</head>
|
: 맨 처음에는 연결이 안되서 당황했는데, css파일은 <head></head> 안에 써주지 않은게 오류 발생의 원인이었다.
: navbar.html에도 똑같이 navbar.css를 적용해준다.
역시 사람도 옷차림에 따라 달라진다고, 웹페이지도 옷을 입히니까 떼깔이 좀 산다. 아직 로그인, 로그아웃 부분 디자인 좀 고치고, 페이지네이션 부분도 해결해야겠지만, 그래도 디자인을 입히니까 좀 그럴싸해보이지 않는가?!
그리고 이전 포스팅에서 문제점이었던 첨부파일이 다운이 안되는 문제점을 해결했다.
1. settings.py에서 미디어 파일에 대한 설정을 추가로 해준다. MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
: 'MEDIA_URL'은 미디어 파일을 제공할 URL경로이고, 'MEDIA_ROOT'는 서버 파일시스템에서 미디어 파일을 저장하는 경로이다.
: 그리고 프로젝트 디렉토리 아래에 'media'디렉토리를 만들어준다. 왜냐하면, 여기에에 나중에 업로드한 파일들이 저장된다.
![]() |
![]() |
-> 업로드를 하면 비어있던 디렉토리 안에 파일들이 들어 있는것을 확인해볼 수 있다.
2. URL 패턴 설정 urlpatterns = [
path('admin/', admin.site.urls),
path('board/', include('board.urls')),
path('common/', include('common.urls')),
path('', views.index, name='index'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
: 'urlspy'파일에서 'MEDIA_URL'을 서빙하기 위한 URL 패턴 추가하기
3. 폼 수정하기 class BoardForm(forms.ModelForm):
class Meta:
model = Board
fields = ['title', 'content', 'attachment']
widgets = {
'title': forms.TextInput(attrs={
'class': 'title',
'placeholder': '제목을 입력하세요'}),
'content': forms.Textarea(attrs={
'placeholder': '내용을 입력하세요'}),
'attachment': forms.FileInput() # 파일 업로드 위젯 설정
}
|
: enctype = "multipart/form-data" 속성을 가진 form을 만들기 위해서는 폼 클래스에서 FileInput 위젯을 사용해야 한다.
4. HTML 수정하기 <form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">게시물 작성</button>
|
: 게시물을 작성하는 HTML 에서도 <form> 엘리먼트에 enctype="multipart/form-data" 속성 추가해주기
: 이렇게 하면 위와 같이 media앱에 미디어 파일들이 잘 올라가는 것을 볼 수 있다.
지금부터는 게시글 삭제와 수정을 구현해보도록 하겠다.
1. 수정 view 작성해주기 from django.contrib.auth import get_user_model
#수정 페이지
User = get_user_model()
@login_required(login_url='common:login')
def modify(request, post_id):
post = get_object_or_404(Board, pk=post_id)
# 첫 번째 조건: 현재 로그인한 사용자가 게시물의 작성자인지 확인
if request.user == post.writer:
if request.method == 'POST':
form = BoardForm(request.POST, instance=post)
if form.is_valid():
post = form.save(commit=False)
post.modify_date = timezone.now()
post.save() # 수정된 게시물을 저장
messages.success(request, '게시물이 성공적으로 수정되었습니다.')
return redirect('board:detail', post_id=post.id)
else:
form = BoardForm(instance=post)
context = {'form': form, 'post': post}
return render(request, 'board/write.html', context)
else:
# 두 번째 조건: 현재 로그인한 사용자가 게시물의 작성자가 아니라면 권한 에러 메시지 표시
messages.error(request, '수정권한이 없습니다.')
return redirect('board:detail', post_id=post.id)
|
: get_user_model()을 사용하여 User만 수정할 수 있도록 한다.
: 만약 User가 post.writer가 맞다면, 수정할 수 있는 form을 넘겨주고, 아니라면 수정권한이 없다는 메시지를 띄운다.
2, 수정 HTML 작성해주기 : 페이지에 수정 버튼을 넣어줘야 하므로, HTML 파일을 수정해준다. <div class="modify">
{% if request.user == post.writer %}
<a href="{% url 'board:modify' post.id %}" class="btn btn-sm btn-outline-secondary">수정</a>
<a href="{% url 'board:delete' post.id %}" class="btn btn-sm btn-outline-secondary">삭제</a>
{% endif %}
</div>
|
: 나중에 삭제 기능도 구현할 예정이라, 삭제 버튼도 미리 넣어두었다.
3. URL 패턴 추가하기 urlpatterns = [
path('', views.index, name='index'),
path('<int:post_id>/', views.detail, name='detail'),
path('write/', views.write, name='write'),
path('modify/<int:post_id>/', views.modify, name='modify'),
path('delete/<int:post_id>/', views.delete, name='delete'),
]
|
: 버튼을 누르면 수정 view와 연결 될 수 있도록 url도 연결해준다. 삭제 기능도 구현할 예정이므로, 삭제 url도 만들어주었으니, 이제 삭제 view도 작성해준다.
4. 삭제 veiw 작성해주기 #삭제 페이지
@login_required(login_url='common:login')
def delete(request, post_id):
post = get_object_or_404(Board, pk=post_id)
if request.user != post.writer:
messages.error(request, '삭제권한이 없습니다')
return redirect('board:detail', post_id=post.id)
post.delete()
return redirect('board:index')
|
: 삭제 veiw도 수정과 동일하게 @login_required를 데코레이터로 사용한다. @login_required는 인증되지 않은 사용자가 뷰로 접근하는 것을 막아준다. 로그인되지 않은 경우, 이 view 자체에 접근하는 것을 거부한다. 즉, 로그인해야 이 view에 접근할 수 있다는 뜻! 여기도 동일하게 user = post.writer여야만 삭제가 가능하다.
: 수정하기를 누르면 바로 수정하기 폼이 뜨고, 삭제 버튼이 누르면 바로 게시물이 삭제되는 것을 볼 수 있다.
마지막으로 댓글 기능을 구현할 수 있도록 detail view를 수정해보도록 하겠다.
1. 댓글을 작성할 수 있는 Comment 모델 만들기 class Comment(models.Model):
post = models.ForeignKey(Board, on_delete=models.CASCADE, null=True)
body = models.TextField()
writer = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateTimeField(default=timezone.now)
|
: Comment 모델에는 4개의 필드가 있다. post 필드는 model이 Board모델과 관련있다는 것을 보여준다. on_delete = models. CASCADE는 게시물이 삭제될 때 댓글 comment도 함께 삭제된다는것을 의미한다.
: body필드는 댓글의 모델을 저장하고, writer 필드는 댓글을 작성한 사용자를 나타낸다. writer 필드는 User모델과 관련이 있는데, writer가 곧 User와 같기 때문이다.
; 마지막으로 date 필드는 댓글이 작성된 날짜와 시간을 저장하는데, models.DateTImeField를 사용하고 이때 default를 timezone.now로 하면 현재 시간으로 자동 저장할 수 있다.
이제 이 comment 모델을 detail 함수에 넣어주어야 한다.
2. ch99 > board 앱 디렉토리 > views.py > detail 함수 수정 #더보기 페이지
@login_required(login_url='common:login')
def detail(request, post_id):
post = get_object_or_404(Board, pk=post_id)
comments = Comment.objects.filter(post = post_id)
if request.method == "POST":
comment = Comment()
comment.post = post
comment.body = request.POST['body']
comment.date = timezone.now()
comment.writer = request.user
comment.save()
return render(request, 'board/detail.html', {'post': post, 'comments': comments})
|
: 만약 POST 요청일 경우 Commnet() 모델을 불러와서댓글을 작성할 수 있다. 양식을 제출하는 경우에 이 코드가 실행된다고 이해하면 된다.
3. HTML 파일 수정하기 <br><br>
댓글 달기 :
<ul>
{% for comment in comments %}
<li>{{ comment.body }}</li>
<li>{{ comment.writer }}</li>
{% empty %}
<li>댓글이 없습니다.</li>
{% endfor %}
</ul>
<form method="POST">
{% csrf_token %}
<input name="body" type="text" value="">
<input class="btn btn-primary" type="submit" value="댓글 남기기">
</form>
{% endblock %}
|
: HTML파일에 달린 댓글을 가져와서 보여줄 수 있도록 코드 작성하기
: POST 요청 - > 모델을 불러온다. input한 내용이 body가 되어 모델의 comment.body가 된다.
: 작성된 댓글을 보여줄 수도 있고, POST 요청으로 댓글을 작성하여 comment객체에 저장할 수도 있다.
이렇게 댓글을 달면 댓글이 잘 달리는 것을 확인할 수 있다 !
그런데, <li> 태그로 다니까, 너무 댓글이 댓글 답지 않은 것 같다.
기사에 나온 댓글처럼 구현을 하려면 javascrip 지식도 필요하더라. . .
결국 웹은 javascript다 이건가 ?!
이번 포스팅은 여기서 마무리
마지막 포스팅이 2주 전이더라.
원래 겨울에는 취약한 인간이라 요즘에는 하던일도 줄이고 줄이고 있다.
그냥 만사가 피곤하고, 집에서 쉬고만 싶은 마음 . . .
다음 포스팅에서는 잠깐 Flutter에 대해 다뤄보려고 한다.
오늘 갑자기 앱 만들기에 또 꽂혀서 찾아보니까 Flutter가 재밌다던데 ~