실제 모델을 만들고, 마이그레이션 파일을 만들어 해당 '앱'의 'schema version'을 만들고 마이그레이션을 하며, model들의 관계설정으로 django ORM 맛보기를 실제 DB 데이터와 비교하면서 진행해보자! 문장이 구어와 문어로 바뀌는 것은,, 문어체가 공식문서 내용이기때문이다.
DB에 대해 기본 개념을 잡기 위해, 멀고 긴 과정을 거쳤다. 사실 무지성으로 Model을 만들어도 되지만, 우리는 확실한 기본이 있어야 나중이 편하다는 것을 항상 후회하며 왔다. 시작이 반이다!
polls라는 app을 만들었다. 해당 app에서 사용할 model(DB schema)들을 정의해주자. mysite/polls/models.py 파일을 아래와 같이 변경해준다.
from django.db import models
# Create your models here.
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)
데이터베이스의 각 필드는 Field 클래스의 인스턴스로서 표현됩니다. CharField 는 문자(character) 필드를 표현하고, DateTimeField 는 날짜와 시간(datetime) 필드를 표현합니다. 이것은 각 필드가 어떤 자료형을 가질 수 있는지를 Django 에게 말해줍니다.
각각의 Field 인스턴스의 이름(question_text 또는 pub_date)은 기계가 읽기 좋은 형식(machine-friendly format)의 데이터베이스 필드 이름입니다. 이 필드명을 Python 코드에서 사용할 수 있으며, 데이터베이스에서는 컬럼명으로 사용할 것입니다.
Field 클래스의 생성자에 선택적인 첫번째 위치 인수를 전달하여 사람이 읽기 좋은(human-readable) 이름을 지정할 수도 있습니다. 이 방법은 Django 의 내부를 설명하는 용도로 종종 사용되는데, 이는 마치 문서가 늘어나는 것 같은 효과를 가집니다. 만약 이 선택적인 첫번째 위치 인수를 사용하지 않으면, Django 는 기계가 읽기 좋은 형식의 이름을 사용합니다. 이 예제에서는, Question.pub_date 에 한해서만 인간이 읽기 좋은 형태의 이름을 정의하겠습니다. 그 외의 다른 필드들은, 기계가 읽기 좋은 형태의 이름이라도 사람이 읽기에는 충분합니다.
몇몇 Field 클래스들은 필수 인수가 필요합니다. 예를 들어, CharField 의 경우 max_length 를 입력해 주어야 합니다. 이것은 데이터베이스 스키마에서만 필요한것이 아닌 값을 검증할때도 쓰이는데, 곧 보게 될것입니다.
또한 Field 는 다양한 선택적 인수들을 가질 수 있습니다. 이 예제에서는, default 로 하여금 votes 의 기본값을 0 으로 설정하였습니다.
마지막으로, ForeignKey 를 사용한 관계설정에 대해 설명하겠습니다. 이 예제에서는 각각의 Choice 가 하나의 Question 에 관계된다는 것을 Django 에게 알려줍니다. Django 는 다-대-일(many-to-one), 다-대-다(many-to-many), 일-대-일(one-to-one) 과 같은 모든 일반 데이터베이스의 관계들를 지원합니다. => ORM은 이 관계설정이 가장 중요 한 부분이자, 일단 잘만 설정해두면 정말 insert에 속시원함을 느끼는 부분이다.
그 외의 field 설정들은 Model에 따라 찾아가면서 진행하면 된다. 자료가 많다!
모델을 만들었으면 해당 모델을 활성화 해줘야 한다. 이 앱을 위한 데이터베이스 스키마 생성(CREATE TABLE 문), Question과 Choice 객체에 접근하기 위한 Python 데이터베이스 접근 API를 생성해야 한다. 가장 먼저 현재 프로젝트에게 polls 앱이 설치되어 있다는 것을 알리는 것이다!
Django의 앱들은 《꼈다뺐다》할 수 있습니다. 앱을 다수의 프로젝트에서 사용할 수 있으며, 앱을 배포할 수도 있습니다. 특정 Django 사이트에 앱들이 묶여있지 않아도 되기 때문입니다. => 장고 앱 모듈화의 강점 / mysite/settings.py 파일에서 installed apps 정보를 아래와 같이 바꿔주자!
INSTALLED_APPS = [
'polls.apps.PollsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
python manage.py makemigrations polls
makemigrations 을 실행시킴으로서, 당신이 모델을 변경시킨 사실과(이 경우에는 새로운 모델을 만들었습니다) 이 변경사항을 migration으로 저장시키고 싶다는 것을 Django에게 알려줍니다.
당신을 위해 migration들을 실행시켜주고, 자동으로 데이터베이스 스키마를 관리해주는 migrate 명령어가 있습니다. 이 명령을 알아보기 전에 migration이 내부적으로 어떤 SQL 문장을 실행하는지 살펴봅시다. sqlmigrate 명령은 migration 이름을 인수로 받아, 실행하는 SQL 문장을 보여줍니다.
python manage.py sqlmigrate polls 0001
2장에서 봤던 그 명령어다. 마이그레이션 실행 전 raw한 쿼리가 어떻게 구성되어있는지 디버깅 차원에서 체크하자.
python manage.py migrate polls
polls APP의 마이그레이트된 파일 기반으로 마이그레이션을 진행해 주자. migrate 명령은 아직 적용되지 않은 마이그레이션을 모두 수집해 이를 실행하며(Django는 django_migrations 테이블을 두어 마이그레이션 적용 여부를 추적합니다) 이 과정을 통해 모델에서의 변경 사항들과 데이터베이스의 스키마의 동기화가 이루어집니다.
마이그레이션은 매우 기능이 강력하여, 마치 프로젝트를 개발할 때처럼 데이터베이스나 테이블에 손대지 않고도 모델의 반복적인 변경을 가능하게 해줍니다. 동작 중인 데이터베이스를 자료 손실 없이 업그레이드 하는 데 최적화 되어 있습니다. 튜토리얼의 나머지 부분에서 이 부분을 조금 더 살펴보겠습니다만, 지금은 모델의 변경을 만드는 세 단계의 지침을 기억하세요.
manage.py 유틸리티로 어떤 일들을 할 수 있는지 django-admin 문서를 읽어보세요.
이제, 대화식 Python 쉘에 뛰어들어 Django API를 자유롭게 가지고 놀아봅시다. Python 쉘을 실행하려면 다음의 명령을 입력합니다. python manage.py shell
>>> from polls.models import Choice, Question # Import the model classes we just wrote.
# No questions are in the system yet.
>>> Question.objects.all()
<QuerySet []>
# Create a new Question.
# Support for time zones is enabled in the default settings file, so
# Django expects a datetime with tzinfo for pub_date. Use timezone.now()
# instead of datetime.datetime.now() and it will do the right thing.
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
# Save the object into the database. You have to call save() explicitly.
>>> q.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>)
# Change values by changing the attributes, then calling save().
>>> q.question_text = "What's up?"
>>> q.save()
# objects.all() displays all the questions in the database.
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>
__init__
, __str__
정도가 있다. import datetime
from django.db import models
from django.utils import timezone
# Create your models here.
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
import datetime은 Python의 표준 모듈인 datetime 모듈을, from django.utils import timezone은 Django의 시간대 관련 유틸리티인 django.utils.timezone을 참조하기 위해 추가한 것입니다. 만약 Python에서 시간대를 조작하는 방법에 대해 익숙하지 않다면, 시간대 지원 문서에서 더 많은 것을 배울 수 있습니다.
이제 다시! python manage.py shell
을 통해 놀아보자!
>>> from polls.models import Choice, Question
# Make sure our __str__() addition worked.
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>
# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>
# 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?>
# Request an ID that doesn't exist, this will raise an exception.
>>> Question.objects.get(id=2)
Traceback (most recent call last):
...
DoesNotExist: Question matching query does not exist.
# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Question.objects.get(id=1).
>>> 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
# Give the Question a couple of Choices. The create call constructs a new
# Choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set to hold the "other side" of a ForeignKey relation
# (e.g. a question's choice) which can be accessed via the API.
>>> 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
# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any question whose pub_date is in this year
# (reusing the 'current_year' variable we created above).
>>> 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()
(1, {'polls.Choice': 1})
(__)
을 이용해서 어떻게 필드를 조회할 수 있는지는 필드 조회를 읽어보세요.데이터베이스 API에 대한 자세한 내용을 보려면, 데이터베이스 API 레퍼런스를 읽어보세요.직원들이나 고객들이 컨텐츠를 수정하기 위한 관리자 사이트를 만드는 것은 딱히 창의적일 필요없는 지루한 작업입니다. 이런 이유로, Django는 모델에 대한 관리용 인터페이스를 모두 자동으로 생성합니다.
Django는 Lawrence Journal-World 신문사의 프로그래머가 처음 개발하였습니다. 이런 태생적인 이유 때문에, 《컨텐츠 게시자》 와 《공개》 사이트의 구분이 명확합니다. 사이트 관리자는 뉴스 기사, 사건, 스포츠 경기 결과 같은 것들을 시스템에 추가합니다. 그렇게 추가된 컨텐츠는 《공개》 사이트에 노출됩니다. Django는 사이트 관리자가 컨텐츠를 편집할 수 있는 통합적인 인터페이스를 생성하는 문제를 해결합니다.
관리자 사이트는 사이트 방문자를 위한 것이 아니라, 사이트 관리자를 위한 것입니다. python manage.py createsuperuser
를 통해서 super user를 만들어 봅시다!
select * from auth_user;
auth_user table에 대한 query로 내가 생성한 admin user를 살펴볼 수 있다. python manage.py runserver
으로 서버를 돌리고 localhost:8000/admin으로 접속하자! 굳이 쿠키 값을 보여주는 이유는, django admin이 '어떻게 admin유저를 인증하고, 관리하는지' 그 기본이 'session'이라는 것을 보여주기 위해서다! 쿠키에 sessionid(key): 3y6...(value)로 들어가 있다.
select * from django_session;
을 통해 확인해 보자!
key에 해당하는 value가 session data로 들어가 있다. 이 data가 실질적으로 해당 유저에 대한 정보를 공개키 암호화 방식으로 담고있다! expire date가 생각보다 후한듯 후하지 않다. 이 개념은 유저 "쿠키 - 새션 - 인가 - 인증" 에 대한 기본 개념이 있으면 쉽게 와 닿을 것이라고 본다.
그리고 지금은 우리가 만든 APP polls에 대한 DB 정보가 하나도 안뜬다. polls/admin.py에 추가하지 않았기 때문이다. 아래와 같이 바꿔주자! 그리고 새로고침하면 hot-reload로 바로 반영되는 모습을 볼 수 있다.
from django.contrib import admin
# Register your models here.
from .models import Question
admin.site.register(Question)
select * from django_admin_log;
역시 어떤 admin user가 어떤 object를 어떻게, 무엇을 했는지 착실하게 기록해준다. 사실 admin page에 대해 이정도 지원이면, django를 사용하지 않을 이유가 무엇이 있겠는가 싶다.. 너무 편하게 잘 만들어 놨다!
모델의 각 필드 유형들은 (DateTimeField, CharField) 적절한 HTML 입력 위젯으로 표현됩니다. 필드의 각 유형들은 Django 관리 사이트에서 어떻게 표현해되어야 할지 알고 있습니다.
각각의 DateTimeField 는 JavaScript 로 작성된 단축 기능과 연결됩니다. 날짜는 《오늘》(《Today》) 버튼과 달력 팝업에서 입력할 수 있으며, 시간은 《지금》(《Now》) 버튼과 일반적으로 입력하는 시간들을 제공하는 편리한 팝업을 통해서도 입력할 수 있습니다.
페이지의 아래 부분에서 다음과 같은 몇가지 옵션을 제공합니다.
만약 《Date published》 의 값이 Tutorial 1 에서 질문을 생성했을때의 시간과 일치하지 않는다면, TIME_ZONE (시간대) 설정을 깜빡 하신것일지도 모릅니다. 이 설정을 바꾸시고 다시 페이지를 불러오시면 올바른 값이 표현됩니다.
《Date published》 의 값을 《오늘》(《Today》) 과 《지금》(《Now》) 단축버튼을 눌러 바꾸십시요. 그런 후, 《저장 및 편집 계속》(《Save and continue editing》) 을 누르십시요. 그런 후, 우측 상단의 《히스토리》(《History》) 버튼을 누르십시요. Django 관리사이트를 통해 누가(username) 언제(timestamp) 무엇을 바꾸었는지 목록을 확인할 수 있습니다.