Programming

; develop a program

Framework/Django

[Django] 장고(Django) 기본요소 - 모델(Model)

Clloud_ 2022. 11. 24. 23:56
반응형

이번 포스팅에서는 장고의 기본 요소 중 하나인 모델(Model)에 대하여 공부를 해보고자 한다.

 


모델(Model)

장고는 모델(Model)을 이용하여 데이터베이스를 처리한다.

보통 데이터베이스에 데이터를 저장하고 조회하기 위해서 SQL 쿼리문을 이용해야 하지만 장고의 모델(Model)을 사용하면 이런 SQL 쿼리 문의 도움 없이 데이터를 쉽게 처리할 수 있다.

 


장고 앱 migrate

python manage.py runserver 실행 시 나오는 문구 중간쯤 보면 18개의 적용되지 않은 migration들이 있다는 문구가 보인다.

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.

admin, auth, contenttypes, sessions 앱들과 관련된 내용이고 이것을 적용하려면 python manage.py migrate 를 실행해야 한다고 나와 있다.

admin, auth, contenttypes, sessions 앱들은 장고 프로젝트 생성 시 기본적으로 설치되는 앱들이다.

 

설치된 앱들은 config/settings.py 파일에서 확인할 수 있다.

 

[파일명: projects/mysite/config/settings.py]

(... 생략 ...)
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
(... 생략 ...)

위에서 언급한 앱들 이외에 messages와 staticfiles 앱들도 추가로 보인다.

이 두 개의 앱은 데이터베이스와 상관이 없는 앱이라서 위의 경고문에 포함되지 않는다.

데이터베이스가 필요한 앱만 migrate가 필요하다.

 

config/settings.py 파일을 잘 살펴보면 설치된 앱뿐만 아니라 사용하는 데이터베이스에 대한 정보도 다음과 같이 정의되어 있다.

 

[파일명: projects/mysite/config/settings.py]

(... 생략 ...)
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
(... 생략 ...)

데이터베이스 엔진은 django.db.backends.sqlite3 라고 정의되어 있다.

그리고 데이터베이스 파일은 BASE_DIR 디렉터리 밑에 db.sqlite3 파일에 저장한다고 정의되어 있다.

BASE_DIR은 프로젝트 디렉터리를 의미한다.

 

SQLite
SQLite는 주로 개발용이나 소규모 프로젝트에서 사용되는 가벼운 파일 기반의 데이터베이스이다.
개발 시에는 SQLite를 사용하여 빠르게 개발하고 실제 운영시스템은 좀 더 규모 있는 DB를 사용하는 것이 일반적인 개발 패턴이다.

 

python manage.py migrate 명령을 실행하여 해당 앱들이 필요로 하는 데이터베이스 테이블들을 생성해야 한다.

(mysite) C:\projects\mysite>python manage.py migrate

다음과 같이 출력된다.

(mysite) c:\projects\mysite>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK

 

migrate를 수행하면 admin, auth, contenttypes, sessions 앱들이 사용하는 테이블들이 생성된다.

어떤 테이블들이 생성되는지 알 필요는 없다.

위의 앱들을 사용하더라도 테이블을 직접 건드릴 일은 없기 때문이다.

 


모델 작성하기

앱이 사용할 데이터 모델을 만들어야 하는데 현재 진행 중인 프로젝트는 질문답변을 할 수 있는 파이썬 게시판 서비스이다.

따라서 앱에는 질문과 답변에 해당하는 데이터 모델이 있어야 한다.

 

모델의 속성

질문(Question) 모델에는 최소한 다음과 같은 속성이 필요하다.

 

[Question 모델]

속성 설명
subject 질문의 제목
content 질문의 내용
create_date 질문 작성 일시

 

답변(Answer) 모델에는 최소한 다음과 같은 속성이 필요하다.

 

[Answer 모델]

속성 설명
question 질문(어떤 질문의 답변인지 알아야함)
content 답변의 내용
create_date 답변 작성 일시

 


models.py

속성을 바탕으로 질문(Question)과 답변(Answer)에 해당되는 모델을 pybo/models.py 파일에 정의한다.

 

[파일명: projects/mysite/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 모델은 제목(subject), 내용(content) 그리고 작성 일시(create_date) 속성을 갖는다.

제목은 최대 200자까지 가능하도록 max_length=200을 설정한다.

제목처럼 글자 수의 길이가 제한된 텍스트는 CharField를 사용한다.

내용(content)처럼 글자 수를 제한할 수 없는 텍스트는 위처럼 TextField를 사용한다.

작성 일시처럼 날짜와 시간에 관계된 속성은 DateTimeField를 사용한다.

 

Answer 모델은 질문에 대한 답변에 해당되므로 Question 모델을 속성으로 갖는다.

기존 모델을 속성으로 연결하려면 ForeignKey를 사용해야 한다. 

ForeignKey는 다른 모델과 연결하기 위해 사용한다. 

on_delete=models.CASCADE의 의미는 이 답변과 연결된 질문(Question)이 삭제될 경우 답변(Answer)도 함께 삭제된다는 의미이다.

 

질문 하나에는 무수히 많은 답변이 등록될 수 있다.

CASCADE 옵션은 질문을 삭제하면 그에 달린 답변들도 모두 함께 삭제한다.

 


테이블 생성하기

작성한 모델을 이용하여 테이블을 생성해야 한다.

테이블 생성을 위해 가장 먼저 앱을 config/settings.py 파일의 INSTALLED_APPS 항목에 추가해야 한다.

 

[파일명: projects/mysite/config/settings.py]

(... 생략 ...)
INSTALLED_APPS = [
    'pybo.apps.PyboConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    (... 생략 ...)
]
(... 생략 ...)

 

INSTALLED_APPS에 추가한 pybo.apps.PyboConfig 클래스는 pybo/apps.py 파일에 있는 클래스이다.

이 파일은 앱 생성 시 자동으로 만들어지는 파일로 따로 만들 필요가 없다.

이미 pybo/apps.py 파일 안에 다음과 같은 클래스가 구현되어 있다.

 

[파일명: projects/mysite/pybo/apps.py]

from django.apps import AppConfig


class PyboConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'pybo'

특별한 경우가 아니라면 PyboConfig 클래스를 수정할 일은 없다.

앱을 INSTALLED_APPS 항목에 추가하지 않으면 이어지는 작업을 할 수 없기 때문에 주의해야 한다.

 


makemigrations

테이블 생성을 위해 다음처럼 migrate 명령을 수행한다.

(mysite) c:\projects\mysite> python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  No migrations to apply.
  Your models in app(s): 'pybo' have changes that are not yet reflected 
  in a migration, and so won't be applied.
  Run 'manage.py makemigrations' to make new migrations, 
  and then re-run 'manage.py migrate' to apply them.

결과 문구를 보면 migrate가 정상적으로 수행되지 않았다.

모델이 신규로 생성되거나 변경되면 makemigrations 명령을 먼저 수행한 후에 migrate 명령을 수행해야 하기 때문이다.

 

따라서 다음처럼 python manage.py makemigrations 명령을 수행해야 한다.

(mysite) c:\projects\mysite> python manage.py makemigrations
Migrations for 'pybo':
  pybo\migrations\0001_initial.py
    - Create model Question
    - Create model Answer

 

makemigrations 명령은 모델을 생성하거나 모델에 변화가 있을 경우에 실행해야 하는 명령이다.

위 명령을 수행하면 pybo\migrations\0001_initial.py 라는 파이썬 파일이 자동으로 생성된다.

 

makemigrations 명령을 수행하더라도 실제로 테이블이 생성되지는 않는다.

makemigrations 명령은 장고가 테이블 작업을 수행하기 위한 작업 파일(예: 0001_initial.py)을 생성하는 명령어다.

실제 테이블 작업은 migrate 명령을 통해서만 가능하다.

 

한번 더 실행하더라도 변경사항 없음을 보여주므로 여러 번 실행 시 뭔가 잘못될까 걱정할 필요는 없다.

 


sqlmigrate

makemigrations로 데이터베이스 작업 파일을 생성하고 migrate 명령을 실행하기 전에 실제 어떤 쿼리문이 실행되는지 sqlmigrate 명령으로 확인해 볼 수 있다.

 

sqlmigrate 명령은 단지 실행되는 쿼리만 조회할 뿐, 실제 쿼리가 수행되지는 않는다.

 

(mysite) c:\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;

 

python manage.py sqlmigrate pybo 0001 명령에서 "pybo"는 앱 이름을 의미하고 "0001"은 생성된 작업파일(예: 0001_initial.py)의 일련번호를 의미한다.

 


migrate

migrate 명령을 수행하여 실제 테이블을 생성한다.

(mysite) c:\projects\mysite> python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, pybo, sessions
Running migrations:
  Applying pybo.0001_initial... OK

오류 없이 잘 수행된다.

"DB Browser for SQLite"을 이용하여 질문과 답변에 해당되는 테이블들이 잘 생성되었는지 확인해 보도록 하자.

 


모델 사용하기

장고 셸을 사용하여 모델을 사용한다.

 

장고 셸 실행

(mysite) c:\projects\mysite> python manage.py shell
Python 3.9.6 (tags/v3.9.6:db3ff76, Jun 28 2021, 15:26:21) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

 

일반적인 파이썬 셸을 실행하는 것이 아니라 python manage.py shell 처럼 장고 셸을 실행해야 한다.

장고 셸은 장고에 필요한 환경들이 자동으로 설정되어 실행된다.

 


Question 생성

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

id는 모델 데이터의 유일한 값으로 프라이머리 키(PK:Primary Key)라고도 한다.

이 id 값은 데이터를 생성할 때마다 1씩 증가된다.

 

2번째 질문 만들기

>>> q = Question(subject='장고 모델 질문입니다.',
content='id는 자동으로 생성되나요?', create_date=timezone.now())
>>> q.save()
>>> q.id
2

 


Question 조회

저장한 데이터를 조회하는 명령

>>> 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 모델에 __str__ 메서드를 추가하면 id 값 대신 제목을 표시할 수 있다.

 

[파일명: projects/mysite/pybo/models.py]

(... 생략 ...)

class Question(models.Model):
    subject = models.CharField(max_length=200)
    content = models.TextField()
    create_date = models.DateTimeField()

    def __str__(self):
        return self.subject

(... 생략 ...)

 

이렇게 수정하고 Question.objects.all( ) 함수를 다시 실행하면 1과 2라는 id 값 대신 이제 제목이 표시되는 것을 확인할 수 있을 것이다.

(mysite) c:\projects\mysite>python manage.py shell
>>> from pybo.models import Question, Answer
>>> Question.objects.all()
<QuerySet [<Question: pybo가 무엇인가요?>, <Question: 장고 모델 질문입니다.>]>
>>>

모델이 변경되었으므로 장고 셸을 재시작해야 변경된 결과를 확인할 수 있다.

장고 셸을 종료하기 위해서는 장고 셸에서 Ctrl+Z 또는 quit( )을 입력하면 된다.

 

모델에 메서드가 추가될 경우에는 makemigrations와 migrate를 수행할 필요가 없다.

makemigrations, migrate 명령이 필요한 경우는 모델의 속성이 변경되었을 때뿐이다.

 

filter를 사용하여 id 값이 1인 Quesiton 데이터를 조회를 하는 경우

>>> Question.objects.filter(id=1)
<QuerySet [<Question: pybo가 무엇인가요?>]>

filter는 조건에 해당되는 데이터를 모두 리턴해 주기 때문에 다건을 의미하는 QuerySet이 리턴된다.

 

id는 유일한 값이므로 filter 대신 get을 이용하여 조회할 수도 있다.

>>> Question.objects.get(id=1)
<Question: pybo가 무엇인가요?>

get으로 조회할 경우 QuerySet이 아닌 Question 모델 객체가 리턴되었다.

filter는 다수의 건을 리턴 하지만 get은 한 건만 리턴하기 때문이다.

 

get으로 조회 시 조건에 맞는 데이터가 없으면 다음과 같은 오류가 발생한다.

>>> Question.objects.get(id=3)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "C:\venvs\mysite\lib\site-packages\django\db\models\manager.py",
  line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "C:\venvs\mysite\lib\site-packages\django\db\models\query.py", line 435, in get
    raise self.model.DoesNotExist(
pybo.models.Question.DoesNotExist: Question matching query does not exist.

get은 반드시 1건의 데이터를 조회할 때 사용한다.

보통 get은 id와 같은 유일한 값으로 조회할 경우에만 사용한다.

 

subject에 "장고"라는 문자열이 포함된 데이터만 조회하는 경우

>>> Question.objects.filter(subject__contains='장고')
<QuerySet [<Question: 장고 모델 질문입니다.>]>

subject__contains='장고'의 의미는 "subject에 '장고'라는 문자열이 포함되어 있는가?"라고 해석할 수 있다. 

subject__contains 에서 언더바(_)가 1개가 아닌 2개이다.

 


Question 수정

저장한 Question 데이터를 수정하는 경우.

 

id 값이 2인 데이터를 조회한다.

>>> q = Question.objects.get(id=2)
>>> q
<Question: 장고 모델 질문입니다.>

 

 

subject 속성을 다음과 같이 수정한다.

>>> q.subject = 'Django Model Question'
>>>

 

수정을 save 해야지만 변경된 데이터가 반영된다.

>>> q.save()
>>> q
<Question: Django Model Question>

 


Question 삭제

id 값이 1인 Question 데이터를 삭제하는 경우

delete를 수행하면 해당 데이터가 삭제된다.

>>> q = Question.objects.get(id=1)
>>> q.delete()
(1, {'pybo.Question': 1})

 

삭제될 때는 위와 같이 추가 정보가 리턴된다. 

(1, {'pybo.Question': 1})은 Question 모델이 1개 삭제되었음을 의미한다.

 

실제로 삭제되었는지 Question.objects.all( ) 로 확인한다.

>>> Question.objects.all()
<QuerySet [<Question: Django Model Question>]>

첫 번째 질문은 삭제되고 두 번째 질문만 조회되는 것을 확인할 수 있다.

 


Answer 작성

답변 데이터를 만들기 위해서는 질문이 필요하므로 id가 2인 질문을 먼저 조회한 후 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()

 

Answer 모델도 Question 모델과 마찬가지로 유일한 값을 의미하는 id가 자동으로 생성된다.

>>> a.id
1

Answer 조회

답변을 조회하는 방법은 질문과 마찬가지로 Answer의 id 값을 사용하면 된다.

>>> a = Answer.objects.get(id=1)
>>> a
<Answer: Answer object (1)>

 

Answer객체인 a를 사용하면 답변에 연결된 질문도 조회할 수 있다.

>>> a.question
<Question: Django Model Question>

Answer 모델 객체인 a를 통해서 질문을 찾는 것은 Answer 모델에 question 속성이 연결되어 있기 때문에 매우 쉽다.

 

질문을 이용하여 답변을 찾는 경우 q.answer_set 을 사용한다.

>>> q.answer_set.all()
<QuerySet [<Answer: Answer object (1)>]>

q.answer_set을 사용하면 질문에 연결된 답변을 가져올 수 있다.

Question 모델에는 answer_set 이라는 속성이 없지만 Answer 모델에 Question 모델이 ForignKey로 연결되어 있기 때문에 q.answer_set 과 같은 역방향 접근이 가능하다.

 

질문 하나에는 여러 개의 답변이 가능하므로 q.answer_set이 가능하지만 답변 하나에는 여러 개의 질문이 있을 수 없으므로 a.question_set은 불가능하다.

답변 하나에는 질문 하나만 가능하기 때문에 a.question만 가능하다.

 


반응형