Django 프로젝트 진행(FBV 형식)
Django 프로젝트에서 함수 기반 뷰(FBV)를 활용한 웹 애플리케이션 개발 과정 가이드.
 Django 프로젝트 진행(FBV 형식) 
 1. 가상환경 생성 및 설정
가상환경 생성
1
python -m venv venv
가상환경 활성화
- Bash - 1 - source venv/Scripts/activate
- PowerShell - 1 - .\venv\Scripts\activate 
2. 필수 라이브러리 설치 및 설정
Django 다운로드
1
pip install Django==4.2
requirements.txt 생성
1
pip freeze > requirements.txt
3. 프로젝트 생성
프로젝트 생성
1
django-admin startproject project01
4. 추가 라이브러리 다운로드
django-extensions 및 ipython 설치
- python manage.py shell명령어로 쉘을 열 수 있지만 Django 기본 Shell보다 더 많은 기능이 있는 shell_plus를 제공하고 있습니다.
- ipython은 python 기본 Shell에 여러가지 기능을 더한것입니다.
1
pip install django-extensions ipython
설정
- settings.py- 1 2 3 4 - INSTALLED_APPS = [ 'django_extensions', ... ] 
- 실행 - 1 - python manage.py shell_plus 
5. 앱 생성 및 등록
앱 생성
1
2
3
python manage.py startapp accounts
python manage.py startapp posts
python manage.py startapp core
앱 등록
- settings.py- 1 2 3 4 5 6 7 8 - INSTALLED_APPS = [ ... # Local app 'accounts', 'posts', 'core', ... ] 
6. 앱 URL 분리
- 메인 프로젝트 앱 - urls.py에- include- 1 2 3 4 5 6 7 8 9 10 - from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('accounts/', include('accounts.urls')), path('posts/', include('posts.urls')), path('', include('core.urls')), ] 
- 각 앱에 - urls.py파일 생성- 1 2 3 4 5 6 - # core 앱 from django.urls import path from . import views urlpatterns = [] - 1 2 3 4 5 6 7 - # accounts 앱 from django.urls import path from . import views app_name = "accounts" urlpatterns = [] - 1 2 3 4 5 6 7 - # posts 앱 from django.urls import path from . import views app_name = "posts" urlpatterns = [] 
7. 모델 정의 및 마이그레이션
Custom User Model 정의
- accounts/models.py- 1 2 3 4 - from django.contrib.auth.models import AbstractUser class User(AbstractUser): bio = models.CharField(max_length=255, default='Default Bio') 
- settings.py- 1 2 - # Custom Model AUTH_USER_MODEL = 'accounts.User' 
- migrations 생성 및 저장 - 1 2 - python manage.py makemigrations accounts python manage.py migrate accounts 
- migrations 확인 - 1 - python manage.py showmigrations 
Post 모델 정의
- posts/models.py- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 - from django.conf import settings from django.db import models class Post(models.Model): title = models.CharField(max_length=50) 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) likes = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='liked_posts', blank=True) def __str__(self): return self.title def total_likes(self): return self.likes.count() class Comment(models.Model): post = models.ForeignKey('Post', related_name='comments', on_delete=models.CASCADE) # 게시글과 연결 author = models.CharField(settings.AUTH_USER_MODEL, max_length=100) content = models.TextField() created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"{self.author} - {self.content[:20]}" 
- migrations 생성 및 저장 - 1 2 - python manage.py makemigrations posts python manage.py migrate posts 
- migrations 확인 - 1 - python manage.py showmigrations 
8. base.html 과 index.html 생성
- settings.py수정- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [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', ], }, }, ] 
- 루트폴더에 - templates/base.html작성- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>{% block title %}Default Title{% endblock %}</title> </head> <body> <header>{% block header %}Default Header{% endblock %}</header> <main>{% block content %}Default Content{% endblock %}</main> <footer>{% block footer %}Default Footer{% endblock %}</footer> </body> </html> 
- core앱 설정
- core앱- urls.py작성- 1 2 3 4 5 6 7 - from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), # 메인 페이지 URL 패턴 ] 
- core앱- views.py작성- 1 2 3 4 5 - from django.shortcuts import render def index(request): return render(request, "core/index.html") 
- core앱에- index.html작성- core\templates\core\index.html- 1 2 3 4 5 6 7 - {% extends "base.html" %} {% block content %} <h1>index</h1> {% endblock %}
 
- 파일 경로: - templates/base.html수정- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 - <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% block title %}Django Project{% endblock %}</title> </head> <body> <header> {% block header %} <nav> <a href="{% url 'index' %}">홈</a> </nav> {% endblock %} </header> <main>{% block content %}메인 글{% endblock %}</main> <footer>{% block footer %}Footer{% endblock %}</footer> </body> </html> 
9. URL 설정
앱별 URL 분리
- 메인 - urls.py- 1 2 3 4 5 6 7 - from django.urls import path, include urlpatterns = [ path('accounts/', include('accounts.urls')), path('posts/', include('posts.urls')), path('', include('core.urls')), ] 
- accounts/urls.py- 1 2 3 4 5 6 7 8 - app_name = 'accounts' urlpatterns = [ path("signup/", views.signup, name="signup"), path("login/", views.login, name="login"), path("logout/", views.logout, name="logout"), path("profile/", views.profile, name="profile"), ] 
- posts/urls.py- 1 2 3 4 5 6 7 8 9 10 11 12 - app_name = 'posts' urlpatterns = [ path('post-list/', views.post_list, name='post_list'), path('post-detail/<int:post_id>/', views.post_detail, name='post_detail'), path('post-create/', views.post_create, name='post_create'), path('post-update/<int:post_id>/', views.post_update, name='post_update'), path('post-delete/<int:post_id>/', views.post_delete, name='post_delete'), path('post-like/<int:post_id>/', views.post_like_toggle, name='post_like_toggle'), path('comment-update/<int:comment_id>/', views.comment_update, name='comment_update'), path('comment-delete/<int:comment_id>/', views.comment_delete, name='comment_delete'), ] 
10. forms.py 작성
- accounts/forms.py작성- 회원가입 폼을 위해 작성 - 1 2 3 4 5 6 7 8 - from django.contrib.auth import get_user_model from django.contrib.auth.forms import UserCreationForm class CustomUserCreationForm(UserCreationForm): class Meta: model = get_user_model() # Custom user model fields = ["username", "email", "password1", "password2"] # 필요한 필드
 
- posts/forms.py작성- 게시글 및 댓글 작성 폼을 위한 작성 - 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 - from django import forms from .models import Post, Comment class PostForm(forms.ModelForm): class Meta: model = Post fields = ['title', 'content'] labels = { 'title': '제목', 'content': '내용', } class CommentForm(forms.ModelForm): class Meta: model = Comment fields = ['content'] labels = { 'content': '댓글', }
 
11. views.py 파일 작성
- accounts/views.py작성- 회원가입을 위한 코드 작성. 아직 미작성 코드는 - pass로 입력- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 - from django.shortcuts import render, redirect from django.contrib.auth import login as auth_login from django.contrib.auth import logout as auth_logout from django.contrib.auth.decorators import login_required from django.views.decorators.http import require_http_methods from django.contrib.auth.forms import AuthenticationForm from .forms import CustomUserCreationForm # 커스텀 폼 임포트 @require_http_methods(["GET", "POST"]) def signup(request): if request.method == "POST": form = CustomUserCreationForm(request.POST) if form.is_valid(): user = form.save() auth_login(request, user) # 가입 후 자동 로그인 return redirect("index") else: form = CustomUserCreationForm() context = {"form": form} return render(request, "accounts/signup.html", context) @require_http_methods(["GET", "POST"]) def login(request): if request.method == "POST": form = AuthenticationForm(request, data=request.POST) if form.is_valid(): user = form.get_user() auth_login(request, user) # 인증 성공 시 로그인 처리 return redirect("index") # 로그인 후 리다이렉트 else: form = AuthenticationForm() context = {"form": form} return render(request, "accounts/login.html", context) @require_http_methods(["POST"]) def logout(request): auth_logout(request) # 세션 정리 return redirect("index") # 로그아웃 후 메인 페이지로 리다이렉트 @require_http_methods(["GET"]) @login_required # 로그인된 사용자만 접근 가능 def profile(request): return render(request, "accounts/profile.html", {"user": request.user}) 
 
- posts/views.py작성- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 - from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.views.decorators.http import require_http_methods from .forms import PostForm, CommentForm from .models import Post, Comment @require_http_methods(["GET"]) def post_list(request): posts = Post.objects.all().order_by('-created_at') return render(request, 'posts/post_list.html', {'posts': posts}) @require_http_methods(["GET", "POST"]) def post_detail(request, post_id): post = get_object_or_404(Post, id=post_id) comments = post.comments.all().order_by('-created_at') # 댓글 최신순 정렬 if request.method == 'POST': # 댓글 작성 요청 if not request.user.is_authenticated: return redirect('accounts:login') form = CommentForm(request.POST) if form.is_valid(): comment = form.save(commit=False) # 데이터베이스 저장 전 인스턴스 생성 comment.post = post # 댓글과 게시글 연결 comment.author = request.user.username # 현재 사용자 설정 comment.save() # 데이터베이스에 저장 return redirect('posts:post_detail', post_id=post.id) else: form = CommentForm() # 빈 폼 생성 return render(request, 'posts/post_detail.html', {'post': post, 'comments': comments, 'form': form}) @login_required @require_http_methods(["GET", "POST"]) 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.username # 작성자를 현재 로그인 사용자로 설정 post.save() # 데이터베이스에 저장 return redirect('posts:post_list') # 게시글 목록으로 리다이렉트 else: form = PostForm() return render(request, 'posts/post_create.html', {'form': form}) @login_required @require_http_methods(["GET", "POST"]) def post_update(request, post_id): post = get_object_or_404(Post, id=post_id) # 작성자만 수정할 수 있도록 권한 확인 if request.user.username != post.author: return redirect('posts:post_list') # 권한 없으면 목록 페이지로 리다이렉트 if request.method == 'POST': form = PostForm(request.POST, instance=post) # 기존 데이터를 기반으로 폼 생성 if form.is_valid(): form.save() # 변경사항 저장 return redirect('posts:post_detail', post_id=post.id) # 상세 페이지로 리다이렉트 else: form = PostForm(instance=post) # 기존 데이터를 폼에 채워줌 return render(request, 'posts/post_update.html', {'form': form}) @login_required @require_http_methods(["GET", "POST"]) def post_delete(request, post_id): post = get_object_or_404(Post, id=post_id) # 작성자만 삭제할 수 있도록 권한 확인 if request.user.username != post.author: return redirect('posts:post_list') # 권한 없으면 목록 페이지로 리다이렉트 if request.method == 'POST': # 삭제 확인 후 처리 post.delete() return redirect('posts:post_list') # 삭제 후 게시글 목록으로 리다이렉트 return render(request, 'posts/post_delete.html', {'post': post}) @login_required @require_http_methods(["POST"]) def post_like_toggle(request, post_id): post = get_object_or_404(Post, id=post_id) if request.user in post.likes.all(): post.likes.remove(request.user) # 좋아요 취소 else: post.likes.add(request.user) # 좋아요 추가 return redirect('posts:post_detail', post_id=post.id) @login_required @require_http_methods(["GET", "POST"]) def comment_update(request, comment_id): comment = get_object_or_404(Comment, id=comment_id) # 작성자만 수정 가능 if request.user.username != comment.author: return redirect('posts:post_detail', post_id=comment.post.id) if request.method == 'POST': form = CommentForm(request.POST, instance=comment) # 기존 댓글 데이터로 폼 생성 if form.is_valid(): form.save() # 데이터베이스에 저장 return redirect('posts:post_detail', post_id=comment.post.id) else: form = CommentForm(instance=comment) # 기존 데이터를 폼에 채워서 렌더링 return render(request, 'posts/comment_update.html', {'form': form, 'comment': comment}) @login_required @require_http_methods(["GET", "POST"]) def comment_delete(request, comment_id): comment = get_object_or_404(Comment, id=comment_id) # 작성자만 삭제 가능 if request.user.username != comment.author: return redirect('posts:post_detail', post_id=comment.post.id) if request.method == 'POST': # 삭제 확인 후 처리 post_id = comment.post.id # 삭제 전 연결된 게시글 ID 저장 comment.delete() return redirect('posts:post_detail', post_id=post_id) return render(request, 'posts/comment_delete.html', {'comment': comment}) 
12. Admin 설정
- accounts/admin.py- 1 2 3 4 5 6 - from django.contrib import admin from .models import User @admin.register(User) class UserAdmin(admin.ModelAdmin): pass 
- posts/admin.py- 1 2 3 4 5 6 7 8 9 10 - from django.contrib import admin from .models import Post, Comment @admin.register(Post) class PostAdmin(admin.ModelAdmin): pass @admin.register(Comment) class CommentAdmin(admin.ModelAdmin): pass 
 This post is licensed under  CC BY 4.0  by the author.