2010년 6월 18일 금요일

ubuntu 10.04 TLS 64bit 버전에서 Andorid 개발환경구축

ubuntu 10.04 버전에는 "시스템 > 관리"를 통해서 들어가면 사앙할 수 있는 시냅틱 패캐지 관리자가 제공된다.

일종의 소프트웨어 관리자로 윈도우즈 소프트웨어 관리자와는 조금 다른 구조를 갖는다.

 

이 녀셕은 시스템에 설치된 SW 뿐만이 아니라 ubuntu에서 제공하는 모든 SW에 대한 리스트를 보여주고 현재 자신에게 설치된 SW들은 별도의 표시를 해서 보여준다.

 

 

Eclipse 설치

기본적으로 이 시냅틱 패키지 관리자를 통해서 eclipse를 설치하였다.

 

스크린샷-시냅틱_꾸러미_관리자_.png

 

위의 스크린샷은 설치된 후이며 위에서 선택된 eclipse를 선택하고 상단의 적용을 클릭하면 디펜던시 등을 맞춰서 필요한 것들을 모두 설치해 준다.

 

 

32Bit 호환 모듈 및 sun-java6-sdk 설치

Android SDK를 설치하여야 하는데 현재 Android SDK는 32Bit 버젼만 등록되어 있다.

이 32Bit를 사용하기 위해서 호환성을 위한 패키지를 설치한다.

안드로이드의 설치 문서를 보면 호환성을 위한 ia32-libs를 설치하고

ubuntu 설치시 제공되는 기본 JDK (openJDK) 대시 Sun의 JDK인 sun-java6-jdk 를 설치하는 것을 기본으로 하고 있다.

 

먼저 ia32-libs 부터 설치하자 (기본적으로 설치되는지 여부를 확인하지 못했다. 만일 설치되어 있다면 넘어가도 된다.)

$ sudo apt-get install ia32-libs

 

다음으로 Sun의 JDK를 설치하자.

여기서 한가지 문제가 있는데 기본 Repository 중 에 Sun의 JDK를 포함하고 있는 곳이 없기 때문이다.

이를 위해 Sun의 JDK를 갖고 있는 Repository를 다음의 순서대로 추가하고 Source List를 Update하자


$ sudo add-apt-repository "deb http://archive.canonical.com/ lucid partner"
$sudo apt-get update

위와 같이 Repository를 변경하고 Sun의 JDK를 설치한다.

 

$ sudo apt-get install sun-java6-jdk

몇가지 묻는 화면이 나오는데 라이센스 동의 여부 등이니 확인하고 넘어가면 된다.

 

 

안드로이드 SDK 설치

이제 안드로이드 SDK를 다운로드 받아 설치하자.

다운로드는 이곳에서 받을 수 있다.  http://developer.android.com/sdk/index.html

일단은 Linux(i386)용을 받고 원하는 곳에서 압축을 해제한다.

 

$ mv android-sdk_r06-linux_86.tgz Somewhere

$ cd Somewhere

$ tar zxvf android-sdk_r06-linux_86.tgz

 

압축을 해제하면 하위에 tools/ 디렉토리가 있다.

이 디렉토리를 본인의 경로에 추가하자

사용자 홈 디렉토리의 .bashrc 파일에 다음을 추가하도록 하자.

 

export PATH=${PATH}:/Addroid SDK 경로/tools

 

 

ADT 플러그인 설치

그 다음으로 ADT 를 Eclipse 플러그인으로 설치하자.

ADT는 Android Development Tools의 약자로 Eclipse 플러그인으로 개발된 개발환경이다.

다음의 경로를 통해 Elipse 플러그인으로 설치한다.

 

https://dl-ssl.google.com/android/eclipse/

 

설치시 Android DDMS 와  Android Development Tools 두가지가 포함되는데 모두 선택하고 설치를 진행하자.

 

 

설치 후 설정 : SDK와 ADT 연결

설치가 완료된 후 다음과 같은 간략한 설정을 거치면 모든 과정이 완료된다.

  1. Window > Preferences... 를 통해 Preferences panel 을 열어 Android 를 선택하자
  2. 에러메세지가 나오는데 이것은 SDK 위치를 입력하라는 것이다. 앞서 설치한 SDK 설치 경로를 찾아서 지정한 후 "Apply"를 클릭한 후 "OK"를 클릭하여 설정을 마무리 하자.

 

스크린샷-Preferences_-1.png

 

 

SDK Component 추가

Eclipse에서 Window > Android SDK and AVD Manager 를 클릭하자

다음의 세가지가 있다.

Virtual Devices 를 통해서 안드로이드 버츄얼 머신인 AVD를 작성한다.

Installed Packages 는 설치된 Package가 나오면 현재 Android SDK Tools, Revision 6 하나만 있다.

Avaliable Packages 는 지정된 Repository를 통해 업그레이드 및 설치 가능한 Package를 보여주는데 일단 현재 Repositoy에 있는 것을 모두 선택하여 업데이트 하자. 이 과정을 거쳐야지만 사용이 가능하며 설치가 완료된다.(윈도우즈에서는 이 과정이 설치과정중에 포함된다.)

 

스크린샷-Android_SDK_and_AVD_Manager_-1.png

 

 

스크린샷-Installing_Archives_.png

 

설치가 되었으면 아주 간단하게 SD 1GB를 갖는 가상머신을 만들어보자 (Virtual Devices)

이름을 sdTest로 하고 SD 카드 부분만 1024MiB로 하고 나머지는 기본 값으로 하자.

스크린샷-Create_new_Android_Virtual_Device_(AVD)_.png

 

 

안드로이드 SDK 2.2 를 통해 테스트할 가상머신이 준비되었다.

 

 

이상으로 기본적인 안드로이드 개발 환경을 갖추었다.

64Bit 버전에서의 사용은 ia32-libs 설치 여부가 중요한 사항이다.

아직 안드로이드 SDK가 64Bit 버전을 지원하지 않기 때문이다.

 

이 환경에서 작업을 하면서 생기는 점들에 대해 추후 기술해 나가도록 하겠다.

 

 

이 글은 스프링노트에서 작성되었습니다.

2010년 6월 11일 금요일

Exponential Family

어떤 확률밀도함수(pdf, pmf)들의 모임을 Exponential Family라고 부를때 이는 다음과 같이 표현된다.

 

f(x|\theta)=h(x)c(\theta)exp \left[ \sum_{i=1}^{k} w_{i}(\theta)t_{i}(x) \right],                   식(1)

여기서

  • h(x) \ge 0,~t_{1}(x),t_{2}(x), \cdots , t_{k}(x)t_{i}(x) 는 모수 \theta에 depend 되지 않은 실값을 갖는 함수
  • c(\theta) \ge 0,~w_{1}(\theta),w_{2}(\theta), \cdots , w_{k}(\theta), w_{i}(\theta)는 값 x에 depend  되지 않은 실값을 갖는 함수

이다.

위의 식은 책마다 표현하는 형태는 다양하지만 위의 두가지 조건을 갖는다. 일례로 위의 식에서 h(x)c(\theta)c(\theta)를 지수 안에 넣어서 다음과 같이 표현하는 책들도 많다.

 

f(x|\theta)=h(x)exp \left[ \sum_{i=1}^{k} w_{i}(\theta)t_{i}(x) - A(\theta) \right]

 

예제) 이항분포

f(x | p) & = & \binom{n}{x} p^{x}(1-p)^{n-x} \ & = & \binom{n}{x} (1-p)^{n} \left( \frac{p}{1-p} \right) ^ {x} \ & = & \binom{n}{x} (1-p)^{n} exp \left( log \left( \frac{p}{1-p} \right) x \right) \" class=

 

 

이 글은 스프링노트에서 작성되었습니다.

2010년 6월 7일 월요일

Android SDK-r06(2.2 Froyo) - avd List

기본 출처 : http://developer.android.com/guide/developing/tools/avd.html


>android list targets

Available Android targets:
id: 1 or "android-2"
     Name: Android 1.1
     Type: Platform
     API level: 2
     Revision: 1
     Skins: HVGA (default), HVGA-L, HVGA-P, QVGA-L, QVGA-P
id: 2 or "android-3"
     Name: Android 1.5
     Type: Platform
     API level: 3
     Revision: 4
     Skins: HVGA (default), HVGA-L, HVGA-P, QVGA-L, QVGA-P
id: 3 or "android-4"
     Name: Android 1.6
     Type: Platform
     API level: 4
     Revision: 3
     Skins: HVGA (default), QVGA, WVGA800, WVGA854
id: 4 or "android-5"
     Name: Android 2.0
     Type: Platform
     API level: 5
     Revision: 1
     Skins: HVGA (default), QVGA, WQVGA400, WQVGA432, WVGA800, WVGA854
id: 5 or "android-6"
     Name: Android 2.0.1
     Type: Platform
     API level: 6
     Revision: 1
     Skins: HVGA (default), QVGA, WQVGA400, WQVGA432, WVGA800, WVGA854
id: 6 or "android-7"
     Name: Android 2.1-update1
     Type: Platform
     API level: 7
     Revision: 2
     Skins: HVGA (default), QVGA, WQVGA400, WQVGA432, WVGA800, WVGA854
id: 7 or "android-8"
     Name: Android 2.2
     Type: Platform
     API level: 8
     Revision: 1
     Skins: HVGA (default), QVGA, WQVGA400, WQVGA432, WVGA800, WVGA854




Hardware Emulation Options

Characteristic Description Property
Device ram size The amount of physical RAM on the device, in megabytes. Default value is "96". hw.ramSize
Touch-screen support Whether there is a touch screen or not on the device. Default value is "yes". hw.touchScreen
Trackball support Whether there is a trackball on the device. Default value is "yes". hw.trackBall
Keyboard support Whether the device has a QWERTY keyboard. Default value is "yes". hw.keyboard
DPad support Whether the device has DPad keys. Default value is "yes". hw.dPad
GSM modem support Whether there is a GSM modem in the device. Default value is "yes". hw.gsmModem
Camera support Whether the device has a camera. Default value is "no". hw.camera
Maximum horizontal camera pixels Default value is "640". hw.camera.maxHorizontalPixels
Maximum vertical camera pixels Default value is "480". hw.camera.maxVerticalPixels
GPS support Whether there is a GPS in the device. Default value is "yes". hw.gps
Battery support Whether the device can run on a battery. Default value is "yes". hw.battery
Accelerometer Whether there is an accelerometer in the device. Default value is "yes". hw.accelerometer
Audio recording support Whether the device can record audio. Default value is "yes". hw.audioInput
Audio playback support Whether the device can play audio. Default value is "yes". hw.audioOutput
SD Card support Whether the device supports insertion/removal of virtual SD Cards. Default value is "yes". hw.sdCard
Cache partition support Whether we use a /cache partition on the device. Default value is "yes". disk.cachePartition
Cache partition size Default value is "66MB". disk.cachePartition.size
Abstracted LCD density Sets the generalized density characteristic used by the AVD's screen. Default value is "160". hw.lcd.density

13 - 3) 태그 페이지 만들기

우리가 갖고 있는 또 다른 정보를 출력하는 방법을 알아보도록 합시다.

사용자로부터 우리는 태그를 입력받았습니다.

이제 이 태그를 중심으로 하여 북마크 링크를 출력해 보도록 하겠습니다.

 

먼저 태그들을 중심으로 한 페이지의 사용자 요청은 "/tag/찾고자 하는 태그"이고 이에 따른 뷰는 tag_page가 되게 하려고 합니다.

복습의 의미로 각자 한번 위의 정보를 가지고 urls.py를 작성해 봅시다. (Hint. 실제 사용은 이와 다르나 사용자 홈페이지의 경우를 참고하여 작성합니다.)

 

사용할 template 입니다.

  1. template/tag_page.html
  2. {% extends "base.html" %}
  3. {% block title %} 태그 : {{ tag_name }} {% endblock %}
  4. {% block head %} 태그 "{{ tag_name }}"의 북마크 {% endblock %}
  5. {% block content %}
  6.     {% include "bookmark_list.html" %}
  7. {% endblock %}

 

이 템플릿을 보시면 아시겠지만 앞서 우리가 작성한 bookmark_list.html을 사용합니다.

앞선 사용자 홈에서의 북마크 리스트 출력과는 사용되는 지점이 다릅니다.

앞에서는 사용자 홈에서 사용하였으므로 show_user 를 통해 누가 북마크 하였는지 알아볼 필요가 없으나 이번 예제의 중심은 Tag이므로 해당 Tag를 누가 적용하였는지도 보여줄 계획입니다.

 

그리고 출력시 해당 태그를 클릭하면 해당 태그의 페이지가 되도록 bookmark_list.html의 {% if show_tags 부분 %} 을 다음과 같이 수정합니다.

 

  1. template/bookmark_list.html
  2. ...
  3.         {% if show_tags %}
  4.        태그 :
  5.            {% if bookmark.tag_set.all %}
  6.            <ul class="tags">
  7.                {% for tag in bookmark.tag_set.all %}
  8.                 <li><a href="/tag/{{ tag.name }}/">{{ tag.name }}</a></li>
  9.                {% endfor %}
  10.            </ul>
  11.            {% else %}
  12. ...

 

이 템플릿과 연동하여 사용자에게 결과를 보여줄 뷰입니다.

 

  1. 애플리케이션/views.py
  2. def tag_page(request, tag_name):
  3.    tag = get_object_or_404(Tag, name=tag_name);
  4.    bookmarks = tag.bookmarks.order_by('-id')
  5.    variables = RequestContext(request, {
  6.                                         'bookmarks': bookmarks,
  7.                                         'tag_name': tag_name,
  8.                                         'show_tags': True,
  9.                                         'show_user': True
  10.                                         })
  11.    return render_to_response('tag_page.html', variables);

 

show_user가 True로 전달되어 누가 해당 태그를 적용하였는지 출력합니다.

 

결과는 다음과 같습니다.

 

shot03.png

 

 

 

간략하게 저장된 값을 출력하는 방법에 대해 알아보았습니다.

지금까지의 예제들을 잘 숙지해 주시기 바랍니다.

수고하셨습니다.

이 글은 스프링노트에서 작성되었습니다.

13 - 2) 입력한 Bookmark 보기

앞선 북마크 작성하기에서 사용자가 생성한 북마크(DB에 저장된 북마크)를 확인하는 페이지를 만들어 보겠습니다.

저장된 북마크를 확인한다는 것은 저장된 내용을 보는 것을 의미하며 우리가 앞서 수립한 데이터 모델(테이블)에 저장되어 있습니다. Bookmark 확인은 다음의 과정을 통해 이뤄집니다.

  1. django 데이터 모델 API를 사용해서 북마크 목록을 가져옵니다.
  2. 템플릿에 북마크 목록을 전달해서 출력합니다.

 

북마크 목록을 가져오는 것은 상황에 따라 달라집니다만 우리는 일단 전체 목록을 가져오는 것으로 할 것입니다. 그 다음으로 목록을 출력하는 페이지의 구성을 거의 유사합니다. 북마크를 링크로 표현하고 태그와 사용자 정보를 그 아래에 출력하도록 할 계획입니다.

 

먼저 다음과 같은 페이지를 만들어 봅시다.(template 디렉토리 아래에 bookmark_list.html 로 저장합니다.)

 

  1. template/bookmark_list.html
  2. {% if bookmarks %}
  3. <ul class="bookmarks">
  4.    {% for bookmark in bookmarks %}
  5.    <li>
  6.        <a href="{{ bookmark.link.url }}" class="title">{{ bookmark.title }}</a><br />
  7.        {% if show_tags %}
  8.        태그 :
  9.            {% if bookmark.tag_set.all %}
  10.            <ul class="tags">
  11.                {% for tag in bookmark.tag_set.all %}
  12.                <li>{{ tag.name }}</li>
  13.                {% endfor %}
  14.            </ul>
  15.            {% else %}
  16.            입력된 태그가 없습니다.
  17.            {% endif %}
  18.            <br />
  19.        {% endif %}
  20.        {% if show_user %}
  21.            추가한 사용자 :
  22.            <a href="/usr/{{ bookmark.user.username }}/" class="username">{{ bookmark.user.username }}</a>
  23.        {% endif %}
  24.    </li>
  25.    {% endfor %}
  26. </ul>
  27. {% else %}
  28.    <p>북마크가 없습니다.</p>
  29. {% endif %}

 

이 파일에서  {% if bookmarks %} 부분은 뷰와 연결시 사용자가 저장한 북마크가 있는지를 판단하는 부분입니다. 사용자가 입력한 북마크가 있을 경우 저장된 북마크를 보는 것이고 그렇지 않다면 아래로 내려와 "북마크가 없습니다"를 화면상에 출력합니다.

사용자가 저장한 북마크가 있을 시 모든 북마크들이 bookmarks 변수에 사용자의 북마크들이 리스트의 형태로 저장됩니다.

여기서 개별 값에 접근하기 위해 {% for bookmark in bookmarks %} 를 사용합니다. bookmarks 변수에 저장된 북마크 만큼 순회하면서 각 순회시 변수 bookmark에 각 북마크들이 임시 저장됩니다. 이제 변수 bookmark는 개별 북마크 값을 갖고 있습니다.

{% if show_tags %}는 뷰에서 전달된 show_tags 변수가 True이면 해당 내용을 보여주는 코드로 아래에 보시면 show_user라는 변수도 있습니다. 역시 show_tags와 동일한 역할이겠죠? 이런 변수들을 추가한 이유는 위에서 생성한 북마크를 보기위한 페이지인 bookamark_list.html을 보다 유연하게 사용하기 위해서 입니다. show_ 계열의 변수들의 참/거짓을 통해 사용자에게 보여주는 부분을 달리 할 수 있기 때문입니다. 지금 우리가 보여주고자 하는 북마크 리스트는 사용자 홈에서 보여지는 것이므로 show_user는 전달되지 않을 계획입니다(뷰에서 전달하지 않으면 False가 됩니다) .

이상으로부터 우리는 북마크 리스트를 보여주기 위한 기본 Template을 만들었습니다.

 

위의 파일은 일종의 모듈화로써 이 bookmark_list.html 파일은 북마크 리스트들을 보여주는 기능을 갖고 있습니다.

이 기능을 사용자 홈페이지에 넣어보도록 하겠습니다.

기존의 userHome.html 을 다음과 같이 변경해 봅시다. (template 아래에 사용자 홈페이지를 보여주기 위해 작성한 파일입니다.)

 

  1. template/userHome.html
  2. {% extends "base.html" %}
  3. {% block title %} Main page {% endblock %}
  4. {% block head %} 안녕하세요! {% endblock %}
  5. {% block content %}
  6.    {{username}} 님의 개인정보 페이지
  7.     <hr />
  8.    {% include "bookmark_list.html" %}
  9. {% endblock %}

 

다음으로 전달하고자 하는 북마크 리스트, 사용자 홈에서 Tag를 보여주기 위해 변수 show_tags를 True로 하여 템플릿에 적절한 변수를 보내기 위해 뷰 함수를 변경해 보겠습니다.

다음과 같이 views.py의 userHomePage 뷰를 변경합니다.

 

  1. 애플리케이션/views.py
  2. from django.shortcuts import get_object_or_404
  3. ...
  4. @login_required
  5. def userHomePage(request, username):
  6.     user = get_object_or_404(User, username=username)
  7.    bookmarks = user.bookmark_set.order_by('-id')
  8.    variables =RequestContext(request,
  9.                              {'username' : username,
  10.                               'bookmarks': bookmarks,
  11.                               'show_tags': True
  12.                               }   )
  13.    return render_to_response('userHome.html', variables)

 

코드상으로 많은 부분이 변경된 것 같지만 기존의 사용자 로그인 확인을 @login_required 데코레이터로 변경하고 사용자의 입력(여기서는 사용자 아이디를 통해 사용자 홈페이지로 이동하는 것입니다.)을 get_object_or_404 를 통해 확인합니다. 즉, 사용자가 입력한 사용자 아이디를 가져와서 User 데이터 모델의 username 열에 있는지 검사하여 없으면 404에러(페이지가 존재하지 않음을 나타내는 에러)를 발생하고 그렇지 않으면 즉, 해당 사용자 아이디가 존재하면 user 변수에 저장합니다. 북마크는 order_by를 이용하여 입력된 역순으로 다시말해 최근 저장된 북마크부터 목록을 가져옵니다. 가져온 값들은 bookmarks 변수에 저장됩니다. 그리고 이제 username과 bookamrks, 그리고 show_tags변수를 True로 하여 Template과 연결하여 사용자에게 보여줍니다.

 

위와 같이 작성을 한 결과는 다음과 같습니다.

 

shot02.png

 

 

이 글은 스프링노트에서 작성되었습니다.

13 - 1) Bookmark 작성하기

먼저 다음과 같이 북마크 등록에 사용될 폼을 작성해 봅시다.

 

  1. myTest/BookmarkSaveForm.py
  2. # -*- coding: utf-8 -*-
  3. from django import forms

  4. class BookmarkSaveForm(forms.Form):
  5.    url = forms.URLField( label = '주소', widget=forms.TextInput(attrs={'size':64}) )
  6.    title = forms.CharField( label = '제목', widget=forms.TextInput(attrs={'size':64}) )
  7.    tags = forms.CharField( label = '태그', widget=forms.TextInput(attrs={'size':64}), required=False )

 

BookmarkSaveForm()은 주소, 제목, 태그 총 세 개의 사용자 입력을 받습니다.

TextInput에 공히 사용된 attr={'size':64} 는 화면상에 나타날 길이 속성을 의미합니다.

자 이에 이 Form과 함께 사용될 View 를 작성해 봅시다.

 

  1. views.py
  2. from webDB.myTest.BookmarkSaveForm import *

  3. from webDB.myTest.models import *


  4. def bookmark_save_page(request):

  5.    if request.method == "POST" :

  6.        form = BookmarkSaveForm(request.POST)

  7.        if form.is_valid() :

  8.            link, dummy = Link.objects.get_or_create(

  9.                                                     url = form.cleaned_data['url']

  10.                                    )

  11.            bookmark, created = Bookmark.objects.get_or_create(

  12.                                                     user = request.user,

  13.                                                      link = link

  14.                                    )

  15.            bookmark.title = form.cleaned_data['title']

  16.            if not created :

  17.                bookmark.tag_set.clear()

  18.            tag_names = form.cleaned_data['tags'].split()

  19.            for tag_name in tag_names :

  20.                tag, dummy = Tag.objects.get_or_create(name=tag_name)

  21.                bookmark.tag_set.add(tag)

  22.            bookmark.save()

  23.            return HttpResponseRedirect('/home/%s/' % request.user.username)

  24.    else :

  25.        form = BookmarkSaveForm()

  26.        variables = RequestContext(request, {'form' : form})

  27.    return render_to_response('bookmark_save.html', variables)  

 

화면상에 보여줄 template을 만들어 보도록 하겠습니다.

template 디렉토리에 아래에 bookmark_save.html 로 다음의 코드를 작성해 주세요.

 

  1. template/bookmark_save.html
  2. {% extends "base.html" %}
  3. {% block title %} 북마크를 저장하세요 {% endblock %}
  4. {% block head %} 북마크를 저장하세요 {% endblock %}
  5. {% block content %}
  6. <form method="post" action=".">
  7.        {{form.as_p}}
  8.        <input type="submit" value="북마크 저장" />
  9. </form>
  10. {% endblock %}

 

 

사용자의 url 요청을 통해 이제까지 작성된 내용을 보여줄 수 있도록 urls.py에 다음을 추가합니다.

 

  1. urls.py
  2. ...
  3. (r'^save/$', bookmark_save_page),
  4. ...

 

 

서버를 가동시켜 이상의 내용을 확인해 봅시다.

> python manage.py runserver 8888

 

shot01.png

 

 

위의 Form에

주소 : http://www.djangoproject.com

제목 : Django 홈페이지

태그 : django homepage

라고 입력하고 북마크 저장을 눌러봅시다.

현재는 저장된 Bookmark를 보여주는 페이지가 없으니 다음과 같이 Shell에서 확인해 봅시다.

 

> python manage.py shell
>>> from webDB.myTest.models import *
>>> bookmark = Bookmark.objects.get(id=1)
>>> bookmark
<Bookmark: test1, http://www.djangoproject.com/>
>>> bookmark.link.url
u'http://www.djangoproject.com/'
<Bookmark: test1, http://www.djangoproject.com/>
>>> bookmark.title
u'Django \ud648\ud398\uc774\uc9c0'

>>> tag = Tag.objects.get(id=1)

>>> tag

<Tag: django>

>>> bookmark.tag_set.all()
[<Tag: django>, <Tag: homepage>]

>>> tag.bookmarks.all()
[<Bookmark: test1, http://www.djangoproject.com/>]

 

 

다음 시간에는 우리가 지금 생성해 놓은 Bookmark를 웹에서 살펴보는 것을 만들어 보겠습니다.

수고하셨습니다.

 

 

학습활동
  1. 앞서 base.html에 보시면 로그인, 로그아웃 등의 Link 버튼을 보여주는 Navigation 부분이 있습니다. 이 Navigation에 사용자가 로그인 되어 있을 때만 "북마크 추가" 라는 Link를 생성하여 save/ 페이지로 연결되게 합시다.
    shot01(1).png

  2. 로그인한 사용자만 북마크를 생성하는 페이지(사용자의 URL 요청이 save 일 때  bookmark_save_page 뷰가 실행되는 페이지)에 접근하도록 view를 변경해 봅니다.

 

이 글은 스프링노트에서 작성되었습니다.

13. Web Application 제작

실제로 웹 상에서 서비스가 가능한 Web Application을 개발해 봅시다.

 

Learning Website Developement with Django(2008, Packt, 번역본 쉽고 빠른 웹 개발 Django, 인사이트)에서 사용된 예제를 인용하여 제작할 Web Application은 Bookmark Application입니다.

이 Application을 제작하면서 실제 웹 상에서의 서비스가 어떻게 이뤄지는 지 알아보도록 하겠습니다.

 

그리고 수업에 사용된 내용은 그대로 유지한 채 Application을 제작할 것이므로 기존 수업 내용에 추가하시면 될 것입니다.

Bookmark Application을 위해 models.py에 다음을 추가해주시기 바랍니다.

  1. models.py
  2. class Link(models.Model):
        url = models.URLField(unique=True)
        def __unicode__(self):
            return self.url
        
    class Bookmark(models.Model):
        title = models.CharField(max_length=200)
        user = models.ForeignKey(User)
        link = models.ForeignKey(Link)
        def __unicode__(self):
            return '%s, %s' % (self.user.username, self.link.url)
        
    class Tag(models.Model):
        name = models.CharField(max_length=64, unique=True)
        bookmarks = models.ManyToManyField(Bookmark)
        def __unicode__(self):
            return self.name

 

작성후 실제 생성되어 작동하는 sql문을 출력해 봅시다.

 

> python manage.py sqlall myTest
BEGIN;

...
CREATE TABLE "myTest_link" (
    "id" integer NOT NULL PRIMARY KEY,
    "url" varchar(200) NOT NULL UNIQUE
)
;
CREATE TABLE "myTest_bookmark" (
    "id" integer NOT NULL PRIMARY KEY,
    "title" varchar(200) NOT NULL,
    "user_id" integer NOT NULL REFERENCES "auth_user" ("id"),
    "link_id" integer NOT NULL REFERENCES "myTest_link" ("id")
)
;
CREATE TABLE "myTest_tag" (
    "id" integer NOT NULL PRIMARY KEY,
    "name" varchar(64) NOT NULL UNIQUE
)
;
CREATE TABLE "myTest_tag_bookmarks" (
    "id" integer NOT NULL PRIMARY KEY,
    "tag_id" integer NOT NULL REFERENCES "myTest_tag" ("id"),
    "bookmark_id" integer NOT NULL REFERENCES "myTest_bookmark" ("id"),
    UNIQUE ("tag_id", "bookmark_id")
)
;
CREATE INDEX "myTest_bookmark_user_id" ON "myTest_bookmark" ("user_id");
CREATE INDEX "myTest_bookmark_link_id" ON "myTest_bookmark" ("link_id");
...
COMMIT;

 

Tag 데이터 모델에 ManyToManyField가 적용되어 myTest_tag_bookmarks 라는 우리가 생성하지 않았던 테이블이 생성됨을 확인할 수 있습니다. (참고 : Insert, Update, Delete)

 

이제 위의 데이터 모델을 적용해 봅시다.

> python manage.py syncdb

 

이제 데이터 모델에 대한 기본적인 준비가 끝났으니 기능들을 하나씩 추가해 보도록 하겠습니다.

 

이 글은 스프링노트에서 작성되었습니다.

12 - 1) 사용자 등록하기

앞선 django Form을 이용하여 새로운 사용자를 등록하여 봅시다.

 

Application에 registrationForm.py라는 파일 만들고 다음과 같이 입력합니다.

 

  1. from django import forms

    class RegistrationForm(forms.Form):
       username = forms.CharField(label='Username', max_length=30)
       email = forms.EmailField(label='e-mail')
       password1 = forms.CharField(label='Password', widget=forms.PasswordInput())
       password2 = forms.CharField(label='Confirm password', widget=forms.PasswordInput())

 

입력된 코드는 사용자 등록을 위한 Form을 생성하기 위해 django에서 제공하는 Form 기능을 이용하기 것입니다.

코드를 살펴보시면 아시겠지만 사용자 등록에 필요한 사용자의 입력은 사용자명, 이메일, 암호입력, 암호 확인의 4가지입니다.

암호 입력을 위한 Form 필드 생성시 forms.PasswordInput()을 widget의 속성값으로 사용하여 암호 입력 필드로 만들고 있음을 잘 보시기 바랍니다.

위의 사용자 입력 폼 생성 코드를 shell에서 확인해 봅시다.

 

> python manage.py shell
>>> from myTest.registrationForm import *
>>> user = RegistrationForm({
... 'username' : 'test2',
... 'email' : 'abc@def.com',
... 'password1' : 'test123',
... 'password2' : 'test123'
... })
>>> user.is_valid()
True

 

잘 사용됨을 알 수 있습니다. 만일 여기서 이메일 입력하는 부분에 이메일 형태와 다른 값을 입력하면 어떻게 될까요?

다음 예를 살펴보겠습니다.

 

>>> user2 = RegistrationForm({
... 'username' : 'test3',
... 'email' : 'abcdef ghijkl',
... 'password1' : 'test123',
... 'password2' : 'test123'
... })
>>> user2.is_valid()
False

 

유효하지 않음을 알 수 있습니다. 유효하지 않다는 것은 Form에 사용자 입력이 잘못되었다는 것을 뜻하는 데 어떤 오류가 있는지 확인해 보겠습니다.

 

>>> user2.errors
{'email': [u'Enter a valid e-mail address.']}

 

이메일 필드의 입력이 잘못되었으며 이메일 형식이 아닌 입력임을 알 수 있습니다.

 

 

다음으로 입력값에 대한 확인을 하기 위한 메소드를 추가해 보도록 하겠습니다.

입력값 확인은 다음의 두가지입니다.

  • 입력받은 두개의 비밀번호가 일치하는지 검사 (is_valid_password2())
  • username이 올바른지, 이미 사용되고 있는지는 아닌지 검사 (is_valid_username())

 

각각에 대해 위에서 작성한 registrationForm.py에 추가해 주시기 바랍니다.

 

  1. import re
    from django.contrib.auth.models import User
    from django.core.exceptions import ObjectDoesNotExist
  2.  
  3. class registrationForm(self) :
  4. ...
  5.     def is_valid_password2(self):
           if 'password1' in self.cleaned_data:
               password1 = self.cleaned_data['password1']
               password2 = self.cleaned_data['password2']
               if password1 == password2 :
                   return password2
               raise forms.ValidationError('입력한 패스워드가 일치하지 않습니다.')
         
       def is_valid_username(self):
           username = self.cleaned_data['username']
           if not re.search(r'^\w+$', username):
               raise forms.ValidationError('username은 알파벳, 숫자, 밑줄(_)만 가능합니다.')
           try:
               User.objects.get(username=username)
           except ObjectDoesNotExist:
               return username
           raise forms.ValidationError('이미 사용중인 username입니다.')


앞서 설명드린 내용을 참고하시면 is_valid_password2()는 쉽게 이해하시리라 믿습니다...제발...

두번째 정의한 메소드에서 re.search(r'^\w+$', username) 는 정규표현식에 대한 검색 함수입니다(import re). 이 함수는 username에서 단어가 한개 이상 등장하면 true를 반환입니다. 단어를 구성하는 문자는 알파벳, 숫자, 밑줄(_)로써 이 세가지 이외의 문자가 username에 있으면 False를 반환합니다. False를 반환할 경우 ValidationError() 예외를 발생시켜(raise) Form으로 되돌아 갈 수 있도록 합니다.

그외 나머지 부분도 이해하시는데 큰 어려움은 없으리라 다시 한번 착각에 빠져봅니다.

 

RequestContext의 사용 (http://docs.djangoproject.com/en/dev/ref/templates/api/#id1)

앞선 11. 페이지 개선의 학습활동에 문제점과 해결책을 말씀드렸습니다.

이제 RequestContext의 사용법을 알아보도록 하겠습니다.

RequestContext 객체는 일반 Context와는 조금 달라서 별도로 지정하지 않아도 자동으로 user 객체를 전달합니다.

따라서 별도의 user 객체를 연결시키지 않더라도 request객체와 템플릿 변수들로 RequestContext 를 만들 수 있습니다.

site_main과 userHomepage 뷰를 수정해 보겠습니다.

 

  1. views.py
  2. from django.template import RequestContext
  3.  
  4. def site_main(request) :
  5. return render_to_response('index.html', RequestContext(request))

  6.  
  7. def userHomePage(request, username):
       if request.user.is_authenticated() :
           try :
               user = User.objects.get(username=username)
           except :
               raise Http404('사용자를 찾을 수 없습니다.')
         
           variables =RequestContext(request, {'username' : username})
           return render_to_response('userHome.html', variables)
       else :
           return HttpResponseRedirect('/accounts/login/')

 

 

사용자 등록 폼이 사용할 템플릿 페이지를 작성해 봅시다.

프로젝트/template/registration/register.html로 다음을 작성합시다.

 

  1. template/registration/register.html

    {% extends "base.html" %}
    {% block title%}사용자 등록 페이지{% endblock %}
    {% block head%}사용자 등록을 합니다.{% endblock %}
    {% block content%}
    <form method="post" action=".">
       {{ form.as_p }}
       <input type="submit" value="가입" />
    </form>
    {% endblock %}

 

 

{{ form.as_p }} 부분에 우리가 작성한 django Form을 사용한 Form 코드가 들어갑니다. 여기서 한가지 주의할 것은 이름이 form.as_p 이므로 이 template과 들어갈 내용을 결합할 때 우리가 작성한 form 코드가 변수명 form으로 결합되어야 합니다. 이를 위한 view 코드가 아래에 나오는데 이 부분을 잘 살펴주시기 바랍니다.

 

  1. views.py

  2. from webDB.myTest.registrationForm import *

  3.  

  4. def register_page(request):
       if request.method == 'POST':
           form = RegistrationForm(request.POST)
           if form.is_valid():
               user = User.objects.create_user(
                                               username=form.cleaned_data['username'],
                                               password=form.cleaned_data['password1'],
                                               email=form.cleaned_data['email']
                                               )
               return HttpResponseRedirect('/')
       else :
           form = RegistrationForm()
       variables = RequestContext(request, {'form': form})
       return render_to_response('registration/register.html', variables)

 

 

이 뷰를 위한 url 세팅은 다음과 같습니다.

 

  1. urls.py

  2. ...

  3.     (r'^register/$', register_page),

  4. ...

 

이 글은 스프링노트에서 작성되었습니다.

12. django Form

django에서 제공하는 Form 기능에 대해 알아보려고 합니다.

 

앞서 우리는 로그인 폼을 직접 작성(로그인 과정)하였습니다.

하지만 django에서는 폼의 하위요소를 HTML로 직접 작성하지 않고 django에서 지원하는 기능을 이용하여 폼 요소를 생성하고 각 폼으로 부터 전달되는 값을 검사할 수 있는 기능을 갖고 있습니다.

 

앞선 로그인 폼을 django에서 지원하는 기능을 이용하여 작성하여 봅시다.

애플리케이션 하위에 loginForm.py라는 이름으로 다음을 만들어 봅시다.

 

  1. loginForm.py
  2. # -*- coding: utf-8 -*-
    from django import forms

    class LoginForm(forms.Form):
        username = forms.CharField(label='사용자 이름', max_length=30, required=True)
        password = forms.CharField(label='암호', widget=forms.PasswordInput(), required=True, max_length=127) 

 

생긴 형태가 마치 데이터 모델과 닮았습니다. 하지만 실제 사용되는 것은 많이 다릅니다.

 

django의 Form은 HTML Form 에 대한 wrapper 클래스를 지원합니다.

자주 사용되는 django Form필드는 forms에 의해 정의되며 다음과 같습니다.

  • CharField : 문자열, 기본 widget : Textinput
  • IntegerField : 숫자, 기본 widget : Textinput
  • DateField : 파이썬의 datetime.date 객체, 기본 widget : Textinput
  • DateTimeField : 파이썬의 datetime.datetime 객체, 기본 widget : Textinput
  • EmailField : 형식이 정확한 이메일 주소, 기본 widget : Textinput
  • URLField : 형식이 정확한 URL, 기본 widget : Textinput
  • 참고 : http://docs.djangoproject.com/en/1.2/ref/forms/fields/#ref-forms-fields

 

사용자로부터 전달될 내용이 위와 같을 경우 이 wrapper 클래스를 통해  비교적 편하게 사용할 수 있습니다.

 

그리고 위의 기본적인 필드 형식에 추가적으로 다음과 같은 인수를 넣어 보다 세밀한 Form을 생성할 수 있습니다.

  • label : 필드의 HTML 코드에서 사용되는이름(HTML의 <label>)
  • required : 필수 필드 여부(True/False)
  • widget : 표현될 HTML Form 요소 다음과 같은 widget이 주로 사용된다.

    • TextInput : 한줄 글입력 필드 <input type="text" />
    • PasswordInput : 비밀번호 입력 필드 <input type="password" />
    • HiddenInput : 숨김 필드 <input type="hidden" />
    • Textarea : 여러줄 글상자 필드 <textarea></textarea>
    • FileInput : 파일선택 필드 <input type="file" />
    • 참고 : http://docs.djangoproject.com/en/1.2/ref/forms/widgets/#ref-forms-widgets
  • help_text : 필드 설명, 필드 아래에 출력

 

앞선 예제에서 사용한 django의 Form에 대해 django shell에서 사용해 보도록 합시다.

 

> python manage.py shell

>>> from myTest.loginForm import *
>>> frm = LoginForm()                        # 1
>>> print frm.as_table()                       # 2
<tr><th><label for="id_username">사용자 이름:</label></th><td><input id="id_username" type="text" name="username" maxlength="30" /></td></tr>
<tr><th><label for="id_password">암호:</label></th><td><input id="id_password" type="password" name="password" maxlength="127" /></td></tr>
>>> print frm["username"]                   # 3
<input id="id_username" type="text" name="username" maxlength="30" />
>>> frm = LoginForm({                        #4
... 'username' : 'test1',
... 'password' : 'password123'
... })
>>> frm.is_valid()                              #5
True
>>> frm = LoginForm({                       #6
... 'username' : '',
... 'password' : 'password123'
... })
>>> frm.is_valid()
False

 

각각에 대한 설명은 다음과 같습니다.

  1. 앞서 작성한 LogiinForm()을 통해 새로운 Form을 생성합니다.
  2. Form을 생성한 결과를 HTML Table 형태로 출력합니다.
  3. 전체 Form 대신 Form의 특정한 하위요소 하나만 출력합니다.
  4. 작성한 Form을 통해 사용자 입력값을 저장합니다.
  5. 입력된 값이 유효한지 검사합니다.
  6. username을 required=True 로 하였으므로 빈값이 들어가면 유효하지 않은 입력이 됩니다.

 

django에서 제공하는 Form은 위와 같이 Form요소에 대한 wrapper 클래스 뿐만이 아니라 메소드를 추가할 수 있습니다.

예를 들어 LoginForm에 입력값에 대한 검사 메소드를 추가해 보도록 하겠습니다.

제작할 메소드는 username이 존재여부입니다.

 

  1. from django.contrib.auth.models import User
    from django.core.exceptions import ObjectDoesNotExist
  2.  
  3. class LoginForm(forms.Form) :
  4. ...
  5.     def checkUsername(self):
            username = self.cleaned_data['username']              # 1
            username = username.lstrip().rstrip()                     # 2
            try :                                                                # 3
                User.objects.get(username=username)
            except  ObjectDoesNotExist:
                raise forms.ValidationError('존재하지 않는 사용자입니다.')               # 4
            else :
                return True

 

  1. django의 Form은 사용자 입력값이 없는 빈 폼을 검사(validation)하려고 하면 예외를 발생합니다. 사용자가 입력한 값은 사전형 데이터로 표현된 form.data로 접근할 수 있으며 검사를 통과한 사용자 입력값은 froms.cleaned_data로 접근할 수 있습니다. 즉, 사용자가 입력한 빈 값이 아닌 username을 가져옵니다.
  2. 입력된 username 문자열의 좌우의 공백문자를 제거합니다.
  3. 예외 처리구문입니다. User.object.get()은 조건으로 넣어준 값이 없을 경우 ObjectDoesNotExist 예외를 발생시킵니다. 이 #3 구문은 입력된 사용자명이 있는지 검사하여 입력된 사용자가 없으면 ObjectDoesNotExist 예외가 발생됩니다.
  4. #4에서 예외가 발생되면 다시 Form의 ValidationError()예외를 발생시켜서 Form 입력을 처음으로 되돌립니다.

 

 

이상으로부터 django에서 제공하는 Form 기능을 알아보았습니다.

다음 시간에 이 Form을 이용하여 사용자를 추가하는 과정에 대해 알아보도록 하겠습니다.

 

 

 

이 글은 스프링노트에서 작성되었습니다.

11. 페이지 개선

웹 페이지의 Presentation을 담당하는 CSS 를 추가하는 방법에 대해 알아보겠습니다.

CSS는 일반 파일로 작성되고 정적인 페이지로 작성이 됩니다.

정적인 페이지이므로 웹 서버의 특정위치에 저장되어 있는데 이 저장된 위치를 나타내는 urls.py 세팅을 알아 보겠습니다.

CSS 파일의 요청 경로는 "웹 서버/site_media" 이며 해당 요청 경로의 실제 경로는 "프로젝트/site_media" 라고 가정하도록 하겠습니다. (즉, 프로젝트 아래에 site_media를 먼저 작성해 주시기 바랍니다.)

 

  1. urls.py
  2. import os.path
  3. ...
  4. site_media = os.path.join(os.path.dirname(__file__), 'site_media')
  5. urlpatterns = patterns('',
  6.   ...
  7.   (r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': site_media}),
  8.   ...
  9. )

 

위와 같이 작성을 하면 "서버명/site_media/*.*' 으로 들어오는 요청에 대해 '프로젝트명/site_media'에서 해당 파일을 찾습니다.

(r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': site_media}),

을 자세히 살펴보기로 하겠습니다.

사용자가 '/site_media/(임의의 경로.확장자)'의 형태로 요청시 django의 내장 뷰(View)에서 정적 파일에 대한 기능을 지원(static)하는 모듈중에 serve 모듈을 연결합니다('django.views.static.serve') serve 모듈의 원형은 serve(request, path, document_root=None, show_indexes=False) 로써 3개의 전달인자를 전달받습니다(request는 제외). 첫번째 전달인자인 path는 소괄호() 안에 묶여진 값이 전달되고 두번째 전달인자 document_root에 대해 사전형 자료로 {'document_root': site_media} 를 전달하고 세번째 전달인자는 기본값인 False를 사용합니다.

두번째 전달인자인 document_root에 대해 위에서 생성한 경로인 os.path.join(os.path.dirname(__file__), 'site_media') 이가 전달됨을 확인해 주시기 바랍니다.

이제 CSS 파일 하나를 '프로젝트/site_media' 디렉토리 아래에 style.css라는 이름으로 다음 내용을 저장해 주시기 바랍니다.

 

  1. /site_media/style.css
  2. #nav {
            float: right, /* 우측 정렬 */
            color: black;  /* 텍스트 색상은 검정 */
            background: gray;  /* 내용, 패딩(padding)은 회색 */
            margin: 0px 12px 0px 12px; /* 위와 아래의 마진은 0px */
            padding: 12px 0px 12px 0px; /* 오른쪽과 왼쪽 패딩은 0px */
            border-style: dashed;
            border-width: medium;  /* 모든 면들에  테두리 너비를 설정 함 */
            border-color: black;
    }

 

 

이제 CSS를 적용하여 보도록 하겠습니다.

base.html을 다음과 같이 수정하여 CSS 문서를 HTML 이 읽어오도록 합니다.

 

  1. base.html
  2. <!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
            <title>Django Page | {% block title %}{% endblock %}</title>
            <link rel="stylesheet" href="/site_media/style.css" type="text/css" />
        </head>
        <body>
            <h1>{% block head %}{% endblock %}</h1>
  3.         {% if user.username %}
  4.             <h4 align="right">{{user.username}} 님 반갑습니다.(<a href='/accounts/logout'>로그아웃</a>)</h4>
  5.         {% else %}
  6.             <h4 align="right"><a href="/accounts/login">로그인</a>하세요</h4>

            {% endif %}
            {% block content%}{% endblock %}
            <hr align="center" />
            <p align="center">Site managed by yoonani(yoonani72@gmail.com)</p>
        </body>
    </html>

 

Template 중에 가장 기본이 되는 base.html을 수정하였으니 모든 Template에 적용이 될 것입니다.

메인 페이지에 대해 적용한 사례를 보도록 하겠습니다.

 

index.html을 다음과 같이 수정해 보시기 바랍니다.

  1. {% extends "base.html" %}
    {% block title %} Main page {% endblock %}
    {% block head %} 안녕하세요! {% endblock %}
    {% block content %}
    django 서비스에 오신것을 환영합니다.
    {% endblock %}

 

수정된 결과는 다음과 같습니다.

 

스크린샷-Django_Page___Main_page_-_Mozilla_Firefox.png

 

 

본 연습을 통해서 고정된 자원인 CSS를 사용할 수 있도록 하는 방법에 대해 알아보았습니다.

이제 site_media에는 웹 사이트의 고정된 자원을 저장하고 그 내용을 사이트에서 필요한 곳에서 불러서 사용할 수 있게 되었습니다.

고정된 자원은 CSS 뿐만이 아니라 JavaScript, 각종 이미지 등입니다.

 

 

학습활동

앞선 10. Template - 상속의 학습활동을 수행해 본 학생들은 본 시간에 학습한 내용대로 base.html을 저장하면 사용자 홈페이지로 이동시 문제가 발생합니다.

그것은 index.html에는 user 객체가 전달되지만 사용자 홈페이지에는 user 객체가 전달되지 않기 때문입니다.

이를 해결하기 위해 user 객체를 사용자 홈페이지에 전달할 수 있지만 다른 페이지에도 이런 일이 필요하다면 여간 귀찮은 일이 아닐 수 없습니다. 이런 문제를 해결하기 위해 RequestContext()라는 객체를 사용하는 데요...

이 객체는 앞서서 보았던 Context() 객체와는 조금 달라서 별도로 지정하지 않더라도 알아서 user 객체를 전달하며 request 객체와 템플릿 변수들로 생성합니다.

이런 RequestContext 객체에 대해 알아보고 개인노트에 "학습활동 - RequestContext"라는 이름으로 조사한 내용을 기록하세요

이 글은 스프링노트에서 작성되었습니다.

10. Template - 상속

사이트의 통일성을 위해 Page별로 기본 골격은 유지한채 들어가는 내용과 이미지등만 바꿔서 제작을 합니다.

이때 기본 골격 즉, 기본 구조는 동일하게 두고 내용만 바뀌는 것이므로 기본 골격에 대한 기본 Template을 작성하고 각 페이지들은 기본 Template을 상속받아 작성될 내용을 집어넣습니다.

이를 구현하기 위한 django의 template 상속에 대해 알아보도록 하겠습니다.

 

설명을 위해 기본 Template 으로 사용될 예를 들어보겠습니다. template 디렉토리에 base.html이란 이름으로 다음 내용을 저장해 주시기 바랍니다.

 

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
            <title>Django Page | {% block title %}{% endblock %}</title>
        </head>
        <body>
            <h1>{% block head %}{% endblock %}</h1>
  2.         {% if user.username %}
  3.             <h4 align="right">{{user.username}} 님 반갑습니다.(<a href='/accounts/logout'>로그아웃</a>)</h4>
  4.         {% else %}
  5.             <h4 align="right"><a href="/accounts/login">로그인</a>하세요</h4>

            {% endif %}

            {% block content%}{% endblock %}

      <hr align="center" />

      <p align="center">Site managed by yoonani(yoonani72@gmail.com)</p>

     

  6.     </body>
    </html>

 

위의 예에서는 세군데의 block이 있습니다. 앞서 설명한 변수를 사용한 Template 예와 잘 비교해 주시기 바랍니다.

앞서 설명드린 Template의 예는 다음과 같습니다.

 

<title>{{header_title}}</title></head>

<h1>{{header_h1}}</h1>

<p>{{contents}}</p>

 

후자의 변수를 사용한 예는 값을 전달받아 바로 출력하는 것입니다. 이와 비교하여 지금 설명드린 것은 block 태그를 사용한 예로써 일단 상속을 받아 사용할 Template 에서 내용을 채워넣습니다.

그럼 이 기본 구조 Template을 상속받아 구현할 index.html을 다음과 같이 변경하여 보도록 하겠습니다.

 

  1. {% extends "base.html" %}
  2. {% block title %} Main page {% endblock %}
  3. {% block head %} 안녕하세요! {% endblock %}
  4. {% block content %}
  5. django 서비스에 오신 것을 환영합니다.
  6. {% endblock %}

 

첫줄의 {% extends "base.html" %} 에서 앞서 작성한 기본 Template을 가져오겠다는 선언입니다.

지금 작성한 index.html을 기존의 index.html과 비교하여 보시면 많은 부분에서 간략해 졌으며 구조에 대한 선언은 없어지고 내용에 대한 것만 남아 있음을 알 수 있습니다.

이제 위와 같은 기본 Template을 바탕으로 앞서 작성한 사용자 홈페이지도 변경해 보도록 하겠습니다.

다음은 변경된 userHome.html입니다.

 

  1. {% extends "base.html" %}
  2. {% block title %} {{username}} {% endblock %}
  3. {% block head %} 안녕하세요! {{username}} 님 {% endblock %}
  4. {% block content %}
  5. {{username}} 님의 개인정보 페이지
  6. {% endblock %}

 

앞으로 우리는 기본 구조를 상속받아 각 페이지를 작성하도록 하겠습니다.

이 방법을 통해서 사이트에 통일된 구조를 적용하는 데 보다 수월하게 되었습니다.

수고하셨습니다.

 

 

학습활동

앞서 작성한 login.html 을 Template 상속을 통해 구현하여 개인 학습노트에 "template 연습"이라는 제목으로 작성하시기 바랍니다.

이 글은 스프링노트에서 작성되었습니다.

09. 로그인 유지

앞서 django의 사용자 로그인 처리 과정을 알아보았습니다.

이제 로그인된 사용자의 로그인 상태 유지를 위한 session 처리과정에 대해 알아보도록 하겠습니다.

먼저 로그인된 사용자라면 {{form.username}}을 통해 로그인할 사용자의 ID를 받아와 user.username에 저장합니다.

이 상태를 알기 위해 다음과 같은 방법을 사용할 수 있습니다.

 

  • username
  • request.user.id_authenticated()
  • decorator : @login_required

 

위의 세가지 사용방법에 대해 알아보겠습니다.

 

username

 

username은 template에서 사용되어 로그인한 사용자에게 보여줄 부분과 그렇지 않은 사용자에게 보여줄 부분을 나눠서 처리할 수 있게 합니다.

다음은 첫화면(/)에 대한 django의 template 예제입니다.

 

  1. templates/index.html
  2. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
  3. <html>
  4. <head>

  5. <title>django Test Page</title>

  6. </head>

  7. <body>

  8. <h2 align="center">Welcome to django Test Page</h2>

  9. {% if user.username %}

  10. <h4 align="right">{{user.username}} 님 반갑습니다.</h4>

  11. {% else %}

  12. <h4 align="right"><a href="/accounts/login">로그인</a>하세요</h4>

  13. {% endif %}

  14. <hr align="center" />

  15. <p></p>

  16. <hr align="center" />

  17. <p align="center">Site managed by yoonani(yoonani72@gmail.com)</p>

  18. </body>

  19. </html>
  20.  

 

위의 예에서와 같이 template 내에서 사용되어 로그인된 사용자에게 보여줄 부분과 로그인 안된 사용자에게 보여줄 부분을 구별지어 보여줄 때 사용할 수 있습니다.

위의 소스를 index.html로 저장하고 사용자가 http://django서버/ 로 접근했을 경우 보여주기 위해 먼저 urls.py를 다음과 같이 작성합니다.

 

  1.  

    urlpatterns = patterns('',

  2. (r'^$', site_main),

  3. (r'^accounts/login/$', 'django.contrib.auth.views.login'),

  4. )

  5.  

 

다음으로 이 main_page 처리를 위한 view를 다음과 같이 작성합니다.(application의 views.py)

 

  1. from django.http import HttpResponse

    from django.contrib.auth.models import User

    from django.template import Context

    from django.template.loader import get_template

  2.  

    def site_main(request):

    template = get_template('index.html')

    variables = Context({'user': request.user})

    output = template.render(variables)

    return HttpResponse(output)

  3.  

 

  • get_template('index.html') : 사용될 template 지정
  • Context({'user': request.user} : 전달될 변수를 dictionary로 만든다. 본 예에서는 user 변수에 대해 로그인된 사용자 정보를 전달한다.
  • template.render(variable) : 앞의 Context()로 만든 변수 dictionary를 template에 적용시킨다.
  • HttpResponse(output) : output으로 만들어지 page를 사용자에게 응답으로 전달한다.

 

 

위의 코드들을 적용하고 개발서버에 작동시키면 로그인 하기 전에는 아래와 같이 나옵니다.

 

 cap01.JPG

 

로그인을 클릭하고 앞서 작성한 로그인 페이지로 이동후 로그인을 하면 다음과 같이 첫 페이지가 등장합니다.

 

 cap01(1).JPG

 

 

 

request.user.is_authenticated()

 

is_authenticated() 메소드는 user 객체의 메소드로 로그인한 사용자의 접속에 대해 True를 반환하는 메소드입니다.

주로 views.py에 사용되어 로그인한 사용자와 그렇지 않은 사용자를 구별하여 처리할 수 있도록 합니다.

다음과 같은 상황을 생각해보도록 합시다.

 

  • 방문자가 http://서버명/home/사용자명으로 접근.

    • /home/사용자명/ 은 로그인한 사용자의 개인 정보 페이지.
  • 로그인 하지 않을 경우 /accounts/login으로 이동.

 

먼저 개인 정보 페이지 template을 작성합니다. (userHome.html)

 

  1. <html>
  2. <head>
  3. <title>django Test : {{username}}</title>
  4. </head>
  5. <body>
  6. <h2 align="center">{{username}} 님의 개인정보 페이지</h2>
  7. </body>
  8. </html>
  9.  

 

urls.py를 수정하여 /home/사용자명/ 을 요청에 대해 view와 연결합니다.

 

  1.  

    urlpatterns = patterns('',
        (r'^$', siteMainPage),
        (r'^accounts/login/$', 'django.contrib.auth.views.login'),
        (r'^home/(\w+)/$', userHomePage),
    )

  2.  

 

위에서 사용된 표현은 정규표현식으로 \w 는 문자, 숫자, 그리고 밑줄(_)을 의미하고 그런 문자가 1개 이상 있는 문자열 그룹(괄호에 묶인것 통째로 한개의 그룹이 됨)을 의미합니다.

 

 

 

views.py에 다음을 추가합니다.

 

  • 로그인한 사용자의 경우 1에서 만든 template과 로그인 정보를 연결하여 사용자의 응답으로 보냅니다.

  • 로그인하지 않은 사용자의 경우 login 페이지로 연결합니다.

 

  1. from django.http import HttpResponseRedirect

     

    def userHomePage(request, username):

    if request.user.is_authenticated() :

  2. template = get_template('userHome.html')
  3. variables = Context({'username' : username})
  4. output = template.render(variables)
  5. return HttpResponse(output)
  6. else :
  7. return HttpResponseRedirect('/accounts/login/')

 

 

로그인 하지 않은 사용자가 http://서버명/home/사용자명/ 으로 접근하면 로그인 페이지로 이동하고 로그인을 해야지만 사용자의 개인정보 페이지로 이동합니다.

 

 

 

decorator : @login_required

위의 userHomePage() view 앞에 @login_reqquired를 추가하면 해당 view는 로그인한 사용자들만 볼 수 있으며 그렇지 않은 사용자들은 /accounts/login/ 으로 이동합니다.

views.py의 userHomePage를 다음과 같이 수정해 보도록 합시다.

 

  1. from django.contrib.auth.decorators import login_required
  2. @login_required
  3. def userHomePage(request, username):
  4. template = get_template('userHome.html')

    variables = Context({'username' : username})

    output = template.render(variables)

    return HttpResponse(output)

  5.  

 

 위와 같이 수정하면 앞선 2번의 예와 동일한 결과를 가져오므로 더욱 편하게 사용할 수 있습니다.

 

 

 

로그아웃

django의 로그아웃 기능은 사용자 인증 기능에서 제공하는 logout()을 사용하면 됩니다.

먼저 로그아웃 기능을 수행을 위해 views.py에 다음을 추가합니다.

 

  1. views.py
  2. from django.contrib.auth import logout
  3.  
  4. def logout_page(request):
        logout(request)
        return HttpResponseRedirect('/')

 

그다음 사용자의 로그아웃 요청과 추가한 view를 연결합니다.

 

  1. urls.py
  2. ...
  3. (r'^accounts/logout/$', logout_page),
  4. ...

 

그럼 이제 http://서버명/accounts/logout/ 요청을 해보시기 바랍니다.

 

이 글은 스프링노트에서 작성되었습니다.

08. 로그인 과정

django의 로그인 처리는 django의 Model-Template-View 기반 처리를 그대로 따릅니다.

Model 은 앞선 models.Users 객체를 사용하며

Template의 경우는 프로젝트의 settings.py 에 정의된 TEMPLATE_DIRS 아래에 Template으로 사용할 html 문서를 사용하는 것 외에 사용자 로그인을 위한 TEMPLATE_DIRS/registration/login.html 을 작성한 것을 로그인 페이지로 사용합니다.

 

로그인 과정중 사용하는 사용자의 요청(결국은 시스템이 만들어내는) 은 다음과 같은 요청들이 존재합니다.

  • ^accounts/login
  • ^accounts/logout
  • ^accounts/profile

 

그리고 요청에 따른 로그인 처리등을 핸들링하는 몇가지 정해진 view 들이 존재합니다.

 

이런 기본 지식을 가지고 실제 사용하는 예를 살펴보도록 합시다.

 

1. 로그인 URL 추가 - urls.py

  1. urlpatterns = patterns('',
  2.     ...
  3.     (r'^accounts/login/$', 'django.contrib.auth.views.login'),
  4.    ...)

 - 추후에 설명하겠지만 위와 같이 urls.py를 작성하면 "http://본인의 서버명/accounts/login"으로 요청하면 django의 login view로 연결합니다.

 

2. login form의 작성

위에 사용한 로그인 뷰는 TEMPLATE_DIRS/registration/login.html을 템플릿으로 사용하며 개발자가 개발하는 시스템에 맞도록 구축하여 사용합니다. 예를 위해 다음과 같이 작성합도록 합시다.

  1. <html>

  2. <head>

  3. <title>로그인 form</title>

  4. </head>

  5. <body>

  6. {% if form.erros %}

  7. <p>로그인 과정중 오류가 발생하였습니다.</p>

  8. <p>ID와 암호를 확인해 주세요</p>

  9. {% endif %}

  10. <h2>사용자 로그인</h2>

  11. <form method="POST" action=".">

  12. <!-- form.username : ID를 위한 <INPUT TYPE="text" /> 삽입 //-->

  13. <p><label for="userID">사용자 ID</label> {{ form.username }}</p>

  14. <!-- form.password : 암호를 위한 <INPUT TYPE="password" /> 삽입 //-->

  15. <p><label for="userPWD">사용자 암호</label> {{ form.password }}</p>

  16. <input type="submit" value="로그인" />

  17. </form>

  18. </body>

  19. </html>

 

위의 코드를 개발 서버에서 요청하면 다음과 같은 결과를 보여줍니다. (http://127.0.0.1:8000/accounts/login/)

 cap01.JPG

 

이 상태에서 바로 사용자 ID와 사용자 암호를 넣고 로그인 버튼을 누르면 다음과 같은 에러를 만날수 있는데 이는 django에서 크로스 사이트 스크립팅(cross-site scripting) 공격에 대해 무방비임을 알리는 에러메세지입니다.

 

cap01(1).JPG 

 

이같은 상황이 발생할 경우 <form> 태그 옆에 아래와 같이 django의 csrf 토큰을 추가하는 것으로 방지할 수 있습니다.(이에 대해서는 추후 설명하도록 하겠다)

 

  1.  <form method="POST" action=".">{% csrf_token %}

 

이제 비어있는 사용자 ID 혹은 틀린 암호를 넣고 로그인하여 보면 다음과 같은 에러가 나오는데 이는 위에서 {% if form.errors %}에 의해 나타나는 것임을 알 수 있습니다.

 

cap01(2).JPG 

 

정상적으로 로그인 하였다면 아직은 로그인 이후 이동할 곳을 작성하지 않아 에러가 나옵니다.

 

3. 로그인 이후 이동할 페이지

이제 로그인 이후 기본적으로는 ^accounts/profile/$로 이동을 하나 form에 next이름을 갖는 숨김 폼필드(<INPUT TYPE="HIDDEN" NAME="next">)를 작성하여 이동할 페이지를 정할 수 있습니다.

예제에서는 첫 페이지로 이동하도록 하겠습니다.

 

<FORM> 안에 다음과 같은 요소를 삽입합니다.

 

  1. <INPUT TYPE="HIDDEN" NAME="next" VALUE="/" />

 

위의 코드는 로그인이 정상적으로 이뤄지면 첫 페이지로 이동하라는 의미입니다.

 

이상으로부터 최종 수정된 login.html은 다음과 같습니다.

 

  1. <html>
  2. <head>

  3.    <title> 로그인 form</title>
  4. </head>
  5. <body>
  6. {% if form.errors %}
  7. <p>로그인 과정중 오류가 발생하였습니다.</p>
  8. <p>ID 와 암호를 확인해 주세요</p>
  9. {% endif %}
  10. <h2>사용자 로그인</h2>
  11. <form method="POST" action=".">{% csrf_token %}
  12.    <!-- form.username : ID를 위한 <INPUT TYPE="text" /> 삽입 //-->
  13.    <p><label for="userID">사용자 ID</label> {{ form.username }}</p>
  14.    <!-- # form.password : 암호를 위한 <INPUT TYPE="password" /> 삽입 //-->
  15.    <p><label for="userPWD">사용자 암호</label> {{ form.password }}</p>
  16.    <INPUT TYPE="HIDDEN" NAME="next" VALUE="/" />
  17.    <INPUT TYPE="submit" VALUE="로그인" />
  18. </form>
  19. </body>
  20. </html>

 

 

 

간략히 로그인 과정에 대해 살펴보았습니다. 이를 요약하면

  기본적인 로그인 화면 구성을 위해

  • 로그인 url 생성
  • 로그인 Form 작성
  • 로그인 후 이동할 페이지 지정

 

으로 볼 수 있습니다.

 

본 예제를 위해 수정한 파일은 urls.py과 TEMPLATE_DIRS/registration/login.html 입니다.

이 글은 스프링노트에서 작성되었습니다.

07. 사용자 인증 - User Data Model

django 사용자 인증 시스템을 내장하여 사용자의 계정, 그룹, 권한과 쿠키 기반의 사용자 세션을 다룹니다.

즉, 사용자의 로그인서부터 사이트를 이용하는 동안의 전과정을 다룰 수 있는 시스템을 내장하고 있습니다.

django의 사용자 인증 시스템을 사용하기 위해서는 settings.py의 INSTALLED_APPS 에 'django.contrib.auth' 가 등록되어 있어야 하며 현재 django 버전에서는 기본적으로 설정되어 있습니다.

 

사용자 인증 시스템은 운영을 위한 별도의 자료 구조를 갖고 있습니다. (django/contrib/auth/models.py)

그중 사용자 정보( class User(models.Model) ) 를 models.py에서 다음과 같이 import 해서 웹 사이트에서 사용할 수 있습니다.

 

  1. models.py
  2. ...
  3. from django.contrib.auth.models import User
  4. ...

 

사용자 인증 시스템에 의해 저장되는 사용자 정보(User model)는 다음과 같습니다.

username : 문자열(최대 30자), Unique 값 (필수값)

first_name : 문자열(최대 30자), 성을 저장

last_name : 문자열(최대 30자), 이름을 저장
email : EmailField,  이메일 저장 필드
password : 문자열(최대 128자), 암호 저장 (필수값)
is_staff : Boolean, 관리자 Group여부, 기본값은 False (필수값)
is_active : Boolean, 사용자의 현재 상태로 정지상태면 False, 기본값은 True (필수값)

is_superuser : Boolean, 관리자 여부로 기본값은 False (필수값)

last_login : DateTime, 마지막 로그인 시간 (필수값)
date_joined : DateTime, 처음 등록된 시간 (필수값)

groups : ManyToManyField, 사용자의 시스템 내장 Group

user_permissions : ManyToManyField, 사용자의 시스템 내장 권한

 

위의 기본정보를 이용하여 사용자 정보를 저장하기 위해 별도의 Table없이 사용자 정보를 저장하고 관리하는 것으로 간단하게 사용자를 추가하여 보도록 하겠습니다.

 

  1. c:> python manage.py shell
  2. >>> from django.contrib.auth.models import User
    >>> User.objects.all()
    [<User: 처음 Models 수립시 작성한 관리자 ID>]
    >>> user1 = User.objects.create_user('test1', 'test@abc.com', 'pasword123')
  3. >>> user1.is_staff = False
    >>> User.objects.all()
    [<User: yoonani>, <User: test1>]
    >>> user1.save()

 

사용자 추가시 고려해야 할 부분은 암호부분입니다. 위의 User  model에서 보면 암호 부분이 128자 인 것을 볼 수 있습니다.

이때 저장되는 사용자의 암호는 일반 평문이 아니라 별도의 암호 알고리즘을 이용하여 암호를 저장합니다.

django에서는 SHA1 이라는 암호화 알고리즘을 이용하여 암호를 생성하고 비교합니다. 따라서 저장시 그냥 평문을 쓰는 것이 아니라 암호로 지정한 문자열을 암호화하여 저장합니다.(관리자라고 하더라도 원래의 평문 암호는 모릅니다.)

별도의 암호화 함수를 사용할 수도 있지만 django 사용자 인증 시스템에서 제공하는 create_user()를 사용하면 쉽게 사용자를 추가할 수 있습니다.

create_user() 메소드는 django의 사용자 인증 시스템에서 제공하는 UserManeger object의 메소드로 순서대로 사용자명, 이메일, 암호를 입력받아 이를 저장하기위한 결과(사용자 객체)를 만들어 냅니다. 내부적으로는 set_password()라는 함수를 사용합니다.

 

>>> user1.password
'sha1$a4d94$ca17fd9fe7c60e31bba78ce20436b4116e1a46d5'

 

 

이제 간략히 사용자 정보를 저장하는 방법에 대해 알아보았으니 로그인 과정에 대해 알아보도록 합시다.

 

사용자 인증 시스템에 대한 좀 더 자세한 안내는 http://docs.djangoproject.com/en/1.1/topics/auth/#topics-auth 을 방문해서 살펴보시기 바랍니다.

이 글은 스프링노트에서 작성되었습니다.

06. Request 처리 - 02

앞서 사용자의 요청(Request)을 django가 어떻게 처리하는지 알아봤습니다.(Request 처리)

그럼 사용자가 URL 요청을 하는 것 외에 서버측에 사용자 정보를 전달하는 방법에 대해 예제를 통해 알아보도록 하겠습니다.

 

 

django.shortcuts package에는 django의 M-T-V를 운영하는데 도움이되는 몇가지를 제공하고 있습니다.

그중에서 render_to_response에 대해 잠깐 알아보도록 하겠습니다.

django의 문서에 의하면 (http://docs.djangoproject.com/en/dev/topics/http/shortcuts/#render-to-response) prototype은 다음과 같습니다.

 

render_to_response(template[, dictionary][, context_instance][, mimetype])

 

이 render_to_response는 template과 HTML redndering에 필요한 정보를 context를 통해 전달받아 HTML로 만들어 HttpResponse객체로 반환합니다.

즉, Template과 나타낼 정보를 적절히 연결시켜주는 역할을 하고 앞서 설명한 Template 을 통한 사용자 요청에 대한 응답을 보다 수월하게 해 줍니다.

여기서 설명드린 render_to_response를 view에서 사용하기 위해서는 views.py의 앞 부분에 사용하기 위해 import를 다음과 같이 합니다.

 

from django.shortcuts import render_to_response

 

 

다음 의 HTML Code를 template 디렉토리에 search_form.html 로 저장합니다.

 

  1. search_form.html
  2. <html>
    <head><title>GET을 통한 정보의 전달</title></head>
    <body>
    <form action='/search/' method='get'>
  3. <label for='searchWord'>검색어</label>
  4. <input type='text' name='searchWord' />
  5. <input type="submit" value="검색" />
  6. </form>
    </body>
    </html>
  7.  

 

그럼 이제 이 template을 view와 연결하여 보도록 하겠습니다.

위에서 설명한 render_to_response를 사용해 보도록 하겠습니다. views.py를 열어 다음을 추가합니다.

redner_to_response는 request 처리에서 사용한 예에서 처럼 Context()와 Template의 render()를 분리해서 사용하지 않고 한번에 디자인과 전달할 데이터를 묶어줍니다. (본 예에서는 전달할 데이터가 없어서 그냥 Template만 전달합니다.)

 

  1. views.py
  2. def search_form(request) :
        return render_to_response('search_form.html')

 

template 과 view 가 준비가 되었으니 urls.py를 열어 URL과 view를 다음과 같이 연결해 줍니다.

 

  1. urls.py
  2. urlpatterns = patterns('',
       ...
       (r'^search_form/$', search_form),
  3.    ...
    )

 

테스트 서버를 구동시킨후 http://127.0.0.1:8000/search_form 을 입력해 보면 아래와 같이 나옵니다.

 

get_post_cap01.jpg

 

이제 이 Form을 통해 사용자가 자료를 서버로 보내면 이를 서버가 어떻게 처리할 지 알아보도록 하겠습니다.

기본적으로 처리 과정은 GET에 의한 전송이나 POST에 의한 전송이나 차이가 없습니다. 단지 전송방법에 따른 차이가 존재하는데 이는 추후에 설명드리도록 하겠습니다.

앞선 예제의 Template으로 사용한 HTML문서를 보시면 action이 'search'로 되어 있는 것을 확인하실 수 있습니다.

즉, 해당 Form의 값이 서버의 search로 전달됨을 의미하는데 서버 입장에서는 하나의 사용자 요청이 들어온 것이 됩니다.

따 라서 사용자 요청에 따른 처리 루틴과의 연결을 담당하는 urls.py와 해당 어떻게 처리할 것인지를 나타내는 views.py를 수정하여야 합니다.

먼저 urls.py를 수정하도록 하겠습니다.

서버는 사용자의 요청으로 /search/ 가 들어온것으로 인식하므로

 

  1. urls.py
  2. urlpatterns = patterns('',
       ...
       (r'^search_form/$', search_form),
  3.    (r'^search/$', search),
  4.    ...
    )

 

와 같이 작성하여 views.py의 search 함수와 연결합니다.

이제 처리를 담당하는 views.py 의 search 함수를 다음과 같이 작성해 보도록 하겠습니다.

 

  1. views.py
  2. def search(request) :
        if 'searchWord' in request.GET :
            return HttpResponse ('사용자 입력 검색어 : %r' % request.GET['searchWord'])
        else :
            return HttpResponse('검색어를 입력하지 않으셨습니다.')

 

views.py 에 사용된 코드를 잠시 살펴보겠습니다.

if문장을 보시면 GET방식을 통해 전달되는 정보는 request객체의 GET이라는 속성을 통해 전달됨을 알수 있습니다.

그리고 이 속성은 Dictionary 형 자료로 구성되어 있는데 앞선 HTML에서 검색어 입력을 위한 코드가 <input type='text' name='searchWord' />로 되어 있었습니다. 여기서 name 속성에 해당하는 것이 django가 값을 받아 처리할 때 request.GET 의 키가 되고 이 Form을 통해 전달된 값이 즉, request.GET['searchWord']의 값이 사용자가 입력한 값이 됩니다.

만 일 사용자가 검색어로 test를 입력하게 되면 django는 다음과 같은 화면을 보여줄 것입니다.

 

get_post_cap02.jpg

 

물론 views.py를 보시면 아시겠지만 아무 검색어도 입력하지 않으면 '검색어를 입력하지 않으셨습니다'라는 메세지를 보여줄 것입니다.

 

일단 아주 간단하게 form을 통해서 값을 서버에 전달하고 이 전달된 값을 서버가 어떻게 가져오는지 알아보았습니다.

차츰 복잡한 부분이 나오겠지만 이상의 동작 방식을 잘 이해하시면 나머지는 그저 기술적인 부분으로만 생각하시면 될 것 같습니다.

이 글은 스프링노트에서 작성되었습니다.

05. Template

django의 동작 기반인 M-T-V의 Template에 대해 알아보겠습니다.

 

웹 프로그래밍을 하다보면 디자인과 업무 로직이 서로 엉켜있어서 개발에서부 유지/보수에 적잖은 어려움이 있습니다.

그리고 저같이 게으른 사람의 경우 처음에는 디자인과 프로그래밍 코드를 철저히 구별해 사용하다 시간이 촉박해지면 그냥 마구 잡이로 싸잡아다가 일단 동작만 되게 해 놓고 차후에 손보자고 생각하고 일을 처리하고 후일을 도모합니다만....결과는....

어쨌든 django 를 통한 개발에서도 기존의 방법대로 디자인과 Python Code를 섞어 사용할 수 있습니다만...

기본적 으로 django의 개발 철학에서는 디자인과 Python Code를 분리합니다.

디자인과 코드를 분리하여야 하는 몇가지 이유를 들어보겠습니다.

  • 코드의 재사용성을 위해 디자인과 코드는 분리되어야 합니다. 디자인과 엉켜있는 프로그래밍 코드를 재사용하는 것은 정말 복잡한 일입니다.
  • 디자인 담당자의 경우 프로그래밍 코드를 이해한다고 하더라도 완벽한 이해가 힘듭니다. 그러니 서로 엉킨 코드에서 디자인을 변경하는 것도 힘든 작업입니다. 일일이 로직 개발자와 이야기하는 것은...

 

그럼 이제 django에서 Template을 사용하는 방법에 대해 알아보겠습니다.

먼저 다음과 같이 간단한 view function이 있다고 가정해 봅시다. (디자인과 Python 코드의 혼용입니다.)

 

  1. 애플리케이션/views.py
  2. from django.http import HttpResponse
    def main_page(request) :
        output = '''
    <html>
    <head><title>%s</title></head>
    <body>
       <h1>%s</h1>
       <p>%s</p>
    </body>
    </html>
        ''' % ('Welcome!', 'My django Page', 'request...')
        return HttpResponse(output)

 

위와 같이 Python Code와 HTML이 엉켜있는 상태에서 HTML을 추가하거나 Python Code를 추가하는 것은 개발자와 디자이너 모두에게 짜증을 불러 일으킵니다.

자 위의 코드를 디자인과 분리시켜 봅시다.

먼저 위의 HTML부분을 main_templates.html로 저장하도록 하겠습니다.

저장위치는 settings.py의 TEMPLATE_DIRS 지시어가 가리키는 곳이 되겠습니다.

TEMPLATE_DIRS 지시어

Template으로 사용될 문서들이 모여있는 곳을 가리키는 지시어로 기본 설정값은 빈값입니다.

이 값을 사용하기 편한 디렉토리로 지정을 하는데 예를 들면 다음과 같이 지정할 수 있습니다.

 

  1. settings.py
  2. import os.path
  3.  
  4. TEMPLATE_DIRS = (
  5. os.path.join(os.path.dirname(__file__), 'templates'),

  6. )

 

위의 설정이 지시하는 TEMPLATE_DIRS은 프로젝트가 설치된 디레토리의 하위에 있는 templates 디렉토리에서 Template으로 사용할 파일을 찾는다는 의미입니다. 디렉토리명은 원하시는 대로 하시면 됩니다.

 

  1. main_templates.html
  2. <html>
    <head><title>{{header_title}}</title></head>
    <body>
       <h1>{{header_h1}}</h1>
       <p>{{contents}}</p>
    </body>
    </html>

 

위와 같이 Template을 만들고 이제 이 Template을 이용하여 앞선 view function을 실행시키기 위해 view function을 다음과 같이 변경해 봅시다.

 

  1. 애플리케이션/views.py
  2. from django.http import HttpResponse
  3. from django.template import Context
  4. from django.template.loader import get_template

  5. def main_page(request) :
       template = get_template('main_templates.html')

       variables = Context({

          'header_title' : 'Welcome!',

          'header_h1' : 'My django Page',

          'contents' : 'request...'})

       output = template.render(variables)
       return HttpResponse(output)

 

위의 결과를 테스트 서버를 구동시켜 (> python manage.py runserver) 확인해 보면 다음과 같습니다.

template_cap01.jpg

 

위와 같이 나오게 과정을 살펴보도록 합시다.

먼 저 우리는 Template 을 작성할 때 중괄호 두개의 쌍({{, }})으로 하여 작성한 부분이 있습니다.

이 부분이 Python Code로 부터 값을 가져올 부분이라는 것을 말해 줍니다. 중괄호 두개의 쌍 안에는 일종의 변수명이 자리잡아 View 에서 적절한 값을 연결해 줍니다.

View에서 Template으로 값을 넘기는 것으로 예제 코드에서 Context 를 이용하였습니다.

이 방법을 살펴보면 먼저 Template에서 중괄호 두개의 쌍안에 묶인 변수명은 header_title, header_h1, contents 세 개 였습니다.

이 세 개의 변수를 딕셔너리의 키로써, 화면상에 출력될 값을 딕셔너리의 값으로써 연결하여 Context의 전달인자로 전달합니다.

그 다음 render 메소드를 이용하여 사용할 template과 출력할 값을 연결하여 사용자에게 응답으로 보내질 HTML을 작성합니다(rendering).

이 렇게 작성된 HTML이 HttpResponse 객체를 통해 사용자에게 보내집니다.

말로된 설명을 보시면 이게 무슨 외계어인가 싶으실 테니 위에 작성한 소스코드와 같이 보시면서 연습해 보시면 충분히 이해 되시리라 생각됩니다.

본 글에서는 간략하게 template을 소개하고 사용하는 방법을 알아보았습니다.

실제 template을 사용하는 것은 여러 상황에 맞게 구성되어 있습니다.

계속해서 내용을 올릴테니 일단 여기서는 template이 어떻게 구현되는지 확인만 해 두시면 될 것 같습니다.

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

04. Request 처리

사용자의 응답을 django는 어떻게 처리하는지 알아봅시다.(본 내용은 http://www.djangobook.com/en/2.0/chapter03/ 의 How django Processes a Request를 참고하여 작성하였습니다.)

 

먼저 사용자가 http://127.0.01/hello/ 로 요청을 하였다고 가정하겠습니다.

 

django는 이 요청을 받으면 먼저 작성한 프로젝트(참고 : django 시작하기)에서 settings.py를 찾아 사용자의 요청에 따른 URL과 view의 연결을 담당하는 ROOT_URLCONF의 값을 읽어옵니다.

ROOT_URLCONF의 기본값은 '프로젝트 명.urls'로 django도 Python의 모듈처리와 동일하게 작동하니 프로젝트디렉토리/urls.py가 됩니다.

만 일 urls.py를 다른 이름으로 바꿔 사용하시려면 ROOT_URLCONF의 값을 바꿔 사용하면 됩니다.

기본값으로 계속 진행한다고 하면 django는 urls.py를 읽어서 사용자의 요청 Pattern(굳이 Pattern이라고 한것은 url을 Pattern으로 처리하여 view와 연동하기 때문입니다.)과 동일한 Pattern이 있으면 해당 Pattern에 대응시킨 view function을 실행시킵니다.

view는 함수이기 때문에 적당한 return이 존재하는데 이 때 return은 HttpResponse 객체가 됩니다.

django는 view function의 return 값인 HttpResponse 객체를 사용자에게 응답하기 위해 적절한 HTTP 형태로 만들어 사용자에게 보여줍니다.

이상을 도식화하여 보면 다음과 같습니다.

 

django-request.jpg

 

 

예제를 위한 스크립트는 다음과 같습니다.

 

urls.py

  1. from django.conf.urls.defaults import *
    from 애플리케이션.views import *
    urlpatterns = patterns('',
        (r'^$', main_page),
        (r'^hello/$', hello),
    )

굵게 표시한 부분은 사용자의 요청으로 들어오는 /hello/ pattern에 대해 view의 함수 hello를 적용하라는 의미입니다.

 

애플리케이션/views.py

  1. from django.http import HttpResponse
    def hello(request) :
        output = '''
    <html>
    <head><title>%s</title></head>
    <body>
    <h1>%s</h1>
    <p>%s</p>
    </body>
    </html>
        ''' % ('django', 'Hello', 'My django Page')
        return HttpResponse(output)

urls.py에 의해 사용자의 요청과 매핑된 hello함수 입니다.

반환 객체로 사용할 HttpRespose를 위해 import한 것을 살펴보시고 함수 마지막의 return도 살펴보시기 바랍니다.

 

아래는 위의 수행의 결과로 사용자가 보게 되는 응답화면입니다.

 

screenshot01.jpg   

 

이 글은 스프링노트에서 작성되었습니다.

03 - 3) Select - 검색

앞서 Model을 이용하여 자료 구조(Table)를 작성하고 자료의 입력, 수정, 삭제에 대해 알아보았습니다.

수 정의 경우 단일 행에 대해서만 알아보았는데요 지금 설명드릴 검색 방법에 대해 알게되면 삭제에서 사용했던 .delete() 메소드와 마찬가지로 .update() 메소드를 이용하여 자료를 수정할 수 있습니다.

방법은 삭제와 동일하였으나 따로 분리드려 설명을 드리는 것은 수정의 경우 특정 조건을 만족하는 복수개의 행에 대해 적용하는 경우가 종종 있어 특정 조건을 검색하는 방법에 대한 이해가 필요하기 때문입니다.

자 그럼 지금부터 django Model에서 검색 방법에 대해 알아 보도록 하겠습니다.

 

먼저 검색의 결과로 반환되는 자료의 종류는 앞서 말씀드렸듯이 QuerySet 이라 불리우는 결과집합과 단일 행의 두가지가 있습니다.

이 사실을 잘 숙지하시기 바랍니다.

 

앞서 UPDATE, DELETE 에서 전체 자료를 불러오는 .objects.all()과 단일행 결과를 반환하는 .objects.get()에 대해 알아보았습니다.

여기서 objects는 django model manager라 불리우며  django Model이 Database에 수행한 Query를 통해 생성된 객체를 의미합니다.

앞서 사용한 Department.objects 는 생성한 Department Table의 모든 객체(즉, 모든 자료)를 가리킵니다.

이런 manager 는 필요에 따라 사용자가 생성하여 사용할 수도 있습니다.

 

앞서 Department.objects.get(dName='Infomation and Statistics')의 결과물이 <Department: Department object>와 같이 나왔던 것을 기억할 것입니다.

objects 는 객체라고 말씀을 드렸는데 .objects.get()이나 .objects.all()은 모두 이 객체를 나타내는 것으로 객체를 화면상에 나타내다 보니 이런 식으로 결과물이 나왔습니다.

이제 객체임을 나타내지 말고 실제 가지고 있는 값을 나타내 보도록 합니다.

실제 값을 나타내기 위해서는 Table을 정의한 models.py의 각 Class에 다음과 같이 __UNICODE__(self)라는 멤버 변수를 작성하여주면 됩니다.

 

  1. models.py
  2. from django.db import models
  3.  
  4. class Department(models.Model):
  5. dName = models.CharField(max_length=40)
  6. def __unicode__(self) :
  7. return self.dName
  8.  
  9. class Student(models.Model):
  10. sID = models.CharField(max_length=8)
  11. sName = models.CharField(max_length=50)
  12. sDept = models.ForeignKey(Department)

    def __unicode__(self) :

       reutrn u"%s, %s, %s, %s" % (self.id, self.sID, self.sName, self.sDept)

  13.  
  14. class Subject(models.Model):
  15. sName = models.CharField(max_length=50)
  16. sStudent = models.ManyToManyField(Student)

    def __unicode__(self) :

       return u"%s, %s, %s" % (self.id, self.sName, self.sStudent)

  17.  

 

__unicode__() 메소드는 Python에게 객체를 UNICODE 로 어떻게 표현할지 알려주는 메소드입니다.

위의 예에서는 일부의 값들만을 출력하도록 하였지만 제작자의 필요에 따라 필요한 정보만 출력하게 할 수 있습니다.

만일 Projection 연 산을 하여 그때 그때 원하는 열들의 값을 확인하기 위해서는 Database에 접근하여 확인하셔야 합니다.

 

__unicode__() 메소드를 적용한 후 검색의 결과를 살펴보겠습니다.

 

>>> Department.objects.all()
[<Department: Infomation and Statistics>, <Department: Computer Science>]

 

결과물이 __unicode__() 메소를 적용하고 나니 출력을 원하는 열의 값이 출력되어 한결 보기 편해 졌습니다.

 

이제 검색에 대해 알아보도록 하겠습니다.

 

 

조건 검색 - .objects.filter()

 

앞서 .objects.get() 메소드에서 열의 값을 검색 조건으로 사용했던 것을 기억할 것입니다.

.objects.get() 은 검색의 결과가 단일 행이었고 이제 QuerySet을 반환하는 조건 검색을 실시해 보도록 하겠습니다.

사용되는 메소드는 .filter() 입니다.

다음의 예를 보도록 하겠습니다.

 

 >>> Department.objects.get(dName='Computer Science')

 [<Department: Computer Science>]

>>> Department.objects.filter(dName='Computer Science')

[<Department: Computer Science>]

 

.objects.filter()나 .objects.get() 모두 똑같은 결과를 반환합니다만 .objects.filter()는 결과의 형태가 QuerySet이란 점을 잘 기억해 주시기 바랍니다.

즉, 1 개 이상의 결과를 가져올 수 있는 반면 .objects.get()은 1개의 결과만을 가져올 수 있습니다.

그리고 .objects.get()의 경우 결과값이 없으면 DoesNotExist 예외를 발생시키지만 .objects.filter()의 경우에는 결과값이 없을 경우 빈값을 반환합니다.

 

검색 조건을 추가로 넣기 위해서는 () 안에 검색에 사용될 열과 값을 튜플로 구성하면 됩니다. 즉,

 

 >>> Department.objects.filter(id=2, dName='Computer Science')
[<Department: Computer Science>]

 

 와 같이 사용할 수 있으며 각 조건은 AND로 결합됩니다.(이 부분은 .objects.get()도 동일합니다.)

 .objects.filter() 나 .objects.get()에서 사용된 =(equal)은 정확한 값을 검색하는데  반해 대소비교(SQL의 연산자)나 패턴(SQL문의 LIKE)등으로 검색할 경우도 있습니다.

예를 들어 값의 시작이 "Computer"로  시작하는 자료를 검색한다든지 하는 경우처럼요...

이를 위해 django 에서는 magic 키워드를 제공하는데 다음 표와 같습니다.

 키워드 설 명 사용예

 __lt / __gt

__lte / __gte

보 다 작다 / 보다 크다

같거나 보다 작다 / 같거나 보다 크다

id가 1보다 큰 자료 검색

>>> Department.objects.filter(id__gt=1)

[<Department: Computer Science>]

 __in 주어진 리스트 안에 존재하는 자료 검색

id 가 2, 3, 5 인 자료 검색

 >>> Department.objects.filter(id__in=[2, 3, 5])
[<Department: Computer Science>]

 __year / __month / __day 해당 년도, 월, 일 자료 검색 >>>Entry.objects.filter(pub_date__year=2005)
__isnull 해 당 열의 값이 null 인 자료  검색

>> Department.objects.filter(dName__isnull=True)

[]

 __contains / __icontains

해당 열의 값이 지정한 문자열을 포함하는 자료 검색

(__icontains 는 대소문자를 구별하지 않음)

>>> Department.objects.filter(dName__contains='puter')

[<Department: Computer Science>]

 __startswith / __istartswith

해당 열의 값이 지정한 문자열로 시작하는 자료 검색

(__istartswith 는 대소문자를 구별하지 않음)

>>> Department.objects.filter(dName__startswith='Com')
[<Department: Computer Science>]
 __endswith / __iendswith

해당 열의 값이 지정한 문자열로 끝나는 자료 검색

(__iendswith 는 대소문자를 구별하지 않음)

>>> Department.objects.filter(dName__contains='nce')
[<Department: Computer Science>]
 __range

문 자, 숫자, 날짜의 범위를 지정함

(SQL의 BETWEEN에

>>> Department.objects.filter(id__range=(2, 10))

 

 

 

자료의 정렬 - .objects.order_by()

 

자료의 정렬은 .objects.order_by() 메소드를 이용합니다.

전달되는 전달이자는 열의 이름을 전달하면 됩니다.

기 본적으로 오름차순으로 정렬하며 내림차순으로 정렬하려면 열 이름 앞에 "-"(minus)를 붙히면 됩니다.

또한 .objects.order_bay()  안에 여러 열을 나열하여 정렬 순서를 결정할 수 있습니다.

다음의 예를 자세히 살펴보시기 바랍니다.

주의할 점은 열 이름 앞뒤에 따옴표(큰 따옴표도 괜찮습니다.)를 붙힌다는 것입니다.

 

- 이름의 오름 차순으로 정렬

>>> Student.objects.order_by('sName')
[<Student: 20100103, Ddochi, Computer Science>,

 <Student: 20100102, Donor, Computer Science>,

 <Student: 20100101, Dooly, Infomation and Statistics>]

 

- 이름의 내림차순으로 정렬
>>> Student.objects.order_by('-sName')
[<Student: 20100101, Dooly, Infomation and Statistics>,

  <Student: 20100102, Donor, Computer Science>,

 <Student: 20100103, Ddochi, Computer Science>]

 

- 1차 정렬 조건은 sName이고 sName이 동일할 경우 sID의 오름차순으로 정렬

>>> Student.objects.order_by('sName', 'sID')
[<Student: 20100103, Ddochi, Computer Science>,

 <Student: 20100102, Donor, Computer Science>,

 <Student: 20100101, Dooly, Infomation and Statistics>]

 

 

앞선 .objects.filter() 메소드 뒤에  다음과 같이 .order_by()를 붙혀 사용할 수 있습니다.

(이 기능은 .objects의 모든 메소드를 같이 연결할 수 있는 chaining 기능입니다.)

 

>>> Student.objects.filter(sDept=Department.objects.get(id=2)).order_by('sName')

[<Student: 20100103, Ddochi, Computer Science>,

  <Student: 20100102, Donor, Computer Science>]

 

 

 

특정행의 선택 - Slicing Data

 

Python의 Slicing Data를 이해한다면 쉽게 이해할 수 있는 기능으로 SQL의 LIMIT ... OFFSET 기능을 의미합니다.

선택된 결과에서 특정 행의 자료를 가져오는 방법을 예제를 통해 알아보도록 하겠습니다.

대괄호 안에 넣은 인덱스에 주의하시면서 자료를 보시기 바랍니다.

 

선택된 결과중 첫번째 행의 자료 가져오기 (본 예는 결과적으로 학번이 가장 빠른 사람을 가져옵니다.)

>>> Student.objects.order_by('sID')[0]
<Student: 20100101, Dooly, Infomation and Statistics>

 

선택된 결과중 처음 두개의 자료 가져오기

>>> Student.objects.order_by('sID')[0:2]
[<Student: 20100101, Dooly, Infomation and Statistics>,

  <Student: 20100102, Donor, Computer Science>]

 

선택된 결과중 두번째 이후 자료가져오기

>>> Student.objects.order_by('sID')[1:]
[<Student: 20100102, Donor, Computer Science>,

  <Student: 20100103, Ddochi, Computer Science>]

 

선택된 결과중 두번째부터 세번째 자료까지 가져오기

 >>> Student.objects.order_by('sID')[1:3]
[<Student: 20100102, Donor, Computer Science>,

  <Student: 20100103, Ddochi, Computer Science>]

 

인 덱스를 보시면 아시겠지만 시작 인덱스는 0번 부터 시작을 하고 콜론(:) 뒤에 나오는 범위 인덱스는 해당 인덱스보다 작은 인덱스까지를 의미합니다.

즉, [1:3]이라고 하면 두번째 자료(인덱스 1)부터 3보다 작은 인덱스인 인덱스 2까지의 자료를 뜻합니다.

Model의 Slicing에서는 음수 인덱스는 사용하지 않습니다.

 

 

선택된 자료의 변경

 

QuerySet 자료의 변경을 의미하는 것으로 사용법은 .objects.all().delete() 와 비슷하게 사용됩니다.

현재 우리의 예에서 학과의 ID가 2인 학생들의 학과 ID 를 1로 변경해 보도록 하겠습니다.


>>> Student.objects.filter(sDept=2).update(sDept=Department.objects.get(id=1).id)
2
>>> Student.objects.all()
[<Student: 20100101, Dooly, Infomation and Statistics>,

  <Student: 20100102, Donor, Infomation and Statistics>,

<Student: 20100103, Ddochi, Infomation and Statistics>]

 

이 글은 스프링노트에서 작성되었습니다.