[Django/Python] 프로젝트 생성부터 작성까지 Django Framework 전반적으로 요약해보기
2023. 11. 27. 09:17
반응형
여름에 Django Project를 진행한 이후, 한동안 사용할 일이 없었다가 더 생각나지 않기 전에 지금까지의 장고 프로젝트 생성부터, MVT 구조, Model, View, Template을 비롯한 Single Web Application을 만드는 전반적인 내용들을 요약하여 작성하고자 한다.
내가 생각하기에 Django Framework의 장점은 관리자 기능이나, 로그인 등 많이 사용할 법한 인터페이스들이 Django App 모듈로 지원되기 때문에 가져다 쓸 수 있다는 것이 가장 큰 장점인 것 같다. 만약에 내가 나중에 급하게 관리자 페이지를 만들어야 하는 일이 생긴다면 장고를 택할 확률도 높을 것이라고 생각한다.
나중에 Django를 사용할 일이 있을 때, 해당 포스팅을 보면서 기억을 더듬을 수 있었으면 좋겠다.
Github : https://github.com/Jaehwi-So/LECTURE_Django_WithCloud
프로젝트 구조
앱 설정 및 세팅
가상환경 구축 및 세팅
pip install django
django-admin startproject my_django_project
데이터베이스 초기설정
python manage.py migrate
- 초기 db.sqlite3이 생성됨
python manage.py createsuperuser
- 관리자 계정 생성
앱 만들기
python manage.py startapp blog
- settings.py에 App을 추가해주어야 함
데이터베이스 마이그레이션
python manage.py makemigrates
python manage.py migrate
MVT 패턴
데이터의 구조, 모양, 로직을 분리하여 개발하는 방법
Model
1. 모델 생성
class Post(models.Model):
title = models.CharField(max_length=30)
content = models.TextField()
created_at = models.DateTimeField()
2. 마이그레이팅
python manage.py makemigrations
python manage.py migrate
3. admin.py 모델 등록
- admin.py에 등록
admin.site.register(Post)
4. URL과 View 연결하기
- URL에 사용할 패턴과 views.py의 메서드 연결
- views.py에서 사용할 템플릿과 연결
5. 모델 정의하기
<models.py>
import os
from django.contrib.auth.models import User
from django.db import models
from markdownx.utils import markdown
from markdownx.models import MarkdownxField
# Create your models here.
class Category(models.Model):
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(max_length=200, unique=True, allow_unicode=True) #Slug의 한글 허용
def __str__(self):
return self.name
# 테이블 복수명 변경
class Meta:
verbose_name_plural = 'Categories'
def get_absolute_url(self):
return f'/blog/category/{self.slug}'
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(max_length=200, unique=True, allow_unicode=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return f'/blog/tags/{self.slug}'
class Post(models.Model):
title = models.CharField(max_length=30)
content = MarkdownxField()
# 이미지업로드 컬럼, 경로는 _media/blog/images/년월일/에 저장
head_image = models.ImageField(upload_to='blog/images/%Y/%m/%d/', blank=True)
# 파일업로드 컬럼, ImageField<FileField 상위경로임
file_upload = models.FileField(upload_to='blog/images/%Y/%m/%d/', blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# N:1 관계연결, N쪽에만 명시하면 됨
author = models.ForeignKey(User, on_delete=models.CASCADE)
#author = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
# N:1 관계연결
category = models.ForeignKey(Category, null=True, blank=True, on_delete=models.SET_NULL)
# N:M 관계연결
tags = models.ManyToManyField(Tag, blank=True) # null=True를 설정필요없음
# 오버라이딩 : 대표 객체 속성 설정
def __str__(self):
return f'[{self.pk}] - {self.title}'
# 블로그 상세보기 URL
def get_absolute_url(self):
return f'/blog/{self.pk}'
# 파일 이름
def get_file_name(self):
return os.path.basename(self.file_upload.name)
def get_content_markdown(self):
return markdown(self.content)
class Comment(models.Model):
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# (작성자, 포스트) : 댓글 = 1 : N
post = models.ForeignKey(Post, on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return f'{self.author} :: {self.content}'
def get_absolute_url(self):
# Post View에서 해당 ID 태그의 위치로 이동
return f'{self.post.get_absolute_url()}#comment-{self.pk}'
class Test(models.Model):
content = models.TextField()
1. DateTime 자동 저장
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
2. Model의 대표값 설정
- str을 오버라이딩하여 객체의 대표가 될 내용 설정
3. Model의 메서드 추가
- get_absolute_url을 생성해 상세보기 페이지로 이동하는 URL 설정
- 사용하려는 메서드를 모델에 넣으면 View에서 사용 가능
4. 이미지 업로드 컬럼 추가
- settings.py와 urls.py에 MEDIA_URL 설정 필요
- 이미지 업로드 컬럼 추가
head_image = models.ImageField(upload_to='blog/images/%Y/%m/%d/', blank=True)
- 파일 업로드 컬럼 추가
file_upload = models.FileField(upload_to='blog/images/%Y/%m/%d/', blank=True)
- 업로드 파일 속성
- file_upload.name : 파일명
- file_upload.url : 파일경로
5. Slug 필드 추가
- 숫자인 pk 대신 읽을 수 있는 텍스트로 URL을 구성할 때 사용
models.SlugField(max_length=2000, unique=True, allow_unicode=True)
6. 각종 속성
- blank=True : 해당 필드를 작성하지 않아도 됨(폼에서 비어있어도 됨), 유효성 검사때 사용
- null=True : Nullable 허용
- unique=True : Unique 속성
- on_delete=models.CASCADE : 연관 레코드가 삭제될 때 함께 삭제
- on_delete=models.SET_NULL : 연관 레코드가 삭제될 때 해당 필드를 NULL로 설정
- allow_unicode=True : Slug 필드에서의 한글 URL 유니코드 허용
7. CBV 컨텍스트에 데이터 담아서 보내기
def get_context_data(self, **kwargs):
context = super(PostList, self).get_context_data()
context['categories'] = Category.objects.all()
context['no_category_post_count'] = Post.objects.filter(category=None).count()
return context
8. 관계 설정
# N:1 관계연결
category = models.ForeignKey(Category, null=True, blank=True, on_delete=models.SET_NULL)
# N:M 관계연결
tags = models.ManyToManyField(Tag, blank=True) # null=True를 설정필요없음
라우팅 설정하기
<urls.py>
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("blog/", include('blog.urls')),
path('markdownx/', include('markdownx.urls')),
path('accounts/', include('allauth.urls')),
path('', include('single_pages.urls')),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
1. URL 패턴
urlpatterns = [
path('', views.index),
path('<int:pk>', views.post_detail)
]
2. MEDIA URL(파일업로드)와 Static Path 연결하기
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
View
요청과 응답 처리. 모델과 템플릿을 사용자 요구에 따라 전달함
<views.py>
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.shortcuts import render, redirect, get_object_or_404
from .forms import CommentForm
from .models import Post, Category, Tag
from django.views.generic import ListView, DetailView, CreateView, UpdateView
#CBV
## 포스팅 리스트
class PostList(ListView):
model = Post
ordering = '-pk'
# render시 아래의 별도 설정이 없을 시 경로는 post_list.html, 모델은 자동으로 post_list로 할당됨
template_name = 'blog/post_list.html' #템플릿 설정
def get_context_data(self, **kwargs):
context = super(PostList, self).get_context_data()
context['categories'] = Category.objects.all()
context['no_category_count'] = Post.objects.filter(category=None).count()
return context
## 포스팅 상세보기
class PostDetail(DetailView):
model = Post
def get_context_data(self, **kwargs):
context = super(PostDetail, self).get_context_data()
context['categories'] = Category.objects.all()
context['no_category_count'] = Post.objects.filter(category=None).count()
context['comment_form'] = CommentForm
return context
## 포스팅 등록
class PostCreate(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content', 'head_image', 'file_upload', 'category', 'tags']
def form_valid(self, form):
current_user = self.request.user
if current_user.is_authenticated and (current_user.is_staff or current_user.is_superuser):
form.instance.author = current_user
return super(PostCreate, self).form_valid(form)
else:
return redirect('/blog')
## 포스팅 수정
class PostUpdate(LoginRequiredMixin, UpdateView):
model = Post
fields = ['title', 'content', 'head_image', 'file_upload', 'category', 'tags']
template_name = 'blog/post_form_update.html'
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated and request.user == self.get_object().author:
return super(PostUpdate, self).dispatch(request, *args, **kwargs)
else:
raise PermissionDenied
#FBV
## 카테고리 모아보기
def categories_page(request, slug):
if slug=='no-category':
category='미분류',
post_list = Post.objects.filter(category=None)
else:
category = Category.objects.get(slug=slug)
post_list = Post.objects.filter(category = category)
context = {
'categories': Category.objects.all(),
'no_category_count': Post.objects.filter(category=None).count(),
'category': category,
'post_list': post_list
}
return render(
request,
'blog/post_list.html',
context
)
## 태그 모아보기
def tags_page(request, slug) :
tag = Tag.objects.get(slug = slug)
# 태그의 엘레멘트가 포함된 포스트를 모두 찾아야 함
post_list = tag.post_set.all()
context = {
'tag': tag,
'categories': Category.objects.all(),
'post_list': post_list,
'no_category_count' : Post.objects.filter(category=None).count()
}
return render(request, 'blog/post_list.html', context)
# 새로운 코멘트 입력
def new_comment(request, pk):
if request.user.is_authenticated: # 1. 인증 여부 확인
post= get_object_or_404(Post, pk=pk)
if request.method == 'POST': # 2. 메서드가 POST일 경우
# request.POST : 사용자가 폼에 입력한 데이터를 담고 있는 POST 요청 객체
comment_form = CommentForm(request.POST)
if comment_form.is_valid(): # 3. 폼 유효성 검사 통과시
comment = comment_form.save(commit=False)
comment.post = post
comment.author = request.user
comment.save()
return redirect(comment.get_absolute_url())
else:
return redirect(post.get_absolute_url())
else:
raise PermissionDenied
1. FBV
def post_list(request):
posts = Post.objects.all().order_by('-pk')
return render(
request,
'blog/post_list.html',
{
'posts': posts
}
)
- MYMODEL.objects.all() : Table의 모든 값 선택
- .order_by('-pk') : PK DESC 정렬, -는 역순
- render : 템플릿과 컨텍스트로 넘겨줄 값 설정
2. CBV
- ListView, DetailView, CreateView, UpdateView
class PostList(ListView):
model = Post
ordering = '-pk'
# render시 아래의 별도 설정이 없을 시 경로는 post_list.html, 모델은 자동으로 post_list로 할당됨
template_name = 'blog/post_list.html' #템플릿 설정
- Context 추가
def get_context_data(self, **kwargs):
context = super(PostDetail, self).get_context_data()
context['categories'] = Category.objects.all()
return context
3. Model Query
- SELECT ONE : Tag.objects.get(slug = slug)
- SELECT LIST : Post.objects.all()
- SELECT LIST WHERE : Post.objects.filter(category = category)
- 연관되어 있는 셋 조회 : post_list = tag.post_set.all()
4. 인증 미들웨어
- class PostCreate(LoginRequiredMixin, CreateView)
5. form_valied
- POST 요청에서 유효한 폼이 제공되면 데이터를 확인 및 처리하여 결과 반환
def form_valid(self, form):
current_user = self.request.user
if current_user.is_authenticated and (current_user.is_staff or current_user.is_superuser):
form.instance.author = current_user
return super(PostCreate, self).form_valid(form)
else:
return redirect('/blog')
6. dispatch
- 요청이 처리되기 전에 호출되는 메서드. HTTP 메서드에 따라 적절한 메서드 호출
- 인증 및 권한 관리와 같은 전처리 작업을 수행함
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated and request.user == self.get_object().author:
return super(PostUpdate, self).dispatch(request, *args, **kwargs)
else:
raise PermissionDenied
7. request
- request.method :Request Method
- request.POST : Form에 입력한 데이터를 담고있는 POST 요청객체
- request.user : 세션의 사용자
- is_authenticated : 인증이 되었는가?
- is_superadmin, is_staff : 권한이 ~인가?
Template
사용자에게 보여지는 HTML 문서의 형태를 결정한다.
<form.py>
1. 사용할 폼 클래스 생성
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('content',) #입력
2. views.py에서 사용하기
def get_context_data(self, **kwargs):
context = super(PostDetail, self).get_context_data()
context['comment_form'] = CommentForm
return context
환경설정
<settings.py>
- TIME_ZONE = 'Asia/Seoul'
- USE_TZ = False2. Static File path 설정
- STATIC_URL = "static/"3. 업로드 이미지 관리 및 Path 설정
- MEDIA_URL = '/media/'
- MEDIA_ROOT = os.path.join(BASE_DIR, '_media')
관리자 페이지 설정
<admin.py>
관리자 페이지 설정
from django.contrib import admin
from markdownx.admin import MarkdownxModelAdmin
from .models import Post, Category, Tag, Comment, Test
# Register your models here.
admin.site.register(Post, MarkdownxModelAdmin)
class AutoSlugAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('name', )}
admin.site.register(Category, AutoSlugAdmin)
admin.site.register(Tag, AutoSlugAdmin)
admin.site.register(Comment)
admin.site.register(Test)
1. 모델 등록
admin.site.register(Post)
2. 모델 생성 시 자동완성
class AutoSlugAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('name', )}
admin.site.register(Category, AutoSlugAdmin)
HTML 템플릿 문법
1. for
{% for p in post_list %}
<p class="card-text">{{ p.content }}</p>
{% endfor %}
2. static path
{% load static %}
//...
<img src="{% static 'images/lena.jpg' %}">
3. 템플릿 필터
첫 100개 문자 <p class="card-text">{{ p.content | truncatechars:100}}</p>
첫 45개 단어 <p class="card-text">{{ p.content | truncatewords:45}}</p>
4. 템플릿 모듈화
<!-- 부모 템플릿 상속 -->
{% extends 'blog/base.html' %}
<!-- 블록 영역 구분(부모 자식 모두) -->
{% block main_area %}
{% endblock %}
<!-- 다른 템플릿 포함 -->
```html
{% include 'blog/navbar.html' %}
반응형