학습 날짜: 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)
- 모델 생성 후 마이그레이션
- python manage.py makemigrations posts
- 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

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에 저장되는 입력 |
'LG U+ Why Not SW Camp 8기 > 학습 로그' 카테고리의 다른 글
| 공부 일지 #47 | Container 개념과 Docker 생성하기 (0) | 2025.10.14 |
|---|---|
| 공부 일지 #46 | Django로 영화 추천 웹 만들기 (0) | 2025.10.05 |
| 공부 일지 #44 | Django 실습3: 로그인과 회원가입 (0) | 2025.10.04 |
| 공부 일지 #43 | Django 실습2: 글 & 이미지 업로드 절차 (0) | 2025.09.30 |
| 공부 일지 #42 | Flask & Django 실습 (0) | 2025.09.28 |