학습 날짜: 2025.09.30 ~ 2025.10.01
🔷Django 추가 실습_Zstargram 만들기
1. 프로젝트 기본 설정
- 학습 일지 42 / 학습 일지 43 참고
- 터미널에서 프로젝트 생성: django-admin startproject config .
- 가상환경 및 PyCharm 인터프리터 설정
- settings.py 에 templates, static, media 경로 설정
- 시간대 변경:
# settings.py
TIME_ZONE = "Asia/Seoul"
2. Custom User 모델 설정
: 확장 가능하게 하기 위해 기본 auth_user 대신 사용자 정의 모델(User) 설정
# models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
# ↳ 이게 USER관리하는 라이브러리
class User(AbstractUser):
# blank=True: Null 허용
profile_image = models.ImageField("프로필 이미지", upload_to="users/profile", blank=True)
short_description = models.TextField("소개글", blank=True)
----------------------------------------------------------------------------------------------
# settings.py
AUTH_USER_MODEL = "users.User" # {APP이름}.{Model이름}으로 설정!
INSTALLED_APPS = [
"users",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
- 이미지가 들어가기 때문에 가상환경에서 Pillow 라이브러리 설치 필요 → pip install Pillow
3. DB 설정 (MySQL)
: 프로젝트 데이터를 저장할 데이터베이스 연결 설정
- MySQL에서 프로젝트 이름으로 스키마 생성
- config > settings.py에 데이터베이스 정보 입력
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": "zstargram", # 각자에 맞게 수정
"USER": "root",
"PASSWORD": "----", # 각자에 맞게 수정
"HOST": "localhost",
"PORT": 3306 # 각자에 맞게 수정
}
}
- Migrations 실행: python .\manage.py makemigrations → python .\manage.py migrate
- .\ 는 있어도 되고, 없어도 됨
- MySQL 에 가보면, 기본 테이블 auth_user 대신 users_user 생성되어 있음!

4. Admin 등록
: 관리자 페이지에서 사용자 모델을 관리할 수 있도록 등록
# admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from users.models import User
# 사용자(User) 모델을 관리자(admin) 페이지에 등록
@admin.register(User)
class UserAdmin(UserAdmin):
# 기본 UserAdmin에서 보여줄 필드 구성 (User 수정/상세 페이지)
fieldsets = [
(None, {"fields": ("username", "password")}),
("개인정보", {"fields": ("first_name", "last_name", "email")}),
("추가정보", {"fields": ("profile_image", "short_description")}),
("권한", {"fields": ("is_active", "is_staff", "is_superuser")}),
("기록", {"fields": ("last_login", "date_joined")}),
]
# 위의 섹션 제목("개인정보", "추가정보" 등)은 자유롭게 작성 가능
# 다만, 여기에서 지정하지 않으면 admin 폼에서 해당 필드를 볼 수 없음
# User 생성(Add User) 페이지에서 보이는 필드 구성
add_fieldsets = (
(None, {
"classes": ("wide",),
"fields": (
"username", "email", "password1", "password2",
"profile_image", "short_description"
),
}),
)
# 관리자 목록(User 리스트)에서 보여줄 컬럼들
list_display = (
"username", "email", "first_name", "last_name",
"profile_image", "short_description", "is_staff"
)
5. 로그인 페이지 생성
: 사용자 입력을 받을 로그인 화면 구성
a. views.py
def login_view(request):
return render(request, "users/login.html")
b. config/urls.py → users/urls.py 로 분리 관리
- URL 연결 시, 모든 URL을 config > urls.py 에 직접 등록하면 함수 개수만큼 경로를 만들어야 해서 관리가 비효율적임
- 따라서 config > urls.py 에서는 include("users.urls") 로 부모-자식 구조를 만든 뒤, 세부 경로는 users > urls.py 에서 처리
- 이렇게 하면 앱 단위로 수정이 편리하고 유지보수가 쉬움
# config > urls.py
from django.contrib import admin
from django.urls import path, include
from config.views import index
urlpatterns = [
path("admin/", admin.site.urls),
path("", index),
path("users/", include("users.urls")) # login 페이지 정보는 users.urls 가서 찾으라는 의미
]
# users > urls.py
from django.urls import path
from users.views import login_view
urlpatterns = [
path("login/", login_view)
]
- 절차 정리:
- 요청이 들어오면 config > urls.py 먼저 확인
- include("users.urls") 확인 후 → users > urls.py 로 이동
- 세부 URL과 view 함수로 연결
c. templates > users > login.html 만들기

6. Posts 앱 생성 및 Feeds 페이지 추가
: 로그인 후 볼 수 있는 피드 화면 만들기
a. posts/views.py에 함수 추가 및 config/urls.py에 url 추가
# posts/views.py
def feeds(request):
return render(request, "posts/feeds.html")
# config/urls.py
path("posts/", include("posts.urls"))
b. templates > posts > feeds.html 만들기

7. 로그인 여부 확인 후 피드 접근 제어
: 로그인하지 않은 사용자는 로그인 페이지로 Redirect 하기
a. posts/views.py 함수 수정
# posts > views.py
def feeds(request):
# 로그인을 했더라면, 어떤 정보를 보내주고,
# 로그인이 되어 있지 않다라면, 로그인을 하도록 해주자.
if not request.user.is_authenticated:
return redirect("/users/login/")
# print(request.user, request.user.is_authenticated)
# 로그인 상태면, username, True/False
# 로그아웃 상태면, AnonymousUser, False
# 인증받은 user만이 세션을 가짐. 세션은 일종의 통로
return render(request, "posts/feeds.html")
b. users/views.py 함수 수정
# users > views.py
def login_view(request):
if request.user.is_authenticated:
return redirect("/posts/feeds/")
return render(request, "users/login.html")
- 세션이 있으면 로그인 상태를 유지해서 화면을 띄워주고, 세션이 없으면 로그인 화면으로 돌려보냄
- 이 정보는 DB의 django_session 테이블에서 관리되며, 로그인/로그아웃 시 테이블 값이 달라짐
8. 로그인 폼(forms) 적용
: Django의 Form 객체를 활용해 입력값 검증 및 렌더링
a. users/forms.py 에 LoginForm 클래스 생성
- django는 객체(Form)만 만들면 HTML에서 바로 다룰 수 있음
- models 가 DB와 관련된 데이터를 다룬다면, forms 는 화면에서 입력되는 데이터를 다룸
- min_length, widget 같은 제약 조건을 설정하면, 폼이 자동으로 검증 및 경고 메시지 출력
# users/forms.py
from django import forms
class LoginForm(forms.Form):
username = forms.CharField(
min_length=3,
widget=forms.TextInput(
attrs={"placeholder": "사용자명(3자리 이상)"}
),
)
password = forms.CharField(
min_length=4,
widget=forms.PasswordInput(
attrs={"placeholder": "비밀번호(4자리 이상)"}
),
)
b. users > views.py수정
# users > views.py
from users.forms import LoginForm
def login_view(request):
if request.user.is_authenticated:
return redirect("/posts/feeds/")
# 인증받지 않았다면,
if request.method == "GET"
form = LoginForm()
context = {
'form' : form
}
return render(request, "users/login.html", context)
else:
# Post로 전달이 된 경우,
form = LoginForm(data=request.POST)
c. templates > users > login.html 수정
<!-- 아래 내용 추가 -->
<body>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">로그인</button>
</form>
</body>
- {{ form.as_p }} → 각 필드가 <p> 태그로 감싸져 출력됨
- {{ form.as_ul }} → 각 필드가 <li>로 감싸짐
- {{ form.as_table }} → <tr><td> 구조로 출력
9. 로그인 로직 구현
: 입력한 아이디/비밀번호를 DB와 비교해 세션 생성
# users > views.py 에 추가
else:
# Post로 전달이 된 경우,
form = LoginForm(data=request.POST) # POST로 넘어온 데이터를 FORM형태로 변환해서 씀
# 전달된 데이터에 대한 유효성 검사
if form.is_valid(): # False/True: 맞게 들어왔는지만 확인
username = form.cleaned_data["username"] # 유효한 데이터를 반환해줌
password = form.cleaned_data["password"]
# DB auth 정보와 일치하는 지 확인
user = authenticate(username=username, password=password)
if user:
login(request, user) # django.contrib.auth에 login 기능도 있음.
return redirect("/posts/feeds/")
else:
# form을 사용해 error처리도 가능
form.add_error(None, "입력한 ID나 PW에 해당하는 사용자가 없습니다.")
context = {'form': form }
return render(request, "users/login.html", context)
10. 로그아웃 기능 추가
: 로그아웃 버튼을 누르면 세션을 종료하고 로그인 페이지로 이동시키기
a. users > views.py에 함수 추가하고, users > urls.py에도 url추가
# users > views.py
def logout_view(request):
logout(request)
return redirect("/users/login")
-------------------------------------------------------------------------------------------
# users > urls.py
from users.views import login_view, logout_view
urlpatterns = [
path("login/", login_view),
path("logout/", logout_view) # 추가
]
b. templates > posts > feeds.html에서 로그아웃 버튼 만들기
<!--templates > posts > feeds.html에 추가 -->
<body>
<div>{{user.username}}</div>
<div>ID: {{ user.id }}</div>
<a href="/users/logout/">로그아웃</a> <!-- 단순히 링크로 추가하기 -->
</body>
11. 회원가입 기능 추가
: 새 사용자를 DB에 추가하고 자동 로그인 처리
a. users > forms.py 에 회원가입 폼 추가
# users > forms.py
class SignupForm(forms.Form):
username = forms.CharField()
password1 = forms.CharField(widget=forms.PasswordInput)
password2 = forms.CharField(widget=forms.PasswordInput)
profile_image = forms.ImageField()
short_description = forms.CharField()
b. users > views.py에 signup 함수 추가
# users > views.py
from django.shortcuts import render, redirect
from users.forms import LoginForm, SignupForm
from users.models import User # for username의 사용 여부 검사
def signup(request):
if request.method == "POST":
form = SignupForm(data=request.POST, files=request.FILES)
# form data 유효성 검사
if form.is_valid():
username = form.cleaned_data["username"]
password1 = form.cleaned_data["password1"]
password2 = form.cleaned_data["password2"]
profile_image = form.cleaned_data["profile_image"]
short_description = form.cleaned_data["short_description"]
# password 검사
if password1 != password2:
# Field: None으로 하면 상단에 에러메시지가 나타나게 됨
form.add_error(None, "비밀번호가 같지 않습니다.")
# username의 사용 여부 검사
if User.objects.filter(username=username).exists():
form.add_error(None, "사용 중인 사용명입니다.")
# form에 error 가 있으면 다시 작성하도록 함.
if form.errors:
context = {"form": form}
return render(request, "users/signup.html")
# 오류가 없으면 DB에 추가
else:
user = User.objects.create_user(
username = username,
password = password1,
profile_image = profile_image,
short_description = short_description,
)
login(request, user)
return redirect("/posts/feeds/")
# POST 아니면 그냥 form만 출력
else:
form = SignupForm()
context = {
"form":form
}
return render(request, "users/signup.html", context)
c. templates > users> signup.html 만들기
<!--templates > users> signup.html-->
<body>
<h2>signup page</h2>
<!--"multipart/form-data" 추가해야 이미지랑 텍스트가 같이 전달됨-->
<form method="POST" enctype="multipart/form-data">
{%% csrf_token %}
{{ form.as_div }}
<button type="submit">회원가입</button>
</form>
</body>
d. users > urls.py 에 url 추가
# users > urls.py
from users.views import login_view, logout_view, signup
urlpatterns = [
path("login/", login_view),
path("logout/", logout_view),
path("signup/", signup), # 추가
]
12. 필드 유효성 검사 추가
: 회원가입 시 입력값을 자동으로 검증하도록 Form 커스터마이징하기
- 기존에는 views.py에서 직접 username 중복, 비밀번호 일치 여부 등을 검사했지만, Django의 Form 클래스 내부에서 직접 유효성 검사를 수행하도록 변경
- 이를 위해 users > forms.py와 users > views.py 수정
a. users > forms.py 수정
# users>forms.py
class SignupForm(forms.Form):
username = forms.CharField()
password1 = forms.CharField(widget=forms.PasswordInput)
password2 = forms.CharField(widget=forms.PasswordInput)
profile_image = forms.ImageField()
short_description = forms.CharField()
def clean_username(self):
username = self.cleaned_data["username"]
# username의 사용 여부 검사
if User.objects.filter(username=username).exists():
# 강제 에러 발생(raise)
raise ValidationError(f"{username}은 이미 사용 중 입니다.")
else:
return username
def clean(self):
password1 = self.cleaned_data["password1"]
password2 = self.cleaned_data["password2"]
# password 검사
if password1 != password2:
# 방법2
self.add_error(None, "비밀번호가 같지 않습니다.")
- clean_fieldname : 단일 필드 검증
- clean() : 두 개 이상 필드 검증
- 함수 옆 과녁 표시는 mother에서 가지고 있는 함수를 overriding한다는 의미
- 검증 로직이 Form 내부로 들어가면서 views 코드가 훨씬 간결해짐
b. users > views.py 수정
# users/views.py
def signup(request):
if request.method == "POST":
form = SignupForm(data=request.POST, files=request.FILES)
if form.is_valid():
# clean 함수에서 모든 검증이 끝났기 때문에 바로 저장 가능
username = form.cleaned_data["username"]
password1 = form.cleaned_data["password1"]
profile_image = form.cleaned_data["profile_image"]
short_description = form.cleaned_data["short_description"]
user = User.objects.create_user(
username=username,
password=password1,
profile_image=profile_image,
short_description=short_description,
)
login(request, user)
return redirect("/posts/feeds/")
else:
# 유효성 검사 실패 시 다시 폼 렌더링
context = {"form": form}
return render(request, "users/signup.html", context)
else:
form = SignupForm()
return render(request, "users/signup.html", {"form": form})
13. save() 메서드로 코드 정리
: 검증 후 데이터 저장까지 Form 클래스에서 처리하도록 리팩토링
- 기존에는 검증과 저장 로직을 views.py에서 모두 처리했음
- 이제 SignupForm에 save() 메서드를 추가해 저장까지 Form 내부에서 처리
- views.py는 단순히 form.save()만 호출하면 됨
a. users > forms.py > SignupForm에 save 함수 추가
# users/forms.py
class SignupForm(forms.Form):
...
def save(self):
username = self.cleaned_data["username"]
password1 = self.cleaned_data["password1"]
profile_image = self.cleaned_data["profile_image"]
short_description = self.cleaned_data["short_description"]
user = User.objects.create_user(
username=username,
password=password1,
profile_image=profile_image,
short_description=short_description,
)
return user
b. users > views.py 수정
# users/views.py
def signup(request):
if request.method == "POST":
form = SignupForm(data=request.POST, files=request.FILES)
if form.is_valid():
user = form.save() # ✅ 검증과 저장을 form 내부에서 처리
login(request, user)
return redirect("/posts/feeds/")
# 에러 검사도 클래스에서 끝냈으니까 else면 바로 다시 폼 작성
else:
return render(request, "users/signup.html", {"form": form})
# POST 아니면 그냥 form만 출력
else:
form = SignupForm()
return render(request, "users/signup.html", {"form": form})
- form.is_valid() → 검증
- form.save() → 저장
- views.py는 흐름만 제어하고, 로직은 전부 Form 안에서 처리
- 코드 구조가 깔끔해지고 유지보수가 쉬워짐
⭐ 보충 개념 정리
1️⃣ config > views.py vs app > views.py
: 프로젝트 레벨과 앱 레벨 뷰의 차이
| 구분 | 프로젝트 레벨 views.py | 앱 레벨 views.py |
| 위치 | config/views.py (보통 없음) | blog/views.py, users/views.py 등 |
| 용도 | 전역 공용, 단순한 페이지 | 앱(기능)별 핵심 뷰 작성 |
| 권장 사용 | 최소화 → 보통 urls.py에서 바로 앱 연결 | 각 앱별 로직 분리하여 관리 |
| 예시 | index 페이지, health check | 블로그 글 목록, 댓글 작성, 로그인 등 |
- 실제 프로젝트에서는 프로젝트 레벨 views.py는 거의 사용하지 않음
- 대신 config/urls.py에서 바로 각 앱의 urls.py로 include 시키는 방식 선호
- 정리
- 프로젝트 레벨 views.py → 공용/테스트용, 최소한으로 사용
- 앱 레벨 views.py → 각 기능 구현의 핵심, 주로 작업하는 영역
2️⃣ redirect vs render
: Django에서 페이지 이동 처리 방식
| 구분 | redirect | render |
| 기능 | 페이지 이동 (URL 변경 발생) | 페이지 출력 (URL 유지) |
| 동작 방식 | 내부적으로 다른 view로 요청 전달 | 템플릿에 데이터를 담아 응답 생성 |
| 사용 예시 | 로그인 후 /posts/feeds/로 이동 | 로그인 화면 렌더링 |
| 핵심 개념 | “서버 내에서 페이지 교체 작업” | “화면에 내용을 쏘는 행위” |
3️⃣ clean 함수와 유효성 검사 개념
- clean_<fieldname>(): 각 필드별 검증
- clean(): 두 개 이상 필드 동시 검증
- 여러 조건 검증 시 clean() 내부에서 if문 또는 보조 메서드로 분리 처리
- clean으로 시작하는 함수들은 유효성 검사(validation) 과정에서 자동 실행
'LG U+ Why Not SW Camp 8기 > 학습 로그' 카테고리의 다른 글
| 공부 일지 #46 | Django로 영화 추천 웹 만들기 (0) | 2025.10.05 |
|---|---|
| 공부 일지 #45 | Django 실습4: 템플릿 적용과 1:N 관계 구현 (0) | 2025.10.05 |
| 공부 일지 #43 | Django 실습2: 글 & 이미지 업로드 절차 (0) | 2025.09.30 |
| 공부 일지 #42 | Flask & Django 실습 (0) | 2025.09.28 |
| 공부 일지 #41 | Flask & 크롤링 첫걸음 (0) | 2025.09.21 |