[Django] 기본 구조 - 모델

haejun-kim·2020년 7월 6일
0

[Django]

목록 보기
4/20
post-thumbnail

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

MTV

MTV패턴이란?

웹 프로그램 개발 시 일반적으로 언급되는 패턴은 MVC(model - view - controller) 패턴이다. M(model)은 데이터, V(view) 는 사용자 인터페이스, C(controller)는 데이터를 처리하는 로직. 이렇게 3가지 개념으로 묶어서 구현하게 된다.
이를 쟝고에서는 그것이 MTV(model-template-view) 패턴으로 바꿔서 정의하고 있다.
모델(Model): 모델 클래스, 데이터 객체 정의와 그 데이터 (models.py)
템플릿(Template): 사용자에게 보여지는 인터페이스 화면 (templates/*.html)
뷰(View): 데이터를 가져오고 적절하게 가공하여 그 결과를 템플릿에 전달하는 역할 (views.py)

http 요청과 응답의 흐름

1) 클라이언트가 특정 주소로 요청을 보낸다.
2) 쟝고 웹앱에 요청이 들어온다.
3) url conf 모듈을 이용하여 요청의 url을 확인한다.
4) 해당 url에 대한 처리를 담당하는 뷰를 결정한다
5) 뷰는 로직을 실행한다.
6) 필요한 경우 모델을 통해 데이터를 처리한다.
7) 템플릿을 기반으로 최종 html 코드를 생성한다.
8) 생성된 html 코드를 클라이언트로 보낸다.
9) 클라이언트가 받은 html 코드를 렌더링한다.


models.py

만들고자 하는 웹페이지의 특성을 고려하여 모델을 생성해야한다. 나는 서로 질문과 답변을 할 수 있는 게시판을 만들고자 하기때문에 질문과 답변을 할 수 있는 모델이 필요하다. 따라서, Question 과 Answer 클래스를 구현하려한다.

Question

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

기본적으로 위와 같은 내용의 속성들이 필요할 것 같다. 이제 이 내용을 바탕으로 models.py에 작성하면 된다.

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

제목은 최대 200자까지 가능하도록 max_length=200을 설정하였다. 제목처럼 글자수의 길이가 제한된 텍스트는 CharField를 사용해야 한다. 내용(content)처럼 글자수를 제한할 수 없는 텍스트는 위처럼 TextField를 사용해야 한다. 작성일시처럼 날짜와 시간에 관계된 속성은 DateTimeField를 사용해야 한다.

Answer

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

Question과 마찬가지로 질문에 대한 답변이 필요하므로 위와 같은 속성이 필요하다.

class Answer(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    content = models.TextField()
    create_date = models.DateTimeField()

Answer 모델은 질문에 대한 답변에 해당되므로 Question 모델을 속성으로 가져가야 한다. 기존 모델을 속성으로 가져갈 경우 ForeignKey를 이용해야 한다. ForeignKey는 다른 모델과의 연결을 의미한다. on_delete=models.CASCADE의 의미는 이 답변과 연결된 질문(Question)이 삭제될 경우 답변(Answer)도 함께 삭제된다는 의미이다.

장고에서 사용하는 속성(Field)의 타입은 이것 외에도 많다. 다음 URL에서 어떤것들이 있는지 참고하도록 하자.

Django Field Type 알아보기


migrate

migrate 명령을 실행하여 해당 앱들이 필요로 하는 테이블들을 생성해 보도록 하자. (※ 테이블은 데이터베이스에서 데이터를 저장하기 위한 데이터 집합의 모임이다.)

python manage.py migrate

makemigrations

모델이 변경 된 경우 makemigrations -> migrate 명령을 수행해주어 변경사항을 적용시켜주어야한다.

python manage.py makemigrations


위 명령을 실행했을 때 위의 파일이 생성되었으면 된다.
※ makemigrations을 수행하더라도 실제로 테이블이 생성되지는 않는다. 테이블을 실제 생성하는 명령어는 migrate명령을 통해서만 가능하다.

sqlmigrate

sqlmigrate 명령을 통해 migrate 실행시 실제 어떤 쿼리문이 실행되는지는 sqlmigrate 명령을 이용하여 확인할 수 있다.

python manage.py sqlmigrate review 0001

확인하고자 하는 앱의 이름과 makemigrations 명령을 통해 생성된 파일의 일련번호를 적어주면 된다.

BEGIN;
--
-- Create model Question
--
CREATE TABLE "review_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 "review_answer" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "content" text NOT NULL, "create_date" datetime NOT NULL, "question_id" integer NOT NULL REFERENCES "review_question" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "review_answer_question_id_f75c1485" ON "review_answer" ("question_id");
COMMIT;

migrate

이제 migrate명령을 통해 실제 테이블을 생성한다.

python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, review, 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 review.0001_initial... OK
  Applying sessions.0001_initial... OK

정상적으로 수행됐다.


장고 쉘로 모델 사용하기

Question 생성

일반 파이썬 쉘이 아닌 장고 쉘은 다음의 명령어로 실행시킬 수 있다.

python manage.py shell

내가 작성한 모델들을 장고쉘에 import시킨다.

>>> from review.models import Question, Answer
>>> from django.utils import timezone
>>> q = Question(subject="무엇을 리뷰하는 게시판인가요?", content="어떤 게시판 웹사이트인지 궁금합니다.", create_date=timezone.now())
>>> q.save()
>>> q.id
1
>>> q = Question(subject="맛집 리뷰합니다!", content="선릉역에 ooooo식당이 맛있어여", create_date=timezone.now())
>>> q.save()
>>> q.id
2

또는

>>> c = Question.objects.create(subject="제목", content="내용", create_date=timezone.now())
>>> c
<Question: 제목>
>>> Question.objects.all()
<QuerySet [<Question: 이집 맛있나요?>, <Question: 제목>]>

이렇게 create를 사용해서도 생성할 수 있다.

Question 조회

저장한 Question 모델의 데이터는 Question.objects 를 통해서 조회할 수 있다. Question.objects.all()은 Question에 저장된 모든 데이터를 조회하는 함수이다. 결과값으로는 QuerySet객체가 리턴되는데 위처럼 2개의 Question객체를 포함하고 있다. Question object (1), Question object (2)에서 1과 2는 Question 데이터의 id에 해당된다.

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

여기서 models.py에 작성한 class__str__을 작성해주면 id 대신 제목으로 결과를 표시하게 할 수 있다.

>>> Question.objects.all()
<QuerySet [<Question: 무엇을 리뷰하는 게시판인가요?>, <Question: 맛집 리뷰합니다!>]>

model을 수정하고 나서는 quit()명령어로 쉘을 종료해준 뒤 다시 실행시켜야 적용된다. 재시작했을 때 다시 import해줘야함을 잊지말자.
( ※ 모델에 메서드가 추가될 경우에는 makemigrations와 migrate 를 수행할 필요는 없다. migrate가 필요한 경우는 모델의 속성이 변경되었을 때 뿐이다. )

filter

>>> Question.objects.filter(id=1)
<QuerySet [<Question: 무엇을 리뷰하는 게시판인가요?>]>

filter기능을 이용하여 조건에 해당하는 데이터를 모두 리턴해준다. filter는 결과값을 QuerySet으로 리턴해준다.

>>> Question.objects.filter(subject__contains="리뷰")
<QuerySet [<Question: 무엇을 리뷰하는 게시판인가요?>, <Question: 맛집 리뷰합니다!>]>

subject리뷰가 포함된 데이터만 조회하려면 위와 같이 작성하면 된다.

filter의 여러 사용법

get

>>> Question.objects.get(id=1)
<Question: 무엇을 리뷰하는 게시판인가요?>

get은 조건에 해당하는 유일한 결과값만을 리턴해준다. id의 경우 유일한 값이므로 get을 사용한다.
getid와 같은 유일한 값으로 조회할 때만 사용한다.

Question 수정

이제 데이터를 수정해보자.

>>> q = Question.objects.get(id=2)
>>> q
<Question: 맛집 리뷰합니다!>
>>> q.subject = "이집 맛있나요?"
>>> q.save()
>>> q
<Question: 이집 맛있나요?>

Question 삭제

데이터를 삭제해보자.

>>> q = Question.objects.get(id=1)
>>> q
<Question: 무엇을 리뷰하는 게시판인가요?>
>>> q.delete()
(1, {'review.Answer': 0, 'review.Question': 1})
>>> Question.objects.all()
<QuerySet [<Question: 이집 맛있나요?>]>

id=1인 데이터를 삭제해본다. q라는 변수에 넣고, q.delete()메소드를 실행시켜준다. 이 메소드를 실행시키면 추가적인 결과를 리턴해주는데 위의 결과값으로 해석해보면 review.Answer는 0개의 데이터가, review.Question은 1개의 데이터가 삭제되었다는 뜻이다.
삭제 후 다시 objects를 확인해보면 id=2인 값만 리턴되어 성공적으로 삭제된것을 확인할 수 있다.

Answer 작성

>>> q = Question.objects.get(id=2)
>>> q
<Question: 이집 맛있나요?>
>>> a = Answer(question = q, content="저는 나쁘지 않았어요~", create_date=timezone.now())
>>> a.save()
>>> a
<Answer: Answer object (1)>
>>> a.id
1

Answer 조회

아래의 명령을 통해 id값 사용하거나 Answer 객체인 a를 사용하여 답변 조회 가능

>>> a = Answer.objects.get(id=1)
>>> a
<Answer: Answer object (1)>
>>> a.question
<Question: 이집 맛있나요?>
>>> a.content
'저는 나쁘지 않았어요~'

또한, 답변을 이용이 아닌 질문을 이용하여 답변을 찾고싶다면 다음처럼 명령하면 된다.

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

Question과 Answer는 서로 연결되어 있기 때문에 answer_set을 사용하면 질문과 연결된 답변을 가져올 수 있다. Answer모델에서 ForignKey로 Question을 연결해 주었기 때문에 answer_set이 가능해진 것이다.

❗️ 연결모델명_set(예:answer_set)은 상식적으로 생각하면 더 쉽다. 질문 하나에는 여러개의 답변이 가능하므로 q.answer_set이 가능하지만 답변 하나에는 여러개의 질문이 있을 수 없으므로 a.question_set은 불가능하다. 답변 하나에는 질문 하나만 가능하기 때문에 a.question만 가능하다.

0개의 댓글