2010년 6월 7일 월요일

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>]

 

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