본문 바로가기

공부 일지 #45 | Django 실습4: 템플릿 적용과 1:N 관계 구현

@studying:)2025. 10. 5. 15:47

학습 날짜: 2025.10.01


🔷 Django 추가 실습: Zstargram 제작 중

1. CSS 적용하기 (static)

  • 경로: static/css/style.css
  • 규칙: .스타일명 { 속성:값; } → 마지막 세미콜론 필수!

a. css 예시

/* static/css/style.css */
.sibtn { background-color: #257e0d; color:white; }
.lgbtn { background-color: black; color:white; }
.fiddv { background-color: green; width: 200px; height: 100px; }

.navbar { background-color: #666; padding: 10px; }
.navbar ul { list-style:none; margin:0; padding:0; display:flex; gap:20px; }
.navbar li { display:inline; }
.navbar a { text-decoration:none; color:white; font-size:18px; padding:8px 12px; border-radius:5px; transition:background .3s; }
.navbar a:hover { background-color:#4CAF50; }

 

 

b. html 에 적용하기 ({% load static %} 먼저!)

<!-- 예시: signup.html 파일 -->
{% load static %} <!-- 경로 설정해줘야 불러올 수 있음-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SIGNUP</title>
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
    <h2>signup page</h2>

    <!--"multipart/form-data" 추가해야 이미지랑 텍스트가 같이 전달됨-->
    <form method="POST" enctype="multipart/form-data">
        {% csrf_token %}
        {{ form.as_div }}
        <!--class="css에서 만든 것"-->
        <button class="btn" type="submit">회원가입</button>
    </form>
</body>
</html>

2. base.html 생성해서 통일 양식 적용해보기

  • 각 페이지에서 base.html을 extends하고, 페이지별 내용은 {% block content %}{% endblock %}에만 작성.
  • 장점: 헤더/네비게이션/공통 스타일을 한 곳에서 관리 → 디자인 통일 & 유지보수 편함

a. templates > base.html 생성

<!-- base.html -->
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
    <nav class="navbar">
        <ul>
          <li><a href="/users/login">로그인</a></li>
          <li><a href="/users/signup">회원가입</a></li>
        </ul>
    </nav>
    {% block content %}{% endblock %}
</body>
</html>

 

 

b. base.html 적용 예시

<!-- signup.html -->
{% extends 'base.html' %}

{% block content %}
  <h2>signup page</h2>
  <form method="POST" enctype="multipart/form-data">
      {% csrf_token %}
      {{ form.as_div }}
      <button class="sibtn" type="submit">회원가입</button>
  </form>
{% endblock %}

<!-- ====================================================================== -->

<!-- index.html -->
{% extends 'base.html' %}

{% block content %}
  <h2>Index page</h2>
{% endblock %}

3. Post class 만들기

 

 

  • 관계 구조
    • User —(1:N)→ Post
    • Post —(1:N)→ PostImage
    • Post —(1:N)→ Comment
  • PK는 장고가 기본 제공.

a. post > models.py 에 class 추가

# post>models.py
from django.db import models

# 글(포스트) 클래스, 작성자 필요
class Post(models.Model):
    user = models.ForeignKey(
        "users.User",
        verbose_name="작성자",
        on_delete=models.CASCADE,
    )
    content = models.TextField("내용") # "내용"은 라벨명
    created = models.DateTimeField("생성일시", auto_now_add=True)

# 글에 이미지 삽시, 해당 글이 필요
class PostImage(models.Model):
    post = models.ForeignKey(
        Post,
        verbose_name="포스트",
        on_delete=models.CASCADE,
    )
    photo = models.ImageField("이미지", upload_to="post")

# 댓글 달 포스트와 댓글 작성자 표시가 필요
class Comment(models.Model):
    user = models.ForeignKey(
        "users.User",
        verbose_name="작성자",
        on_delete=models.CASCADE,
    )
    post = models.ForeignKey(
        Post,
        verbose_name="포스트",
        on_delete=models.CASCADE,
    )
    content = models.TextField("댓글")
    created = models.DateTimeField("생성일시", auto_now_add=True)
  • 모델 생성 후 마이그레이션
    1. python manage.py makemigrations posts
    2. python manage.py migrate

4. Post Class 를 Admin에 적용하기

  • Admin 페이지 적용 및 댓글·이미지 inline 클래스 추가
  • 이미지 파일명만 표시되는 문제 해결을 위한 미리보기 기능 추가

a. posts/admin.py에 inline 클래스 추가

# posts > admin.py
from django.contrib import admin
from posts.models import Post, PostImage, Comment

# 1:N 관계일 때, 자식 모델(PostImage,Comment)을 부모 모델(Post) admin 페이지에 표시
class CommentInline(admin.TabularInline):
    model = Comment
    extra = 1 # 기본으로 표시할 빈 form 개수

class PostImageInline(admin.TabularInline):
    model = PostImage
    extra = 1

 

b. 이미지 미리보기 (방법1: 커스텀 위젯)

# posts/admin.py (일부)

# 필요 import
from django.contrib.admin.widgets import AdminFileWidget
from django.utils.safestring import mark_safe

class InlineImageWidget(AdminFileWidget):
    def render(self, name, value, attrs=None, renderer=None):
        html = super().render(name, value, attrs, renderer)
        if value and getattr(value, "url", None):
            html = mark_safe(f"<img src='{value.url}' height='150'>") + html
        return html

class PostImageInline(admin.TabularInline):
    model = PostImage
    extra = 1
    formfield_overrides = {
        models.ImageField: {"widget": InlineImageWidget}
    }

커스텀 위젯을 사용한 경우

 

c. 이미지 미리보기 (방법2: 패키지 사용)

  • 더 간단한 방법: 터미널에서 가상환경 설정되어 있는지 확인하고 pip install 'django-admin-thumbnails<0.3' 입력
  • django-admin-thumbnails: 장고가 썸네일을 관리하는 패키지
# posts/admin.py (썸네일 데코레이터 활용)
import admin_thumbnails

# 아래만 코드만 사용
@admin_thumbnails.thumbnail("photo")
class PostImageInline(admin.TabularInline):
    model = PostImage
    extra = 1

django-admin-thumbnails 패키지를 사용한 경우


5. 피드 페이지에 데이터 출력

: 위 내용을 feeds.html에 적용하기

 

a. posts>views.py 수정

# posts/views.py
from django.shortcuts import render, redirect
from posts.models import Post

def feeds(request):
    if not request.user.is_authenticated:
        return redirect("/users/login/")

    posts = Post.objects.all()
    context = {"posts": posts}
    return render(request, "posts/feeds.html", context)

 

b. templates>posts>feeds.html 수정

<!--templates>posts>feeds.html-->
{% extends 'base.html' %}
{% block content %}
    <h2>Feed page</h2>
    <!--세션이 유지되고 있기 때문에, login_view()에서 redirect을 할 떄, user객체가 같이 옴-->
    {% if posts %}
        {% for post in posts %}
            <article>
                <table style="width:100%">
                  <tr>
                    <th>Post ID</th>
                    <th>User Profile Image</th>
                    <th>User Name</th>
                  </tr>
                  <tr>
                    <td>{{post.id}}</td>
                    <td>
		        <!-- PostImage의 FK가 post, post에서 역참조하여 이미지 불러옴-->
                        <!-- 역참조 이름: postimage_set -->
                        {% for image in post.postimage_set.all %}
                            <img src="{{ image.photo.url }}">
                        {% empty %}
                            None Profile Image
                        {% endfor %}
                    </td>
                    <td>{{post.user.username}}</td>
                  </tr>
                </table>
            </article>
        {% endfor %}
    {% endif %}
{% endblock %}
  • 역참조 이름: ForeignKey로 연결된 하위 모델 클래스 이름(소문자) + _set
  • 따라서, PostImage 모델을 Post에 연결했으니 → postimage_set 이 됨

6. 댓글 입력 기능 구현하기

a. 댓글 db에 적용하기 위해 posts>forms.py생성

# posts/forms.py
from django import forms
from posts.models import Comment

class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ["post", "content"]

# forms.Form 방식
# class CommentForm(forms.ModelForm):
#   content = forms.CharField(min_length ==200)

# (참고) forms.Form vs forms.ModelForm
# - ModelForm은 연결된 모델에 바로 저장(.save()) 가능, 코드 간결
# - Form은 모델 비연결: cleaned_data로 직접 저장 로직 작성

 

b. views.py & urls.py 수정

# posts/views.py
from django.shortcuts import render, redirect
from django.views.decorators.http import require_POST
from posts.models import Post
from posts.forms import CommentForm

def feeds(request):
    if not request.user.is_authenticated:
        return redirect("/users/login/")

    posts = Post.objects.all()
    comment_form = CommentForm() # 추가
    context = {"posts": posts, "comment_form": comment_form} # 추가
    return render(request, "posts/feeds.html", context)

# 추가
@require_POST
def comment_add(request):
    form = CommentForm(data=request.POST)
    if form.is_valid():
        comment = form.save(commit=False)  # 임시 저장
        comment.user = request.user        # 현재 접속해 있는 user를 작성자로 주입
        comment.save()                     # 최종 저장
    return redirect("/posts/feeds/")
# posts/urls.py
from django.urls import path
from posts.views import feeds, comment_add

urlpatterns = [
    path("feeds/", feeds),
    path("comment_add/", comment_add), # 추가
]

 

c. templates>posts>feeds.html 에 댓글 form 추가

{% extends 'base.html' %}
{% block content %}
  <h2>Feed page</h2>
  {% if posts %}
    {% for post in posts %}
      <article>
        <table style="width:100%">
          <tr>
            <th>Post ID</th>
            <th>User Profile Image</th>
            <th>Post Content</th>
            <th>User Name</th>
            <th>Likes</th>
            <th>Comment</th>
          </tr>
          <tr>
            <td>{{ post.id }}</td>
            <td>
              {% if post.user.profile_image %}
                <img src="{{ post.user.profile_image.url }}">
              {% else %}
                None Profile Image
              {% endif %}
            </td>
            <td>{{ post.content | linebreaksbr }}</td>
            <td>{{ post.user.username }}</td>
            <td>10</td>
            <td>1</td>
          </tr>

          {% for comment in post.comment_set.all %}
          <tr>
            <td colspan="4">{{ comment.content | linebreaksbr }}</td>
            <td>{{ comment.user.username }}</td>
            <td>{{ comment.created }}</td>
          </tr>
          {% empty %}
          <tr>
            <td colspan="6">댓글 없음</td>
          </tr>
          {% endfor %}
        </table>

        <br>
        <form method="POST" action="/posts/comment_add/">
          {% csrf_token %}
          <!-- 댓글 저장 시, 어떤 post에 달리는지 넘기기 -->
          <input type="hidden" name="post" value="{{ post.id }}">
          {{ comment_form.content }}
          <button type="submit">댓글 입력</button>
        </form>
      </article>
    {% endfor %}
  {% endif %}
{% endblock %}

⭐ 보충 내용 정리

  • 데이터베이스는 이미지 파일 자체를 저장하지 않음 → 이미지 경로만 저장, 실제 파일은 디스크에 저장
  • 코드 작성 후 refactoring 과정 필수 (가독성과 유지보수성 향상 목적)
  • HTML 디자인 참고: W3Schools Table 예제
  • 색상 참고: HTML CSS Color
  • forms.Form vs forms.ModelForm
구분  forms.Form forms.ModelForm
모델 연결 ❌ 없음 ✅ 특정 모델과 연결
필드 정의 직접 모든 필드 정의 모델의 필드 자동 생성
저장 방식 직접 save() 함수 구현 필요(cleaned_data 사용) 기본 제공 .save()로 DB 저장
유연성 자유롭지만 번거로움 모델 기반이라 코드 간결
사용 사례 Contact Form, 검색창, 회원가입 등 모델과 직접 관계 없는 입력 게시글 작성, 댓글 작성 등 DB에 저장되는 입력

 

studying:)
@studying:) :: what i studied

studying:) 님의 학습 여정을 기록하는 블로그입니다.

목차