[Request 객체]


REST FRAMEWORK는 일반 HttpRequest 를 확장하고 Request Parsing에 있어서 좀더 유연한 방법을 제공합니다.


Request 객체의 핵심기능은 request.data 속성입니다. 이것은 request.POST와 유사합니다. 그러나 WEB API에 있어서 훨씬더 유용합니다.


request.POST  # 오직 form Data만 핸들링가능. 오직 POST방식만 동작.
request.data  # 속성 데이터를 핸들링.  POST, PUT, PATCH 방식에 동작.

[Response 객체]


REST FRAMWORK는 또한 Response 객체도 도입했는데 이것은 TemplateResponse 타입으로 랜더링 되지 않은 콘텐츠를 사용하고 콘텐츠를 올바르게 가공하여 클라이언트에게 올바른 콘텐츠를 반환 할 수 있도록 합니다.



return Response(data)  # 클라이언트가 요청한 타입으로 컨텐츠를 리턴합니다.

[상태코드]


View에서 HTTP 상태 코드를 사용한다고 해서 분명하게 확인 할 수 있는 것은 아니며, 오류 코드를 통지하는 것이 쉽지는 않습니다. REST FRAMEWORK는 각 상태코드에 대해서 보다 명확히 식별자를 제공합니다. 예를들면 status 모듈 안의  HTTP_400_BAD_REQUEST 입니다. 숫자 식별자를 사용하는 것 보다 전체적으로 이러한 방식을 사용하는것이 훨씬 효과적입니다.


[ API views ]


REST FRAMEWORK는 API 보기를 작성하는데 사용할 수 있는 2개의 Wrapper 를제공합니다.

1. 함수 기반 View 작업을 위한 @api_view 데코레이터

2. 클래스 기반 View 작업을 위한 APIView 클래스


이러한 Wrapper는 Reqest 인스턴스를 전달 받는지 확인하는 것과 같은 몇가지 기능을 제공합니다. 그리고 Content를 가공 할 수 있도록 컨텍스트를 Response객체에 추가합니다.


해당 랩퍼는 405 Methond Not Allowed 와 같은 허용하지 않는 응답을 리턴하는 것과 같은 동작도 제공합니다.


request.data에 엑세스 할떄 발생하는 모든 ParseError 예외를 처리할 수도 있습니다.


[실전]


이제 위의 구성요소들을 사용해서 몇가지 예를 작성해보겠습니다.


첫번째 포스팅에서 사용했던 예제를 사용하겠습니다.


기존의 snippets/view.py를 열고 내용을 아래로 대체합니다.


from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


@api_view(['GET', 'POST'])
def snippet_list(request):
    """
    List all snippets, or create a new snippet.
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

방금 삭제한 소스코드 보다 지금의 예제는 훨씬더 세련되어져 있습니다. 코드가 좀더 간결해지고, Forms API를 사용하는 경우와 매우 비슷합니다. 또한 응답의 의미를 분명하게 해주는 상태코드를 사용하고 있습니다.


list를 작성했으니 detail을 작성해볼게요.


@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a snippet instance.
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)

    elif request.method == 'PUT':
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

위의 예제들은 일반적으로 Django View의 작업과 크게 다르지 않다는 것을 알수 있습니다.

더이상 특정 콘텐츠에 대한 타입에 대한 요청이나 응답을 명시적으로 묶어두지 않습니다.

request.data는 들어오는 json 요청을 처리할 수 있지만, 다른 형식도 처리할 수 있습니다.

마찬가지로 data로 응답 객체를 반환하지만, REST FRAMEWORK가 Response를 올바른 콘텐츠로 랜더링 하도록 허용합니다.


[ URL에 접미사 추가하기 ]


어떤 방식인지 한마디로 설명하자면 http://example.com/api/items/4.json.과 같이 처리 할수 있다는 말입니다.


list와 detail 2가지 view 모두에 format 키워드 인수를 추가하는것으로 시작합니다.


def snippet_list(request, format=None):
def snippet_detail(request, pk, format=None):
이제 urls.py 파일을 약간 손보기로 하겠습니다. format_suffix_patterns 를 추가해줍니다.


from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
    url(r'^snippets/$', views.snippet_list),
    url(r'^snippets/(?P<pk>[0-9]+)$', views.snippet_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)

이러한 추가 URL 패턴을 반드시 추가 할필요는 없지만, 특정 형식을 간단하고 명확하게 참조 할 수 있도록 합니다.


[결과를 봅시다]


첫번쨰 포스트에서 테스트했던것 처럼 테스트해보세요. 첫번째 포스트와 비슷하게 작동할 겁니다. 유효하지 않은 request 에 대해서 훨씬 nice하게 error을 핸들링 할 수 있습니다.


http http://127.0.0.1:8000/snippets/

HTTP/1.1 200 OK
...
[
  {
    "id": 1,
    "title": "",
    "code": "foo = \"bar\"\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  },
  {
    "id": 2,
    "title": "",
    "code": "print \"hello, world\"\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  }
]

Accept 헤더를 사용해서 응답의 형식을 제어할수도 있죠


http http://127.0.0.1:8000/snippets/ Accept:application/json  # Request JSON
http http://127.0.0.1:8000/snippets/ Accept:text/html         # Request HTML

또는 format 접미사를 추가해도 됩니다.


http http://127.0.0.1:8000/snippets.json  # JSON suffix
http http://127.0.0.1:8000/snippets.api   # Browsable API suffix

마찬가지로 Content-Type 헤더를 사용해서 보내는 요청의 형식을 제어할수 있습니다.


# POST using form data
http --form POST http://127.0.0.1:8000/snippets/ code="print 123"

{
  "id": 3,
  "title": "",
  "code": "print 123",
  "linenos": false,
  "language": "python",
  "style": "friendly"
}

# POST using JSON
http --json POST http://127.0.0.1:8000/snippets/ code="print 456"

{
    "id": 4,
    "title": "",
    "code": "print 456",
    "linenos": false,
    "language": "python",
    "style": "friendly"
}

위의 http 요청에 --debug 스위치를 추가하면 요청 헤더에서 요청 유형을 볼 수 있습니다.



Posted by C마노
,

원래는 Django 자체에 관한것을 계속적으로 포스팅하고 있었지만,  RESTFRAMEWORK를 실제로 사용할 일이 생겨서 정리차원으로 RestFulFramwork 공식 홈페이지에서 제공하는 튜토리얼을 제가 공부한 방식대로 번역한 글입니다.


== 환경설정 ==


1. virtual env 설정.


virtualenv env
source env/bin/activate

2. django와 restfulframwork 설치.


pip install django
pip install djangorestframework
pip install pygments # Syntax Highlight용.

프로젝트를 시작해봅시다.


cd ~
django-admin.py startproject tutorial
cd tutorial

app을 하나 만들어주시고~

python manage.py startapp snippets
rest_framework와 방금만든 snippets를 settings.py안의 INSTALL_APPS에 추가해봅시다.


INSTALLED_APPS = (
    ...
    'rest_framework',
    'snippets.apps.SnippetsConfig',
)

[모델 만들기]


간단하게 snippet 모델을 한번 만들어 보겠습니다.


snippets/models.py에 들어가서 해당 파일을 수정해봅시다.


from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles

LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())


class Snippet(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, blank=True, default='')
    code = models.TextField()
    linenos = models.BooleanField(default=False)
    language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)

    class Meta:
        ordering = ('created',)

그후 makemigration과 migrate를 해줍시다~.


python manage.py makemigrations snippets
python manage.py migrate

[직렬화 클래스 만들기]


WEB으로 API를 만들기 전에 중요한 일은 JSON데이터를 파이썬에서 제공하는 Dictionary객체로 직렬화하고 비직렬화 하는 방법이 제일 중요할겁니다. 가장 기초적이기도 하구요.

Django에서 제공하는 Model의 형식과 비슷한 serializers.py를 선언해서 작업을 수행할 수 있습니다.

snippets 디렉토리에 serializers.py 라는 파일을 만들고 아래를 추가해주세요.


from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES


class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

    def create(self, validated_data):
        """
        Snippet 인스턴스를 생성하고 리턴합니다. 검증된 데이터가 주어집니다.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        존재하는 Snippet인스턴스를 업데이트하거나 리턴합니다. 검증된 데이터가 주어집니다.
        """
        instance.title = validated_data.get('title', instance.title)
        instance.code = validated_data.get('code', instance.code)
        instance.linenos = validated_data.get('linenos', instance.linenos)
        instance.language = validated_data.get('language', instance.language)
        instance.style = validated_data.get('style', instance.style)
        instance.save()
        return instance
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

위의 부분은 serialize / deserialize 되는 필드를 정의하는 겁니다.

create() 및 update() 메서드는 serializer.save()를 호출 할때, 인스턴스가 create 되거나 update되는것을 정의합니다.


serializer 클래스는 Django Form 클래스와 비슷하며, required, max_length 및 default와 같은 여러가지 필드에 유효성 검사 Flag를 포함 합니다.


[ 직렬화 / 비직렬화 방법 ] 


우선 해당 방법을 배우기 전에 django의 shell에 접속합니다.

python manage.py shell
그리고 아래의 코드를 한번 입력해 봅시다.

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser

snippet = Snippet(code='foo = "bar"\n')
snippet.save()

snippet = Snippet(code='print "hello, world"\n')
snippet.save()

해당 코드에 대한 Snippet을 생성하였습니다.


해당 코드를 한번 serialize 해보겠습니다.


serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}

위의 시점에서 모델 인스턴스를 Python 에서 제공하는 딕셔너리 데이터 유형으로 변환하였고, 직렬화를 마루리 하기위해서 데이터를 json으로 변환합니다.


content = JSONRenderer().render(serializer.data)
content
# '{"id": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'

자 여기까지는 OK이라고 가정하고 그렇다면 JSON 데이터를 Python 딕셔너리 타입으로 바꾸려면 어떻게해야할까요?


from django.utils.six import BytesIO

stream = BytesIO(content)
data = JSONParser().parse(stream)

우선 JSONParser를 사용해서 해당 stream을 파싱한 후에,

SnippetSerializer에 해당 값을 주입해서 완전한 객체 인스턴스로 복원시킵니다. (아래코드를 보세요)


serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>

또한 Django의 쿼리셋을 model대신에 직렬화 할 수도 있습니다.

이럴 때는 간단하게 many=True를 매개변수에 집어넣으면 됩니다.


serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]

[ ModelSerializers 사용 ]


SnippetSerializer 클래스는 Snippet 모델에 포함된 많은 정보를 복제하고 있습니다. 그렇지만 실제로 보면 코드가 너무 복잡합니다. 코드의 내용만 좀 다를뿐 중복된 내용도 존재하구 있구요.

Django에서 From 클래스와 ModelForm 클래스를 제공하는 것 처럼 RESTFUL FRAMEWORK도 Serializer클래스와 ModelSerializer 클래스를 모두 지원합니다.


자, 그럼 ModelSerializer 클래스를 사용해서 serializer를 리팩토링 하는 방법을 실제로 한번 해보겠습니다.

파일 snippets / serializers.py 파일을 다시 열고, SnippetSerializer 클래스를 바꿔줍니다.


class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style')

serializer 가 가지고 있는 장점 중 하나는 serializer 인스턴스의 모든 필드를 검사 할 수 있다는 점입니다. 

Django 쉘을 python manage.py 쉘로 엽니다.


from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))

* ModelSerializer 는 아래와 같은 기능을 합니다.

1. 자동으로 필드 셋을 결정해줍니다.

2. create 와 update 메소드를 기본적으로 구현해줍니다.


[ 실제로 Django 의 View에 적용해보기]


snippets/views.py 파일에 아래를 추가합니다.


from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer

API의 루트는 모든 snippet을 나열하거나 새로운 snippet을 만드는것을 지원하는 view 입니다.


@csrf_exempt
def snippet_list(request):
    """
    List all code snippets, or create a new snippet.
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return JsonResponse(serializer.data, safe=False)

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)

만약 CSRF 토큰이 없는 클라이언트로부터 이 View로 POST를 할 수 있기를 원한다면 View에다가 csrf_exempt 데코레이터를 추가해줘야 합니다.

이것은 일반적으로 이렇게 구현하면 아니지만, REST Framwork view는 실제로 이러한 방법을 사용하지 않지만 지금은 설명을 위해 사용할것입니다.


또한 개별 snippet에 해당하는 view가 필요하며, snippet을 검색 업데이트 삭제 하는데 사용 되어질 수 있습니다.


@csrf_exempt
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JsonResponse(serializer.data)

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data)
        return JsonResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

마지막으로 이러한 view을 연결해봅시다. snippets/urls.py 파일을 만듭니다.


from django.conf.urls import url
from snippets import views

urlpatterns = [
    url(r'^snippets/$', views.snippet_list),
    url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]

그리고 totorials/urls.py에 해당 라인을 추가합니다.


from django.conf.urls import url, include

urlpatterns = [
    url(r'^', include('snippets.urls')),
]

그렇지만 현재 문제점이 존재하기는 합니다. 정형화된 json을 보내지 않거나 request에 정상적인 데이터를 넣지않으면 500에러가 발생합니다. 해결방법은 다음 포스팅에서 정리하도록 하겠습니다.


[ API 테스팅 시도하기 ]


우선 shell을 닫고 quit()


django를 실행합니다.


python manage.py runserver

Validating models...

0 errors found
Django version 1.8.3, using settings 'tutorial.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

그뒤 아래 URL로 요청해보면.


http http://127.0.0.1:8000/snippets/

HTTP/1.1 200 OK
...
[
  {
    "id": 1,
    "title": "",
    "code": "foo = \"bar\"\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  },
  {
    "id": 2,
    "title": "",
    "code": "print \"hello, world\"\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  }
]

ID로도 조회가 가능합니다.~


http http://127.0.0.1:8000/snippets/2/

HTTP/1.1 200 OK
...
{
  "id": 2,
  "title": "",
  "code": "print \"hello, world\"\n",
  "linenos": false,
  "language": "python",
  "style": "friendly"
}

우선은 여기까지로 포스팅을 끝내고 2부를 계속 포스팅하도록 하겠습니다.

Posted by C마노
,

저번 포스트에서 포스팅 했듯이 시스템에는 기본 제공 태그와 필터가 함께제공됩니다.

이번 포스트에서는 가장 일반적인 태그와 필터를 정리해보겠습니다.


[태그]


* if / else


{% if %} 태그는 변수를 평가합니다. 


{% if today_is_weekend %}
<p>Welcome to the weekend!</p>
{% endif %}

An `{% else %}` tag is optional:

{% if today_is_weekend %}
<p>Welcome to the weekend!</p>
{% else %}
<p>Get back to work.</p>
{% endif %}

elif 절도 사용 가능하죠.


{% if athlete_list %}
Number of athletes: {{ athlete_list|length }}
{% elif athlete_in_locker_room_list %}
<p>Athletes should be out of the locker room soon! </p>
{% elif ...
...
{% else %}
<p>No athletes. </p>
{% endif %}

{% if %} 태그는 and, or 또는 not을 사용하여 여러 변수를 테스트하거나 주어진 변수를 무효화 합니다.


{% if athlete_list and coach_list %}
<p>Both athletes and coaches are available. </p>
{% endif %}

{% if not athlete_list %}
<p>There are no athletes. </p>
{% endif %}

{% if athlete_list or coach_list %}
<p>There are some athletes or some coaches. </p>
{% endif %}

{% if not athlete_list or coach_list %}
<p>There are no athletes or there are some coaches. </p>
{% endif %}

{% if athlete_list and not coach_list %}
<p>There are some athletes and absolutely no coaches. </p>
{% endif %}

동일한 태그 내에서 and와 or절을 모두 사용할 수 있으며 and 가 or 보다 우선순위가 높습니다.


{% if athlete_list and coach_list or cheerleader_list %}

결과는 아래와 같죠


if (athlete_list and coach_list) or cheerleader_list

하지만 if 문에서 ()의 사용은 잘못된 방법입니다. 즉 다른 연산자를 결합하지 마세요.


만약 우선 순위를 나타 내기 위해서 괄호가 필요한 경우는 중첩된 if 태그를 사용해야 합니다.


조작 순서 제어에 괄호를 사용하는 것은 지원되지 않습니다.


괄호가 필요하다고 생각되면 템플릿 외부에서 먼저 로직을 수행하고 그 결과를 전용 템플릿 변수로 전달하는 방법을 먼저 고려하세요..


또는 아래와 같이 중첩된 태그를 사용하세요.


 {% if athlete_list %}
     {% if coach_list or cheerleader_list %}
         <p>We have athletes, and either coaches or cheerleaders! </p>
     {% endif %}
 {% endif %}

동일한 논리연산자를 여러번 사용하는 것은 좋지만, 다른 연산자를 결합 할 수는 없습니다. 예를 들면 아래와 같습니다.


{% if athlete_list or coach_list or parent_list or teacher_list %}

각 {% if %}와 {% endif %}를 닫아야 합니다. 그렇지 않으면 Django는 TemplateSyntaxError를 던집니다.


* for


{% for %} 태그를 사용하면 시퀀스의 각 항목을 반복할 수 있습니다.


<ul>
    {% for athlete in athlete_list %}
    <li>{{ athlete.name }}</li>
    {% endfor %}
</ul>

반대방향으로 루핑하려면 태그에 반전을 추가하세요


{% for athlete in athlete_list reversed %}
...
{% endfor %}

for태그를 중첩할 수도 있습니다.


{% for athlete in athlete_list %}
<h1>{{ athlete.name }}</h1>
<ul>
    {% for sport in athlete.sports_played %}
    <li>{{ sport }}</li>
    {% endfor %}
</ul>
{% endfor %}

리스트 안의 리스트를 반복해야 하는 경우 각 하위 목록의 값을 개별 변수로 따낼수 있습니다.


예를 들면 컨텍스트에 point라고 불리는 (x,y) 좌표 목록이 있으면 다음을 사용하여 점 목록을 출력 할 수 있습니다.


{% for x, y in points %}
<p>There is a point at {{ x }},{{ y }}</p>
{% endfor %}

딕셔너리의 항목에 엑세스하는 경우에도 유용하게 사용할 수 있습니다.


{% for key, value in data.items %}
{{ key }}: {{ value }}
{% endfor %}

매우 일반적인 패턴은 반복하기 전에 list의 크기를 확인하고 목록이 비어있는 경우에는 특수 텍스트를 출력하는 방법입니다.


{% if athlete_list %}

{% for athlete in athlete_list %}
<p>{{ athlete.name }}</p>
{% endfor %}

{% else %}
<p>There are no athletes. Only computer programmers.</p>
{% endif %}

이 패턴은 매우 일반적이므로 for 태그는 목록이 비어있는 경우 출력할 내용을 정의할수 있는 {% empty %} 절을 지원합니다!.


{% for athlete in athlete_list %}
<p>{{ athlete.name }}</p>
{% empty %}
<p>There are no athletes. Only computer programmers.</p>
{% endfor %}

알아 두어야 할 것은 break 와 continue를 지원하지 않는 다는것을 유념하십시오.

각 {% for %} 루프 내에서 forloop이라는 템플릿 변수에 엑세스 할 수 있습니다.


이 변수는 루프의 진행상황에 대한 정보를 제공하는 몇가지 속성이 있습니다.


forloop.counter는 루프가 돈 count 입니다. 1부터 시작합니다.


 {% for item in todo_list %}

  <p>{{ forloop.counter }}: {{ item }}</p>
  {%  endfor %}

forloop.counter0 는 1이아니고 0부터 시작하는것만 빼고는 위와같습니다.

forloop.revcounter는 항상 루프의 나머지 항목수를 나타냅니다. 마지막루프를 돌때 1입니다.

forloop.revcounter0는 마지막 0입니다. 위와같습니다.

forloop.first는 루프를 처음 실행 할 경우 True입니다. (특수 한경우 처리에 편합니다.)



  {% for object in objects %}

  {% if forloop.first %}<li class="first">{% else %}<li>
      {% endif %}
      {{ object }}
  </li>
  {% endfor %}

forloop.last 동일한데 마지막입니다.


{% for link in links %}
  {{ link }}{% if not forloop.last %} | {% endif %}
{% endfor %}

위의 결과는 아래처럼 나올 수 있습니다.


  Link1 | Link2 | Link3 | Link4

단어사이에 쉼표도 넣을수 있죠


  <p>Favorite places:</p>
      {% for p in places %}{{ p }}{% if not forloop.last %}, {% endif %}
      {% endfor %}

forloop.parentloop 부모 실행에 대한 forloop 객체에 대한 참조입니다.


 {% for country in countries %}
  <table>
      {% for city in country.city_list %}
      <tr>
          <td>Country #{{ forloop.parentloop.counter }}</td>
          <td>City #{{ forloop.counter }}</td>
          <td>{{ city }}</td>
      </tr>
      {% endfor %}
  </table>
  {% endfor %}

forloop 변수는 루프 내에서만 사용할 수 있습니다.

템플릿 파서가 {% endfor %}에 도달하면 forloop를 소멸시켜버립니다.


* ifequal/ifnotequal


<<우선 여기까지만 적기로 했습니다.>>

'Python > Django' 카테고리의 다른 글

[Django] RestFrameWork 튜토리얼 - 2. Request와 Response  (0) 2017.03.10
[Django] RestFrameWork 튜토리얼 - 1. 직렬화  (0) 2017.03.10
[Django] 템플릿  (0) 2017.03.08
[Django] Dynamic URL  (0) 2017.03.08
[Django] VIew와 URL conf  (0) 2017.03.07
Posted by C마노
,

[Django] 템플릿

Python/Django 2017. 3. 8. 17:31

이전 장에서 View에서 텍스트를 반환하는 방법에서 특이한 점이 있었을 것인데,

아래와 같이 HTML이 하드코딩 되었다는 점입니다.


def current_datetime(request):
    now = datetime.datetime.now()
    html = "It is now %s." % now
    return HttpResponse(html)

이 방법은 작동원리를 설명하기에는 정말 편리하겠지만, HTML을 하드코딩 하는 방법은 좋은 방법이 아닙니다.

이유는 아래와 같습니다.


1. 페이지의 디자인을 변경하려면 PYTHON 코드를 변경해야합니다. 사실 HTML과 CSS 등은 파이썬 코드보다 훨씬더 자주 변경됩니다. 그렇기 때문에 파이썬코드와 디자인을 변경하는 편이 훨씬 더 편할 것입니다.


2. 일반적인 웹페이지 템플릿에는 수백에서 수천줄의 HTML과 스크립트가 있습니다.


3. 파이썬 코드를 작성하고 HTML을 디자인 하는것은 각각 다른분야입니다. 결국 나누게 되면 각각의 책임을 별도의 사람들에게 나눠서 분업할 수 있다는 장점이 있습니다.


4. 백단이 Python 코드로 작업할 수 있고 프론트 단이 동시에 템플릿을 작업할 수 있다면 가장 Best 하다고 볼 수 있고, 한 사람이 다른 사람이 파이썬과 HTML을 모두 포함하는 하나의 파일 편집을 끝내기를 기다릴 필요가 없습니다.

즉 분업이 완벽하게 될 수 있다고 볼수 있습니다.


이러한 1~4의 이유로 페이지의 디자인을 파이썬 코드 자체와 분리하는 것이 훨씬 깔끔하고 유지보수가 용이하다고 볼 수있습니다.


우리는 이것을 Django의 템플릿 시스템으로 할 수 있습니다.


우선 Django의 템플릿은 문서의 표시를 데이터와 분리하기 위한 텍스트 문자열입니다.


[Django 템플릿의 예입니다.]


<html> <head> <title>Ordering notice</title> </head> <body> <h1>Ordering notice</h1> <p>Dear {{ person_name }},</p> <p>Thanks for placing an order from {{ company }}. It's scheduled to ship on {{ ship_date|date:"F j, Y" }}.</p> <p>Here are the items you've ordered:</p> <ul> {% for item in item_list %} <li>{{ item }}</li>{% endfor %} </ul> {% if ordered_warranty %} <p>Your warranty information will be included in the packaging.</p> {% else %} <p> You didn't order a warranty, so you're on your own when the products inevitably stop working. </p> {% endif %} <p>Sincerely,<br />{{ company }}</p> </body> </html>

이 템플릿은 기본 HTML이며, 일부 변수와 템플릿 태그가 들어있습니다.


단계별로 살펴보겟습니다.


1. 한 쌍의 중괄호로 묶인 텍스트 ( EX : {{ pserson_name }} ) 는 변수입니다. 

그렇다면 변수값을 어떻게 할당할 것인지에 관한건 조금뒤에 살펴보기로 합시다.

2. 중괄호와 %기호 (EX : {% if ordered_warranty %} )로 둘러싸인 텍스트는 템플릿 태그입니다. 

태그의 정의는 매우 광범위 합니다. 이 태그는 템플릿 체제에 "무언가를 하도록" 지시합니다.

3. 위의 예제 템플릿은 for 태그와 if 태그를 포함합니다.

for 태그는 Python의 for 문과 매우 유사하게 작동하고 if 태그는 예상대로 if 문으로 작동합니다.

4. 마지막으로 위의 예제를 보시면 filter 예가 포함되어있습니다. {{ ship_date|date:"F j, Y" }} 인데요, ship_date 변수를 date 필터에 전달합니다. date 필터는 해당 인수에 지정된 날짜 형식으로 반환합니다.

필터는 Unix 파이프 방식으로 문자 (|)를 사용하려 연결됩니다.


각 Django 템플릿은 여러 내장 태그와 필터에 액세스할 수 있습니다.


[템플릿 시스템 사용하기]


Django 프로젝트는 하나 또는 여러개의 템플릿 엔진으로 구성 될 수 있습니다.

Django는 자체 템플릿 시스템을 위한 Django-Template-Language(DTL)을 제공합니다.

장고 1.8부터는 Jinja2 도 지원하기 시작하였습니다.


다른 백엔드를 선택할 이유가 없다면 DTL을 무조건 사용하십쇼 


Django에서 contrib 에서 django 템플릿을 포합합니다.


django.contrib.admin 처럼 DTL을 사용하시기 바랍니다. 이번 포스트의 모든 예제는 DTL을 사용할 것입니다.


Django의 템플릿 시스템을 파이썬 코드로 사용할 수 있는 가장 기본적인 방법은 아래와 같습니다.


1. raw 템플릿 코드를 문자열로 제공해서 템플릿 객체를 만듭니다.

2. 주어진 변수세트 template.Context({'name': 'Nige'}) 로 Template 객체의 render() 메소드를 호출하세요

이렇게 하면 완전히 렌더링 된 템플릿이 문자열로 반환되며 모든 변수와 템플릿 대그는 Context 에 따라서 Return 이 다르게 됩니다. 


 >>> from django import template
 >>> t = template.Template('My name is {{ name }}.')
 >>> c = template.Context({'name': 'Nige'})
 >>> print (t.render(c))
 My name is Nige.
 >>> c = template.Context({'name': 'Barry'})
 >>> print (t.render(c))
 My name is Barry.

[템플릿 객체 만들기]


Template 객체를 생성하는 가장 쉬운 방법은 직접 인스턴스화 하는 것입니다.

Template 클래스는 django.template 모듈에 존재하고 있고, 생성자는 raw 템플릿 코드인 하나의 인수를 가집니다. (Ex : Template('My name is {{ name }}.') )

파이썬 인터프리터에서 해당 코드를 어떻게 해석하는지 살펴보겠습니다.


이전 포스트에서 만든 mysite 프로젝트 디렉토리에서 python manage.py shell 을 입력해서 인터프리터를 시작합니다.


아래의 코드를 칩니다.


>>> from django.template import Template
>>> t = Template('My name is {{ name }}.')
>>> print (t)

그럼 아마 아래와 같이 결과가 나올겁니다.


<django.template.base.Template object at 0x030396B0>

0x030396B0는 매번 달라지며 관련이 없습니다. Template 객체를 만들면 템플릿 시스템에서 Raw 템플릿 코드를 내부의 최적화 된 형식으로 컴파일 하여 랜더링 할 준비를 합니다.


그러나 템플릿 코드에 구문 오류가 있는 경우 Template() 을 호출하면 TemplateSyntaxError 가 발생합니다.


>>> from django.template import Template
>>> t = Template('{% notatag %}')
Traceback (most recent call last):
File "", line 1, in ?
...
django.template.base.TemplateSyntaxError: Invalid block tag: 'notatag'

TemplateSnytaxErorr는 아래과 같은 경우에 발생합니다.

1. 유효하지 않은 tag

2. 태그는 유효하지만, 잘못된 인수가 있을경우.

3. 유효하지 않은 필터

4. 필터는 유요하지만, 잘못된 인수가 있을경우.

5. 잘못된 템플릿 구문.

6. 닫히지 않은 태그.


[템플릿 랜더링하기]


Template 객체를 얻은 후에는 컨텍스트를 지정하여 데이터를 전달 할 수 있습니다.

컨텍스트는 단순히 템플릿 변수 이름 및 관련 값의 집합입니다.


템플릿은 이 컨텍스트를 변수를 사용하여 템플릿에 변수를 할당하고 태그를 평가합니다.


컨텍스트는 Django 에서 django.template 모듈에 있는 Context 클래스로 표현됩니다.


생성자는 하나의 선택적 인수를 사용하는데, 즉 변수이름을 변수값에 맵핑하는 딕셔너리를 사용합니다.


컨텍스트를 사용해서 Template 객체의 render() 메서드를 호출하여 템플릿을 채웁니다.


>>> from django.template import Context, Template
>>> t = Template('My name is {{ name }}.')
>>> c = Context({'name': 'Stephane'})
>>> t.render(c)
'My name is Stephane.'

* 여기서 잠깐 : 왜 쉘에서 python3 로 실행하면 되지 python manage.py shell로 실행하나요?

=> 가장 큰 차이는 Django shell은 Django 설정파일을 기본적으로 불러오기 때문입니다.

인터프리터를 시작하기 전에 Django에게 어떤 설정파일을 사용할 것인지를 알려줍니다. 템플릿 시스템을 포함한 Django의 많은 부분은 설정에 의존하는 부분이 많고, 프로그램워크가 사용할 설정을 아는 경우가 아니라면 사용할 수 없습니다.


Django shell을 실행하면 Django는 DJANGO_SETTINGS_MODULE이라는 환경변수를 찾습니다.

이는 settings.py의 경로를 말합니다.


예를들면 DJANGO_SETTINGS_MODULE은 아마 mysite.settings 일겁니다.


이제 python manage.py shell 을 실행할때 이 명령은 DJANGO_SETTINGS_MODULE 설정을 처리합니다.


[딕셔너리와 컨텍스트]


파이썬의 딕셭너리는 키와 변수값 사이를 맵핑하는 것인데, Context는 사전과 유사하지만 컨텍스트는 추가 기능을 제공합니다.


>>> from django.template import Template, Context
>>> raw_template = """<p>Dear {{ person_name }},</p>
...
... <p>
    Thanks for placing an order from {{ company }}. It's scheduled to
    ... ship on {{
ship_date|date:"F j, Y"
    }}.
</p>
...
... {% if ordered_warranty %}
... <p>Your warranty information will be included in the packaging.</p>
... {% else %}
... <p>
    You didn't order a warranty, so you're on your own when
    ... the products inevitably stop working.
</p>
... {% endif %}
...
... <p>Sincerely,<br />{{ company }}</p>"""
>>> t = Template(raw_template)
>>> import datetime
>>> c = Context({'person_name': 'John Smith',
...     'company': 'Outdoor Equipment',
...     'ship_date': datetime.date(2015, 7, 2),
...     'ordered_warranty': False})
>>> t.render(c)
"<p>Dear John Smith,</p>\n\n<p>Thanks for placing an order from Outdoor Equipment. It\
's scheduled to\nship on July 2,2015.</p>\n\n\n<p>You didn't order a warranty, so you\
're on your own when\nthe products inevitably stop working.</p>\n\n\n<p>Sincerely,<br\
 />Outdoor Equipment</p>"

1. 먼저 django.template 모듈에 있는 Template과 Context 클래스를 가져옵니다.

2. 템플릿의 raw 텍스트를 raw_template 변수에 저장합니다.

3. 여러 줄에 걸쳐있기 때문에 문자열을 지정하기 위해서 """를 사용합니다.

4. raw_template을 Template 클래스 생성자에 전달해서 템플릿 객체 t를 만듭니다.

5. 파이썬 표준 라이브러리에서 datetime 모듈을 가지고 옵니다.

6. 그런 다음 Context 객체를 만듭니다. Context 생성자는 변수이름을 값에 맵핑하는 딕셔너리를 사용합니다.

7. 마지막으로 템플릿 객체에서 render() 메소드를 호출하여 컨텍스트를 전달합니다.

이것은 랜더링 되어진 템플릿을 반환합니다. 즉 템플릿 변수를 실제 변수값으로 바꾸고 템플릿 태그를 실행한 값을 반환합니다. ( 랜더링 되어진 템플릿은 필터까지 같이 처리된 결과가 리턴됩니다. )


[같은 템플릿에 여러가지의 콘텍스트]


Template 객체를 얻으면 이를 통해 여러가지 컨텍스트를 랜더링 가능합니다.


>>> from django.template import Template, Context
>>> t = Template('Hello, {{ name }}')
>>> print (t.render(Context({'name': 'John'})))
Hello, John
>>> print (t.render(Context({'name': 'Julie'})))
Hello, Julie
>>> print (t.render(Context({'name': 'Pat'})))
Hello, Pat

이와 같은 여러 콘텍스트를 랜더링 하기 위해서 동일한 템플릿 소스를 사용할 때마다 Template 객체를 한번 생성한 다음에 render ()를 여러번 호출 하는 것이 훨씬 효율적입니다.

 # Bad

for name in ('John', 'Julie', 'Pat'):
t = Template('Hello, {{ name }}')
print (t.render(Context({'name': name})))

# Good
t = Template('Hello, {{ name }}')
for name in ('John', 'Julie', 'Pat'):
print (t.render(Context({'name': name})))

Django의 템플릿 파싱은 꽤 빠른편이고, 내부적으로는 정규식 기반으로 작동합니다. 이것은 XML 기반 템플릿 엔진과 완전히 대조됩니다. XML파서의 오버헤드도 없기 때문에 일반적으로 XML기반 템플릿 엔진보다 훨씬 빠릅니다.


[ 콘텍스트 .검색 ] 


지금까지의 예제는 컨텍스트에서 간단한 값을 전달하였습니다. 대부분 문자열과 datetime 예제였습니다. 그러나 템플릿 시스템은 리스트, 딕셔너리 및 사용자정의 객체와 같이 보다 복잡한 데이터 구조를 효율적으로 처리합니다. Django 템플릿에서 복잡한 데이터 구조를 탐색하는 핵심은 dot(.) 입니다.


.을 사용해서 딕셔너리 키, 속성, 메소드 또는 index에 액세스 하세요.

이것은 몇가지 예를 통해 설명해드리겠습니다.


예를들어, 파이썬 딕셔너리를 템플릿에 전달한다고 가정하볼게요.


해당 딕셔너리의 값에 딕셔너리 키로 액세스 하려면 점을 사용하십시오.


>>> from django.template import Template, Context
>>> person = {'name': 'Sally', 'age': '43'}
>>> t = Template('{{ person.name }} is {{
person.age
}} years old.')
>>> c = Context({'person': person})
>>> t.render(c)
'Sally is 43 years old.'

비슷하게 객체 속성에 대한 엑세스도 마찬가지입니다.


예를 들어, 파이썬 datetime.date 객체는 year, month, day 속성을 가지고 있으며, .을 사용하여 장고 템플릿의 속성에 엑세스할수 있습니다.


>>> from django.template import Template, Context
>>> import datetime
>>> d = datetime.date(1993, 5, 2)
>>> d.year
1993
>>> d.month
5
>>> d.day
2
>>> t = Template('The month is {{ date.month }}
and the year is {{ date.year }}.')
>>> c = Context({'date': d})
>>> t.render(c)
'The month is 5 and the year is 1993.'

아래의 예제에서는 사용자 정의 클래스를 사용해서 dot(.) 이 임의의 객체에 대한 속성 액세스도 허용한다는 것을 보여줍니다.


>>> from django.template import Template, Context
>>> class Person(object):
...     def __init__(self, first_name, last_name):
...         self.first_name, self.last_name =
first_name, last_name
>>> t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.')
>>> c = Context({'person': Person('John', 'Smith')})
>>> t.render(c)
'Hello, John Smith.'

.은 객체의 메서드를 참조할 수도 있습니다. 예를 들어, 각각의 파이썬 문자열은 upper() 와 isdigit() 메서드를 가지고 있으며, 같은 닷 구문을 사용하여 Django 템플릿에서 호출 가능합니다.


>>> from django.template import Template, Context
>>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}')
>>> t.render(Context({'var': 'hello'}))
'hello -- HELLO -- False'
>>> t.render(Context({'var': '123'}))
'123 -- 123 -- True'

파이썬과 다르게 메서드 호출에 괄포를 포함하지 않는것을 보세요. 또한 메서드에 매개변수를 전달할 수도 없습니다. 매개 변수가 필요없는 메서드만 호출 가능합니다. 마지막으로 점들도 리스트에 엑세스 하는데 사용됩니다 예를 들면 아래와 같습니다.


>>> from django.template import Template, Context
>>> t = Template('Item 2 is {{ items.2 }}.')
>>> c = Context({'items': ['apples', 'bananas',
'carrots']})
>>> t.render(c)
'Item 2 is carrots.'

- 연산자를 가진 색인은 허용되지 않습니다. 예를들어 템플릿 변수 {{items.-1}} 은 TemplateSyntaxError를 발생시킵니다.


템플릿 . 조회를 요약하자면 템플릿 시스템이 변수 이름에 점을 발견하면 다음 순서로 조회합니다.


1. Dictinary 조회

2. 속성 조회

3. 메서드 콜링.

4. 리스트 인덱스 조회


시스템은 동작하는 첫번째 조회 유형을 사용합니다. 도트 조회는 여러 단계로 중첩도 가능합니다. 예를 들어 다음예제는 {{person.name.upper}}를 사용합니다.


딕셔너리 조회 (person['name'])과 메소드 호출 (upper())로 변환됩니다.


>>> from django.template import Template, Context
>>> person = {'name': 'Sally', 'age': '43'}
>>> t = Template('{{ person.name.upper }} is {{
person.age
}} years old.')
>>> c = Context({'person': person})
>>> t.render(c)
'SALLY is 43 years old.'

[메소드 호출 방법]


메소드 호출은 다른 조회 유형보다 조금 더 복잡합니다. 다음은 유의사항입니다. 메소드 조회중에 메서드가 예외를 발생시키면 silent_variable_failure 값이 Ture이면 변수는 엔진의 string_if_invalid ( 기본적으로 빈 문자열 )으로 렌더링 됩니다.


아래의 예제를 보세요.


  >>>
  t = Template("My name is {{ person.first_name }}.")
  >>> class PersonClass3:
  ...     def first_name(self):
  ...         raise AssertionError("foo")
  >>> p = PersonClass3()
  >>> t.render(Context({"person": p}))
  Traceback (most recent call last):
  ...
  AssertionError: foo

  >>> class SilentAssertionError(Exception):
  ...     silent_variable_failure = True
  >>> class PersonClass4:
  ...     def first_name(self):
  ...         raise SilentAssertionError
  >>> p = PersonClass4()
  >>> t.render(Context({"person": p}))
  'My name is .'

* 메서드 호출은 메서드가 매개변수가 필요없는 경우에만 작동합니다. 그렇지 않으면 시스템은 다음 조회유형으로 (리스트) 이동합니다.


* Django에서는 템플릿에서 사용할 수 있는 처리량을 의도적으로 제한하기 때문에 템플릿 내에서 엑세스 되는 메서드 호출에 매개변수를 전달 할 수 없는 것입니다.


[ 유효하지 않은 변수를 처리하는 방법 ]


일반적으로 변수가 없으면 템플릿 시스템은 엔진의 string_if_invalid 구성 옵션값을 삽입합니다 (기본적으로 빈 문자열입니다.)


>>> from django.template import Template, Context
>>> t = Template('Your name is {{ name }}.')
>>> t.render(Context())
'Your name is .'
>>> t.render(Context({'var': 'hello'}))
'Your name is .'
>>> t.render(Context({'NAME': 'hello'}))
'Your name is .'
>>> t.render(Context({'Name': 'hello'}))
'Your name is .'

이러한 방법은 사람의 Operation 에따라 오류가 해결될 수 있기 때문에 예외를 발생시키는 것보다 효율적인 방법입니다.

이러한 경우 변수의 대소문자나 이름이 잘못되어서 모든 조회가 실패하였습니다.

현실적으로는 작은 템플릿 구문 오류 ( 대소문자, 이름실수 등 )으로 인해 웹사이트에 엑세스 할수 없게되는것은 굉장히 비효율 적입니다.



'Python > Django' 카테고리의 다른 글

[Django] RestFrameWork 튜토리얼 - 1. 직렬화  (0) 2017.03.10
[Django] 템플릿 태그와 필터  (0) 2017.03.08
[Django] Dynamic URL  (0) 2017.03.08
[Django] VIew와 URL conf  (0) 2017.03.07
[Django] urls.py 특징.  (0) 2017.03.07
Posted by C마노
,
urlpatterns = [
    url(r'^time/$', current_datetime),
    url(r'^time/plus/1/$', one_hour_ahead),
    url(r'^time/plus/2/$', two_hours_ahead),
    url(r'^time/plus/3/$', three_hours_ahead),
]

해당 URL 매핑이 있다고 가정하자, /time/plus/1 은 1시간 미래를 보여주고

/time/plus/2 는 2시간 미래,

/time/plus/3 는 3시간 미래를 보여주는 url 맵핑이다.


분명이 위의 URL 맵핑에는 문제가 있다.


위의 urlpattern들은 중복적인 기능을 제공 하고, 기능자체가 (1시간, 2시간, 3시간) 만 지원할 수 있으므로 효율성이 떨어집니다.


만약, 4시간의 기능이 필요할 경우 URL conf를 새로하나 추가해줘야한다는 번거로움이 생깁니다.


임의의 시간으로 오프셋을 처리하기 위해서 어플리케이션을 다시 설계해 봅시다.


urlpatterns = [
    # ...
    url(r'^time/plus/\d+/$', hours_ahead),
    # ...
]

저렇게 만들면 가능할 것 같지만, /time/plus/100000000 도 가능한 값이 되어버리므로, 좀더 합당한 값을 가질 수 있도록 다시 수정해봅시다.


url(r'^time/plus/\d{1,2}/$', hours_ahead),

자 이제 이것을 View 함수에 전달하기 위해서 ()로 쌉니다.


url(r'^time/plus/(\d{1,2})/$', hours_ahead),

정리하자면 아래와 같습니다.


from django.conf.urls import include, url
from django.contrib import admin
from mysite.views import hello, current_datetime, hours_ahead  
urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^hello/$', hello),
    url(r'^time/$', current_datetime),
    url(r'^time/plus/(\d{1,2})/$', hours_ahead),
]

여기서 보통은 이런식으로 처리하면 되지 않을까 생각할 수 있습니다. (일반적인 다른 웹프로그래밍 언어등을 따져보자면, )


/time/plus?hours=3


위와 같은 ? 로 처리하는 방식이 낫다고 생각 할 수 있지만 Django의 철학중 하나는 URL이 깨끗하고 심플하고 더 읽기쉽고 더 쉬워야 한다는 철학이 있습니다.

아름다운 URL은 퀄리티 높은 웹 응용 프로그램의 특징입니다.


결국 /time/plus/hours/3 을 사용하라는 말입니다.



자 이제 hours_ahead 뷰를 작성해 보겠습니다. 이전에 작선한 current_datetime 뷰와 매우 비슷합니다.


from django.http import Http404, HttpResponse
import datetime

def hours_ahead(request, offset):
    try:
        offset = int(offset)
    except ValueError:
        raise Http404()
    dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
    html = "In %s hour(s), it will be  %s." % (offset, dt)
    return HttpResponse(html)

위의 URLconf에서 보면 숫자가 1~2글자일경우만 가능하지만, 만약의 경우를 대비해서 ValueError을 계속 확인하는 것은 좋은 습관입니다.


def hours_ahead(request, offset):
    # try:
    #     offset = int(offset)
    # except ValueError:
    #     raise Http404()
    dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
    html = "In %s hour(s), it will be %s." % (offset, dt)
    return HttpResponse(html)

자 해당 try except 구문의 주석을 해보고 

/time/plus/3/ 을 호출해 봅시다.


그럼 TypeError가 날겁니다.


왜냐하면 timedelta에 string 형 변수를 전달했기 때문입니다.



자 위의 사진을 보고 에러를 판단하는 방법에 대해서 알아봅시다.


페이지의 상단에서 우선 정보를 얻습니다.


unsupported type 그리고 Exception Location 에서 에러가 난 행 번호를 봅니다.


Django 에서는 파일 이름, 함수, 메소드 이름, 행 번호 및 해당 행의 소스코드를 표시해 줍니다.


소스라인을 선택하면 오류가 있는 라인의 여러라인이 표시됩니다.



자여기서 밑에 사진이 살짝 짤렸지만 Local vars를 클릭해보면 해당 프레임 즉 저 에러가 난 부분에서의 모든 Local 변수들을 볼 수 있습니다.



예외가 발생한 코드의 정확한 위치를 알 수 있고, 위의 디버깅 정보는 개발하는데 있어서 큰 도움이 될겁니다.



일시적으로 Assertion을 일부러 삽입해서 오류 페이지를 강제로 띄우게 할 수도 있습니다. 그런 다음 프로그램의 로컬 변수와 상태를 볼 수 있죠,


아래와 같이 코드하면 됩니다.


def hours_ahead(request, offset):
    try:
        offset = int(offset)
    except ValueError:
        raise Http404()
    dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
    assert False
    html = "In %s hour(s), it will be %s." % (offset, dt)
    return HttpResponse(html)


'Python > Django' 카테고리의 다른 글

[Django] RestFrameWork 튜토리얼 - 1. 직렬화  (0) 2017.03.10
[Django] 템플릿 태그와 필터  (0) 2017.03.08
[Django] 템플릿  (0) 2017.03.08
[Django] VIew와 URL conf  (0) 2017.03.07
[Django] urls.py 특징.  (0) 2017.03.07
Posted by C마노
,

python manage.py runserver 를 했을때, 스크립트는 프로젝트 내부 디렉토리의 settings.py 파일을 찾는다.


이 파일은 장고 프로젝트를 위한 모든 설정을 포함하고 있습니다.


모든것은 대문자이고 TEMPLATE_DIRSDATABASES, etc. 


가장 중요한것은 ROOT_URLCONF 입니다.


이 옵션이 Django 에서 URL 의 ROOT로 지정되어야 한다고 알려준다.


ROOT_URLCONF = 'mysite.urls'
이것을 잘 보면 실제 경로는 mysite/urls.py 이라는 것을 알 수 있다.


특정 URL에 대한 요청이 들어올때 Django는 ROOT_URLCONF 를 보고 urls.py를 로드합니다.


그리고 matching 되는 url을 찾습니다.


매칭되는 하나를 찾으면, 그 패턴과 관련된 뷰 함수를 호출하여 첫번째 매개 변수로 HttpRequest 객체를 전달합니다.


그리고 View 함수는 무조건 HttpResponse를 반환해야 합니다.


우선 이 작업이 끝나고 나면 Django가 나머지 작업을 수행합니다. 


적절한 HTTP 헤더와 Body ( 즉 웹페이지의 내용 )을 사용하여 Python 객체를 적절한 Web Response로 변환합니다.


요약하면 아래와 같습니다.


1. 사용자가 /hello/ 의 요청을 보냅니다.


2. Django는 ROOT_RULCONF 를 보고 루트 URLconf 를 결정합니다.


3. Django는 /hello/ 와 일치하는 URLconf 의 모든 URL 패턴을 찾습니다.


4. 만약 일치하는 것을 찾으면 관련 뷰 함수를 호출합니다.


5. 해당 View 함수는 HttpResponse를 반환합니다.


6. Django는 HttpResponse를 적절한 HTTP 응답으로 변환하여 웹페이지를 생성합니다.


이것은 장고로 구동되는 페이지를 만드는 기본입니다.



'Python > Django' 카테고리의 다른 글

[Django] RestFrameWork 튜토리얼 - 1. 직렬화  (0) 2017.03.10
[Django] 템플릿 태그와 필터  (0) 2017.03.08
[Django] 템플릿  (0) 2017.03.08
[Django] Dynamic URL  (0) 2017.03.08
[Django] urls.py 특징.  (0) 2017.03.07
Posted by C마노
,

This tells Python that the string is a “raw string” – its contents should not interpret backslashes.


In normal Python strings, backslashes are used for escaping special characters – such as in the string “\n”, which is a one-character string containing a newline. When you add the r to make it a raw string, Python does not apply its backslash escaping – so, “r'\n'” is a two-character string containing a literal backslash and a lowercase “n”.


Django는 URL 패턴을 검사하기 전에 들어오는 모든 URL 앞에 슬래시를 제거합니다.


즉 /hello/ 라고 치면 제일 처음 / 는 의미가 없어져버리는 것임.


그리고 제일 뒤에 $ 달러사인이 없어버리면 모든 


^hello/ 라고 쳤을 경우 뒤에 모든 문자열이 매칭이 되어버림.


꼭 뒤에 $ 를 붙이도록 하자.


후행슬래쉬는 무조건 필요하다. APPEND_SLASH 설정이 되어있을경우는

없어도 된다.


또한 중요하게 생각해야 한다는 것은. hello라는 Function을 전달했다는 것이 포인트이다.


이것은 Python의 핵심기능중 하나임. (함수포인터라고 생각하면 편할듯...)


[장고에서 자주사용하는 정규표현식]


SymbolMatches
. (dot)Any single character
\dAny single digit
[A-Z]Any character between A and Z (uppercase)
[a-z]Any character between a and z (lowercase)
[A-Za-z]Any character between a and z (case-insensitive)
+One or more of the previous expression (e.g., \d+ matches one or more digits)
[^/]+One or more characters until (and not including) a forward slash
?Zero or one of the previous expression (e.g., \d? matches zero or one digits)
*Zero or more of the previous expression (e.g., \d* matches zero, one or more than one digit)
{1,3}Between one and three (inclusive) of the previous expression (e.g., \d{1,3} matches one, two or three digits)


'Python > Django' 카테고리의 다른 글

[Django] RestFrameWork 튜토리얼 - 1. 직렬화  (0) 2017.03.10
[Django] 템플릿 태그와 필터  (0) 2017.03.08
[Django] 템플릿  (0) 2017.03.08
[Django] Dynamic URL  (0) 2017.03.08
[Django] VIew와 URL conf  (0) 2017.03.07
Posted by C마노
,
class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        print "getter of x called"
        return self._x

    @x.setter
    def x(self, value):
        print "setter of x called"
        self._x = value

    @x.deleter
    def x(self):
        print "deleter of x called"
        del self._x


'Python' 카테고리의 다른 글

[Flask] Flask - wsgi - nginx 연동.  (0) 2017.05.15
[Python] YAPF  (0) 2017.03.11
[Python] 공부해야 할 것.  (0) 2017.02.15
[Python]pywin32 설치기.  (0) 2016.12.15
python vim설정  (0) 2016.12.05
Posted by C마노
,

for문을 돌거나 다른 반복 처리중 보이는 마지막 몇가지 항목을 유지하고 싶다면 어떻게 할까?


그렇다면 collections 모듈에 deque를 import해서 사용하면 된다.


아래의 코드는 특정 텍스트를 인자로 던져주면 해당 특정 텍스트를 파일에서 찾게되면,


해당라인의 전 history 라인만큼 리턴해주는 Python 코드이다


from collections import deque

def search(lines, pattern, history=5):
    previous_lines = deque(maxlen=history)
    for line in lines:
        if pattern in line:
            yield line, previous_lines
        previous_lines.append(line)

# Example use on a file
if __name__ == '__main__':
    with open('somefile.txt') as f:
        for line, prevlines in search(f, 'python', 5):
            for pline in prevlines:
                print(pline, end='')
            print(line, end='')
            print('-'*20)

어떠한 item을 검색하기 위한 코드를 작성할때는 yield를 쓰는것이 일반적이다.


Posted by C마노
,

길이를 알수 없는 요소를 Unpacking 하고 싶다면?


이럴때는 *를 사용하는데, 예를 들면, 리스트중 첫번째와 마지막을 버리고 중간으로만 평균을 낸다.

라고 가정을 하면 어떻게 만들수 있을까?


def drop_first_last(grades):
    first, *middle, last = grades
    return avg(middle)

굉장히 간단하게 구현이 가능하다.


또한 아래와 같은 경우에도 사용이 가능하다


[ '이름', '이메일', '전화번호', '전화번호', '전화번호' ]


>>> record = ('Dave', 'dave@example.com', '773-555-1212', '847-555-1212')
>>> name, email, *phone_numbers = user_record
>>> name
'Dave'
>>> email
'dave@example.com'
>>> phone_numbers
['773-555-1212', '847-555-1212']
>>>

위의 코드에서 phone_number는 항상 list라는 것이 포인트다

전화번호의 개수에 상관없이 unpacking이 가능하다.


*된 변수는 제일 마지막이라는 법은 없다. 처음이 될 수도 있다.


1~8 분기 까지의 판매실적이 있는데 1~7까지만 평균을 보고 8분기는 현재실적을 리턴하고 싶다면 아래와 같이 구현할수 있다


*trailing_qtrs, current_qtr = sales_record
trailing_avg = sum(trailing_qtrs) / len(trailing_qtrs)
return avg_comparison(trailing_avg, current_qtr)

파이썬 입장에서 해석되는 코드 (아래)


>>> *trailing, current = [10, 8, 7, 1, 9, 5, 10, 3]
>>> trailing
[10, 8, 7, 1, 9, 5, 10]
>>> current
3

*을 사용하면 길이가 분명하지 않은 반복가능한 객체에 대해서 값을 출력할수 있음.


활용을하자면 다양한 길이의 튜플이 존재할 경우 아래와 같이 활용할 수있음.


records = [
     ('two', 1, 2),
     ('one', 'hello'),
     ('two', 3, 4),
]

def do_foo(x, y):
    print('two', x, y)

def do_bar(s):
    print('one', s)

for tag, *args in records:
    if tag == 'two':
        do_foo(*args)
    elif tag == 'one':
do_bar(*args)

*사용하면 split 함수와도 굉장히 잘 어울립니다.!


>>> line = 'nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false'
>>> uname, *fields, homedir, sh = line.split(':')
>>> uname
'nobody'
>>> homedir
'/var/empty'
>>> sh
'/usr/bin/false'
>>>

또한 아래와 방법으로도 사용이 가능하다. (이부분에서 좀 놀랐음.)


record = ('ACME', 50, 123.45, (12, 18, 2012)) 의 데이터가 있으면

이름과 마지막 2012의 년도만 값으로 얻고싶으면 어떻게할까?


>>> record = ('ACME', 50, 123.45, (12, 18, 2012))
>>> name, *_, (*_, year) = record
>>> name
'ACME'
>>> year
2012
>>>

위와같이 사용하면 된다.


첫값을 가지고 오고, 나머지는 리스트로 반환하려면?


>>> items = [1, 10, 7, 4, 5, 9]
>>> head, *tail = items
>>> head
1
>>> tail
[10, 7, 4, 5, 9]
>>>

위의 방법을 활용하면 '효율적인 재귀알고리즘'을 만들 수 있다.


>>> def sum(items):
...     head, *tail = items
...     return head + sum(tail) if tail else head
...
>>> sum(items)
36
>>>

그렇지만 추천 하는 방법은 아님. 그냥 이런 방법이 있구나 하고 알아두자.

Posted by C마노
,