[Django] 장고의 기본 요소 익히기 - 모델

싱숭생숭어·2023년 4월 28일
0

Django

목록 보기
6/19
post-thumbnail

위 글은 점프 투 장고를 참고해 작성하였습니다.

모델

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

  • 보통 데이터베이스에 데이터를 저장하고 조회하기 위해서 SQL 쿼리문을 이용해야하나, 장고의 모델(Model) 을 사용하면 이런 SQL 쿼리문의 도움없이 데이터를 쉽게 처리 가능


장고 앱 migrate

직전 포스팅에서 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.settingsINSTALLED_APP 부분을 보면 된다.

...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
...
  • 위에서 언급한 앱들 이외에 messages와 staticfiles 앱은, 데이터베이스와 상관이 없는 앱이라서 위의 경고문에 포함되지 않음(데이터베이스가 필요한 앱만 migrate가 필요함)

이 외에도 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 명령을 실행해 해당 앱이 필요로 하는 데이터베이스 테이블들을 생성한다.

  • 위 migrate를 통해 admin, auth, contenttypes, sessions 앱들이 사용하는 테이블이 생성됨. 앱을 사용하더라도 테이블을 직접 건드릴 일은 없으므로 테이블의 생성여부는 알 필요 X

DB Browser for SQLite

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답변을 작성한 일시

models.py

위와 같이 생각한 속성들을 바탕으로 질문(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'

makemigrations, sqlmigrate, migrate

테이블 생성을 위해 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은 장고에 필요한 환경들이 자동으로 설정되어 실행

Question 생성

장고 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
  • id는 모델 데이터의 유일한 값으로 기본 키(Primary Key)라고 함. 이 값은 데이터를 생성할 때마다 1씩 증가함.

두번째 질문 데이터는 아래와 같이 생성

>>> q = Question(subject='장고 모델 질문입니다.', content='id는 자동으로 생성되나요?', create_date=timezone.now())
>>> q.save()
>>> q.id
2
  • 앞서 말했던대로, 두번째로 생성한 질문의 id는 예상대로 2(1에서 1 증가)

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 모델(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 값 대신 제목이 표시되는 걸 볼 수 있다.

  • 모델에 메서드(def~ 함수)가 추가될 경우에는 makekigrations와 migrate를 수행할 필요가 없음. 위 두가지는 모델의 속성(suject, content, create_date)가 변경되었을 때만 !

다음은 filter, get를 사용해 id 값이 1인 Question 데이터만 조회해보자.

>>> Question.objects.filter(id=1)
<QuerySet [<Question: pybo가 무엇인가요?>]>
  • filter는 조건에 해당되는 데이터를 리턴해주는 함수로, 여러건을 의미하는 QuerySet이 리턴됨
>>> 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: 장고 모델 질문입니다.>]>
  • subject__contains='장고': subject에 '장고'라는 문자열이 포함되어 있는지에 대한 조건문(언더바가 두개)

데이터를 조회하는 filter의 사용법은 장고 공식 문서 참조

Question 수정

앞서 저장한 데이터를 수정해보자.

>>> 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를 수정해 주어야 변경된 데이터가 반영됨 !!

Question 삭제

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 데이터를 조회했을때 두번째 질문만 반환되는 것을 볼 수 있음

Answer 작성

답변 데이터를 생성해보자.

>>> 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 조회

답변을 조회하는 방식은 질문과 마찬가지로 Answer의 PK값인 id를 사용

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

>>> a.question
<Question: Django Model Question>
  • id가 1인 답변을 조회해 객체 a로 지정한 후 a에 연결된 질문 찾기 -> Answer 모델 안의 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만 가능

profile
공부합시당

0개의 댓글