2018-02-02 TIL WPS. 20 Django Tutorial Polls

첫 번째 장고 앱 작성하기 part 1

설문조사 앱 만들기!

  • 사람들이 투표할 수 있는 개방된 사이트
  • 설문을 추가, 변경, 삭제할 수 있는 관리자 사이트

가상환경 설정 및 장고 설치

$ mkdir FirstDjangoApp
$ pyenv virtualenv 3.6.4 firstapp
$ pyenv local firstapp
$ pip install django

프로젝트 만들기

$ django-admin startproject mysite

startproject에서 생성되는 것들

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py

첫 번째 mysite의 이름을 Poll로 바꿔주고 하위의 mysite를 config로 *refactor한다.

*refactor: To rewrite existing source code in order to improve its readability

개발서버

Django project가 제대로 동작하는지 확인해보기

$ python manage.py runserver

설문조사 앱 만들기

프로젝트와 앱의 차이?

앱 : 특정한 기능을 수행하는 웹 어플리케이션
프로젝트 : 앱들과 각 설정들을 한데 묶어놓은 것

앱 생성하기

$ python manage.py startapp polls

다음과 같은 polls 디렉토리가 생성됨

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

첫 번째 뷰 작성하기

view.py 수정

# polls/views.py

from django.http import HttpResponse

def index(request):
	return HttpResponse("Hello, world. You're at the polls index.")

polls/urls.py 생성 및 수정

from django.urls import path

from . import views

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

config/urls.py 수정(최상위 URLconf)

from django.urls import include, path
from django.contrib import admin

urlpatterns = [
    path('polls/', include('polls.urls')),
    path('admin/', admin.site.urls),
]
  • include() 함수는 다른 URLconf들을 참조할 수 있게 도와줌. path('polls/', include('polls.urls')) 구문을 해석하자면 받아온 URL에서 polls/까지만 잘라내고 그 이하의 남은 문자열은 polls.urls에서 처리하도록 하는 것!

  • path() 함수는 2개의 필수 인수 route와 view와 2개의 선택 가능 인수 kwargs, name가 전달됨.

    1. route : URL 패턴을 가진 문자열
    2. view : 일치하는 URL패턴을 찾으면 HttpRequest객체를 첫번째 인수로 하고, 경로로 부터 캡쳐된 값을 키워드 인수로하여 특정한 view함수를 호출함
    3. kwargs : 목표한 view에 사전형으로 전달
    4. name : URL에 이름을 지으면 템플릿을 포함한 어디에나 명확하게 참조가능

첫 번째 장고 앱 작성하기 part 2

데이터베이스 설치

setting.py

config/setting.py 파일은 Django 설정을 모듈 변수로 표현한 파이썬 모듈이다. 여기서 데이터 베이스에 맞게 설정을 해주어야함. 기본적으로 데이터베이스는 SQLite을 사용하도록 구성(가장 간단한 방법, 파이썬에서 기본제공)

INSTALLED_APPS 설명 (Django 인스턴스에서 활성화된 모든 Django앱이 담겨있음)

  • django.contrib.admin – 관리용 사이트, 곧 사용하게 될겁니다.
  • django.contrib.auth – 인증 시스템.
  • django.contrib.contenttypes – 컨텐츠 타입을 위한 프레임워크.
  • django.contrib.sessions – 세션 프레임워크.
  • django.contrib.messages – 메세징 프레임워크.
  • django.contrib.staticfiles – 정적 파일을 관리하는 프레임워크.

이러한 몇몇의 어플리케이션은 최소한 하나 이상의 데이터베이스 테이블을 사용하는데 테이블을 만들어주어야함! 다음과 같이 실행

$ python manage.py migrate

migrate는 1. mysite/setting.py의 데이터베이스 설정, 2. app, 3. 데이터베이스 migrations에 따라 필요한 데이터베이스 테이블을 생성함.

모델 만들기

부가적인 메타데이터를 가진 데이터베이스의 구조를(layout)

Question과 Choice 모델 만들기

polls/model.py 파일 수정

from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

각 모델은 models.Model의 서브클래스, 각각의 클래스 변수들은 모델의 데이터베이스 필드를 나타냄. 데이터베이스의 각 필드는 Field클래스의 인스턴스로 표현됨.

모델의 활성화

위에서 만든 모델로 할 수 있는 것

  • 이 app에 대한 데이터베이스 스키마 생성
  • Question과 Choice객체에 접근하기 위한 Python 데이터베이스 접근 API를 생성

그러려면, app을 INSTALLED_APPS 설정에 추가시켜야함. 그 다음에 makemigratioins

$ python manage.py makemigrations polls

모델의 변경을 적용하고 이 변경을 migration으로 저장함
migration은 Django가 모델의 변경사항을 저장하는 방법으로 0001_initial.py 형태로 저장됨.

image

데이터베이스에 모델과 관련된 테이블을 생성하기 위해 migrate실행

$ python manage.py migrate

migrate명령은 아직 적용되지 않은 모든 migration들을 수집하여 이를 실행함.

$ python manage.py showmigrations

migrations들을 보여줌

$ python manage.py migrate polls 006

migration적용을 컨트롤 할 수도 있다

모델의 변경을 만드는 세단계

  1. model.py에서 모델 변경
  2. makemigrations으로 변경사항에 대한 migration만들기
  3. migrate로 데이터베이스 생성 및 적용

참고할 점!

  • 테이블 이름은 app 의 이름과 모델의 이름(소문자)이 조합되어 자동으로 생성됩니다. 이 경우, 앱의 이름인 polls 와 소문자로 표기된 모델의 이름인 question 과 choice 가 합쳐집니다. 이 동작을 재지정(override) 하여 수정할 수 있습니다.)
  • 기본 키(Primary, ID) 는 자동으로 추가됩니다. (역시 이 동작도 재지정(override) 할 수 있습니다.)
  • 관례에 따라, Django 는 외래 키(foreign key) 필드명에 “_id” 이름을 자동으로 추가합니다. (물론 이것도 재지정 할 수 있습니다.)

API 가지고 놀기

iPython을 깔고 Python 쉘에서 API를 이용해보기

$ pip install ipython
$ python manage.py shell
>>> from polls.models import Question, Choice   # Import the model classes we just wrote.

# No questions are in the system yet.
>>> Question.objects.all()
<QuerySet []>

>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

>>> q.save()
# save를 해야 데이터베이스에 적용됨
# Now it has an ID.
>>> q.id
1

# Access model field values via Python attributes.
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)
>>> q.save()

>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>

__str__()메소드로 객체 표현하기

Question.objects.all() 명령어를 입력했을 때 나오는 <Question: Question object (1)> 을 바꾸기 위해서 __str__() 메소드를 추가해봄

터미널에서 편하게 보려는 이유 뿐만 아니라 관리사이트에서 객체의 표현으로 보기 위해서

from django.db import models

class Question(models.Model):
    # ...
    def __str__(self):
        return self.question_text

class Choice(models.Model):
    # ...
    def __str__(self):
        return self.choice_text

was_published_recently 메소드 추가

import datetime

from django.db import models
from django.utils import timezone


class Question(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

쉘 다시 실행해서 갖고놀기

>>> from polls.models import Question, Choice

>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>

>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>

# filter를 쓰면 쿼리셋이 나오고, get을 쓰면 객체를 불러옴

# Get the question that was published this year.
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>

>>> Question.objects.get(pk=1)
<Question: What's up?>

# Make sure our custom method worked.
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True
>>> q = Question.objects.get(pk=1)

# Display any choices from the related object set -- none so far.
>>> q.choice_set.all()
<QuerySet []>

# Create three choices.
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)

# Choice objects have API access to their related Question objects.
>>> c.question
<Question: What's up?>

# And vice versa: Question objects get access to Choice objects.
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3


>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

# Let's delete one of the choices. Use delete() for that.
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()

관리자 생성하기

$ python manage.py createsuperuser

관리사이트에서 poll app을 변경가능하도록 만들기

from django.contrib import admin

from .models import Question

admin.site.register(Question)

127.0.0.1:8000/admin 사이트 접속해보기

image

첫 번째 장고 앱 작성하기 part 3

조금 더 view 작성하기

polls/views.py 수정

def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

polls/urls.py 수정

from django.urls import path

from . import views

urlpatterns = [
    # ex: /polls/
    path('', views.index, name='index'),
    # ex: /polls/5/
    path('<int:question_id>/', views.detail, name='detail'),
    # ex: /polls/5/results/
    path('<int:question_id>/results/', views.results, name='results'),
    # ex: /polls/5/vote/
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

정규표현식으로 쓰면 다음와 같다!

urlpatterns = [
    # ex: /polls/
    path('', views.index, name='index'),
    # ex: /polls/5/
    re_path(r'(?P<question_id>\d+)/$', views.detail, name='detail'),
    # ex: /polls/5/results/
    re_path(r'(?P<question_id>\d+/results/', views.results, name='results'),
    # ex: /polls/5/vote/
    re_path(r'<?Pquestion_id>\d+/vote/', views.vote, name='vote'),
]

view가 실제로 뭔가 하도록 만들기

view는 HttpResponse 객체를 반환하거나, Http404같은 예외를 발생해야함(모든 Django는 둘 중에 하나를 원하기 때문 -> 그렇게 다루는게 편하다) 또 view는 데이터베이스의 레코드도 읽을 수 있고 Django나 Python에서 third party로 제공되는 템플릿 시스템을 사용할 수도 있다.

from django.http import HttpResponse

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

Template 시스템 사용하기

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}

    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

Template에서 하드코딩된 URL 제거학

index.html에서 다음과 같은 내용을

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

이렇게 바꿔주면 된다.
polls.urls모듈의 path()함수에서 정한 인수의 이름으로 바꾸면 의존성을 제거할 수 있다.

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

URL의 이름공간(namespace) 나누기

다음과 같이 urlpatterns에 앱 이름을 추가함으로써 다른 앱과의 URL pattern을 구분할 수 있다. 실제 장고 프로젝트는 여러 앱을 사용할 수 있으므로 구분이 필요하다.

app_name = 'polls'
urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    path('<int:question_id>/results/', views.results, name='results'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

위에서 바꿨던 내용을

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

다시 이렇게 바꿀 수 있다.

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

첫 번째 장고 앱 작성하기 part 4

간단한 폼 만들기

폼(양식, forms)으로 웹사이트의 강력한 인터페이스를 만들 수 있다

POST 폼 : 자료를 수정하는 효과를 가짐

Comments