실습 확장

jurin·2021년 8월 10일
0

책을 출판하는 데 필요한 정보들인 책, 저자, 출판사에 대한 정보들을 관리하는 웹 애플리케이션인 books 애플리케이션을 설계한다.

새로운 애플리케이션을 만들기 위해 먼저 상위 프로젝트를 만든다. 프로젝트는 예제에서 사용한 testsite를 사용하고 하위에 books 애플리케이션을 코딩할 것이다.

코딩 순서

  • 애플리케이션 설계
  • 프로젝트 뼈대 만들기
  • Model 코딩
  • URLconf 코딩
  • View 코딩
  • Template 코딩

애플리케이션 설계

책, 저자, 출판사의 정보를 보여주고 입력, 수정, 삭제할 수 있는 books 애플리케이션

  • Book 테이블 설계

  • Author 테이블 설계

  • Publisher 테이블 설계

  • 뷰 흐름 설계

프로젝트 뼈대 만들기

  • 애플리케이션 추가

  • setting.py에 books 애플리케이션 등록

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'voting.apps.VotingConfig',
    'books.apps.BooksConfig',
]

Model 코딩

1. models.py에 테이블 정의

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)
    
    # MTM으로 여러권의 Book과 여러 명의 Author 간의 관계
    authors = models.ManyToManyField('Author')
    
    # FK로 여러권의 Book과 하나의 Publisher 간의 관계
    # FK 사용시 on_delete 옵션 필수
    publisher = models.ForeignKey('Publisher', on_delete=models.CASCADE)
    publication_date = models.DateField()

    def __str__(self):
        return self.title

class Author(models.Model):
    name = models.CharField(max_length=50)
    salutation = models.CharField(max_length=100)
    email = models.EmailField()

    def __str__(self):
        return self.name

class Publisher(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=100)
    website = models.URLField()

    def __str__(self):
        return self.name
  • 테이블 간 관계를 나타내는 필드 3가지
  1. ForeignKey --> N:1
  2. ManyToManyField --> N:N
  3. OneToOneField --> 1:1

2. Admin 사이트에 보이도록 admin.py 파일에서 등록

from django.contrib import admin

from books.models import Book, Author, Publisher

admin.site.register(Book)
admin.site.register(Author)
admin.site.register(Publisher)

정의한 테이블을 DB에 반영

python manage.py makemigrations
python manage.py migrate

URLconf 코딩

URLconf는 testsite/urls.py와 books/urls.py 2개의 파일에 코딩한다. 기존의 testsite/urls.py 파일에 books 앱의 URL 설정을 불러오는 내용을 추가한다.

urlpatterns = [
    path('admin/', admin.site.urls),
    path('voting/', include('voting.urls')),
    path('books/', include('books.urls'))
]

이제 books/urls.py에 /book/, /author/ 및 /publisher/등의 URL을 정의해준다.

from . import views
from django.urls import path

app_name = 'books'

urlpatterns = [
    # /books/
    path('', views.BooksModeView.as_view(), name='index'),
    
    # /books/book/
    path('book/', views.BooksList.as_view(), name='book_list'),

    # /books/author/
    path('author/', views.AuthorList.as_view(), name='author_list'),
    
    # /books/publisher/
    path('publisher/', views.PublisherList.as_view(), name='publisher_list'),
    
    # /books/book/99/
    path('book/<int:pk>', views.BookDetail.as_view(), name='book_detail'),
    
    # /books/author/99/
    path('author/<int:pk>', views.AuthorDetail.as_view(), name='author_detail'),

    # /books/publisher/99/
    path('publisher/<int:pk>', views.PublisherDetail.as_view(), name='publisher_detail'),

]

7개의 URL과 그에 해당하는 클래스형 뷰 7개를 정의했다. 함수형 뷰가 아닌 클래스형 뷰로 정의하기 위해 URL에 따른 뷰 클래스 및 as_view() 메소드를 지정했다.

View 코딩

URLconf에서 지정한 클래스형 뷰를 코딩한다.

books/views.py

from django.views.generic import ListView, DetailView
from django.views.generic.base import TemplateView
from books.models import Book, Author, Publisher


# TemplateView
# books 애플리케이션의 첫 화면을 보여주기 위한 뷰
# 특별한 로직이 없고 템플릿 파일만을 렌더링하는 경우
# TemplateView 제네릭 뷰를 상속받아 사용
class BooksModelView(TemplateView):
    # TemplateView를 사용하는 경우 필수적으로 template_name 클래스 변수를
    # 오버라이딩해서 지정해야 한다.
    template_name = 'books/index.html'

    # 템플릿 시스템으로 넘겨줄 컨텍스트 변수가 있는 경우
    # get_context_data() 메소드 오버라이딩
    def get_context_data(self, **kwargs):
        # get_context_data() 메소드 정의 시 반드시 첫 줄에 super() 메소드 호출
        context = super().get_context_data(**kwargs)
        # books 애플리케이션의 첫 화면에 테이블 리스트를 보여주기 위해
        # 컨텍스트 변수 model_list에 담아 템플릿 시스템에 넘겨준다.
        context['model_list'] = ['Book', 'Author', 'Publisher']
        return context

# ListView
# ListView를 상속받는 경우 객체가 들어있는 리스트를 구성해서
# 이를 컨텍스트 변수로 템플릿 시스템에 넘겨주면 된다.
# 만일 리스트를 테이블에 들어있는 모든 레코드를 가져와 구성하는 경우
# 모델 클래스명만 지정해주면 된다.
class BookList(ListView):
    # Book 테이블로부터 모든 레코드를 가져와 object_list 컨텍스트 변수 구성
    # 템플릿 파일은 디폴트로 books/book_list.html파일이 된다.
    model = Book

class AuthorList(ListView):
    # Author 테이블로부터 모든 레코드를 가져와 object_list 컨텍스트 변수 구성
    # 템플릿 파일은 디폴트로 books/author_list.html파일이 된다.
    model = Author

class PublisherList(ListView):
    # Publisher 테이블로부터 모든 레코드를 가져와 object_list 컨텍스트 변수 구성
    # 템플릿 파일은 디폴트로 books/publisher_list.html파일이 된다.
    model = Publisher

# DetailView
# DetailView를 상속받는 경우 특정 객체 하나를 컨텍스트 변수에 담아
# 템플릿 시스템에 넘겨주면 된다.
# 만일 테이블에서 PK로 조회해서 특정 객체를 가져오는 경우
# 모델 클래스명만 지정해주면 된다.
# 조회시 사용할 PK값은 URLconf에서 추출하여 뷰로 넘어온 파라미터를 사용
class BookDetail(DetailView):
    model = Book

class AuthorDetail(DetailView):
    model = Author

class PublisherDetail(DetailView):
    model = Publisher

명시적으로 지정하지 않아도 장고에서 디폴트로 알아서 지정해주는 속성 2가지가 있다.
1. 컨텍스트 변수로 object_list 사용
2. 템플릿 파일을 ListView를 상속받은 경우엔 모델명 소문자_list.html, DetailView를 상속받은 경우엔 모델명 소문자_detail.html 형식의 이름으로 지정해준다.

View를 코딩하면서 제네릭 뷰의 강력함을 볼 수 있었다. 특히 DB 객체의 리스트를 보여주거나 상세 내용을 보여주는 작업을 코딩할 때 강점이 나타난다.

Template 코딩

뷰 클래스가 7개이고 템플릿 파일도 7개가 필요하다.

index.html

{% extends "base_books.html" %}

{% block content %}
    <h2>Books Management</h2>
    <ul>
        {% for modelname in model_list %}
        {% with "books:"|add:modelname|lower|add:"_list" as urlvar %}
            <li><a href="{% url urlvar %}">{{ modelname }}</a></li>
        {% endwith %}
        {% endfor %}
    </ul>
{% endblock content %}

base_books.html 템플릿을 상속받아 content 블록만을 재정의하였다.

뷰로부터 model_list 컨텍스트 변수를 전달받아서 model_list에 들어있는 모델명들을 순회하면서 화면에 하나씩 보여주고 있다. 또한 모델명을 클릭 시 접속할 URL을 추출하기 위해 {% url urlvar %} 템플릿 태그를 사용했고, urlvar 인자는 {% with %} 태그를 사용해 정의했다.

book_list.html

뷰로부터 object_list 컨텍스트 변수를 전달받아서 object_list에 들어있는 객체들을 순회하면서 하나씩 보여준다.

{% extends "base_books.html" %}

{% block content %}
    <h2>Book List</h2>
    <ul>
        {% for book in object_list %}
            <li><a href="{% url 'books:book_detail' book.id %}">{{ book.title }}</a></li>
        {% endfor %}
    </ul>
{% endblock content %}

book 객체의 title 속성(book.title)을 표시하고 해당 텍스트를 클릭하는 경우 books:book_detail URL 패턴으로 웹 요청을 보낸다.

author_list.html

화면에 표시하는 내용이 {{ author.name }}라는 점만 다르고 book_list.html과 동일하다.

{% extends "base_books.html" %}

{% block content %}
    <h2>Author List</h2>
    <ul>
        {% for author in object_list %}
            <li><a href="{% url 'books:author_detail' author.id %}">{{ author.name }}</a></li>
        {% endfor %}
    </ul>
{% endblock content %}

publisher_list.html

같은 원리

{% extends "base_books.html" %}

{% block content %}
    <h2>Publisher List</h2>
    <ul>
        {% for publisher in object_list %}
            <li><a href="{% url 'books:publisher_detail' publisher.id %}">{{ publisher.name }}</a></li>
        {% endfor %}
    </ul>
{% endblock content %}

book_detail.html

특정 레코드를 object라는 컨텍스트 변수로 전달받아서 object 객체, 즉 레코드에 들어있는 컬럼값들을 보여준다.

{% extends 'base_books.html %}

{% block content %}
<h1>{{ object.title }}</h1>
<br>
<li>Authors:
{% for author in object.authors.all %}
    {{ author }}
<!--    공동 저자인 경우 저자 이름 뒤에 콤마 출력하는 로직-->
    {% if not forloop.last %}, {% else %}{% endif %} 
{% endfor %}
</li>
<li>Publisher: {{ object.publisher }}</li>
<li>Publication date: {{ object.publication_date }}</li>
{% endblock content %}

author_detail.html

{% extends 'base_books.html %}

{% block content %}
<h1>{{ object.name }}</h1>
<p>{{ object.salutation }}</p>
<li>Email: {{ object.email }}</li>
{% endblock content %}

publisher_detail.html

{% extends 'base_books.html %}

{% block content %}
<h1>{{ object.name }}</h1>
<p>{{ object.website }}</p>
<li>Address: {{ object.address }}</li>
{% endblock content %}

Template 상속 기능 추가

부모 템플릿인 base.html 및 base_books.html 템플릿을 작성한다.

위 2개의 부모 템플릿은 개별 애플리케이션 템플릿이 아닌 공통 템플릿이므로web_programing/templates 디렉토리에 생성한다.

이 디렉토리는 settings.py가 TEMPLATES 항목으로 정의한 디렉토리임의 유의하자

TEMPLATES = [
		...
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        ...

_**base.html**_
```html
<!DOCTYPE html>
<html lang="en">
<head>
    {% load static %}
    <link rel="stylesheet" href="{% static 'admin/css/base.css' %}" />
    <title>{% block title %}My Amazing Site{% endblock %}</title>
</head>

<body>
    <div id="sidebar">
        {% block sidebar %}
        <ul>
            <li><a href="/">Project_Home</a></li>
            <li><a href="/admin">Admin</a></li>
        </ul>
        {% endblock %}
        <br>
    </div>
    
    <div id="content">
        {% block content%}
        {% endblock %}
    </div>
</body>
</html>

{% load static %} 템플릿 태그는 static이라는 사용자 정의 태그를 로딩해주고, {% static %} 사용자 정의 태그를 통해 admin/css/base.css 스타일시트 파일을 찾는다. 이는 장고의 Admin 사이트에서 사용하는 스타일시트를 사용하여 룩앤필을 보여주는 효과를 준다.

base_books.html

{% extends 'base.html' %}

<title>{% block title %}Books Application Site{% endblock %}</title>

{% block sidebar %}
{{ block.super }}
<ul>
    <li><a href="/books/"Books_Home></a></li>
</ul>
{% endblock%}

모든 작업을 마친 후 확인을 위해 서버를 구동시켜봤는데 detail.html에서 templatesyntaxerror 오류가 떴다.

deatil.htaml에서 base_books.html을 두 번 상속받는 다는 뜻인 거 같아서 지워줬더니 해결됐다. 아마 list에서 한번 상속 받고 detail로 넘어와서 또 상속받아서 그런 게 아닐까 싶다.





출처: Django로 배우는 파이썬 웹 프로그래밍(기초) - 김석훈님

profile
anaooauc1236@naver.com

0개의 댓글