728x90
글과 댓글 모델(블로그) 구현
01. 1대다 연결
- 하나의 부모 요소에 여러 자식 요소가 연결된 연결
- 블로그 댓글은 수에 상관없이 반드시 하나의 글과 연결되므로 글과 댓글은 1대다 연결
- 하나의 글에 여러 댓글이 달렸을 때, 이를 하나의 테이블로 나타낸다면 반드시 중복 데이터가 나타나게 됨
- 이 경우 글 제목을 수정하려면 반드시 3개의 데이터가 동시에 수정되어야 함
- 이런 비효율적인 구조를 막기 위해 1대다 관계를 가지는 데이터는 각각의 테이블에 나누어 저장
- ID열은 테이블에 있는 데이터를 구분할 수 있는 유일한 값
- 하지만 이렇게 나누면 각 댓글이 어떤 글에 달린 글인지 알 수 없게 됨
- 따라서 댓글 테이블의 각 행에 해당 댓글이 어떤 글과 연결되는지 나타내야함
1대다 관계 모델 구현
# blog/models.py
class Post(models.Model):
# 포스트제목, 포스트내용은 주석
title = models.CharField("포스트 제목", max_length = 100)
content = models.TextField("포스트 내용")
def __str__(self):
return self.title
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete = models.CASCADE)
content = models.TextField("댓글 내용")
def __str__(self):
return f"{self.post.title}의 댓글 (ID: {self.id})"
관리자 페이지 등록
# blog/admin.py
from django.contrib import admin
from blog.models import Post
# Register your models here.
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
pass
02. 글과 댓글 보여주기
- 전체 글 목록 보여주기
# blog.view.py
def post_list(request):
posts = Post.objects.all()
print("전체 블로그 글 리스트: ", posts)
context = {
"posts" : posts,
}
return render(request, 'post_list.html', context)
# blog/templates/post_list.html
<!DOCTYPE html>
<html lang="ko">
<body>
<h1>게시글 목록</h1>
<!-- 글 목록을 ul 요소 내에 표시 -->
<ul>
{% for post in posts %}
<li>
<h2>{{ post.title }}</h2>
<p>{{ post.content }}</p>
</li>
{% endfor %}
</ul>
</body>
</html>
03. 댓글 목록 보여주기
Django ORM을 사용한 1:N 객체 접근
- Comment 모델은 post속성으로 자신과 연결된 Post에 직접 엑세스 할 수 있음(정방향 관계)
- 반대로 1:N 에서 1 방면의 객체에서 N 방면의 객체로 접근하는 것은 역방향 관계
- 역방향 접근을 위한 속성은 {N방향 모델명의 소문자화}_set 이라는 이름으로 Django ORM이 자동 생성해줌
- post.comment_set 은 RelatedManager 객체
- N방향의 객체로 접근할 수 있도록 도와주는 역할
- Comment 입장에서 연결된 Post는 1개 뿐이기 때문에 곧바로 접근이 가능하지만
- Post 입장에서는 연결된 Comment들이 여럿이므로 Manager객체를 통해서 접근
# blog/templates/post_list.html
{% for post in posts %}
<li>
<h2>{{ post.title }}</h2>
<p>{{ post.content }}</p>
<!-- 순회 중인 post와 연결된 모든 comment QuerySet -->
<ul>
{% for comment in post.comment_set.all %}
<li>{{ comment.content }}</li>
{% empty %} <!-- post.comment_set.all 에 순회할 항목이 없는 경우 -->
<li>아직 댓글이 없습니다</li>
{% endfor %}
</ul>
</li>
{% endfor %}
- 순회할 항목이 없는 경우를 뜻하는 {% empty %} 태그를 중간에 사용하여 댓글이 없는 경우에는 "아직 댓글이 없습니다" 라는 문장을 출력
04. CSS와 정적파일
- 정적 파일(Static files)
- 사전적으로는 변화가 없는 파일
- 웹 프레임워크에서 정적파일이란 프레임워크의 소스코드를 제외한 나머지 이미지, 동영상, CSS, JavaScript파일 등
- 소스코드는 일반적으로 동적인 결과물을 만들어내는 데 사용하지만 정적파일은 사용자에게 변형 없이 언제나 동일한 형태로 제공됨
# pylog/settings.py
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
05. 템플릿에서 정적파일 사용
- 템플릿에서 정적파일을 불러올 때는 {% static '정적파일경로' %} 태그를 사용
{% load static %}
<!DOCTYPE html>
<html lang="ko">
<head>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
06. 유저가 업로드하는 정적파일
정적파일의 분류
- Django에서 정적파일은 두 가지로 나뉨
- 소스코드에 포함되는 정적파일
- 유저가 업로드하는 정적파일
- 이전에 정적파일을 넣어뒀던 디렉터리는 소스코드에 포함되는 정적파일을 두는 곳
- 프로젝트의 일부분으로 취급됨
- 유저가 업로드하는 정적파일은 프로젝트에 포함되지 않음
- 블로그라는 전체 프로젝트와는 별개로 블로그를 사용하는 사용자들이 업로드하는 글에 포함된 이미지와 같은 데이터를 의미
- Django에서 소스코드에 포함되는 정적파일은 Staticfile이라 부르고, 유저가 업로드하는 정적파일은 User-uploaded static file이라 부름
유저가 업로드하는 정적파일 설정
- settings.py 에서 소스코드에 포함되는 정적파일의 설정은 STATIC_ 으로 시작하고, 유저가 업로드하는 정적파일과 관련된 설정은 MEDIA_ 로 시작함
- MEDIA_URL
- 유저가 업로드한 파일에 접근할 수 있도록 브라우저에 제공하는 경로 접두어
- 소스코드에 포함되는 정적파일은 STATIC_URL 이라는 설정값을 사용하며 기본값은 "/static/"
- 유저가 업로드한 파일의 경로 접두어는 "/media/"를 사용
- MEDIA_ROOT
- 실제로 유저가 업로드한 파일이 저장될 경로
- 유저가 업로드한 정적파일은 프로젝트 디렉터리 하위의 media 디렉터리를 사용
# pylog/settings.py
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"
- 위와 같이 설정하면 pylog 프로젝트에서 유저가 업로드한 파일들은 BASE_DIR/media/ 디렉터리에 업로드됨
- 사용자가 처음으로 파일을 업로드할 때 MEDIA_ROOT 설정에 의해 Django가 자동으로 생성하므로 직접 해당 디렉터리를 생성할 필요는 없음
정적파일을 저장하는 필드 추가
- 각각의 글에 썸네일 이미지를 저장할 필드를 추가
- 이미지필드에서 선택한 파일은 BASE_DIR/media/post/ 디렉터리 안에 업로드됨
- settings.py 에서 MEDIA_ROOT 가 BASE_DIR/media/ 까지의 경로를 만들고, Post모델의 ImageField내의 upload_to="post"가 그 다음 경로를 생성하고 최종 경로에 업로드한 파일명으로 저장됨
- Django는 STATIC_URL 을 사용해 소스코드에 포함된 정적파일을 불러오는 것은 기본적으로 설정되어 있으나
- MEDIA_URL 을 사용해 유저가 업로드한 정적파일을 불러오는 것은 별도의 설정을 추가해야함
# pylog/ urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("blog.urls")),
]
urlpatterns += static(
# URL의 접두어가 MEDIA_URL일 때는 정적파일을 돌려준다
prefix = settings.MEDIA_URL,
# 돌려줄 디렉터리는 MEDIA_ROOT를 기준으로 한다
document_root = settings.MEDIA_ROOT,
)
템플릿에 업로드된 파일 보여주기
# blog/admin.py
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ["title", "thumbnail"]
# blog/templates/post_list.html
{% for post in posts %}
<li class="post">
<h2>{{ post.title }}</h2>
<p>{{ post.content }}</p>
<!-- 순회 중인 post와 연결된 모든 comment QuerySet -->
<ul class="comments">
{% for comment in post.comment_set.all %}
<li class="comment">{{ comment.content }}</li>
{% empty %} <!-- post.comment_set.all 에 순회할 항목이 없는 경우 -->
<li>아직 댓글이 없습니다</li>
{% endfor %}
</ul>
<img src="{{ post.thumbnail.url }}">
</li>
{% endfor %}
07. 유저가 업로드하는 정적파일 설정
- settings.py 에서 소스코드에 포함되는 정적파일의 설정은 STATIC_ 으로 시작하고, 유저가 업로드하는 정적파일과 관련된 설정은 MEDIA_ 로 시작함
- MEDIA_URL
- 유저가 업로드한 파일에 접근할 수 있도록 브라우저에 제공하는 경로 접두어
- 소스코드에 포함되는 정적파일은 STATIC_URL 이라는 설정값을 사용하며 기본값은 "/static/"
- 유저가 업로드한 파일의 경로 접두어는 "/media/"를 사용
- MEDIA_ROOT
- 실제로 유저가 업로드한 파일이 저장될 경로
- 유저가 업로드한 정적파일은 프로젝트 디렉터리 하위의 media 디렉터리를 사용
# pylog/settings.py
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"
- 위와 같이 설정하면 pylog 프로젝트에서 유저가 업로드한 파일들은 BASE_DIR/media/ 디렉터리에 업로드됨
- 사용자가 처음으로 파일을 업로드할 때 MEDIA_ROOT 설정에 의해 Django가 자동으로 생성하므로 직접 해당 디렉터리를 생성할 필요는 없음
08. 정적파일을 저장하는 필드 추가
- 각각의 글에 썸네일 이미지를 저장할 필드를 추가
- 이미지필드에서 선택한 파일은 BASE_DIR/media/post/ 디렉터리 안에 업로드됨
- settings.py 에서 MEDIA_ROOT 가 BASE_DIR/media/ 까지의 경로를 만들고, Post모델의 ImageField내의 upload_to="post"가 그 다음 경로를 생성하고 최종 경로에 업로드한 파일명으로 저장됨
- Django는 STATIC_URL 을 사용해 소스코드에 포함된 정적파일을 불러오는 것은 기본적으로 설정되어 있으나
- MEDIA_URL 을 사용해 유저가 업로드한 정적파일을 불러오는 것은 별도의 설정을 추가해야함
# pylog/ urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("blog.urls")),
]
urlpatterns += static(
# URL의 접두어가 MEDIA_URL일 때는 정적파일을 돌려준다
prefix = settings.MEDIA_URL,
# 돌려줄 디렉터리는 MEDIA_ROOT를 기준으로 한다
document_root = settings.MEDIA_ROOT,
)
09. 템플릿에 업로드된 파일 보여주기
# blog/admin.py
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ["title", "thumbnail"]
# blog/templates/post_list.html
{% for post in posts %}
<li class="post">
<h2>{{ post.title }}</h2>
<p>{{ post.content }}</p>
<!-- 순회 중인 post와 연결된 모든 comment QuerySet -->
<ul class="comments">
{% for comment in post.comment_set.all %}
<li class="comment">{{ comment.content }}</li>
{% empty %} <!-- post.comment_set.all 에 순회할 항목이 없는 경우 -->
<li>아직 댓글이 없습니다</li>
{% endfor %}
</ul>
<img src="{{ post.thumbnail.url }}">
</li>
{% endfor %}
10. 파일이 없는 ImageField 처리
- Post 의 thumbnail 에 이미지를 저장하지 않은 경우, ImageField와 연결되는 파일이 없기 때문에 url을 만들어낼 수 없다는 오류
- 파일이 저장되지 않은 경우에도 오류없이 화면을 표시하도록 템플릿을 수정해야함
# blog/templates/post_list.html
</ul>
{% if post.thumbnail %}
<img src="{{ post.thumbnail.url }}">
{% else %}
<img src="" alt="">
{% endif %}
</li>
{% endfor %}
글 상세 페이지
- 상세 페이지 기본 구조
- view : blog/views.py의 post_detail
- url : ID가 1번인 글은 /post/1/, ID가 2번인 글은 /posts/2/를 사용
- template : templates/post_detail.html을 사용
- 전체 글 목록을 볼 수 있는 /posts/와는 다르게 글의 상세 페이지는 자신의 ID값에 따라 서로 다른 동적인 URL을 가져야 함
01. 동적 URL 경로
- 새로 만들어질 글은 어떤 ID를 가지게 될 지 알 수 없음
- 어떤 숫자를 입력하든 입력받은 숫자의 ID에 해당하는 글의 상세 화면을 보여주어야 함
- 동적으로 숫자를 받을 수 있도록 URL을 수정해야함
#blog/views.py
def post_detail(request):
return render(request, "post_detail.html")
# blog/templates/post_detail.html
{% load static %}
<!DOCTYPE html>
<html lang="ko">
<head>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<div id="navbar">
<span>pylog</span>
</div>
<div id="post-detail">
<span>Post Detail</span>
</div>
</body>
</html>
# blog/urls.py
urlpatterns = [
path("", views.index),
path("posts/", views.post_list),
path("posts/<int:post_id>/", views.post_detail),
]
- int: 정수 형태의 값을 받도록 제한한다는 의미
- post_id: <와 >사이의 영역이 post_id라는 이름을 가진다는 의미
인수를 전달받을 수 있는 view 함수
- post_detail 함수는 현재 request 매개변수를 하나 받을 수 있음
- 모든 view 역할을 하는 함수는 request라는 인수를 받을 수 있어야 하며, 이는 Django의 규칙임
- view 함수의 request 매개변수에 값을 전달하는 것은 자동으로 Django가 처리하며 우리가 직접 값을 전달할 수 없음
- post_detail과 연결된 URL에 접근하면 post_detail을 호출
- 동적으로 값을 받을 수 있도록 패턴이 정의되었다면 패턴 부분에 해당하는 값을 view함수로 전달
- 예) /posts/1000/ 에 접근한다면 post_detail(post_id = 1000)으로 인수를 전달
# blog/views.py
def post_detail(request, post_id):
# id값이 URL에서 받은 post_id 값인 Post 객체
post = Post.objects.get(id = post_id)
print(post)
context = {
"post" : post,
}
return render(request, "post_detail.html", context)
# blog/templates/post_detail.html
<body>
<div id="navbar">
<span>pylog</span>
</div>
<div id="post-detail">
<h1>Post Detail</h1>
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
<p>{{ post.thumbnail }}</p>
</div>
</body>
02. CSS 적용
- 스타일은 잘 적용되어도 줄바꿈은 적용되지 않음
- HTML에는 줄바꿈 한 내용이 전달되지만, 기본적으로 HTML요소 내의 텍스트는 줄바꿈이 무시됨
- linebreaker를 추가하면 줄바꿈을 적용할 수 있음
# blog/templates/post_detail.html
<body>
<div id="navbar">
{% if post.thumbnail %}
<img src="{{ post.thumbnail.url }}">
{% endif %}
<span>{{ post.title }}</span>
</div>
<div id="post-detail">
<p>{{ post.content|linebreaksbr }}</p>
</div>
</body>
03. 댓글 기능
# blog/templates/post_detail.html
<div id="post-detail">
<p>{{ post.content|linebreaksbr }}</p>
<ul class="comments">
{% for comment in post.comment_set.all %}
<li class="comment">{{ comment.content }}</li>
{% empty %} <!-- post.comment_set.all 에 순회할 항목이 없는 경우 -->
<li>아직 댓글이 없습니다</li>
{% endfor %}
</ul>
</div>
# 글/댓글 작성
- 글 작성 페이지 기본 구조
- view : blog/views.py 의 post_add
- url : /posts/add
- Template : blog/templates/post_add.html
04. 사용자의 입력을 받는 Template
- HTML에서 사용자의 입력을 받는 요소는
- 입력에 대한 제목 : <label>
- 한 줄 짜리 텍스트 입력 : <input type="text">
- 여러 줄의 텍스트 입력 : <textarea>
- 버튼 : <button>
# blog/templates/post_add.html
<body>
<div>
<h1>POST ADD</h1>
<form method="GET">
<div>
<label>제목</label>
<input type="text">
</div>
<div>
<label>내용</label>
<textarea></textarea>
</div>
<button>작성</button>
</form>
</div>
</body>
- 사용자의 입력을 받는 요소는 form 태그로 감싸줌
- 사용자가 입력한 값을 처리하려면 단순히 input이나 textarea로 값을 받는 것 외에, 그 요소들을 반드시 form 태그 내부에 넣어야 함
- div로 각각의 입력할 항목들을 감싸고, 내부에 label로 해당 항목의 이름을 표시
- 제목은 여러 줄일 필요가 없으므로 input 요소를 사용
- 글 내용은 여러 줄로 이루어져 있으므로 textarea 요소를 사용
# blog/templates/post_add.html
<form method="GET">
<div>
<label>제목</label>
<input type="text" name="title">
</div>
<div>
<label>내용</label>
<textarea name="content" cols="50" rows="10"></textarea>
</div>
<button type="submit">작성</button>
</form>
- name 속성을 추가해서 각각의 요소에 입력된 값이 어떤 이름으로 view에 전달될지 지정
- button에 type="submit" 속성을 추가하여 버튼 클릭 시 form의 데이터를 전송하도록 지정
- URL로 전달한 값은 Django View에서 사용할 수 있음
- 사용자가 입력한 값을 단순한 조회가 아닌 DB에 새로운 객체를 생성하기 위해 사용할 때는 일반적으로 데이터를 URL로 전달하지 않음
- URL은 데이터의 길이 제한이 있어서 긴 텍스트는 전달할 수도 없고, 보안상으로도 좋지 않기 때문
- URL로 데이터를 전달하는 방식을 GET 메서드를 통한 전송이라 부르는데
- URL을 통하지 않고 더 많은 데이터를 제약없이 보내려면 POST 메서드를 사용해야함
- 어떤 방식으로 데이터를 전달할지는 form태그의 method속성에서 정의
# blog/templates/post_add.html
<div>
<h1>POST ADD</h1>
<form method="POST">
<div>
- Post 요청에 대한 Forbidden 오류
- 403 Forbidden : 요청은 받았으나 그 요청을 처리할 권한이 없기 때문에 서버에서 거부함
- CSRF 공격
- CSRF 인증에 실패하여 요청이 중단됨
- CSRF : Cross-Site Request Forgery(사이트 간 요청 위조)
- Django에서 처리하는 GET과 POST 요청
- 지금까지 CSRF 인증 오류가 발생하지 않은 이유는 Django 가 데이터를 처리하는 방식이 GET/POST에 따라 다르기 때문
- GET은 사이트의 특정 페이지에 접속하거나, 검색을 하는 등의 읽기/조회 행동을 수행하는 데 쓰임
- POST는 사이트의 특정 데이터를 변경/작성 하는데 쓰임
- 따라서 Django는 POST 요청에 대해서 GET 요청보다 더 높은 보안 수준을 적용
- GET 방식의 요청에서는 CSRF 보안을 적용하지 않음
- Django의 CSRF 공격 방어기법
- CSRF 공격 방어의 핵심은 로그인한 사용자가 의도하지 않은 POST 요청을 거부하는 것
- Django는 새로운 요청을 하는 브라우저마다 구분되는 값을 서버에 저장
- POST 요청을 하는 form이 브라우저별로 구분되는 값을 가지지 않는다면 요청을 거부
- 브라우저별로 구분되는 값은 서버에 저장되므로 브라우저를 이용하는 사람(이용자 또는 해커)은 그 값을 알 수 없음
- 이 기능을 이용하면 실 사용자의 POST 요청도 거부되게 되므로 Django는 실 사용자가 해당 값을 사용할 수 있는 기능을 제공
- Template 파일에서 {% csrf_token %} 태그를 사용하면 이 영역은 브라우저별로 구분되는 값으로 치환됨
- 이 값을 확인할 수 있는 시점은 HTML파일이 사용자에게 전달되어 브라우저에 그려진 다음이며, 해커는 이 값을 미리 알 수 없음
- CSRF 공격 방어의 핵심은 로그인한 사용자가 의도하지 않은 POST 요청을 거부하는 것
# blog/templates/post_add.html
<form method="POST">
{% csrf_token %}
<div>
- hidden type을 갖게 되면 해당 input은 브라우저에 나타나지 않으며 단순히 고정된 데이터를 담게 됨
05. view에서 POST요청 처리
# blog/views.py
def post_add(request):
if request.method == "POST": # method 가 POST일 때
# print(request.POST) # POST 메서드로 전달된 데이터를 출력
title = request.POST["title"]
content = request.POST["content"]
print(title)
print(content)
# method가 POST가 아닐 때
# else:
# print("method GET")
return render(request, "post_add.html")
- GET 메서드로 보낸 데이터는 request.GET 에 담겨오며, POST메서드로 보낸 데이터는 request.POST에 담겨서 전달됨
- View로 전달된 요청이 GET인지, POST인지는 request.method 속성에 정의됨
06. POST 데이터를 사용한 DB row 생성
- ORM을 사용해서 DB에 데이터를 생성할 때는 create메서드를 사용
- created_instance = 모델.objects.create(필드명=필드값)
# blog/views.py
def post_add(request):
if request.method == "POST": # method 가 POST일 때
# print(request.POST) # POST 메서드로 전달된 데이터를 출력
title = request.POST["title"]
content = request.POST["content"]
# print(title)
# print(content)
Post.objects.create(
title=title,
content=content,
)
# method가 POST가 아닐 때
# else:
# print("method GET")
return render(request, "post_add.html")
# blog/views.py
from django.shortcuts import render, redirect
...
def post_add(request):
if request.method == "POST": # method 가 POST일 때
# print(request.POST) # POST 메서드로 전달된 데이터를 출력
title = request.POST["title"]
content = request.POST["content"]
# print(title)
# print(content)
new_post = Post.objects.create(
title=title,
content=content,
)
return redirect(f"/posts/{new_post.id}/")
# method가 POST가 아닐 때
# else:
# print("method GET")
return render(request, "post_add.html")
- 기존에는 작성 버튼을 누르면 글이 생성된 후 다시 글 작성 페이지로 이동했음
- 다시 글 작성 페이지로 오는건 어색하고 작성한 글을 볼 수 있는 글 목록 페이지나 글 상세 페이지가 자연스러움
- 글 목록 페이지의 URL은 언제나 변하지 않지만 상세페이지는 각각의 게시글마다 URL이 달라짐
- 포매팅을 이용해 생성된 게시글의 id를 포함한 URL로 이동
07. 댓글 작성
- 글 작성과의 차이점
- 제목이 없음
-
- 작성한 댓글이 반드시 어떤 글(Post)에 소속되어야 함
08. 댓글 생성 form을 template에 추가
- 일반적으로 댓글 목록은 글 상세 페이지의 가장 하단에 보임
# templates/post_detail.html
<div id="navbar">
{% if post.thumbnail %}
<img src="{{ post.thumbnail.url }}">
{% endif %}
<span>{{ post.title }}</span>
</div>
<div id="post-detail">
<p>{{ post.content|linebreaksbr }}</p>
<ul class="comments">
{% for comment in post.comment_set.all %}
<li class="comment">{{ comment.content }}</li>
{% empty %} <!-- post.comment_set.all 에 순회할 항목이 없는 경우 -->
<li>아직 댓글이 없습니다</li>
{% endfor %}
</ul>
<form method="POST">
{% csrf_token %}
<div>
<label>내용</label>
<textarea name="comment" cols="1" rows="1"></textarea>
</div>
<button type="submit" class="btn btn-primary">댓글작성</button>
</form>
</div>
</body>
</html>
09. View에서 Comment생성
- form이 POST요청을 하므로 view에서 POST요청을 처리할 수 있도록 해야함
# blog/views.py
def post_detail(request, post_id):
# id값이 URL에서 받은 post_id 값인 Post 객체
post = Post.objects.get(id = post_id)
print(post)
if request.method == "POST":
# textarea의 name 속성값("comment")을 가져옴
comment_content = request.POST["comment"]
print(comment_content)
# 전달된 "comment"의 값으로 Comment 객체를 생성
Comment.objects.create(
post = post,
content = comment_content,
)
# GET 요청으로 글 상세 페이지를 보여주거나
# POST 요청으로 댓글이 생성되거나
# 두 경우 모두, 이 글의 상세 페이지를 보여주면 됨
context = {
"post" : post,
}
return render(request, "post_detail.html", context)
10. 글 작성시 이미지 업로드
- 파일을 첨부할 때는 <input type="file"> 태그를 사용
# blog/templates/post_add.html
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div>
<label>제목</label>
<input type="text" name="title">
</div>
<div>
<label>내용</label>
<textarea name="content" cols="50" rows="10"></textarea>
</div>
<div>
<label>썸네일</label>
<input name="thumbnail" type="file">
</div>
<button type="submit" class="btn btn-primary">작성</button>
</form>
- 파일을 전송해야 하는 form에는 enctype="multipart/form-data" 속성을 추가
- enctype 속성은 데이터를 서버로 전송할 때 어떤 인코딩 유형을 사용할 것인지 나타냄
- form에 별도의 enctype을 지정하지 않으면 텍스트 데이터만 보낼 수 있는 인코딩 방식을 사용
- view에서 POST로 전달받은 데이터는 request.POST에서 가져옴
- 전송된 파일은 request.FILES에서 가져와야함
# blog/views.py
def post_add(request):
if request.method == "POST": # method 가 POST일 때
# print(request.FILES)
# print(request.POST) # POST 메서드로 전달된 데이터를 출력
title = request.POST["title"]
content = request.POST["content"]
thumbnail = request.FILES["thumbnail"] # 이미지 파일
# print(title)
# print(content)
new_post = Post.objects.create(
title=title,
content=content,
thumbnail = thumbnail, # 이미지 파일을 객체 생성 시에 전달
)
return redirect(f"/posts/{new_post.id}/")
# method가 POST가 아닐 때
# else:
# print("method GET")
return render(request, "post_add.html")
- request.FILES 에는 thumbnail이라는 이름으로 InMemoryUploadFile이 전달됨
728x90
'03_Web' 카테고리의 다른 글
18_ 블로그 Django 프로젝트 (1) | 2025.02.17 |
---|---|
17_인스타그램 구현 (1) | 2025.02.08 |
15_검색기능이 되는 화면 만들기 (2) | 2025.02.06 |
14_VIEW, URL, Template (1) | 2025.02.06 |
13_Django_게시물 작성/수정/삭제 기능 (0) | 2025.02.05 |