티스토리 뷰

2020/08/13 - [Django] - localLibrary 관리자 페이지

 

localLibrary 관리자 페이지

2020/08/04 - [Django] - localLibrary 모델(models.py) 편집 localLibrary 모델(models.py) 편집 2020/08/03 - [Django] - localLibrary 뼈대 만들기 localLibrary 뼈대 만들기 본 내용은 Django 튜토리얼 사이트..

editor752.tistory.com

본 내용은 Django 튜토리얼 사이트를 공부하면서 내용을 정리한 것이다.

지금까지는 localLibrary 사이트를 만들기 위한 서버 쪽 작업이 중심이었다. 이제는 사용자가 접하게 되는 웹페이지를 만들어갈 것이다.

앞으로 할 작업을 간단히 나열하면 다음과 같다.

  1. 먼저 페이지에서 어떤 정보를 보여줄 것인지를 결정한다.
  2. 해당 요소들을 반환하는 데 사용되는 URL(해당 요소들이 뿌려질 페이지의 주소)들을 정의한다.
  3. 웹페이지를 나타내기 위한 URL 매퍼, views, 그리고 템플릿들을 생성한다.
    • URL 매퍼: 적절한 view 함수들을 위해 지원되는 URL들(그리고 URL들 안에 인코딩된 어떤 정보라도)을 포워딩하기 위해.
    • View 함수: 요청된 데이터를 모델들에게서 가져오고, 데이터를 표시하는 HTML 페이지를 생성하고 그리고 브라우저 안의 view로 페이지들을 사용자에게 반환하기 위해.
    • 템플릿: 데이터를 뷰들 안에 렌더링할 때 사용하기 위해.

다음 다이어그램은 사이트 내의 데이터 흐름과 django의 각 요소가 협응하는 방식을 도식화한 것이다.

resource URLs 정의

localLibrary는 사용자들에게 읽기 전용으로 제작될 것이다. 이때 사이트는 ① 방문 페이지(index page) ② 책 목록, ③ 저자 목록, ④ 세부 view들을 보여주는 페이지가 제공된다.

각 페이지를 위해 필요한 URL들은 아래와 같다.

  • catalog/: 홈, 색인(index) 페이지.
  • catalog/books/: 모든 책들의 목록.
  • catalog/authors/: 모든 저자들의 목록.

  • catalog/book/<id>: <id> 라는 이름의(기본값) 프라이머리 키(primary key) 필드를 가지는 특정한 책을 위한 세부 사항 뷰(detail view). 예를 들어, 목록에 추가된 세 번째 책은 /catalog/book/3이 될 것이다.
  • catalog/author/<id>: <id> 라는 이름의 프라이머리 키(primary key) 필드를 가지는 특정한 저자를 위한 세부 사항 뷰(detail view). 예를 들어, 목록에 추가된 11번째 저자는 /catalog/author/11이 될 것이다.

note: 주의: 장고를 이용해서 당신이 원하는 대로 URL들을 만들 수 있다. 즉 위와 같이 URL의 본문(body)에 정보를 인코딩할 수도 있고, 또는 URL 안에 GET 매개 변수들을 포함시킬 수 있습니다(예: /book/?id=6). 어떤 방식이건 URL들은 깨끗하고, 논리적이고, 읽기 쉬워야 한다.
장고 문서는 더 나은 URL 설계(design)를 위해 URL의 본문(body)에 정보를 인코딩하는 것을 권장한다.

index 페이지 만들기

index 페이지는 데이터베이스 안의 서로 다른 레코드들의 생성된 개수(count) 와 함께 몇 가지 정적 HTML을 포함한다. 이것이 작동하도록 하기 위해서 URL 매핑, 뷰 그리고 템플릿을 생성하여야 한다.

url 매핑

앞서 사이트의 뼈대를 만드는 과정에서 locallibrary/urls.py 파일에 이미 아래의 코드를 추가하고 catalog/ 아래에 urls.py 파일도 생성했었다.

urlpatterns += [
    path('catalog/', include('catalog.urls')),
]

이는 catalog/ 주소로 들어오는 경우 하위 페이지의 주소는 catalog.urls의 매핑에 따르겠다는 의미이다. 우리의 index 페이지는 catalog의 하위에 놓일 것이므로 이 페이지의 url을 제대로 전달하기 위해서는 catalog/urls.py를 아래와 같이 편집해 주어야 한다.

urlpatterns = [
    path('', views.index, nam='index'),
]

이 코드는 일정한 패턴의 url 주소가 들어오면 어떤 파일을 보일 것이지를 지정하고 있다. 즉 빈 문자열 url 패턴 ''이 올 경우 view 함수인 views.index가 호출된다. 이 함수는 views.py 파일 안에서 index()로 이름지어져 있다. name 매개변수는 특정한 url 매핑을 위한 고유 id이다. 이 id를 이용하여 매퍼를 반전시킬 수도 있다.

view 함수 편집

django에서는 뷰(views.py)는 사용자에게 보여질 실제 페이지를 생성한다. 뷰는 페이지 생성을 위해 아래와 같은 작업을 수행한다.

  • HTTP 요청(request)을 처리한다.
  • 데이터베이스에서 요청된 데이터를 가져온다.
  • HTML 템플릿을 이용해서 데이터를 HTML 페이지에 렌더링한다.
  • 생성된 HTML을 HTTP 응답(response)으로 반환하여 사용자들에게 페이지를 보여준다.

즉 index 뷰도 데이터베이스 안에 있는 Book, BookInstance, BookInstance, Author 레코드들의 개수에 대한 정보를 가져오고, 그 정보를 디스플레이(display)를 위해 템플릿에 전달해야 한다.

이제 view.py 파일을 아래와 같이 편집해 보자. 템플릿과 데이터를 이용해 HTML 파일을 생성하는 render() 바로가기 함수를 포함(import)하고 있는지 확인하자.

from catalog.models import Book, Author, BookInstance, Genre

def index(request):
    """View function for home page of site."""

    # Generate counts of some of the main objects
    num_books = Book.objects.all().count()
    num_instances = BookInstance.objects.all().count()

    # Available books (status = 'a')
    num_instances_available = BookInstance.objects.filter(status__exact='a').count()

    # The 'all()' is implied by default.    
    num_authors = Author.objects.count()

    context = {
        'num_books': num_books,
        'num_instances': num_instances,
        'num_instances_available': num_instances_available,
        'num_authors': num_authors,
    }

    # Render the HTML template index.html with the data in the context variable
    return render(request, 'index.html', context=context)

우선 models.py에서 사용할 모델들을 모두 임포트하였다. 다음으로 모델 클래스에서 objects.all() 속성을 이용하여 데이터베이스에 저장된 전체 레코드 개수를 가져왔다.

    num_books = Book.objects.all().count()
    num_instances = BookInstance.objects.all().count()
    num_authors = Author.objects.count()

다음은 objects.filter() 속성을 이용하여 실물 책에서 사용이 가능한 책(statusa인) 레코드의 개수를 가져왔다.

    num_instances_available = BookInstance.objects.filter(status__exact='a').count()

마지막엔 HTML 페이지를 생성하고 이 페이지를 응답으로서 반환하기 위해 render() 함수를 호출하였다. render() 함수는 아래 매개 변수들를 사용한다.

  • HttpRequest인 원본 request 객체.
  • 데이터에 대한 플레이스홀더(placeholder)들을 갖고 있는 HTML 템플릿.
  • 파이썬 딕셔너리(dictionary)인, 플레이스홀더에 삽입할 데이터를 갖고 있는 context 변수.

템플릿 편집

템플릿은 HTML 페이지의 구조나 레이아웃을 정의하는 텍스트 파일이다. 템플릿은 실제 콘텐츠를 나타내기 위해 플레이스홀더(placeholder)들을 사용한다. 장고는 당신의 애플리케이션 안에서 'templates' 라고 이름지어진 경로 안에서 자동적으로 템플릿들을 찾을 것이므로 템플릿 파일은 이 폴더 안에 있어야 한다. 이 폴더 안에 해당 템플릿 파일이 없다면 에러를 표시할 것이다. 서버를 시작한 다음 127.0.0.1:8000/catalog/에 접속하면 아래와 같은 에러 메시지를 확인할 수 있다.

이제 실제로 템플릿 파일을 작성해 보자. 우선 템플릿은 사이트의 전체에 적용된 기본 구조와 각 페이지별로 추가되어야 할 요소가 있을 것이다. 따라서 django는 장고 템플릿 언어(Django templating language)를 사용하여 기본 템플릿을 선언하고 템플릿을 확장하여 각각의 페이지마다 다른 부분들만을 대체(replace)하도록 하고 있다.


기본 템플릿(base_generic.html)를 검토해 보자. 이 샘플은 제목, 사이드바를 위한 섹션과 이름이 지정된 blockendblock 템플릿 태그가 마크된 주요 내용(main contents)들이 포함된 일반적인 HTML을 포함한다. 블럭(block)들을 비워두거나, 또는 템플릿에서 파생된 페이지들을 렌더링할 때 사용할 기본 내용을 포함할 수 있다.

<!DOCTYPE html>
<html lang="en">
<head>
  {% block title %}<title>Local Library</title>{% endblock %}
</head>
<body>
  {% block sidebar %}<!-- insert default navigation text for every page -->{% endblock %}
  {% block content %}<!-- default content text (typically empty) -->{% endblock %}
</body>
</html>

특정한 view를 위해 탬플릿을 정의할 땐, 먼저 extends 탬플릿 태그를 이용하여 기본 탬플릿을 지정합니다 — 아래 코드 샘플을 참조하세요. 그러고 나서 기본 탬플릿에서와 같이 block/endblock 섹션들을 이용해서 대체할 탬플릿의 섹션들을 선언한다.
예를 들어, 아래와 같이 extends 템플릿 태그의 사용 및 content 블럭(block)을 재정의하는 방법을 보여준다. 생성된 HTML은 기본 템플릿에서 정의된 코드와 구조를 포함할 것이다(title 블럭에서 정의한 기본 내용은 포함하지만, 기본 contents 블럭 대신 새로운 contents 블럭 포함).

{% extends "base_generic.html" %}

{% block content %}
  <h1>Local Library Home</h1>
  <p>Welcome to LocalLibrary, a website developed by <em>Mozilla Developer Network</em>!</p>
{% endblock %}

이제 localLibrary의 기본 템플릿(base_generic.html)을 /locallibrary/catalog/templates/에 아래와 같이 만들어 보자. 보는 바와 같이, 이것은 HTML 코드를 조금 포함하고 title, sidebar 그리고 content 블럭을 정의하였다. 그리고 기본 제목과 모든 책들 및 저자들에 대한 링크를 갖고 있는 기본 사이드바를 갖고 있다. 둘 다 쉽게 변경하기 위해 블럭들 안에 묶여 있다.

<!DOCTYPE html>
<html lang="en">
<head>
  {% block title %}<title>Local Library</title>{% endblock %}
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
  <!-- Add additional CSS in static file -->
  {% load static %}
  <link rel="stylesheet" href="{% static 'css/styles.css' %}">
</head>
<body>
  <div class="container-fluid">
    <div class="row">
      <div class="col-sm-2">
      {% block sidebar %}
        <ul class="sidebar-nav">
          <li><a href="{% url 'index' %}">Home</a></li>
          <li><a href="">All books</a></li>
          <li><a href="">All authors</a></li>
        </ul>
     {% endblock %}
      </div>
      <div class="col-sm-10 ">{% block content %}{% endblock %}</div>
    </div>
  </div>
</body>
</html>

템플릿에는 HTML 페이지의 레이아웃과 프레젠테이션을 개선하기 위한 Bootstrap 의 CSS가 포함되어 있다.
또한 기본 템플릿은 추가적인 꾸미기(styling)를 제공하는 로컬 css 파일(styles.css)을 참조한다. styles.css 파일을 /locallibrary/catalog/static/css/ 경로 안에 생성하고 아래 코드를 파일 안에 복사해 넣는다.

.sidebar-nav {
    margin-top: 20px;
    padding: 0;
    list-style: none;
}

드디어 대망의 index 템플릿이다!
index.html/locallibrary/catalog/templates/ 경로 안에 생성하여 아래 코드를 파일에 넣자. 보는 바와 같이 첫째 행에서 우리의 기본 탬플릿을 확장하고, 탬플릿의 기본 content 블럭을 새로운 블럭으로 대체하였다.

{% extends "base_generic.html" %}

{% block content %}
  <h1>Local Library Home</h1>
  <p>Welcome to LocalLibrary, a website developed by <em>Mozilla Developer Network</em>!</p>
  <h2>Dynamic content</h2>
  <p>The library has the following record counts:</p>
  <ul>
    <li><strong>Books:</strong> {{ num_books }}</li>
    <li><strong>Copies:</strong> {{ num_instances }}</li>
    <li><strong>Copies available:</strong> {{ num_instances_available }}</li>
    <li><strong>Authors:</strong> {{ num_authors }}</li>
  </ul>
{% endblock %}

동적 콘텐츠 섹션에서 우리는 우리가 포함하고 싶은 view의 정보를 위한 플레이스홀더(탬플릿 변수)를 선언한다. 이 변수들은 코드 샘플에서 이중 중괄호로(핸들 바)로 묶인다.

note: 템플릿 변수들은 이중 중괄호로 감싸여져 있고({{ num_books }}) , 템플릿 태그들은 퍼센트 기호와 단일 중괄호로 감싸여 있습니다({% extends "base_generic.html" %}).

여기서는 변수들의 이름은 key로 정해진다는 점에 유의해야 한다. key들은 우리의 view의 render() 함수 안의 딕션너리 context로 전달하는 key이다. 변수들은 템플릿이 렌더링될 때 해당 key의 값으로 대체된다.

Templates 에 정적 파일 참조하기(referencing)

프로젝트는 자바스크립트, CSS 그리고 이미지를 포함하는 정적 리소스들을 사용할 가능성이 높다. 이 파일들의 위치를 알 수 없기 때문에(또는 바뀔 수 있기 때문에), 장고는 STATIC_URL 전역 설정을 기준으로 템플릿에서의 위치를 특정할 수 있도록 한다. 앞서 기본 뼈대 만들기에서 STATIC_URL의 값을 '/static/'으로 설정하지만, 필요하다면 이것들을 콘텐츠 전달 네트워크(content delivery network)나 다른 곳에서 호스트할 수도 있다.

아래 코드(base_generic.html)처럼, 템플릿 안에서 템플릿 라이브러리를 추가하기 위해 static을 지정하는 load 템플릿 태그를 호출한다. 그러고 나서 static 템플릿 태그를 사용할 수 있고 관련 URL을 요구되는 파일에 지정할 수 있다.

<!-- Add additional CSS in static file --> 
{% load static %} 
<link rel="stylesheet" href="{% static 'css/styles.css' %}">

비슷한 방법으로 이미지를 페이지에 추가할 수 있습니다. 예를 들어:

{% load static %}
<img src="{% static 'catalog/images/local_library_model_uml.png' %}" alt="UML diagram" style="width:555px;height:540px;">

URL 링크하기(Linking to URLs)

위의 기본 템플릿(base_generic.html)은 url 템플릿 태그를 소개했습니다.

<li><a href="{% url 'index' %}">Home</a></li> 

이 태그는 urls.py에서 호출된 path() 함수의 이름 및 연관된 view가 그 함수에서 수신받을 모든 인자들을 위한 값들을 허용하고, 리소스에 링크하는 데 사용할 수 있는 URL을 반환한다.

탬플릿을 찾을 수 있는 곳 설정하기

템플릿 폴더 안에서 템플릿을 찾아볼 수 있도록 장고에게 위치를 지정해주어야 한다. 아래 코드처럼 settings.py을 수정하여 TEMPLATES 객체에 templates 경로(dir)를 추가하자.

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

여기까지 잘 따라왔다면 드디어 사용자들은 아래와 같은 localLibrary 사이트의 index 페지이를 볼 수 있을 것이다.

![index page.png](resources/A44692BDE2A57CBF81608412313946A3.png =917x550)


## 추가 작업
1. LocalLibrary 기본 탬플릿(base template)에는 title 블록이 정의되어 있습니다. 색인 탬플릿(index template) 안에 이 블록을 덮어쓰기하고 페이지를 위한 새로운 제목을 만들어 보세요.
```{HTML}
<head>
  {% block title %}<title>Local Library</title>{% endblock %}
</head>

이처럼 처리하면 이 사이트의 모든 하위 페이지에서 동일하게 적용된다. 따라서 각각의 페이지별로 제목을 지정할 수 있도록 코드를 변경해 보자. 이는 index.htmlbase_generic.html파일을 각각 아래와 같이 수정하면 된다.

  • base_generic.html에서 템플릿 태그를 이용하여 페이지별로 들어갈 자리를 지정한다.

      <title>{% block title %}{% endblock %}</title>
  • index.html에 실제로 들어갈 텍스트를 지정한다.

    {% block title %}
    Local Library
    {% endblock %}
  1. 대소문자 구분 없이 특정한 단어를 포함하는 장르와 책들의 개수(count)를 생성하도록 view 를 수정하고, 결과를 context에 전달해 보세요. 이것은 num_books와 num_instances_available을 생성하고 사용하는 것과 비슷한 방법으로 달성할 수 있습니다. 그러고 나서 이 변수들을 포함시키기 위해 index template 를 업데이트 하세요.

내 경우에는 데이터베이스에 경제, 추리, 역사 장르의 책이 존재한다. 따라서 추리 장르의 책의 권수를 표시하는 것을 목표로 삼았다. 우선 view.py 파일에 아래의 코드를 추가한다.

    # Genre('추리')
    num_genre = Book.objects.filter(genre__name__contains='추리').count()

    <생략>

    context = {
        'num_books': num_books,
        'num_instance': num_instance,
        'num_instance_available': num_instance_available,
        'num_authors': num_authors,
        'num_genre': num_genre
    }

일대다 관계를 다른 모델에 정의하는 필드를 필터링해야 한다(예: ForeignKey). 이 경우에 추가적인 이중 밑줄을 사용하여 관련 모델 안의 필드에 색인(index)할 수 있다. 위와 같이 특정한 장르의 책을 필터링하려면, genre 필드를 통해서 name에 색인(index)해야 한다. 대소문자 구분 없이라는 제한을 두었으나 한글에는 대소문자가 없으므로 고려할 이유가 없다. 다만 특정 단어가 포함이라는 조건을 고려하여 __contains를 추가하였다.
그리고 context'num_genre': num_genre를 추가하여 index.html에 해당 데이터를 넘겨준다.

필터링할 때는 field_name__match_type 형식을 사용해야 한다. 이때 match_type의 종류는 아래와 같다. 자세한 내용은 여기를를 참고하라.

  • __icontains: 대소문자를 구분하지 않음
  • __iexact: 대소문자를 구분하지 않는 정확히 일치
  • __exact: 대소문자를 구분하는 정확한 일치
  • __startswith: 특정 문자로 시작

다음으로 index.py를 아래와 같이 수정한다.

  <h2>Dynamic content</h2>
  <p>The library has the following record counts:</p>
  <ul>
    <li><strong>Books:</strong> {{ num_books }}</li>
    <li><strong>Copies:</strong> {{ num_instances }}</li>
    <li><strong>Copies available:</strong> {{ num_instances_available }}</li>
    <li><strong>Authors:</strong> {{ num_authors }}</li>
    <li><strong>추리물:</strong> {{ num_genre }}</li>
  </ul>

순서 없는 리스트 항목으로 추리물을 추가하고 딕셔너리인 context에서 num_genre의 값을 가져오도록 한다. 여기까지 수행하면 리스트 마지막 항목으로 추리물: 3라는 정보가 추가된다.

한 걸음 더!
그렇다면 장르별 권수를 모두 보이려면 어떻게 해야 할까? 간단할 줄 알았는데 꽤 시간이 걸렸다. 먼저 장르의 종류가 확정되어 있지 않으므로 위와 같이 context를 이용해서 장르별 권수 정보를 넘길 수는 없다. render() 함수의 인자로 여러개의 context를 넘길 수 있다면 장르별 권수를 단독의 딕셔너리로 만들어 전달하면 될 것이다.(이것은 획인해 보지 않았다. 고민할 때는 context 하나만 넘길 수 있다고 보았다. 그래서 context의 원소의 하나로 키 'genre_all'의 valu로 딕셔너리(장르별 권수가 들어 있는)를 넘기는 방법을 생각했다. 하지만 view에서 템플릿 태그와 변수만으로는 딕셔너리에서 데이터를 뽑아 html로 변환해 줄 방법이 없었다. 결국 딕셔너리 자체를 넘기는 것은 우선 포기하고 html 코드를 문자열로 생성해서 이 문자열을 context의 원소로 넘겼다.
먼저 view.py파이에 아래 코드를 추가한다.

    string_genre_all = ''
    Genres = Genre.objects.all()
    for genre in Genres:
        count = Book.objects.filter(genre__name__contains=genre).count()
        string_genre_all += '<li><strong>{}:</strong> {}</li>'.format(genre, count)

장르 전체 목록을 Genres로 받은 다음 이를 for 문으로 돌렸다. 각 장르마다 책 권수는 count = Book.objects.filter(genre__name__contains=genre).count()로 얻었다. 마지막으로 순서 없는 목록을 만드는 html 코드(<li><strong>{}:</strong> {}</li>)를 기본 문자열로 삼아 데이터(장르, 권수)는 .format()을 이용하여 추가하였다. 이렇게 하연 데이터베이스 전체의 장르변 권수를 표시하는 코드가 하나의 문자열로 변수(string_genre_all)에 저장된다. 그리고 context'string_genre_all': string_genre_all를 추가한다.
그리고 base_generic.html에 템플릿 태그를 아래와 같이 추가한다.

    <div class="col-sm-10 ">
          {% block content %}{% endblock %}
          {% block genres %}{% endblock %}
      </div>

마지막으로 index.html에 아래의 코드를 추가한다.

{% block genres %}
  <h2>Genre</h2>
  <ul>
    {% autoescape off %}
    {{ string_genre_all }}
    {% endautoescape %}
  </ul>
{% endblock %}

이 코드에서 주의해야 할 부분은 {% autoescape off %}{% endautoescape %}이다. 처음에는 {{ string_genre_all }}만 추가했었다. 그러자 브라우저에는 장르별 목록이 아닌, string_genre_all에 담긴 html 코드가 그대로 출력이 되었다.

페이지 소스를 열어 보니 <>&lt;li&gt;으로 바뀌어 버려 발생하는 문제였다. 아마도 django에서 html 태그가 실행되지 않도록 <>의 자동 변환이 일어난 모양이었다. 이를 막기 위해서는 {% autoescape off %}{% endautoescape %} 안에 ``{{string_genre_all}}`을 넣어주어야 했다. 여기까지 마치면 아래와 같은 화면을 볼 수 있다.


댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
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
글 보관함
05-05 22:12