Django: Python Web Framework

Ohback·2025년 4월 9일

1. 장고(Django)란?

장고는 “빠르게, 안전하게, 확장 가능하게” 웹 서비스를 만들 수 있도록 돕는 파이썬 웹 프레임워크로 ORM, 인증, 관리자(admin), 폼, 템플릿, 라우팅 등 웹앱의 필수 부품을 기본 탑재하고, 재사용 가능한 앱 구조로 대규모 개발에도 유리하다.

1-1. 장고 프로젝트 구조 예시

config/                 # 프로젝트 설정(전역)
 ├─ settings.py         # 환경설정
 ├─ urls.py             # 전역 URL 라우팅
 ├─ wsgi.py/asgi.py     # 배포/비동기 엔트리
blog/                   # 앱(기능 단위)
 ├─ apps.py             # 앱 설정
 ├─ models.py           # 데이터 모델(ORM)
 ├─ views.py            # 뷰(비즈니스 로직)
 ├─ urls.py             # 앱 라우팅
 ├─ admin.py            # 관리자 사이트 등록
 ├─ templates/          # 템플릿(HTML)
 ├─ migrations/         # 마이그레이션 파일
 ├─ tests.py            # 단위 테스트
manage.py               # 관리 커맨드 엔트리



2. 장고의 특징

  • 배터리 포함(batteries-included): ORM, 인증, 세션, 폼, 관리자, 캐시, 국제화 등 내장
  • 보안 우선: CSRF/XSS/SQL Injection 예방 기능 기본 제공
  • ORM 기반: SQL 대신 파이썬 모델로 DB 다루기
  • MTV 패턴: Model–Template–View(철학은 MVC와 유사, 명칭만 다름)
  • 앱 단위 구조: 기능을 독립 앱으로 나눠 재사용 및 유지보수 용이
  • 마이그레이션: 스키마 버전 관리 자동화
  • 에코시스템: Django REST framework, django-allauth, Celery 등과의 강력한 호환



3. 사용 방법 (Quick Start)

# 1) 설치
pip install django

# 2) 프로젝트 생성
django-admin startproject config .

# 3) 앱 생성 (예: blog)
python manage.py startapp blog

# 4) DB 준비 & 슈퍼유저 생성
python manage.py migrate
python manage.py createsuperuser

# 5) 개발 서버 실행
python manage.py runserver

앱 등록config/settings.py

INSTALLED_APPS = [
    # Django 기본 앱들...
    'blog',              # 생성한 앱 등록
]



4. 장고의 주요 파일에 대해 알아보자

4-1. apps.py — 앱 설정

# blog/apps.py
from django.apps import AppConfig

class BlogConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "blog"
    verbose_name = "블로그"

4-2. models.py — 데이터 모델(ORM)

# blog/models.py
from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()

class Post(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    is_published = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return self.title

마이그레이션:

python manage.py makemigrations
python manage.py migrate

4-3. admin.py — 관리자 등록

# blog/admin.py
from django.contrib import admin
from .models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ("id", "title", "author", "is_published", "created_at")
    search_fields = ("title", "body")
    list_filter = ("is_published", "created_at")

/admin 접속 후 슈퍼유저로 로그인하면 CRUD UI 자동 제공.

4-4. views.py — 뷰(로직)

# blog/views.py
from django.views.generic import ListView, DetailView
from .models import Post

class PostListView(ListView):
    model = Post
    queryset = Post.objects.filter(is_published=True)
    template_name = "blog/post_list.html"
    context_object_name = "posts"

class PostDetailView(DetailView):
    model = Post
    template_name = "blog/post_detail.html"

4-5. urls.py — 라우팅

# config/urls.py (전역)
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("blog.urls")),
]
# blog/urls.py (앱)
from django.urls import path
from .views import post_list, post_detail, PostListView, PostDetailView

urlpatterns = [
    path("", PostListView.as_view(), name="post_list"),
    path("posts/<int:pk>/", PostDetailView.as_view(), name="post_detail"),
    # 함수형으로 쓰려면 아래처럼:
    # path("", post_list, name="post_list"),
    # path("posts/<int:pk>/", post_detail, name="post_detail"),
]

4-6. 템플릿 — templates/

<!-- blog/templates/blog/post_list.html -->
{% extends "base.html" %}
{% block content %}
  <h1>게시글</h1>
  <ul>
    {% for post in posts %}
      <li><a href="{% url 'post_detail' post.pk %}">{{ post.title }}</a> — {{ post.author }}</li>
    {% empty %}
      <li>게시글이 없습니다.</li>
    {% endfor %}
  </ul>
{% endblock %}
<!-- blog/templates/blog/post_detail.html -->
{% extends "base.html" %}
{% block content %}
  <h1>{{ post.title }}</h1>
  <p>{{ post.body|linebreaks }}</p>
  <small>{{ post.created_at }}</small>
{% endblock %}
<!-- templates/base.html (프로젝트 공통 레이아웃) -->
<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <title>{% block title %}My Blog{% endblock %}</title>
  <link rel="stylesheet" href="{% static 'css/main.css' %}">
</head>
<body>
  <header><a href="{% url 'post_list' %}"></a></header>
  <main>{% block content %}{% endblock %}</main>
</body>
</html>

템플릿 경로 설정config/settings.py

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],   # 프로젝트 공통 템플릿 폴더
        "APP_DIRS": True,                   # 각 앱 내부 templates/ 사용
        "OPTIONS": {"context_processors": [
            "django.template.context_processors.request",
            "django.contrib.auth.context_processors.auth",
            # ...
        ]},
    },
]

4-7. 정적 파일(Static) & 미디어

# settings.py
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]     # 개발용
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"              # 업로드 파일 저장 경로

템플릿에서:

{% load static %}
<link rel="stylesheet" href="{% static 'css/main.css' %}">

5. 폼 & 유효성 검사 — forms.py

# blog/forms.py
from django import forms
from .models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ("title", "body", "is_published")
# blog/views.py (폼 사용 예)
from django.shortcuts import redirect
from .forms import PostForm

def post_create(request):
    if request.method == "POST":
        form = PostForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.save()
            return redirect("post_detail", pk=post.pk)
    else:
        form = PostForm()
    return render(request, "blog/post_form.html", {"form": form})

6. 인증/권한(로그인 필수)

# views.py
from django.contrib.auth.decorators import login_required

@login_required
def secret_page(request):
    return render(request, "secret.html")
# settings.py
LOGIN_URL = "/accounts/login/"
LOGIN_REDIRECT_URL = "/"

7. 테스트 — tests.py

# blog/tests.py
from django.test import TestCase
from django.urls import reverse
from django.contrib.auth import get_user_model
from .models import Post

class PostTests(TestCase):
    def setUp(self):
        self.user = get_user_model().objects.create_user("u", "u@test.com", "pw")
        self.post = Post.objects.create(title="t", body="b", author=self.user)

    def test_list(self):
        res = self.client.get(reverse("post_list"))
        self.assertEqual(res.status_code, 200)
        self.assertContains(res, "t")

    def test_detail(self):
        res = self.client.get(reverse("post_detail", args=[self.post.pk]))
        self.assertEqual(res.status_code, 200)
        self.assertContains(res, "b")

profile
기록은 기억을 지배한다.

0개의 댓글