디장고 블로그 만들기 튜토리얼

 

 

유저앱을 어카운츠앱으로 변환하면서 상세코드에 뺴먹고 안바꾼거 있음!
근데 공부하는 사람이라면 이정도 오류는 스스로 찾아서 수정하시길...!

 

 

 

1. 작업 디렉토리 만들기(루트폴더)

mkdir [프로젝트_폴더명]
cd [프로젝트_폴더명]

 

2. 가상환경 설정 (프로젝트 격리를 위해)

깃이그노어 만들고 밴브폴더 넣어두기

필요한 것 : Django, Pillow

python -m venv venv

#가상환경 활성화
깃배쉬 (Windows): source venv/Scripts/activate
맥 (Mac) / 리눅스: source venv/bin/activate

#리콰이어 있으면 다운로드 
pip install -r requirements.txt

#리콰이어 없으면 받아서 저장
pip install django
pip freeze -> requirments.txt

#끄는법
deactivate

 

3. 프로젝트, 앱 생성

django-admin startproject config .
python manage.py startapp accounts
python manage.py startapp post

 

4. config/settings.py에 앱 추가하기

'''
기존 코드에 끼워넣기
'''
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'accounts',
    'post',
]

LANGUAGE_CODE = "ko-kr"
TIME_ZONE = "Asia/Seoul"

TEMPLATES = [
    {
        "DIRS": [BASE_DIR / "templates"],
    }
]

'''
아래부터는 코드 추가
'''
AUTH_USER_MODEL = "users.CustomUser"

# 미디어 파일 설정
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

# 로그인/로그아웃 리다이렉트 설정
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'

 

5. 앱 models.py 작성

'''
accounts/models.py
'''
from django.contrib.auth.models import AbstractUser
from django.db import models

class CustomUser(AbstractUser):
    profile_image = models.ImageField(upload_to='profile_images/', null=True, blank=True)
    bio = models.TextField(max_length=500, blank=True)

'''
post/models.py
'''
from django.db import models
from django.conf import settings

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

 

6. 앱 마그레, 마그렛 하기

python manage.py makemigrations accounts post
python manage.py migrate

 

7. admin 계정 만들기

python manage.py createsuperuser

 

8. forms.py 작성

# accounts/forms.py
from django.contrib.auth.forms import UserCreationForm
from .models import CustomUser

class CustomUserCreationForm(UserCreationForm):
    class Meta:
        model = CustomUser
        fields = ('username', 'email', 'profile_image', 'bio')

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

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['title', 'content']

 

9. 프로젝트 urls.py 설정

# config/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings


urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("posts.urls")),
    path("users/", include("users.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

 

10. 앱  urls.py 설정

'''
accounts/urls.py
'''
from django.urls import path
from django.contrib.auth import views as auth_views
from . import views

urlpatterns = [
    path('signup/', views.signup, name='signup'),
    path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name='login'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
    path('profile/', views.user_profile, name='user_profile'),
]

'''
post/urls.py
'''
from django.urls import path
from . import views

urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('post/<int:pk>/', views.post_detail, name='post_detail'),
    path('post/new/', views.post_create, name='post_create'),
    path('post/<int:pk>/edit/', views.post_update, name='post_update'),
    path('post/<int:pk>/delete/', views.post_delete, name='post_delete'),
]

 

11. views.py 작성

'''
accounts/views.py
'''
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
from .forms import CustomUserCreationForm

def signup(request):
    if request.method == 'POST':
        form = CustomUserCreationForm(request.POST, request.FILES)
        if form.is_valid():
            user = form.save()
            login(request, user)
            return redirect('post_list')
    else:
        form = CustomUserCreationForm()
    return render(request, 'users/signup.html', {'form': form})

@login_required
def user_profile(request):
    return render(request, 'users/profile.html', {'user': request.user})
    
'''    
post/views.py
'''
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from .models import Post
from .forms import PostForm

def post_list(request):
    posts = Post.objects.all().order_by('-created_at')
    return render(request, 'posts/post_list.html', {'posts': posts})

def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    return render(request, 'posts/post_detail.html', {'post': post})

@login_required
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, 'posts/post_form.html', {'form': form})

@login_required
def post_update(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.user != post.author:
        return redirect('post_list')
    
    if request.method == 'POST':
        form = PostForm(request.POST, instance=post)
        if form.is_valid():
            post = form.save()
            return redirect('post_detail', pk=post.pk)
    else:
        form = PostForm(instance=post)
    return render(request, 'posts/post_form.html', {'form': form})

@login_required
def post_delete(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.user != post.author:
        return redirect('post_list')
    
    if request.method == 'POST':
        post.delete()
        return redirect('post_list')
    return render(request, 'posts/post_confirm_delete.html', {'post': post})

 

12. 기본 템플릿 작성

<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Django Board{% endblock %}</title>
</head>
<body>
    {% include 'navbar.html' %}
    <main>
        {% block content %}
        {% endblock %}
    </main>
    {% include 'footer.html' %}
</body>
</html>

<!-- templates/navbar.html -->
<nav>
    <ul>
        <li><a href="{% url 'post_list' %}">홈</a></li>
        {% if user.is_authenticated %}
            <li><a href="{% url 'user_profile' %}">프로필</a></li>
            <li><a href="{% url 'logout' %}">로그아웃</a></li>
        {% else %}
            <li><a href="{% url 'login' %}">로그인</a></li>
            <li><a href="{% url 'signup' %}">회원가입</a></li>
        {% endif %}
    </ul>
</nav>

<!-- templates/footer.html -->
<footer>
    <p>&copy; 2025 Django Board. All rights reserved.</p>
</footer>

 

13. 유저앱 템플릿

<!-- templates/accounts/signup.html -->
{% extends 'base.html' %}

{% block title %}회원가입{% endblock %}

{% block content %}
<div class="form-container">
    <h2>회원가입</h2>
    <form method="post" enctype="multipart/form-data">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">가입하기</button>
    </form>
    <p>이미 계정이 있으신가요? <a href="{% url 'login' %}">로그인</a></p>
</div>
{% endblock %}

<!-- templates/accounts/login.html -->
{% extends 'base.html' %}

{% block title %}로그인{% endblock %}

{% block content %}
<div class="form-container">
    <h2>로그인</h2>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">로그인</button>
    </form>
    <p>계정이 없으신가요? <a href="{% url 'signup' %}">회원가입</a></p>
</div>
{% endblock %}

<!-- templates/accounts/profile.html -->
{% extends 'base.html' %}

{% block title %}{{ user.username }}님의 프로필{% endblock %}

{% block content %}
<div class="profile-container">
    <h2>프로필</h2>
    <div class="profile-info">
        {% if user.profile_image %}
            <img src="{{ user.profile_image.url }}" alt="프로필 이미지" width="300">
        {% endif %}
        <h3>{{ user.username }}</h3>
        <p>{{ user.bio|default:"소개글이 없습니다." }}</p>
    </div>
    
    <div class="user-posts">
        <h3>작성한 게시글</h3>
        {% for post in user.post_set.all %}
            <div class="post-item">
                <a href="{% url 'post_detail' post.pk %}">{{ post.title }}</a>
                <span>{{ post.created_at|date:"Y-m-d" }}</span>
            </div>
        {% empty %}
            <p>작성한 게시글이 없습니다.</p>
        {% endfor %}
    </div>
</div>
{% endblock %}

 

14. 포스트앱 템플릿

<!-- templates/post/post_list.html -->
{% extends 'base.html' %}

{% block title %}게시글 목록{% endblock %}

{% block content %}
<div class="post-list">
    <h2>게시글 목록</h2>
    {% if user.is_authenticated %}
        <a href="{% url 'post_create' %}" class="btn-create">글쓰기</a>
    {% endif %}
    
    {% for post in posts %}
        <div class="post-item">
            <h3><a href="{% url 'post_detail' post.pk %}">{{ post.title }}</a></h3>
            <div class="post-meta">
                <span>작성자: {{ post.author.username }}</span>
                <span>작성일: {{ post.created_at|date:"Y-m-d H:i" }}</span>
            </div>
        </div>
    {% empty %}
        <p>게시글이 없습니다.</p>
    {% endfor %}
</div>
{% endblock %}

<!-- templates/post/post_detail.html -->
{% extends 'base.html' %}

{% block title %}{{ post.title }}{% endblock %}

{% block content %}
<div class="post-detail">
    <h2>{{ post.title }}</h2>
    <div class="post-meta">
        <span>작성자: {{ post.author.username }}</span>
        <span>작성일: {{ post.created_at|date:"Y-m-d H:i" }}</span>
        {% if post.updated_at != post.created_at %}
            <span>수정일: {{ post.updated_at|date:"Y-m-d H:i" }}</span>
        {% endif %}
    </div>
    
    <div class="post-content">
        {{ post.content|linebreaks }}
    </div>
    
    {% if user == post.author %}
        <div class="post-actions">
            <a href="{% url 'post_update' post.pk %}" class="btn-edit">수정</a>
            <a href="{% url 'post_delete' post.pk %}" class="btn-delete">삭제</a>
        </div>
    {% endif %}
    
    <a href="{% url 'post_list' %}" class="btn-back">목록으로</a>
</div>
{% endblock %}

<!-- templates/post/post_form.html -->
{% extends 'base.html' %}

{% block title %}
    {% if form.instance.pk %}
        게시글 수정
    {% else %}
        새 게시글 작성
    {% endif %}
{% endblock %}

{% block content %}
<div class="form-container">
    <h2>
        {% if form.instance.pk %}
            게시글 수정
        {% else %}
            새 게시글 작성
        {% endif %}
    </h2>
    
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">저장</button>
        <a href="{% url 'post_list' %}" class="btn-cancel">취소</a>
    </form>
</div>
{% endblock %}

<!-- templates/post/post_confirm_delete.html -->
{% extends 'base.html' %}

{% block title %}게시글 삭제{% endblock %}

{% block content %}
<div class="delete-confirm">
    <h2>게시글 삭제</h2>
    <p>정말로 "{{ post.title }}" 게시글을 삭제하시겠습니까?</p>
    
    <form method="post">
        {% csrf_token %}
        <button type="submit" class="btn-delete">삭제</button>
        <a href="{% url 'post_detail' post.pk %}" class="btn-cancel">취소</a>
    </form>
</div>
{% endblock %}

 

끗! 서버 켜보기

python manage.py runserver