영벨롭 개발 일지

[Django]글 작성 & 사진 첨부 & 댓글 작성 가능한 모델 만들기 - 정리 본문

Back-end/Django

[Django]글 작성 & 사진 첨부 & 댓글 작성 가능한 모델 만들기 - 정리

영벨롭 2022. 5. 23. 15:34

[ 기본 세팅 ] 

 

1. 가상환경 생성 및 활성화

$ python -m venv myvenv
$ source myvenv/Scripts/activate

 

2. django 설치, 프로젝트 생성 및 이동

$ pip install django
$ django-admin startproject myproj
$ cd myproj

 

3. Application 생성 및 settings.py에서 application 등록

$ python manage.py startapp myapp

 

# myproj/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp'  # 어플리케이션 등록
]

 

 

 

 

 

[ 기본 페이지 작성 및 URL 등록 ] 

 

1. myapp/templates 폴더 생성 후, index.html 생성

 

 

2. index.html 작성

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h1>Django로 글 작성하기</h1>
</body>
</html>

 

3. urls.py & views.py 작성

# myapp/views.py 
from django.shortcuts import redirect, render

def home(request):
    return render(request, 'index.html')
    
    
# myproj/urls.py
from django.contrib import admin
from django.urls import path
from myapp import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home')
]

 

 

 

 

[ static & media 세팅 ] 

 

1. myproj/ 하위에 static, media 폴더 생성

 

2. settings.py 수정

# settings.py
import os

...

STATIC_URL = 'static/'

STATICFILES_DIRS = [
    BASE_DIR / 'static'
]

# 사용자가 업로드한 미디어 파일이 저장되는 루트 경로
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

# 사용자에 의해 업로드 된 미디어 파일을 접근할 경로 
MEDIA_URL = 'media/'

...

 

 

 

[ 모델 & 폼 만들기 ] 

 

1. Post, Comment 모델 만들기 

# myapp/models.py
from distutils.command.upload import upload
from django.db import models  # django의 models improt!!


class Post(models.Model):
    # 데이터마다 타입 명시
    title = models.CharField(max_length=50)  # post의 제목
    body = models.TextField()
    # 첨부한 사진 media/post_photo에 업로드
    photo = models.ImageField(blank=True, null=True, upload_to='post_photo')
    # auto_now_add=True: 자동으로 현재 시간을 추가
    date = models.DateTimeField(auto_now_add=True)
    # admin 사이트에서 Post 객체를 title로 표시

    def __str__(self):
        return self.title


class Comment(models.Model):
    comment = models.TextField()
    date = models.DateTimeField(auto_now_add=True)
    # Comment 객체는 Post 객체를 참조하기 때문에 외래키 설정
    # on_delete=models.CASCASE : 참조중인 Post 객체가 삭제된다면 해당 댓글도 삭제
    post = models.ForeignKey(Post, on_delete=models.CASCADE)

    def __str__(self):
      return self.comment

 

2. PostForm, CommentForm 모델폼 만들기

# myapp/forms.py
from dataclasses import field
from django import forms
from .models import Post, Comment


class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['title', 'photo', 'body']


class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ['comment']

 

3. admin 사이트에 모델 등록

# myapp/admin.py
from django.contrib import admin
from .models import Post, Comment

admin.site.register(Post)
admin.site.register(Comment)

 

4. migration

$ python manage.py makemigrations
$ python manage.py migrate

 

 

[ 새 글 작성 페이지 ] 

 

1.  views.py 에서 로직 작성

# myapp/views.py

from .models import Post
from .forms import PostForm

# 새 글 작성 폼
def post_form(request):
    # POST 요청이라면 -> 작성한 폼을 가져와서 DB에 저장하고 'home' 페이지로 이동
    if request.method == 'POST' or request.method == 'FILES':
        form = PostForm(request.POST, request.FILES)
        if form.is_valid():  # 유효성 검사
            form.save()  # DB에 저장
            return redirect('home')

    # GET 요청이라면 -> 폼을 보여줘야 함
    else:
        form = PostForm()
    return render(request, 'post_form.html', {'form': form})

 

 

2. 폼 페이지 

<!-- myapp/templates/post_form.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>새 글 작성 페이지</title>
</head>
<body>
  <!-- action=데이터를 처리할 서버 url, 빈문자열이면 현재 페이지로
  method='POST' POST 요청 보내야함
  enctype: 폼 데이터가 서버로 제출될 때 해당 데이터가 인코딩되는 방법 명시
    : method가 'POST'인 경우에만 사용 가능
   multipart/from-data: 모든 문자를 인코딩하지 않음을 명시, 파일이나 이미지 제출할때 사용-->
  <form action="" method="POST" enctype="multipart/form-data">
    <!-- django에서 form 태그를 사용하기 위해선 token 사용 필수 -->
    {% csrf_token %}

    <!-- django의 폼은 table로써 표현 가능 -->
    <table>
      {{ form.as_table }}
    </table>

    <!-- 서버로 데이터 전송 -->
    <input type="submit" value="새 글 작성">
  </form>
</body>
</html>

 

 

3. urls.py 에 폼 페이지 추가 

# myproj/urls.py
from django.contrib import admin
from django.urls import path
from myapp import views
from django.conf import settings  # media
from django.conf.urls.static import static  # media

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),

    path('postform/', views.post_form, name='post_form'),
]

# media 파일을 접근할 수 있는 url도 추가해야 함 (외우는 것이 좋음)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

 

 

4. index.html 에서 새 글 작성 페이지로 가는 <a> 태그 추가 

<!-- myapp/templates/index.html -->

<body>
  <h1>Django로 글 작성하기</h1>

  <!-- '새 글 작성' 을 누르면 urls.py에 등록한 post_form 이름의 path() 실행 -->
  <div class="new-post">
    <a href="{% url 'post_form' %}">새 글 작성</a>
  </div>
</body>

 

 

 

[ 작성된 글 목록 띄우기 ] 

 

1. views.py 에서 작성된 모든 Post 객체 넘겨주기

# myapp/views.py

def home(request):
    # 작성한 글들을 index.html에 띄우기
    # Post 객체를 DB에서 모조리 가져오기, 'date' 내림차순으로
    posts = Post.objects.filter().order_by('-date')
    return render(request, 'index.html', {'posts': posts})

 

 

2. index.html에서 넘겨받은 Post 객체를 for 문을 통해 띄우기

<!-- index.html -->

<body>
  <h1>Django로 글 작성하기</h1>

  <!-- '새 글 작성' 을 누르면 urls.py에 등록한 post_form 이름의 path() 실행 -->
  <div class="new-post">
    <a href="{% url 'post_form' %}">새 글 작성</a>
  </div>
  <hr/>

  <!-- 게시글 띄우기 -->
  {% for post in posts %}

  <ul class="post-list">
    <li>
      <a href="#">{{ post.title }}</a>
      <span>{{ post.date }} (post.id: {{ post.id }})</span>
    </li>
  </ul>

  {% endfor %}

</body>

 

 

 

 

[ 각 게시글의 디테일 페이지 & 댓글 기능 ] 

 

1. index.html 수정 -> 해당 게시글의 제목을 누르면 각 게시글의 디테일 페이지로 이동할 수 있는 url 설정 

<!-- index.html -->
...

  <!-- 게시글 띄우기 -->
  {% for post in posts %}

  <ul class="post-list">
    <li>
      <!-- name='detail' 인 url로 요청을 보내는데, 이때 각 게시글을 구분하기 위해
      post.id 도 같이 넘겨주어야 함-->
      <a href="{% url 'detail' post.id %}">{{ post.title }}</a>
      <span>{{ post.date }}  (post.id: {{ post.id }})</span>
    </li>
  </ul>

  {% endfor %}

 

 

2. urls.py 에 URL 등록

# myproj/urls.py

from django.contrib import admin
from django.urls import path
from myapp import views
from django.conf import settings  # media
from django.conf.urls.static import static  # media

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),

    path('postform/', views.post_form, name='post_form'),

    # 만약 post.id 가 1이라면 127.0.0.1:8000/detail/1 url에선
    # post.id가 1인 게시글을 보여주어야 함
    # 이때 views.py의 detail() 함수의 첫번째 인수는 request, 두번째 인수는 post_id
    path('detail/<int:post_id>', views.detail, name='detail'),

    # 댓글 객체를 보여줄 함수
    path('comment/<int:post_id>', views.comment, name='comment'),
]

# media 파일을 접근할 수 있는 url도 추가해야 함 (외우는 것이 좋음)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

 

 

3. views.py 에서 detail() 함수 정의 -> 디테일 페이지를 렌더링해주는 함수 

# myapp/views.py

def detail(request, post_id):
    # 이때 request는 GET 요청!

    # get_object_or_404 메소드를 통해 DB에서 Post 객체 중,
    # 기본키 pk가 post_id 인 객체를 찾고 찾지 못하면 404를 반환
    post_detail = get_object_or_404(Post, pk=post_id)

    # 각 게시글의 댓글 폼 보여주어야 함
    comment_form = CommentForm()

    return render(request, 'detail.html',
                  {'post_detail': post_detail, 'comment_form': comment_form})

 

 

 

4. detail.html 작성

<!-- detail.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Detail 페이지</title>
</head>
<body>
  <h1>제목: {{ post_detail.title }}</h1>
  <h3>작성날짜: {{ post_detail.date }}</h3>

  <h4>본문</h4>
  <p>
    {{ post_detail.body }}
  </p>

  <!-- 사진이 첨부되어 있다면 사진 보여주기 -->
  {% if post_detail.photo %}
    <img src="{{ post_detail.photo.url }}" alt="본문사진" height="200">
  {% endif %}

  <!-- 댓글 폼 -->
  <!-- 제출 버튼을 누르면 댓글 폼을 Post 해줄 서버로 넘겨주어야 함-->
  <!-- 이때 Comment 객체는 Post 객체를 외래키로 가지고 있기 때문에
   post_detail의 id도 넘겨주기 -->
  <form action="{% url 'comment' post_detail.id %}" method="POST">
    {% csrf_token %}
    {{ comment_form }}
    <input type="submit" value="댓글 작성">
  </form>
</body>
</html>

 

 

5. views.py 에서 각 게시글의 댓글 목록을 보여줄 함수 작성

# myapp/views.py

def comment(request, post_id):
    # CommentForm 객체 가져오기
    form = CommentForm(request.POST)

    if form.is_valid():
        # 일단은 DB에 저장하지 않고 대기하기
        finished_form = form.save(commit=False)
        # post_id에 상응하는 Post 객체를 찾고 Comment 객체의 post에 저장해야함
        # post는 Comment 객체의 외래키!
        finished_form.post = get_object_or_404(Post, pk=post_id)
        # DB에 반영
        finished_form.save()

    return redirect('detail', post_id)

 

 

 

6. detail.html 에서 댓글들의 목록 보여주기 

<!-- detail.html -->

...
 
  <hr>
  <!-- 댓글 목록들 -->
  <!-- django에서는 특정 객체의 집합들을 객체_set 이라고 함-->
  {% for comment in post_detail.comment_set.all %}
    <div class="comment">
      <span>{{ comment }}</span>
      <span>{{ comment.date }}</span>
    </div>
  {% endfor %}

 

 

 

 

 

 

반응형