
회사 내부 스터디 정리용 입니다.
장고 기본 파일 용도
**manage.py**
- Django 명령어를 실행하는 파일(다양한 기능 지원: python manage.py runserver)
**settings.py**
- 프로젝트의 다양한 설정 값을 관리하는 파일(필요에 따라 직접 수정하고 관리)
**urls.py**
- 라우팅(routing): 클라이언트의 요청을 어디서 어떻게 처리할 지 결정
startproject 사용법
- 2번째 방식으로 프로젝트 셋팅 진행 (디렉터리 이름이 헷갈림)
**django-admin startproject {프로젝트 이름}**
- {프로젝트 이름}만 적으면 같은 이름의 디렉토리를 두 개 생성
- django-admin startproject hello_world
- 루트 디렉토리: hello_world
- 설정 디렉토리: hello_world/hello_world
**django-admin startproject {프로젝트 이름} {프로젝트 경로} ✅**
- {프로젝트 이름}에 config, {프로젝트 경로}에 프로젝트 이름 입력
- django-admin startproject config hello_world
Hacker News 클론코딩
가상환경 생성
py -3.13 -m venv .hacker_news
Django 설치
pip install django
Django 프로젝트 생성
django-admin startproject config hacker_news
Django App 생성
- 장고에서 앱(APP)이란 프로젝트 내에서 특정 기능을 담당하는 독립적인 모듈이다.
- 프로젝트를 어떻게 논리적으로 구성할 건지에 따라서 앱을 생성한다.
- 예: user(회원 관리) 앱, post(게시글 관리) 앱 등
python manage.py startapp post
{app이름 지정}
- 장고에서 app을 만들면 아래와 같은 폴더들이 자동으로 생긴다.

- views가 controller 역할
Django 앱 생성 후, 반드시 할 일
앱 등록
- 방법: settings.py 안에 INSTALLED_APPS에 AppConfig 추가
- 목적: Django가 앱을 인식하고, 그 앱에 대한 여러 기능들을 활성화
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'post.apps.PostConfig',
]
- apps.py에 app과 관련 된 설정들이 적혀있음
- 습관적으로 app들을 등록하기!
View 작성
Django View
- 웹 애플리케이션의 비즈니스 로직을 처리하는 부분
- 클라이언트의 요청을 받아서 적절한 응답을 반환하는 역할

- 반드시 첫번째 매개변수로 request를 지정해줘야함
from django.http import HTTPResponse
from django.shortcuts import render
# Create your views here.
def index_view(request):
return HTTPResponse("Hello, Hacker News!")
라우팅 설정
# post/urls.py
from django.urls import path
from post import views
urlpatterns = [
path("index/", views.index_view, name="index"),
]
#config/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('posts/', include('post.urls')),
]

INSTALLED_APPS
- Django 프로젝트에서 사용하는 애플리케이션을 관리하는 목록
INSTALLED_APPS = [
'django.contrib.admin', #관리자
'django.contrib.auth', # 인증
'django.contrib.contenttypes', # 콘텐츠 타입
'django.contrib.sessions', # 세션 관리
'django.contrib.messages', # 메시징 프레임 워크
'django.contrib.staticfiles', # 정적 파일 관리
'post.apps.PostConfig', # Post 앱
]
- installed_apps에 모듈 단위인 APP을 추가해서 사용할 수 있다.
Migration
You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
- 18개의 적용되지 않은 migration 파일이 존재한다고 뜸
- migration파일이란 데이터베이스의 스키마 변경을 관리하는 파일이다.
python manage.py migrate
- 장고는 기본적으로 sqllite를 사용한다.
Post 모델 정의
# 게시물
# - 제목, 본문, 작성자 이름, 좋아요 개수, 게시물 생성 시간, 게시물 수정 시간
class Post(models.Model):
title = models.CharField(max_length=128)
body = models.CharField(max_length=1024)
author_name = models.CharField(max_length=32)
points = models.PositiveIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
Django 모델의 역할
- Django 모델 생성 방법
- mdoels.Model을 상속받아 class 정의
- 타입에 맞는 field를 통해 속성 정의
- ORM (=Object-Relational Mapping)
- 편리한 데이터베이스 스키마(테이블, 컬럼 등) 생성/수정/삭제 지원
- Python을 통해 데이터를 쉽게 조회/변경할 수 있도록 지원

python manage,py makemigrations
# 해당 명령어로 인해 스키마가 생성되지 않으면 모델 확인
python manage.py migrate
# 최신 상태로 변경

showmigrations # migration 상태와 database 상태를 확인.
# 현재 어디까지 데이터베이스에 적용됐는 지 보여줌
python manage.py migrate post zero
python manage.py migrate post 0001
⚠️ migrate를 이용해서 스키마를 rollback 할 때 데이터가 삭제 되는 경우도 있기 때문에 주의해야함
ORM 데이터 생성
>>> from post.models import Post
>>> Post.objects.all()
<QuerySet []>
>>> new_post = Post(title="제목", body="본문", author_name="작성자")
>>> new_post
<Post: Post object (None)>
>>> Post.objects.all()
<QuerySet []>
>>> new_post.save()
>>> Post.objects.all()
<QuerySet [<Post: Post object (1)>]>
>>> new_post
ORM 데이터 수정
record 표시 형식 바꾸기
- 바꾼 이후엔 Shell 껐다가 켜기
def __str__(self):
return f"{self.pk}, {self.title}"
변수 할당도 가능
>>> post = Post.objects.get(id=1)
>>> post
<Post: 1, 제목>
- 데이터 업데이트
>>> post.title = "새로운 제목"
>>> post.title
'새로운 제목'
>>> post.save()
QuerySet
- QuerySet이라는 클래스로 반환됨
- 장고 ORM에서 데이터베이스의 Query를 나타내는 객체
- 복잡한 조건문을 표현할 수 있게 도와주거나 불필요한 데이터를 조회하는 것을 막아줘서 효율성을 높아준다.
- 장고에 의해서 최적화 된다.
특징1: Iterable하다.
- 반복가능한 객체다.
- for문을 통해서 내부 인스턴스를 순회한다.
>>> for p in posts
... print(p)
...
1, 새로운 제목
2, 제목2
>>>
특징2: Lazy 로딩을 사용한다.
- 값이나 객체가 바로 동작하는 게 아니라 최대한 미뤘다가 마지막에 한번에 처리를 한다.
- 성능 최적화를 위해 불필요한 계산을 줄이고, 메모리 최적화를 한다.
- posts 객체를 사용하는 순간에 Query가 발생한다.
>>> posts = Post.objects.all() # 이때는 Query 호출 X
>>> list(posts) # 이때 DB에서 조회
[<Post: 1, 새로운 제목>, <Post: 2, 제목2>]
>>>
ORM 데이터 조회(filter)
- 앞서 사용한 get() 과 유사함
>>> Post.objects.filter(id=2)
<QuerySet [<Post: 2, 제목2>]>
>>> Post.objects.filter(title="새로운 제목")
<QuerySet [<Post: 1, 새로운 제목>]>
>>> Post.objects.filter(title="abcd")
<QuerySet []>
- startswith
- 앞에 새로운이 붙은 걸 찾아줌
>>> Post.objects.filter(title__startswith="새로운")
<QuerySet [<Post: 1, 새로운 제목>]>
- points_lt, points_gte (less than, greater than or equal)
- 10 이하인 거
- 1 이상인거
>>> Post.objects.filter(points__lt=10)
<QuerySet [<Post: 1, 새로운 제목>, <Post: 2, 제목2>]>
>>> Post.objects.filter(points__gte=1)
<QuerySet []>
- created_at__lt 도 사용가능
>>> Post.objects.filter(created_at__lt="2099-01-01")
C:\\Users\\Seeds\\Desktop\\스터디\\projects\\inflearn\\ch2\\.hacker_news\\Lib\\site-packages\\django\\db\\models\\fields\\__init__.py:1671: RuntimeWarning: DateTimeField Post.created_at received a naive datetime (2099-01-01 00:00:00) while time zone support is active.
warnings.warn(
<QuerySet [<Post: 1, 새로운 제목>, <Post: 2, 제목2>]>
현재 날짜에 문자를 넣어서 런타임 오류가 남
>>> from django.utils import timezone
>>> timezone.now()
>>> Post.objects.filter(created_at__lt=timezone.now())
<QuerySet [<Post: 1, 새로운 제목>, <Post: 2, 제목2>]>
>>> Post.objects.filter(created_at__gt=timezone.now())
<QuerySet []>
- timezone으로 바로 찍어보기 가능
Get()과 Filter()의 차이
- Get은 단일 객체 반환, Filter는 QuerySet 반환
- Get은 LazyLoading이 없음. Filter는 LazyLoading이 적용돼서 데이터가 실제로 필요한 순간에 실행
- Get 은 데이터가 없으면 오류를 뱉고, filter는 데이터가 없으면 빈 QuerySet 을 반환하기 때문에 데이터를 안전하게 가져와야 할 때는 filter권장
>>> Post.objects.get(id=4)
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "C:\\Users\\Seeds\\Desktop\\스터디\\projects\\inflearn\\ch2\\.hacker_news\\Lib\\site-packages\\django\\db\\models\\manager.py", line 87, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "C:\\Users\\Seeds\\Desktop\\스터디\\projects\\inflearn\\ch2\\.hacker_news\\Lib\\site-packages\\django\\db\\models\\query.py", line 635, in get
raise self.model.DoesNotExist(
"%s matching query does not exist." % self.model._meta.object_name
)
post.models.Post.DoesNotExist: Post matching query does not exist.
>>> Post.objects.filter(id=4)
<QuerySet []>
>>>
ORM 데이터 삭제
- 삭제할 장고 인스턴스를 호출 한 다음 delete
- save()를 호출할 필요없이 바로 삭제가 된다.
>>> from post.models import Post
>>> post = Post.objects.get(id=1)
>>> post
<Post: 1, 새로운 제목>
>>> post.delete()
(1, {'post.Post': 1})
다건 삭제
Post.objects.filter(title="제목").delete()
HackerNews 기능 정의 & REST API 설계
Hacker News 기능 정의
- 전체 게시글 목록을 확인할 수 있다 (작성자, points, 작성일)
- 새로운 게시글을 올릴 수 있다
- 게시글 내용을 읽을 수 있다
- 게시글에 좋아요를 누를 수 있다 (중복 요청 가능)
API 개념 알기
- API란 서버가 클라이언트에게 특정한 기능을 제공하는 형식이고, 상호간의 약속이다.

REST API
- 아키텍처 스타일

- 기능에 따른 REST API 설계
[GET] /posts/
[POST] /posts/
[GET] /posts/1
[POST] /posts/1/like/
Trailing Slash(트레일링 슬래쉬)
- URL 경로 가장 마지막에 붙는 슬래쉬 (/)
- Django에서 트레일링 슬래쉬를 사용하는 것이 권장
- Django 서버에 트레일링 슬래쉬가 없는 URL 요청을 하면 자동으로 리디렉션
- 예: /example로 요청시, 이를 /example/로 리디렉션
- 리디렉션을 원하지 않는다면, settings.py에 APPEND_SLASH=False 추가
- 자동 리디렉션 기능 비활성화
HackerNews 전체 게시글 목록 조회
- save 없이 한번에 객체를 생성하고, DB에 생성
>>> Post.objects.create(title="ChatGPT 활용법", body="프롬프트 엔지니어링의 이해", author_name="샘 알트만", points=10)
<Post: 4, ChatGPT 활용법>
>>> Post.objects.create(title="투자의 정석", body="첫번째 원칙은 잃지 않는 것이며, 두번째 원칙은 첫번째 원칙을 잊지 않는 것이다.", author_name="워렌 버핏", points=20)
<Post: 5, 투자의 정석>
>>> Post.objects.create(title="Dau1 정신", body="매일을 첫날처럼 사세요", author_name="제프 베조스", points=5)
<Post: 6, Day1 정신>
>>> Post.objects.all()
<QuerySet [<Post: 4, ChatGPT 활용법>, <Post: 5, 투자의 정석>, <Post: 6, Dau1 정신>]>
전체 게시글 조회 API 구현
views.py 생성
# post/views.py
from post.models import Post
def posts_view(request):
posts = Post.objects.all()
result = ", ".join([p.title for p in posts])
# "제목1, 제목2, 제목3"
return HttpResponse(result)
[p.title for p in posts] (리스트 컴프리헨션)
- posts를 하나씩 돌면서(p)
- 각 글의 제목(p.title)만 꺼내서
- 리스트로 만듦
", ".join(리스트)
- 문자열 함수 join은 리스트(또는 iterable)의 문자열들을 연결
- 앞의 ", "가 구분자(separator) 역할을 함
- 문자열 함수 join을 통해 최종적으로 문자열로 반환
route생성 urls.py
# post/urls.py
urlpatterns = [
path("", views.posts_view, name="posts_view"),
]
# config/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('posts/', include('post.urls')),
]
- config/urls.py에 post.urls를 연결을 해두었기 때문에 /posts로 들어가면 post/urls.py의 엔드포인트가 호출이 된다.
Django Template(템플릿)

HTML이란?
- 웹 페이지의 구조와 콘텐츠를 정의하는 마크업 언어

Django Template 사용 방법
- HTML 파일을 생성하여 기본적인 화면의 구조를 만든다.
- HTML 안에서 어떤 데이터를 어떻게 그릴지, Template Language로 표현한다.
- View의 응답으로 HTML을 반환한다.
Django Template Language
- HTMl 문서에 동적인 데이터를 삽입하거나 로직을 추가하기 위해 사용하는 문법
- {{}} ← 템플릿 언어
<p> HEllo, {{ user.username }}! </p>
HTML 작성
# post/templates/post_list.html
<h2>게시글 목록</h2>
{% if posts %}
<ul>
{% for post in posts %}
<li>{{ post.title }}</li>
{% endfor %}
</ul>
{% else %}
<p>No Posts!</p>
{% endif %}
- if, for 등을 사용할 땐 항상 endif, endfor 형식으로 닫아줘야한다.
Context 전달
# post/views.py
from django.shortcuts import render
from post.models import Post
def posts_view(request):
posts = Post.objects.all()
context = {"posts": posts}
return render(request, "post_list.html", context) # render가 html을 그려준다.
HackerNews 상세 게시글 조회
- post_detail.html 생성
# post/templates/post_detail.html
<h2>{{ post.title }}</h2>
<ul>
<li>내용: {{ post.body }}</li>
<li>작성자: {{ post.author_name }}</li>
<li>점수: {{ post.points }}</li>
<li>작성일: {{ post.created_at }}</li>
</ul>
<a href="{% url 'posts' %}"> 💭 게시글 목록</a>
# Django Template에서 url 태그로 이 url에 해당하는 링크를 장고가 알아서 찾아서 넣어준다.
# urlpatterns의 name을 넣어주면 된다.
<a href="{% url 'posts' %}"></a>
# post/urls.py
urlpatterns = [
path("", views.posts_view, name="posts"),
]
# post/views.py
def post_view(request, post_id):
post = get_object_or_404(Post, id=post_id) #get은 데이터가 없으면 에러 반환, 장고에서 지원해주는 404 에러 처리 사용가능
context = {"post": post}
return render(request, "post_detail.html", context)
- int를 통해 숫자 값이 들어오면 post_id로 판별
# post/urls.py
urlpatterns = [
path("", views.posts_view, name="posts"), # 전체게시글 목록
path("<int:post_id>/", views.post_view, name="post"), # 상세 게시글 조회
]
게시글 목록에서 제목을 클릭 했을 때 상세조회 처리
# post/templates/post_list.html
<ul>
{% for post in posts %}
<li>
<a href="{% url 'post' post.id %}">{{ post.title }}</a> # 추가
</li>
{% endfor %}
</ul>
DEBUG모드
# config/settings.py
**# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []**
- DEBUG모드가 True로 설정되어 있어서 404 에러가 떴을 때 장고가 상세한 내용을 보여줌

- 실제 사용자가 보는 에러 화면으로 보고 싶을 땐 False로 변경
- DEBUG False를 사용할 땐 꼭 ALLOWED_HOSTS를 설정해줘야한다.
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = ["*"]
HackerNews 게시글 작성
- 게시글 작성 html 추가
# post/templates/post_list.html
<form action="{% url 'posts' %}" method="post"> # posts로 전송
{% csrf_token %}
<h2>새로운 게시글 작성</h2>
<label for="title">제목: </label>
<input type="text" id="title" name="title" autocomplete="off" required> # 이전내용 기록 or 필수값지정
<label for="title">본문: </label>
<input type="text" id="body" name="body" autocomplete="off" required>
<label for="title">작성자: </label>
<input type="text" id="author_name" name="author_name" autocomplete="off" required>
</form>
CSRF(Cross-Stie Requeset Forgery) TOKEN
- CSRF 공격을 방지하기 위해 사용되는 보안 메커니즘
- CSRF 공격: 인증된 사용자가 악의적인 요쳥을 서버로 보내게 만드는 공격

- B 사이트에서 CSRF 악성코드를 받게 된다면, A웹 사이트에 로그인 된 정보로 돈 송금, 데이터 삭제와 같은 악성 공격이 발생된다.
- 악의적인 요청을 방지하기위해서
- Django에서는 각 요청마다 고유한 토큰을 생성하여, 요청의 진위를 검증하는 방식으로 CSRF 공격을 방지한다.
- Cookie 안에 csrftoken 저장
- csrf_token을 작성해주면 알아서 보안 메커니즘이 실행된다.
유효성 검사 및 게시글 작성 views추가
# post/views.py
def posts_view(request):
if request.method == "GET":
posts = Post.objects.all()
context = {"posts": posts}
return render(request, "post_list.html", context)
elif request.method == "POST":
request.POST
# title, body, author_name (html imput tag name)
title = request.POST.get("title") # None(default)
body = request.POST.get("body") # None(default)
author_name = request.POST.get("author_name") # None(default)
if not (type(title) is str and 128 >= len(title) > 0) :
return HttpResponseBadRequest("Invalid title")
if not (type(body) is str and 1024 >= len(body) > 0) :
return HttpResponseBadRequest("Invalid body")
if not (type(author_name) is str and 32 >= len(author_name) > 0) :
return HttpResponseBadRequest("Invalid author_name")
post = Post.objects.create(title=title, body=body, author_name=author_name)
context = {"post": post}
return render(request, "post_detail.html", context)
'개발 > Django 스터디' 카테고리의 다른 글
| [Django스터디] 6. DRF를 활용한 API 개발 (0) | 2026.02.28 |
|---|---|
| [Django스터디] 5. Django 내장 기능 활용하기 (0) | 2026.02.28 |
| [Django스터디] 4. Django ORM 이해하기 (0) | 2026.01.13 |
| [Django스터디] 3. 회원시스템 (0) | 2026.01.06 |
| [Django스터디] 2. Django 시작하기 (0) | 2025.12.09 |