첫 번째 Django 앱 만들기! part 2

이 튜토리얼은 튜토리얼 1에 이어 계속됩니다. 앞서 개발한 투표 애플리케이션과 함께 Django에서 자동으로 생성되는 관리자 사이트에 대해 알아보도록 하겠습니다.

철학

관리자나 고객들이 컨텐츠를 추가, 변경, 삭제할 수 있는 관리자 사이트를 생성하는 것은 그다지 창조적이지 않은 지루한 작업 입니다. 이러한 작업을 위해서 Django는 모델을 위한 관리자 인터페이스 생성을 완전히 자동화하였습니다.

Django는 뉴스를 준비하는 환경을 위해 만들어졌으며, “컨텐츠 발행인”과 “공개” 사이트를 완벽히 분리하였습니다. 사이트 관리자들은 새로운 스토리나 이벤트, 스포츠 점수 결과 등을 추가하기 위해 시스템을 이용하며, 이런 컨텐츠들은 공개 사이트에 노출됩니다. Django는 사이트 관리자들이 컨텐츠를 수정할 수 있는 통합 인터페이스를 제공함으로서 문제를 해결합니다.

관리자 사이트는 사이트 방문자를 위한 것이 아닙니다. 이는 관리자들을 위한 것입니다.

관리자 사이트 활성화 하기

관리자 사이트는 활성화되지 않는 것이 기본으로 되어 있습니다. 관리자 사이트를 활성화시키기 위해서는 다음 세 가지의 작업이 필요합니다.

  • INSTALLED_APPS 설정에 있는 "django.contrib.admin"의 주석을 제거합니다.

  • python manage.py syncdb 명령어를 실행합니다. 여러분이 INSTALLED_APPS에 새로운 어플리케이션을 설치하였기 때문에 데이터베이스 테이블을 갱신해야 합니다.

  • mysite/urls.py 파일을 열어 관리 사이트에 관련된 행의 주석을 제거합니다. 총 세 행의 주석을 제거해야 합니다. 이 파일은 URL 설정(URLconf) 파일이며 다음 튜토리얼에서 보다 자세히 알아보겠습니다. 지금 여러분이 알아야 할 부분은 URL roots 를 어플리케이션에 연결시키는 것입니다. 연결 후 아래와 같은 urls.py 파일을 확인하실 수 있습니다.

    from django.conf.urls import patterns, include, url
    
    # 관리자 사이트를 활성화시키기 위해 다음 두 줄의 주석을 제거해주세요.:
    from django.contrib import admin
    admin.autodiscover()
    
    urlpatterns = patterns('',
        # 예제:
        # url(r'^$', '{{ project_name }}.views.home', name='home'),
        # url(r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')),
    
        # 관리자 사이트 문서를 활성화시키기 위해 admin/doc 의 주석을 제거해주세요.:
        # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
    
        # 관리자 사이트를 활성화 시키기 위해 다음 줄의 주석을 제거해주세요.:
        url(r'^admin/', include(admin.site.urls)),
    )
    

    (굵게 표시된 행이 주석이 제거된 행입니다.)

개발 서버 실행하기

이제 개발 서버를 실행시키고 본격적으로 관리자 사이트를 탐험해 보도록 하겠습니다.

첫 번째 튜토리얼에서 배운 내용을 참고하여 아래와 같이 개발 서버를 실행해 보세요.

python manage.py runserver

이제 웹 브라우저를 실행하고 로컬 도메인의 “/admin/” 으로 이동하세요 – 예: http://127.0.0.1:8000/admin/. 관리자 사이트의 로그인 화면을 볼 수 있을 것입니다.

Django 관리자 사이트 로그인 화면

여러분에게 보이는 페이지와 다른가요?

만약 위에서 보이는 로그인 페이지 대신에 아래와 같은 error page reporting이 보인다면:

ImportError at /admin/
cannot import name patterns
...

여러분이 사용하시는 Django의 버전이 본 튜토리얼의 버전과 맞지 않을 수 있습니다. 사용하시는 Django 버전에 맞는 튜토리얼를 참고 하거나, 새로운 버전의 Django를 사용하시기 바랍니다.

관리자 사이트 들어가기

자, 이제 로그인 해 보세요. (첫 번째 튜토리얼에서 최고 관리자 계정을 생성한 것을 기억하시나요? 만약 생성하지 않았거나, 비밀번호가 기억나지 않는다면 다른 계정을 생성해 보세요.) 장고 관리자 사이트의 첫 페이지를 확인할 수 있을 것입니다.

Django 관리자 사이트 첫 페이지

Groups, users, sites 등 몇 개의 수정 가능한 컨텐츠를 확인할 수 있는데, 이들은 Django에서 기본적으로 제공하는 핵심기능입니다.

관리자 사이트에서 수정 가능한 투표 앱 만들기

그런데 우리의 투표 앱은 어디에 있나요? 아직 관리자 사이트 첫 페이지에 보이지 않습니다.

단 한가지 할 일: 우선 관리자 사이트에게 Poll 개체가 관리자 인터페이스가 필요하다고 알려줘야 합니다. 이를 위해 polls 디렉토리 안에 admin.py 파일을 생성하고 아래와 같이 수정합니다.

from polls.models import Poll
from django.contrib import admin

admin.site.register(Poll)

수정사항을 확인하기 위해 개발 서버를 재시작해야 합니다. 일반적으로 파일이 수정되면 서버는 자동으로 수정된 코드를 다시 불러오지만 새로 생성된 파일은 자동으로 불러오지 않습니다.

무료 관리 기능 탐험하기

자, 이제 Poll이 등록되었고, Django는 Poll을 관리 사이트 첫 페이지에 보여줘야 함을 알고 있습니다.

polls가 보여지는 Django 관리자 사이트 첫 페이지

이제 “Polls”를 클릭하면 polls의 “change list”를 볼 수 있습니다. 이 페이지에서는 데이터베이스의 모든 poll이 보여지게 되며 한 개를 선택하여 변경할 수 있습니다. 여기에 우리가 첫 번째 튜토리얼에서 생성한 “What’s up?” poll을 확인할 수 있습니다.

Poll 변경 리스트 페이지

Poll을 수정하기 위해 “What’s up?” poll을 클릭하세요.

Poll 개체 수정 폼

여기서 짚고 넘어가기:

  • 폼은 Poll 모델로부터 자동적으로 생성되었습니다.
  • 서로 다른 모델의 필드 타입 (DateTimeField, CharField) 에 따라 적절한 HTML 입력 위젯이 보여집니다. 각각의 필드는 장고의 관리자 사이트에서 어떻게 보여질지 스스로 알고 있습니다.
  • 각각의 DateTimeField에는 간편한 JavaScript 기능이 제공됩니다. 날짜는 “Today” 바로입력과 달력 팝업이 제공되며, 시간에는 “Now” 바로입력과 일반적으로 시간 입력에 사용되는 간편한 팝업이 제공됩니다.

페이지 하단에는 몇가지 옵션이 제공됩니다.

  • Save – 변경사항을 저장하고 현재 개체 타입의 변경된 목록 페이지를 보여줍니다.
  • Save and continue editing – 변경사항을 저장하고 현 개체의 관리 페이지를 다시 보여줍니다.
  • Save and add another – 변경사항을 저장하고 현재 개체 타입의 신규 정보를 등록할 수 있는 비어있는 폼을 보여줍니다.
  • Delete – 삭제 확인 페이지를 보여줍니다.

만약 “Date published”의 시간이 여러분이 튜토리얼 1에서 poll을 생성한 시간과 맞지 않는다면 정확한 TIME_ZONE을 설정하지 않았기 때문일 수 있습니다. 값을 정확히 입력하고 페이지를 다시 읽어들이면 정확한 값이 보여지는 것을 확인할 수 있을 것입니다.

“Today” 와 “Now” 바로입력 버튼을 누르면 “Date published”의 정보를 바꿀 수 있습니다. 그리고 “Save and continue editing”을 클릭한 후 오른쪽 위의 “History” 를 클릭해 보세요. 이 페이지 에서는 Django 관리 사이트에서 이루어진 현 개체의 모든 변경 사항의 목록을 확인할 수 있으며, 수정 시간과 수정을 한 사용자 이름도 확인할 수 있습니다.

Poll 개체의 History 페이지

관리자 사이트 폼 커스터마이징

몇 분 동안 간단히 코드를 살펴보며 감탄하였듯이 여러분은 코드를 작성할 필요가 없습니다. 단지 Poll 모델과 admin.site.register(Poll)을 등록하는 것만으로, Django는 기본적인 폼의 외형을 생성할 수 있습니다. 때때로 여러분은 관리자 사이트 폼의 외형과 작업을 커스터마이즈해야 할 필요가 있을 것입니다. 이는 여러분이 개채를 등록할 때 필요한 옵션을 Django에게 알려주면 됩니다.

수정 폼의 필드를 재정렬하는 예제를 통해서 어떻게 커스터마이즈할 수 있는지 알아봅시다. admin.site.register(Poll) 라인을 아래와 같이 수정해봅시다.

class PollAdmin(admin.ModelAdmin):
    fields = ['pub_date', 'question']

admin.site.register(Poll, PollAdmin)

여러분은 위와 같은 패턴 – Model admin 개체 생성, admin.site.register()의 두 번째 인자로 전달 – 을 통해 언제든 개체의 관리 사이트 옵션을 변경할 수 있습니다.

이번 예제는 “Publication date” 필드가 “Question” 필드의 앞에 오도록 수정하였습니다.

재정렬된 필드들

단지 두 개의 필드에서는 크게 인상적이지 않을 지 모르지만 수십개의 필드가 있는 폼에서는 직관적인 순서를 선택하는 것이 보다 세세한 사용성 측면에서 중요합니다.

또한 수십개의 필드를 포함한 폼을 다룰 때에는 이들을 폼의 필드셋(fieldsets)으로 분리할 수도 있습니다.

class PollAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question']}),
        ('Date information', {'fields': ['pub_date']}),
    ]

admin.site.register(Poll, PollAdmin)

필드셋의 튜플(tuple)에 있는 각각의 첫 번째 요소는 필드셋의 제목을 의미합니다. 아래에서 우리가 만든 폼이 어떻게 보이는지 확인할 수 있습니다.

필드셋을 보여주는 폼

각각의 필드셋에는 여러분이 원하는 HTML 클래스를 할당할 수 있습니다. Django는 특정 필드셋이 처음에 접혀 있는 모습으로 보여질 수 있도록``”collapse”`` 클래스를 제공합니다. 이는 잘 사용하지 않지만 많은 필드를 가지고 있는 길이가 긴 폼이 있을 때 유용할 것입니다.

class PollAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question']}),
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
    ]
처음에 접혀있는 필드셋

관계 있는 개체 추가 하기

자, 이제 우리는 Poll 관리자 페이지를 가지게 되었습니다. 그런데 Poll은 여러개의 Choices를 포함하고 있는데 관리자 페이지는 Choices를 보여주지 않습니다.

아직까지는요.

두 가지 방법을 통해 이 문제를 해결할 수 있습니다. 첫 번째 방법은 관리자 페이지에 Poll과 함께 Choice를 등록하는 것입니다. 참 쉽죠?

from polls.models import Choice

admin.site.register(Choice)

이제 Django 관리자 페이지에서 “Choices”를 사용할 수 있게 되었습니다. 다음 그림과 같은 “Add choice” 폼을 보실 수 있을 것입니다.

Choice 의 관리 페이지

폼 안에서 “Poll” 필드는 선택 박스(Select Box)로서 데이터베이스의 모든 Poll을 포함하고 있습니다. Django는 ForeignKey가 관리 페이지에서 <select> 박스로 보여져야 한다는 것을 알고 있습니다. 현재 예제 에서는 한개의 Poll만 존재하는 상태입니다.

“Poll” 다음에 있는 “Add Another” 링크에 주목하세요. 다른 개체에 대한 ForeignKey 관계를 가지고 있는 모든 개체는 손쉽게 이용할 수 있습니다. “Add Another” 를 클릭하면 “Add poll” 폼이 있는 팝업 윈도우를 볼 수 있습니다. 팝업 윈도우에서 Poll을 추가 하고 “Save” 버튼을 누르면 Django는 Poll을 데이터베이스에 저장하고 여러분이 보고있는 “Add choice”의 선택된 Choice에 동적으로 추가합니다.

그러나 시스템에 Choice 개체를 추가하는 작업은 비효율적인 작업입니다. 만약 여러분이 Poll 개체를 만들 때 여러개의 Choice를 추가할 수 있다면 더 좋을 것입니다. 자, 그럼 어떻게 하는지 알아봅시다.

Choice 모델을 호출하는 register()를 제거하세요. 그런 다음 Poll을 불러오기 위해 등록 코드를 수정하세요.

class ChoiceInline(admin.StackedInline):
    model = Choice
    extra = 3

class PollAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question']}),
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
    ]
    inlines = [ChoiceInline]

admin.site.register(Poll, PollAdmin)

위의 코드는 Django에게 “Choice 개체들을 Poll 관리자 페이지에서 수정하며, 기본적으로 3가지 선택을 할 수 있는 필드를 제공한다”고 알려줍니다.

“Add poll” 페이지를 열어서 어떻게 보이는지 확인해 봅시다. 개발 서버를 재시작할 필요가 있을 것입니다.

Choice 가 포함된 Poll 페이지

이렇게 작동합니다.: 관련된 세 개의 슬롯 – extra에 의해 선택된 – 이 있고 언제든지 생성된 개체의 “수정” 페이지로 되돌아올 수 있으며, 또 다른 세 개의 추가 슬롯을 사용할 수도 있습니다.

현재 세 개의 슬롯 끝부분에서 “Add another Choice” 링크를 찾을 수 있을 것입니다. 그것을 클릭하면 새로운 슬롯이 추가됩니다. 추가된 슬롯을 삭제하려면, 추가된 슬롯의 오른쪽 위의 X를 클릭합니다. 원래 있던 세 개의 슬롯은 삭제할 수 없습니다. 다음의 그림에서 추가된 슬롯을 보실 수 있습니다.

동적으로 추가된 슬롯

한 가지 작은 문제에 대해 생각해봅시다. 관련된 Choice 개체를 입력하기 위해 모든 필드가 넓은 공간에 보여지고 있습니다. 이러한 이유로 Django는 표를 이용하여 연관된 개체를 한 줄로 보여줍니다. 다음과 같이 ChoiceInline를 변경하세요.

class ChoiceInline(admin.TabularInline):
    #...

StackedInline 대신에 TabularInline을 사용하면 연관된 개체는 보다 간소화된 표 기반 형식으로 표현됩니다.

보다 간소화된 Choice를 포함한 Poll 추가 페이지

“Delete?” 컬럼을 체크하면, “Add Another Choice” 버튼을 사용하여 추가된 행 및 이미 저장된 행을 삭제할 수 있다는 점도 알아두시기 바랍니다.

관리자 수정 목록 커스터마이징

이제 Poll 관리 페이지가 보기 좋아졌습니다. 다음으로 시스템의 모든 Poll을 보여주는 “수정 목록(change list)” 페이지에 손을 대어봅시다.

현재는 아래와 같이 보입니다.

Poll 수정 목록 페이지

기본적으로 Django는 각 개체를 str()로 보여줍니다. 그러나 각 필드를 직접 보여주는 것이 더 효과적일 수도 있습니다. 이를 위해 list_display 관리 옵션을 사용합니다. list_display 옵션은 개체의 변경 리스트에서 컬럼 이름으로 사용되는 필드 이름의 튜플로 구성됩니다.

class PollAdmin(admin.ModelAdmin):
    # ...
    list_display = ('question', 'pub_date')

좋은 지표로 삼기 위해 첫 번째 튜토리얼에서 만든 커스텀 메소드인 was_published_recently을 포함시켜 봅시다.

class PollAdmin(admin.ModelAdmin):
    # ...
    list_display = ('question', 'pub_date', 'was_published_recently')

이제 아래와 같은 poll 수정 목록 페이지를 보실 수 있습니다.

업데이트된 Poll 수정 목록 페이지

컬럼 값에 따라 정렬하기 위해 컬럼 헤더를 클릭해 보세요. – 커스텀 메소드인 was_published_recently는 정렬을 지원하지 않습니다. was_published_recently 의 컬럼 해더는 기본적으로 메소드의 이름으로 포현되며(공백은 밑줄로 대체) 각 컬럼은 문자열로 표현되게 됩니다.

여러분은 아래와 같이 models.py에 있는 몇 개의 어트리뷰트를 변경하여 보다 보기 좋게 만들 수 있습니다.

class Poll(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
    was_published_recently.admin_order_field = 'pub_date'
    was_published_recently.boolean = True
    was_published_recently.short_description = 'Published recently?'

admin.py 파일을 또 다시 수정하여 Poll 수정 목록 페이지를 개선해봅시다. 필터입니다. PollAdmin에 아래 코드를 추가하세요.

list_filter = ['pub_date']

사용자들이 수정 목록을 pub_date 필드로 필터링할 수 있도록 해주는 “Filter” 사이드 바를 추가하였습니다.

업데이트된 Poll 수정 목록 페이지

여러분이 필터링하는 필드의 타입에 따라 필터의 타입이 보여지게 됩니다. pub_dateDateTimeField이기 때문에 Django는 적절한 옵션을 제공합니다.: “Any date,” “Today,” “Past 7 days,” “This month,” “This year.”

꽤 사용하기 편하게 구성되죠? 이제 검색 기능을 추가해 봅시다.

search_fields = ['question']

위의 옵션은 수정 목록 상단에 검색창(Search Box)를 추가합니다. 사용자가 검색어를 입력하면 Django는 question 필드를 검색합니다. 여러분은 여러분이 원하는 만큼 필드를 추가할 수 있으며 내부에서 LIKE 질의를 하기 때문에 데이터베이스의 효율을 높일 수 있습니다.

마지막으로 Poll 개체는 날짜를 포함하므로 손쉽게 날짜 순으로 보여줄 수 있습니다. 아래 코드를 추가하세요.

date_hierarchy = 'pub_date'

이는 수정 목록 페이지의 상단에서 날짜 순의 계층적 탐색을 추가합니다. 최상위 수준에서 선택 가능한 모든 연도가 표시됩니다. 또한 월에서 일까지 순차적으로 선택할 수 있습니다.

이제 수정 리스트의 페이지네이션에 대하여 알아봅시다. 기본적으로 한 페이지에 100개의 아이템이 보여집니다. 이제 수정 목록 페이지네이션(Change-list pagination), 검색창(search boxes), 필터(filters), 날짜 구조화(date-hierarchies) and 컬럼 헤더 정렬(column-header-ordering) 모두 여러분이 생각하는 대로 작동합니다.

관리자 사이트 룩앤필(Look and feel) 커스터마이징

모든 관리자 페이지의 최상단에 존재하는 “Django administration”은 확실히 이상합니다. 이것은 임시로 써놓은 문구입니다.

Django의 템플릿 시스템을 이용하면 쉽게 변경할 수 있습니다. Django 관리자 사이트는 Django 기반으로 개발되었기 때문에 Django의 자체 템플릿 시스템을 사용하여 인터페이스를 제공하고 있습니다.

설정 파일(mysite/settings.py, 기억해두세요)을 열어 TEMPLATE_DIRS 설정을 봅시다. TEMPLATE_DIRS은 Django 템플릿을 불러올 때 확인하는 파일 시스템 디렉토리의 튜플입니다. 이것는 검색 경로입니다.

기본적으로 TEMPLATE_DIRS은 비어 있습니다. 이제 Django에게 어디에 우리의 템플릿이 있는지 알려주기 위해 한 줄을 추가해봅시다.

TEMPLATE_DIRS = (
    '/home/my_username/mytemplates', # 여러분의 디렉토리로 변경해 주세요.
)

이제 Django 소스코드의 기본 Django 관리자 템플릿 디렉토리 (django/contrib/admin/templates)에 있는 admin/base_site.html를 여러분이 TEMPLATE_DIRS에 지정한 디렉토리의 admin 하위 디렉토리로 복사합니다. 예를 들어, 만약 여러분의 TEMPLATE_DIRS에 위와 같이 '/home/my_username/mytemplates'로 설정했다면, django/contrib/admin/templates/admin/base_site.html/home/my_username/mytemplates/admin/base_site.html로 복사하세요. admin의 하위 디렉토리를 잊지 마세요.

Django의 소스파일들은 어디에 있나요?

독자 여러분의 시스템에서 Django의 소스파일들을 찾기 힘들다면 아래 명령어를 실행해 보세요.

python -c "
import sys
sys.path = sys.path[1:]
import django
print(django.__path__)"

다음으로, 파일을 수정하여 이미 입력되어 있는 Django 텍스트를 적절한 여러분의 사이트 이름으로 교체해 주세요.

이 템플릿 파일은 여러 개의 {% block branding %}{{ title }}을 포함하고 있습니다. {%{{는 Django 템플릿 언어 중 일부입니다. Django가 admin/base_site.html 를 출력할 때 HTML 페이지를 완성하기 위하여 이 템플릿 언어는 평가됩니다. 여러분이 지금 템플릿에 대해 잘 알지 못해도 걱정하지 마세요. 세 번째 튜토리얼에서 Django의 템플릿 언어에 대해 자세히 알아볼 것입니다.

Django의 기본 관리자 사이트 템플릿은 재정의(Override)될 수 있다는 것만 알아두세요. 템플릿을 재정의하기 위해, 위에서 한 작업들을 base_site.html에 해주세요. – 파일을 기본 디렉토리에서 여러분의 커스텀 디렉토리에 복사한 후 수정하세요.

여기서 날카로운 의문이 생길 겁니다. 만약 기본적으로 TEMPLATE_DIRS가 비어있다면, Django는 어떻게 기본 관리자 사이트 템플릿을 찾나요? 정답은, Django는 기본적으로 각 앱 패키지의 하위 디렉토리에 있는 templates/의 템플릿을 자동으로 대체하여 사용합니다. 보다 자세한 정보를 위해 template loader documentation 을 참조하세요.

관리자 사이트 인덱스 페이지 커스터마이징

여러분은 이제 Django 관리 사이트의 인덱스 페이지의 룩앤필도 커스터마이즈하고 싶을 것입니다.

기본적으로 인덱스 페이지는 INSTALLED_APPS에 등록된 모든 앱을 알파벳 순서로 보여줍니다. 여러분은 아마 레이아웃을 여러분이 원하는 대로 변경하고 싶을 것 입니다. 결국, 인덱스는 관리자 사이트에서 가장 중요한 페이지이며 쉽게 사용할 수 있어야 합니다.

커스터마이즈할 템플릿은 admin/index.html 파일 입니다. (이전 섹션에서 admin/base_site.html에서 한 작업들을 그대로 해 주세요. – 파일을 기본 디렉토리에서 여러분의 커스텀 디렉토리에 복사한 후 수정하세요.) 파일을 수정할 때 app_list 변수를 확인할 수 있을 것입니다. 변수는 여러분이 설치한 모든 Django 앱이 포함되어 있을 것입니다. 이 링크를 그대로 사용해도 되지만, 어려분이 생각하는 대로 관리자 페이지의 개체에 링크를 직접 연결할 수도 있습니다. 다시 한번 말씀드리지만, 템플릿 언어를 모른다고 걱정하지 마세요. – 튜토리얼 3에서 보다 자세히 알아볼 것입니다.

이제 관리자 사이트에 익숙해졌다면, 세 번째 튜토리얼을 통해 공개된 poll 뷰에 대해 알아봅시다.