위 글은 점프 투 장고를 참고해 작성하였습니다.
장고는 모델(Model)을 이용하여 데이터베이스를 처리함
보통 데이터베이스에 데이터를 저장하고 조회하기 위해서 SQL 쿼리문을 이용해야하나, 장고의 모델(Model) 을 사용하면 이런 SQL 쿼리문의 도움없이 데이터를 쉽게 처리 가능
직전 포스팅에서 python manage.py runserver
를 진행했을 때 터미널 결과창에 나오는 구문에 대해 살펴보자.
...
You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
...
이를 해석하면,
18개의 적용되지 않은 migration들이 있다는 문구
admin, auth, contenttypes, sessions는 장고 프로젝트 생성 시 기본으로 설치되는 앱
이 앱들을 적용하려면 python manage.py
를 실행해야 함
장고 프로젝트에 설치된 앱을 확인하려면, config.settings
의 INSTALLED_APP
부분을 보면 된다.
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
...
이 외에도 config/setting.py
안을 보면 데이터베이스에 대한 정보도 정의되어 있다.
...
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
...
이를 해석하면,
데이터베이스 엔진은 django.db.backends.sqlite3
데이터베이스 파일은 BASE_DIR 디렉터리 밑 db.sqlite3
파일에 저장됨
BASE_DIR은 프로젝트 디렉터리로, 나의 경우 \gyu\workspace\projects\mysite
sqlite는 주로 개발용이나 소규모 프로젝트에서 사용되는 가벼운 파일 기반의 데이터베이스. 개발 시에는 sqlite를 사용하여 빠르게 개발하고 실제 운영시스템은 좀 더 규모있는 DB를 사용하는 것이 일반적 !
경고 문구에서 안내하는 것처럼 터미널에서 python manage.py migrate
명령을 실행해 해당 앱이 필요로 하는 데이터베이스 테이블들을 생성한다.
SQLite의 GUI 도구인 DB Browser for SQLite
를 설치하면 데이터베이스의 테이블들을 확인 가능
윈도우 사용자의 경우 아래 64bit용 링크로 다운 받으면 됨 !
DB Browser for SQLite - standard installer for 64-bit Windows
설치 후 실행하면 아래와 같은 GUI 도구를 볼 수 있다.
데이터베이스 열기 -> \gyu\workspace\projects\mysite\db.sqlite3
경로를 누르면
위 사진처럼 database is locked
돼서 열지 못한다는 에러문구가 계속 떴다 ㅠㅠ
이런 저런 방법을 해보다가 결국 구글링으로 못찾아서, 그냥 linux에 있는 db.sqlite3
파일을 window의 c로 복붙하고 경로를 c의 db.sqlite3
로 수정하니까 드디어 열렸다 !!(이유는 모름 ...)
위는 데이터베이스에서 자동으로 생성된 테이블들이다. 장고의 장점 중 하나는 테이블 작업을 위해 쿼리문을 수정하는 대신 ORM(Object Relational Mapping)을 사용해 데이터 작업을 할 수 있다는 것!
ORM(Object Relational Mapping)?
- ORM이란 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 것!
- 객체 지향 프로그래밍은 클래스를 사용, 관계형 데이터베이스는 테이블을 사용
- 객체 모델과 관계형 모델 간 발생하는 불일치는 ORM을 통해 객체간의 관계를 바탕으로 SQL을 자동으로 생성하여 해결 ~
- 전통적으로 데이터베이스를 사용하는 프로그램들은 데이터베이스를 저장하기 위해 쿼리문을 사용해야 하지만, 여러가지 단점이 존재함. (개발자마다 통일성 없는 쿼리문, 쿼리문으로 인한 성능 저하, 데이터베이스를 MYSQL에서 오라클로 변경 시 수정해야하는 어려움 등)
- ORM을 사용하면 데이터베이스의 테이블을 모델화해 사용하므로 위의 SQL 단점이 없어짐. SQL 쿼리가 아닌 직관적인 코드로 데이터를 조작하고 데이터베이스 종류가 변경되더라도 쿼리문이 아닌 모델을 사용하므로 따로 수정할 필요가 없기 때문 !
질문과 답변을 할 수 있는 게시판 서비스인 pybo가 사용할 데이터 모델을 생성하자.
모델을 작성하기에 앞서, 우리가 만들 모델에는 어떤 속성이 필요한지 생각하는 것이 필요하다.
<Question 모델>
속성 | 설명 |
---|---|
subject | 질문의 제목 |
content | 질문의 내용 |
create_date | 질문을 작성한 일시 |
<Answer 모델>
속성 | 설명 |
---|---|
question | 질문 (어떤 질문의 답변인지 알아야하므로 질문 속성이 필요하다) |
content | 답변의 내용 |
create_date | 답변을 작성한 일시 |
위와 같이 생각한 속성들을 바탕으로 질문(Question)과 답변(Answer)에 해당되는 모델을 pybo/models.py
에 정의한다.
from django.db import models
class Question(models.Model):
subject = models.CharField(max_length=200)
content = models.TextField()
create_date = models.DateTimeField()
class Answer(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
content = models.TextField()
create_date = models.DateTimeField()
이를 해석하면,
<Question 모델>
제목은 최대 200자까지 가능하도록 max_length=200
를 설정하였고, 제목처럼 글자수가 제한된 텍스트는 CharField를 사용
내용처럼 글자수 제한이 없는 텍스트는 Textfield를 사용
작성일시처럼 날짜와 시간에 관계된 속성은 DateTimefield 사용
<Answer 모델>
질문은 기존 Question모델을 속성으로 연결하기 위해 ForeignKey
사용
on_delete=models.CASCADE
의 의미는 이 답변과 연결된 질문(Question)이 삭제될 경우 답변(Answer)도 함께 삭제된다는 의미
장고에서 사용하는 속성(Field)의 타입은 아래 링크에서 확인할 수 있음
위에서 작성한 모델을 이용해 테이블을 생성한다. 테이블 생성을 위해 pybo 앱을 config/settings.py
파일의 INSTALLED_APPS 항목에 추가한다.
...
INSTALLED_APPS = [
'pybo.apps.PyboConfig', #새로 추가한 부분
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
...
pybo.apps.PyboConfig
클래스는 pybo/apps.py
파일에 있는 클래스로, 이 파일은 pybo 앱 생성시 자동으로 만들어지는 파일로 따로 구현할 필요 X
INSTALLED_APPS에 추가할 클래스는 pybo/apps.py
를 열어서 확인하면 됨⬇️
from django.apps import AppConfig
class PyboConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'pybo'
테이블 생성을 위해 migrate를 해주어야 한다.
모델을 생성하거나 변경할 경우 항상 python manage.py makemigrations
명령 -> python manage.py migrate
명령 순으로 수행해야 한다.
makemigrations
명령을 수행하면 pybo\migrations\0001_initial.py
라는 파이썬 파일이 자동으로 생성된다.
makemigrations
명령과 migrate
명령은 여러번 실행하더라도 변경사항 없음을 보여주므로 문제는 X
makemigrations
명령: 장고가 테이블 작업을 수행하기 위한 작업 파일(예: 0001_initial.py)을 생성하는 명령어
migrate
명령: 실제로 테이블 작업을 하는 명령어
sqlmigrate
makemigrations
명령과migrate
명령 사이에 실행 가능한sqlmigrate
명령은 실제 어떤 쿼리문이 실행되는지 확인가능한 명령어이다.
sqlmigrate
명령은 단지 실행되는 쿼리만 조회할 뿐, 실제 쿼리를 수행하지는 X(mysite) gyu@DESKTOP-4OUGKIK:~/workspace/projects/mysite$ python manage.py sqlmigrate pybo 0001 BEGIN; -- -- Create model Question -- CREATE TABLE "pybo_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "subject" varchar(200) NOT NULL, "content" text NOT NULL, "create_date" datetime NOT NULL); -- -- Create model Answer -- CREATE TABLE "pybo_answer" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "content" text NOT NULL, "create_date" datetime NOT NULL, "question_id" bigint NOT NULL REFERENCES "pybo_question" ("id") DEFERRABLE INITIALLY DEFERRED); CREATE INDEX "pybo_answer_question_id_e174c39f" ON "pybo_answer" ("question_id"); COMMIT; (mysite) gyu@DESKTOP-4OUGKIK:~/workspace/projects/mysite$
python manage.py sqlmigrate pybo 0001
명령에서 "pybo"는 앱 이름을 의미하고 "0001"은 생성된 작업파일(예: 0001_initial.py)의 일련번호를 의미한다.
장고 shell을 활용해서 모델을 사용할 것이다.
VSCODE 터미널에 python manage.py shell
입력 시
(mysite) gyu@DESKTOP-4OUGKIK:~/workspace/projects/mysite$ python manage.py shell
Python 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
장고 shell 내부에서 작성가능한 창(>>>)이 뜸
장고 shell은 장고에 필요한 환경들이 자동으로 설정되어 실행
장고 shell에서 Question과 answer을 import
>>> from pybo.models import Question, Answer
Question 모델을 이용해 질문 데이터를 생성
>>> from django.utils import timezone
>>> q = Question(subject='pybo가 무엇인가요?', content='pybo에 대해서 알고 싶습니다.', create_date=timezone.now())
>>> q.save()
Question 모델의 create_date 속성은 DateTimeField 타입이므로 timezone.now()로 현재일시를 대입
위처럼 Question 모델의 객체 q를 생성한 후 save함수를 실행하면 질문 데이터가 1건 생성
데이터가 1건 생성되면 반드시 다음처럼 id 값이 생성
>>> q.id
1
두번째 질문 데이터는 아래와 같이 생성
>>> q = Question(subject='장고 모델 질문입니다.', content='id는 자동으로 생성되나요?', create_date=timezone.now())
>>> q.save()
>>> q.id
2
위에서 저장한 데이터를 조회하자.
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>, <Question: Question object (2)>]>
저장한 Question 모델의 데이터는 Question.objects 를 통해서 조회 가능
Question.objects.all()은 모든 Question 데이터를 조회하는 함수
결과값으로는 QuerySet 객체가 리턴되는데 위처럼 Question 객체를 포함
Question object (1), Question object (2) 에서 1과 2는 Question 데이터의 id 값
다음처럼 Question 모델(pybo/models.py)에 str 메서드를 추가하면 데이터 조회시 id 값 대신 제목을 표시할 수 있다.
...
class Question(models.Model):
subject = models.CharField(max_length=200)
content = models.TextField()
create_date = models.DateTimeField()
def __str__(self):
return self.subject
...
이후 ctrl+z
또는 quit()
으로 장고 shell을 종료 및 재시작 한 후 Question.objects.all()
함수를 다시 실행하면,
>>> from pybo.models import Question, Answer
>>> Question.objects.all()
<QuerySet [<Question: pybo가 무엇인가요?>, <Question: 장고 모델 질문입니다.>]>
>>>
1과 2라는 id 값 대신 제목이 표시되는 걸 볼 수 있다.
다음은 filter, get를 사용해 id 값이 1인 Question 데이터만 조회해보자.
>>> Question.objects.filter(id=1)
<QuerySet [<Question: pybo가 무엇인가요?>]>
>>> Question.objects.get(id=1)
<Question: pybo가 무엇인가요?>
여기서 id는 유일한 값이므로 filter 대신 get을 이용하여 조회할 수도 있음
get으로 조회할 경우 QuerySet이 아닌 Question 모델 객체가 리턴됨(filter는 여러건, get은 오직 한건만 리턴하기 때문!)
조건에 맞는 데이터가 없으면 pybo.models.Question.DoesNotExist: Question matching query does not exist.
라는 오류 발생
이번에는 subject에 "장고"라는 문자열이 포함된 데이터만 조회해보자.
>>> Question.objects.filter(subject__contains='장고')
<QuerySet [<Question: 장고 모델 질문입니다.>]>
데이터를 조회하는 filter의 사용법은 장고 공식 문서 참조
앞서 저장한 데이터를 수정해보자.
>>> q = Question.objects.get(id=2)
>>> q
<Question: 장고 모델 질문입니다.>
>>> q.subject = 'Django Model Question'
>>>
>>> q.save()
>>> q
<Question: Django Model Question>
id가 2인 데이터를 조회해 객체 q에 넣어준 후 q의 subject 항목을 "Django Model Question"으로 수정
이때 q.save()
를 통해 save를 수정해 주어야 변경된 데이터가 반영됨 !!
id가 1인 Question을 삭제해보자.
>>> q = Question.objects.get(id=1)
>>> q.delete()
(1, {'pybo.Question': 1})
>>> Question.objects.all()
<QuerySet [<Question: Django Model Question>]>
id가 1인 데이터를 조회해 객체 q로 지정한 후 delete함수를 이용해 해당 데이터 삭제
삭제 시에는 (1, {'pybo.Question': 1})
과 같은 추가 정보가 리턴됨 = Question 모델이 1개 삭제되었다는 뜻
실제로 삭제되었는지 여부는 Question.objects.all()
로 확인 => 모든 Question 데이터를 조회했을때 두번째 질문만 반환되는 것을 볼 수 있음
답변 데이터를 생성해보자.
>>> q = Question.objects.get(id=2)
>>> q
<Question: Django Model Question>
>>> from django.utils import timezone
>>> a = Answer(question=q, content='네 자동으로 생성됩니다.', create_date=timezone.now())
>>> a.save()
>>> a.id
1
답변 데이터를 만들기 위해 질문이 필요하기에, id가 2인 질문을 조회해 question 속성에 넣어주고, 답변 내용을 content 속성에 담아 생성해줌
Answer 모델로 Question 모델과 마찬가지로 유일한 값을 의미하는 id가 자동으로 생성됨
답변을 조회하는 방식은 질문과 마찬가지로 Answer의 PK값인 id를 사용
>>> a = Answer.objects.get(id=1)
>>> a
<Answer: Answer object (1)>
>>> a.question
<Question: Django Model Question>
>>> q.answer_set.all()
<QuerySet [<Answer: Answer object (1)>]>
q.answer_set
을 사용하면 질문에 연결된 답변을 가져오기 가능
Question 모델에는 answer_set 이라는 속성이 없지만 Answer 모델에 Question 모델이 ForignKey로 연결되어 있기 때문에 q.answer_set 과 같은 역방향 접근이 가능 !!
연결모델명_set (예:answer_set)
- q는 Question을, a는 answer을 담은 객체일 경우
- 질문 하나에는 여러개의 답변이 가능하므로
q.answer_set
이 가능하지만 답변 하나에는 여러개의 질문이 있을 수 없으므로a.question_set
은 불가능- 답변 하나에는 질문 하나만 가능하기 때문에
a.question
만 가능