本教程使用的环境是Python 3.10.8 ,Django 4.1.3
시작하기
# Django 설치
pip install django
# Django 버전 확인
py -m django --version
# 프로젝트 생성
mkdir project
django-admin startproject blog project
# 생성된 프로젝트 구조
# project/blog 디렉토리
# __init__.py
# asgi.py
# settings.py
# urls.py
# wsgi.py
# manage.py
# 개발 서버 실행
py manage.py runserver 8000
디렉토리 구조 설명
- manage.py: Django 프로젝트 관리용 커맨드라인 도구입니다.
- blog/init.py: 빈 파일로, 해당 디렉토리가 Python 패키지임을 나타냅니다.
- blog/settings.py: Django 프로젝트의 환경설정 파일입니다.
- blog/urls.py: URL 라우팅을 정의하는 파일입니다.
- blog/asgi.py: ASGI 호환 웹 서버를 위한 진입점입니다.
- blog/wsgi.py: WSGI 호환 웹 서버를 위한 진입점입니다.
URL 추가하기
# blog 디렉토리에 views.py 파일 생성
from django.shortcuts import render,HttpResponse
def greeting(request):
return HttpResponse("안녕하세요!")
# blog/urls.py 수정
from django.contrib import admin
from django.urls import path
from . import views
urlpatterns = [
path('greeting/', views.greeting),
]
앱 추가하기
py manage.py startapp article
article 앱이 생성되고 article 디렉토리에 admin.py, apps.py, models.py, tests.py, views.py, init.py 파일이 포함됩니다.
# blog/settings.py의 INSTALLED_APPS에 article 추가
INSTALLED_APPS = [
...
'article'
]
# article/views.py에 새 뷰 추가
from django.shortcuts import render,HttpResponse
def index(request):
return HttpResponse('article/index')
# article/urls.py 파일 생성
from django.urls import path
from . import views
urlpatterns = [
path('index', views.index, name="index"),
]
# blog/urls.py 수정
from django.urls import include
urlpatterns = [
...
path('article/', include('article.urls')),
]
모델 생성하기
Django에서는 관계형 데이터베이스의 테이블을 클래스로 표현합니다. 각 레코드는 해당 클래스의 인스턴스가 되며, 객체 지향 방식으로 데이터베이스를 조작할 수 있습니다.
# article/models.py
from django.db import models
class Article(models.Model):
"""게시글"""
subject = models.CharField(max_length=100)
body = models.CharField(max_length=1000)
written_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
"""모델의 문자열 표현 반환"""
return self.subject
# 모델을 기반으로 마이그레이션 생성
py manage.py makemigrations
# 데이터베이스에 마이그레이션 적용
py manage.py migrate article
# article/views.py에 목록 및 생성 뷰 추가
from article.models import Article
def listing(request):
articles = Article.objects.all()
article_names = map(str, articles)
return HttpResponse("<p>" + " ".join(article_names) + "</p>")
def write(request):
article = Article(subject='새글', body='본문 내용입니다')
article.save()
return HttpResponse("<p>새글이 등록되었습니다</p>")
# article/urls.py에路径 추가
urlpatterns = [
...
path('listing', views.listing, name="listing"),
path('write', views.write, name="write"),
]
템플릿 사용하기
# blog/settings.py의 TEMPLATES 설정 수정
import os
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# article/views.py의 listing 함수 수정
def listing(request):
articles = Article.objects.all()
article_names = map(str, articles)
return render(request, 'article/listing.html', {
'data': ' '.join(article_names),
'count': len(articles)
})
# project/templates 디렉토리 생성 후 article/listing.html 파일 생성
<p>외부 템플릿: {{data}}</p>
<p>개수: {{count}}</p>
# article/templates/article/listing.html 파일도 생성 가능
# INSTALLED_APPS 순서대로 템플릿을 검색하므로同名 파일이 있으면 충돌 발생
<p>내부 템플릿: {{data}}</p>
<p>개수: {{count}}</p>
# http://127.0.0.1:8000/article/listing 접근 시
# project/templates/article/listing.html이优先사용됨
템플릿 문법 활용
# article/views.py의 listing 함수 수정
def listing(request):
articles = Article.objects.all()
return render(request, 'article/listing.html', {'articles': articles})
# templates/article/listing.html 생성
{% if articles %}
{% for item in articles %}
{% if forloop.first %}
<div style="color: red">
<p>번호: {{item.id}}</p>
<p>제목: {{item.subject}}</p>
<p>내용: {{item.body}}</p>
<p>작성일: {{item.written_at}}</p>
</div>
{% elif forloop.last %}
<div style="color: blue">
<p>번호: {{item.id}}</p>
<p>제목: {{item.subject}}</p>
<p>내용: {{item.body}}</p>
<p>작성일: {{item.written_at}}</p>
</div>
{% else %}
<div style="color: green">
<p>번호: {{item.id}}</p>
<p>제목: {{item.subject}}</p>
<p>내용: {{item.body}}</p>
<p>작성일: {{item.written_at}}</p>
</div>
{% endif %}
{% endfor %}
<p>총 {{articles|length}}개의 글</p>
<p>{{ value|default:'데이터なし' }}</p>
{% else %}
데이터가 없습니다
{% endif %}
정적 파일 설정
# blog/settings.py에 정적 파일 경로 설정
STATIC_URL = 'static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
# project/static 디렉토리 생성
# 사용할 정적 파일(예: bootstrap) 여기에 배치
# templates/layout.html 파일 생성
{% load static %}
<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" />
<link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.min.css' %}" />
<script src="{% static 'plugins/bootstrap/js/bootstrap.bundle.min.js' %}"></script>
<title>Document</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">블로그</a>
<div class="collapse navbar-collapse">
- [Home](#)
- [Link](#)
</div>
</div>
</nav>
<div class="container-fluid mt-3">
{% block content %}
<p>환영합니다</p>
{% endblock %}
</div>
</body>
</html>
# templates/article/listing.html을heritance로 수정
{% extends "layout.html" %}
{% block content %}
{% if articles %}
<div class="row">
{% for item in articles %}
<div class="col-3">
<div class="card">
<div class="card-header">{{item.id}}</div>
<div class="card-body">
<h5 class="card-title">{{item.subject}}</h5>
<p class="card-text">{{item.body}}</p>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="alert alert-danger" role="alert">데이터가 없습니다</div>
{% endif %}
{% endblock %}
폼과 데이터 저장
# templates/article/write.html 생성
{% extends "layout.html" %}
{% block content %}
<div class="row">
<div class="col-6">
<div class="card">
<div class="card-header">글쓰기</div>
<form action="/article/write" method="post">
{% csrf_token %}
<div class="card-body">
<div class="input-group mb-3">
<span class="input-group-text">제목</span>
<input type="text" class="form-control" name="subject" />
</div>
<div class="input-group mb-3">
<span class="input-group-text">내용</span>
<textarea class="form-control" name="body"></textarea>
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-primary">저장</button>
</div>
</form>
</div>
</div>
{% if saved_article %}
<div class="col-6">
<div class="card">
<div class="card-header">{{saved_article.id}}</div>
<div class="card-body">
<h5 class="card-title">{{saved_article.subject}}</h5>
<p class="card-text">{{saved_article.body}}</p>
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}
# article/views.py에 쓰기 뷰 추가
def write(request):
if request.POST:
subject = request.POST['subject']
body = request.POST['body']
article = Article(subject=subject, body=body)
article.save()
return render(request, 'article/write.html', {'saved_article': article})
return render(request, 'article/write.html')
폼 데이터 검증
# article/forms.py 파일 생성 (forms.py는 직접 생성 필요)
from django import forms
class ArticleForm(forms.Form):
subject = forms.CharField(max_length=100)
body = forms.CharField(max_length=1000)
# article/views.py의 write 함수 수정
def write(request):
if request.POST:
form = ArticleForm(request.POST)
if form.is_valid():
subject = form.cleaned_data['subject']
body = form.cleaned_data['body']
article = Article(subject=subject, body=body)
article.save()
return render(request, 'article/write.html', {'saved_article': article})
else:
return render(request, 'article/write.html', {'validation_errors': form.errors})
return render(request, 'article/write.html')
# templates/article/write.html의 오류 표시 영역
{% if validation_errors %}
<div class="alert alert-warning" role="alert">
{% for error in validation_errors.values %}
{{error}}
{% endfor %}
</div>
{% endif %}
Django Admin 활용
# article/models.py에 추가 모델 정의
class Author(models.Model):
name = models.CharField(max_length=200)
age = models.IntegerField(default=0)
email = forms.EmailField()
def __str__(self):
return self.name
class Category(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
def __str__(self):
return self.name
# 마이그레이션 생성 및 적용
py manage.py makemigrations
py manage.py migrate
# 관리자 계정 생성
py manage.py createsuperuser
# article/admin.py 수정
from django.contrib import admin
from article.models import Article, Author, Category
class CategoryInline(admin.TabularInline):
model = Category
class AuthorAdmin(admin.ModelAdmin):
list_display = ('name', 'age', 'email')
inlines = [CategoryInline]
search_fields = ('name', 'email')
fieldsets = (
['기본 정보', {
'fields': ('name', 'email'),
}],
['추가 정보', {
'classes': ('collapse',),
'fields': ('age',),
}]
)
admin.site.register(Author, AuthorAdmin)
admin.site.register([Article, Category])
자주 사용하는 명령어
# Django 설치
python -m pip install Django
# 버전 확인
python -m django --version
# 프로젝트 생성
django-admin startproject myproject
# 개발 서버 실행
python manage.py runserver
python manage.py runserver 8080
python manage.py runserver 0.0.0.0:8000
# 앱 생성
python manage.py startapp myapp
# MySQL 클라이언트 설치
python -m pip install mysqlclient
# 데이터베이스 마이그레이션
python manage.py migrate
python manage.py makemigrations myapp
python manage.py sqlmigrate myapp 0001
# 관리자 사용자 생성
python manage.py createsuperuser
# Django 소스 코드 위치 확인
python -c "import django; print(django.__path__)"