/ PYTHON

Python Django 웹 프레임워크(8) - Blog project 개발

Python 기초강의는 여러 절로 구성되어 있습니다.


Blog project 개발

Poll Project의 내용을 기반으로 이번에는 ModelForm을 이용한 CRUD 구현 및 Django에 Bootstrap4를 적용해 보도록 하겠습니다.

project 생성

새로운 project blog를 생성합니다.

C:/python-Django> django-admin startproject blog

project와 application을 모두 포함하는 폴더 이름을 MyBlog로 변경합니다.

C:/python-Django> move blog MyBlog

working directory를 MyBlog 폴더로 변경합니다.

C:/python-Django> cd MyBlog

posts application을 생성합니다.

C:/python-Django/MyBlog> python manage.py startapp posts

이후부터는 PyCharm을 이용해 작업을 진행합니다.

project 환경설정

환경설정을 위해 project 폴더의 settings.py파일을 수정합니다. 기본적인 DEBUG=TRUE 설정에 따른 ALLOWED_HOSTS에 대한 내용을 다음과 같이 수정합니다.


ALLOWED_HOSTS = ['localhost', '127.0.0.1']

INSTALLED_APPS 부분에 posts application을 등록합니다.


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'posts.apps.PostsConfig'
]

blog project 폴더안에 templates 폴더를 생성한 후 TEMPLATES 부분을 수정하여 해당 templates 폴더를 기본 Template 경로로 설정합니다.


TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'blog', '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',
            ],
        },
    },
]

TIME_ZONE 부분은 세계표준시에서 한국시간으로 변경합니다.


TIME_ZONE = 'Asia/Seoul'

Static File(CSS, JavaScript, Image)을 사용하기 위해서 Static File 폴더를 지정하고 폴더를 BASE_DIR 하단에 생성합니다.


STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static')
]

기본 Table 생성

기본 테이블을 생성하기 위해 다음의 명령을 수행합니다.

C:/python-Django/MyBlog> python manage.py migrate

관리자 계정 생성

관리자 page에 접속하기 위한 계정을 생성합니다.

C:/python-Django/MyBlog> python manage.py createsuperuser

서버 기동

project가 정상적으로 생성되었는지 확인하기 위해 내장서버를 이용해 deploy합니다.

C:/python-Django/MyBlog> python manage.py runserver

Admin Page 접속 확인

http://localhost:8000/admin으로 접속 후 관리자 계정으로 로그인


Model 생성

Model을 생성하기 위해 posts application 내의 models.py 파일에 다음과 같은 내용을 입력합니다.


class Post(models.Model):
    author = models.CharField('작성자', max_length=20)
    contents = models.CharField('글내용', max_length=100)

    def __str__(self):
        return self.contents

Admin Page에 반영하기 위해서 posts application 내의 admin.py에 class를 등록합니다.


from django.contrib import admin
from posts.models import Post

admin.site.register(Post)

Database 변경사항을 반영하기 위해서 migration 초안을 생성하고 설정된 Schema를 Database에 실제로 적용해야 합니다.

마이그레이션 초안을 생성하기 위해서 다음과 같이 실행합니다.

C:/python-Django/MyBlog> python manage.py makemigrations

설정된 Schema를 Database에 적용하기 위해서 다음과 같이 실행합니다.

C:/python-Django/MyBlog> python manage.py migrate


URL 경로 설정

blog project의 urls.py파일을 다음과 같이 수정하여 계층적으로 URL을 관리하기 위한 설정을 합니다.


# blog/urls.py 

from django.contrib import admin
from django.urls import path, include
from django.conf.urls import url
from django.views.generic.base import TemplateView

urlpatterns = [
    url(r'^$', TemplateView.as_view(template_name='index.html'),
        name='home'),
    path('admin/', admin.site.urls),
    path('posts/', include('posts.urls'))
]

/posts/ 경로로 들어오는 모든 request를 처리하기 위해서 posts application의 urls.py파일을 다음과 같이 작성합니다.


# posts/urls.py 

from django.urls import path
from . import views

app_name = 'posts'  

urlpatterns = [
    path('list/', views.p_list, name='list'),
]

약간의 코드를 추가해 http://localhost:8000에 대한 Homepage설정을 추가했습니다. blog project 폴더안에 templates 폴더 안에 index.html을 위치시킵니다. Bootstrap Example 중 하나를 이용해서 처리했습니다.


<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Blog Project</title>

    <!-- Bootstrap core CSS -->
    <link rel="stylesheet" 
          href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" 
          integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" 
          crossorigin="anonymous">

    <!-- Custom styles for this template -->
    <link href="/static/css/cover.css" rel="stylesheet">
  </head>

  <body class="text-center">
    <div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
      <header class="masthead mb-auto">
        <div class="inner">
          <h3 class="masthead-brand">My Blog</h3>
        </div>
      </header>

      <main role="main" class="inner cover">
        <h1 class="cover-heading">Blog Project</h1>
        <p class="lead">Django Framework</p>
        <p class="lead">
          <a href="/posts/list/" class="btn btn-lg btn-secondary">Enter BBS</a>
        </p>
      </main>

      <footer class="mastfoot mt-auto">
        <div class="inner">
          <p>Copyright 2020</p>
        </div>
      </footer>
    </div>
  </body>
</html>



기본 Template 설정

하나의 html 파일을 생성해 모든 template 파일의 base html로 사용합니다.

blog project folder 하단의 templates 폴더안에 기본적인 형태로 base.html을 생성합니다.

Django에 Bootstrap4을 적용하기 위해서는 django-bootstrap4를 설치해야 합니다. 아래의 명령어로 package를 설치합니다.

pip install django-bootstrap4

설치가 완료된 다음에 blog project 폴더안의 settings.pyINSTALLED_APPS을 다음과 같이 수정하여 bootstrap application을 추가합니다.


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'posts.apps.PostsConfig',
    'bootstrap4',
]

이후에 기본적으로 생성된 base.html에 Bootstrap을 사용하기 위해서 CDN을 설정합니다. 해당 CDN은 Bootstrap 홈페이지에서 copy해서 사용합니다.

아래와 같이 base.html을 수정합니다.



<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
</head>
<body>
    {% block container %}
    {% endblock %}
</body>
</html>



Model Form 생성

Model Form을 이용해서 modelfields를 지정하면 Model Form이 자동으로 폼 필드를 생성해주기 때문에 Form 처리를 상당히 쉽게 처리할 수 있습니다.

어떤 Model을 기반으로 폼을 작성할 것인지를 Meta.model 에 지정하고 fields는 Model class의 field 중 일부만 폼 클래스에서 사용하고자 할 때 지정하는 옵션입니다.

posts application 폴더 내에 forms.py 파일을 생성한 후 아래의 코드와 같이 Model Form class를 생성합니다.


from django import forms
from .models import Post


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


list 페이지 작성

posts application의 views.py의 내용을 수정합니다.


from django.shortcuts import render
from posts.models import Post


def p_list(request):
    posts = Post.objects.all().order_by('-id')
    return render(request, 'list.html', {'posts': posts})


list.html 파일을 Bootstrap을 이용해서 수정합니다.




{% extends 'base.html' %}
{% block container %}

<script src="/static/js/posts.js"></script>
<div class="container">
    <h1>Bulletin Board System(BBS)</h1>
    <button type="button" class="btn btn-primary"
    onclick="new_post()">새글 작성</button>
    <div class="m-1"></div>

    <table class="table table-hover">
      <thead class="thead-dark">
        <tr>
          <th scope="col">#</th>
          <th scope="col">글작성자</th>
          <th scope="col">글내용</th>
          <th scope="col">수정</th>
          <th scope="col">삭제</th>
        </tr>
      </thead>
      <tbody>
        {% for post in posts %}
        <tr>
          <th scope="row">{{ post.id }}</th>
          <td>{{ post.author }}</td>
          <td>{{ post.contents }}</td>
          <td></td>
          <td></td>
        </tr>
        {% endfor %}
      </tbody>
    </table>
</div>

{% endblock %}



create 페이지 작성

views.py 내용을 수정합니다.

POST방식일때와 GET방식일때를 구분하여 ModelForm을 create.html로 전달합니다.

POST방식일 경우는 ModelForm객체에 데이터가 담겨서 저장되어야 하는 경우이며 GET방식인 경우는 사용자에게 빈 양식을 보여주기 위해서 사용됩니다.


from django.shortcuts import render, redirect
from posts.models import Post
from posts.forms import PostForm

def p_list(request):
    posts = Post.objects.all().order_by('-id')
    return render(request, 'list.html', {'posts': posts})


def p_create(request):
    # POST 방식
    if request.method == 'POST':
        post_form = PostForm(request.POST)
        
        if post_form.is_valid():
            post_form.save()
            return redirect('posts:list')

    # GET 방식
    else:
        post_form = PostForm()
    return render(request, 'create.html', {'post_form': post_form})

create.html 파일을 Bootstrap을 이용해서 수정합니다.




{% extends 'base.html' %}
{% load bootstrap4 %}
{% block container %}

<div class="container">
    <h1>New Post</h1>

    <form method="post">
        {% csrf_token %}
         <!-- {{ post_form }}-->
        {% bootstrap_form post_form %}
        <button type="submit" class="btn btn-primary">등록</button>
    </form>
</div>

{% endblock %}



delete 기능 작성

이번에는 list 화면에서 삭제버튼을 붙여서 delete 기능을 구현해 보겠습니다.

urls.py 를 수정합니다.


from django.urls import path
from . import views

app_name = 'posts'

urlpatterns = [
    path('list/', views.p_list, name='list'),
    path('create/', views.p_create, name='create'),
    path('<int:post_id>/delete/', views.p_delete, name='delete'),
]

views.py를 수정합니다.


from django.shortcuts import render, redirect
from posts.models import Post
from posts.forms import PostForm


def p_list(request):
    posts = Post.objects.all().order_by('-id')
    return render(request, 'list.html', {'posts': posts})


def p_create(request):
    # POST 방식
    if request.method == 'POST':
        post_form = PostForm(request.POST)

        if post_form.is_valid():
            post_form.save()
            return redirect('posts:list')

    # GET 방식
    else:
        post_form = PostForm()
    return render(request, 'create.html', {'post_form': post_form})


def p_delete(request, post_id):
    post = Post.objects.get(id=post_id)
    post.delete()

    return redirect('posts:list')

list.html을 수정해서 버튼을 붙입니다.




{% extends 'base.html' %}
{% block container %}

<script src="/static/js/posts.js"></script>
<div class="container">
    <h1>Bulletin Board System(BBS)</h1>
    <button type="button" class="btn btn-primary"
    onclick="new_post()">새글 작성</button>
    <div class="m-1"></div>

    <table class="table table-hover">
      <thead class="thead-dark">
        <tr>
          <th scope="col">#</th>
          <th scope="col">글작성자</th>
          <th scope="col">글내용</th>
          <th scope="col">수정</th>
          <th scope="col">삭제</th>
        </tr>
      </thead>
      <tbody>
        {% for post in posts %}
        <tr>
          <th scope="row">{{ post.id }}</th>
          <td>{{ post.author }}</td>
          <td>{{ post.contents }}</td>
          <td></td>
          <td><a href="{% url 'posts:delete' post.id %}" class="btn btn-danger">삭제</a></td>
        </tr>
        {% endfor %}
      </tbody>
    </table>
</div>

{% endblock %}



update 기능 작성

마지막으로 수정기능을 구현해보겠습니다.

urls.py 파일을 수정합니다.


from django.urls import path
from . import views

app_name = 'posts'

urlpatterns = [
    path('list/', views.p_list, name='list'),
    path('create/', views.p_create, name='create'),
    path('<int:post_id>/delete/', views.p_delete, name='delete'),
    path('<int:post_id>/update/', views.p_update, name='update'),
]

views.py 파일을 수정합니다.


from django.shortcuts import render, redirect, get_object_or_404
from posts.models import Post
from posts.forms import PostForm


def p_list(request):
    posts = Post.objects.all().order_by('-id')
    return render(request, 'list.html', {'posts': posts})


def p_create(request):
    # POST 방식
    if request.method == 'POST':
        post_form = PostForm(request.POST)

        if post_form.is_valid():
            post_form.save()
            return redirect('posts:list')

    # GET 방식
    else:
        post_form = PostForm()
    return render(request, 'create.html', {'post_form': post_form})


def p_delete(request, post_id):
    post = Post.objects.get(id=post_id)
    post.delete()

    return redirect('posts:list')


def p_update(request, post_id):

    post = get_object_or_404(Post, id=post_id)

    if request.method == 'POST':
        postform = PostForm(request.POST, instance=post)

        if postform.is_valid():
            postform.save()
            return redirect('posts:list')

    else:
        postform = PostForm(instance=post)
        return render(request, 'create.html', {'post_form': postform})

마지막으로 list.html 파일을 수정하여 수정버튼을 붙이면 됩니다.

End.


Python 강좌는 아래의 책과 사이트를 참조했습니다. 조금 더 자세한 사항을 알고 싶으시면 해당 사이트를 방문하세요!!