장고(Django) - 모델(Model)
Django Project 생성
Django는 파이썬으로 만들어진 오픈소스 웹 애플리케이션 프레임워크이다. 쉽고 빠르게 웹사이트를 개발할 수 있도록 돕는 구성요소로 이루어진 웹 프레임워크이다.
Django Project 생성
mysite라는 이름의 새로운 Django 프로젝트를 생성하는 명령어이다. 명령어를 실행하면, mysite의 디렉터리와 함께 여러 py 파일들이 생성된다. 그중 manage.py는 프로젝트를 터미널에서 관리할 수 있도록 명령어를 제공해 준다.
django-admin startproject mysite
Django Server 실행
mysite 디렉터리에 들어가 아래 명령어로 서버를 실행하고, "http://127.0.0.1:8000/"와 같이 로컬 호스트로 접속하면 웹 페이지에 접속되고 아래의 사진과 같은 모습을 확인할 수 있다. "ctrl + c"를 눌러 서버를 종료할 수 있다.
python manage.py runserver
Django App 생성 및 실습
하나의 프로젝트는 여러 개의 App으로 구성된다. App을 생성하고 특정 페이지에 접속하면 "Hello, world."를 확인할 수 있도록 실습을 진행한다.
Django App 생성
mysite 디렉터리 내에서 아래의 명령어를 실행하면, polls라는 이름을 가진 새로운 App을 생성된다.
python manage.py startapp polls
mysite/urls.py
mysite의 urlpatterns에 'polls/'의 경로를 추가하고, 해당 내용은 'polls.urls'에서 처리하도록 코드를 작성한다.
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('polls/', include('polls.urls')), # 추가
path('admin/', admin.site.urls),
]
polls/urls.py
뒤에 아무것도 입력되지 않았을 경우 'views.index'를 페이지에 띄우도록 코드를 작성한다. 즉, polls/views.py에 존재하는 index 메서드를 실행하는 것이다. 해당 디렉터리의 views를 확인할 수 있어야 하기에 import를 진행해 주었다.
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='ind'),
]
polls/views.py
index 메서드에서는 Http 응답으로 "Hello, world."를 전송한다.
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world.")
URL 경로 설정하기
여기서는 'polls/some_url'이라는 페이지를 동작하도록 한다. 경로를 설정해 주기 전에는 아래처럼 Page not Found 오류가 발생한다.
polls/urls.py
위 실습과 똑같이 urls.py에 경로를 추가하고, some_url 메서드를 실행하도록 코드를 작성하였다.
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('some_url', views.some_url)
]
polls/views.py
url에 접속했을 때 실행할 some_url 메서드를 구현하였고, 정상 작동을 확인하였다.
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world.")
def some_url(request):
return HttpResponse("some url 구현")
모델 만들기
App에 존재하는 models.py는 데이터베이스를 저장된 값을 코드로 테이블 별로 읽을 수 있도록 도와준다. 여기서는 간단한 설문에 답변하는 기능을 구현하기 위한 모델을 생성한다.
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=2000)
votes = models.IntegerField(default=0)
mysite/settings.py
INSTALLED_APPS = [
'polls.apps.PollsConfig', # 추가
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
migration 파일 생성
polls의 migration을 만드는 코드이다. 위의 코드에서 작성했던 Question과 Choice 모델의 migration 파일이 생성된 것을 확인할 수 있다.
python manage.py makemigrations polls
migration으로 실행될 SQL 문장 살펴보기
생성한 migration의 테이블을 어떻게 만들 수 있는지에 대한 설명을 확인할 수 있다. Django에서 테이블을 만들 때, 작성했던 부분 이외에 'id' 컬럼을 항상 추가된다. 또한 CREATE INDEX는 Question에 대한 ForeignKey로 설정해 주었기 때문에 추가된 SQL 문장이다. 이는 인덱싱이 되어있는 것이며, 특정 질문에 대한 Choice를 확인할 수 있다.
python manage.py sqlmigrate polls 0001
migration 실행
migration을 진행하려 했던 polls 뿐 아니라 default로 존재하는 admin, autn 등의 테이블도 migration되는 모습을 볼 수 있다. 또한 다시 migration을 진행하려고 하면, "No migrations to apply"라는 문구가 출력되며 진행되지 않는다.
python manage.py migrate
다양한 모델 필드 활용하기
BooleanField, CharField 등 다양한 필드에 대한 내용은 Django 문서에서 참고할 수 있다.
모델 필드 추가(polls/models.py)
Question 모델에 BooleanField와 FloatField를 하나씩 추가하였다. 해당 코드를 추가하고, makemigration 및 migrate 명령을 실행해 준다.
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")
is_something = models.BooleanField(default=False) # 추가
average_score = models.FloatField(default=0.0) # 추가
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=2000)
votes = models.IntegerField(default=0)
db.sqlite3
Django에서 기본으로 제공하는 SQLite 데이터베이스 파일이다. sqlite3을 통해 데이터베이스를 조작할 수 있다.
sqlite3 db.sqlite3
sqlite 쉘 내에서 .table을 입력하면 데이터베이스에 존재하는 테이블을 확인할 수 있다. django_migrations의 내용을 확인하면 migration이 진행된 상황을 살펴볼 수 있고, polls_question의 스키마를 확인하면 해당 테이블의 스키마를 확인할 수 있다.
sqlite > .table
sqlite > SELECT * FROM django_migrations
sqlite > .schema polls_question
migration 롤백
진행했던 migration을 아래의 명령어로 0001 버전으로 다시 되돌릴 수 있다. 아래의 명령을 실행한 후 polls/migrations 디렉터리에 존재하는 0002 migration 파일을 지우고, models.py에 변경했던 내용을 지워주면 된다. 만약 변경했던 내용을 지우지 않으면 오류 메시지를 출력한다.
python manage.py migrate polls 0001
Django Admin - 관리자 계정 생성하고 접속하기
Admin 페이지는 시스템을 관리하는 관리자들이 데이터를 추가/수정하는 페이지를 말한다. Django에서는 만들어진 모델에 대해 쉽게 정보를 추가/삭제하는 기능을 제공한다.
- CRUD : Create(생성), Read(읽기), Update(갱신), Delete(삭제)
mysite/urls.py
mysite의 urls.py에 관리자 페이지에 접속 가능한 url이 적혀있다. 해당 페이지에 접속할 수 있는 계정을 생성해 보자.
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('polls/', include('polls.urls')),
path('admin/', admin.site.urls),
]
superuser(admin) 생성 (1)
python manage.py createsuperuser
superuser(admin) 생성 (2)
admin 계정으로 관리자 페이지("http://127.0.0.1:8000/admin/")에 접속하여 사용자 추가 및 설정을 해주면, 똑같이 관리자 계정으로 사용 가능하다.
Django Admin - 모델 등록하기
이전에 생성한 polls를 관리자 페이지에서 관리할 수 있도록 모델을 등록하려고 한다.
polls/admin.py
해당 App에 존재하는 admin.py 파일에 등록해 주어야 관리자 페이지에서 관리가 가능하다.
from django.contrib import admin
from .models import *
# Register your models here.
admin.site.register(Question)
admin.site.register(Choice)
Questions 요소 추가
Add 버튼을 통해 다음과 같이 질문과 날짜를 입력 및 생성할 수 있다. 그러나 제목이 'Question object (1)'과 같이 어떤 내용인지 확인할 수 없게 저장된다.
제목 설정
작성했던 Question 클래스에 __srt__ 형식을 활용하여 해당 형식으로 제목을 지정할 수 있다. 아래와 같이 원하는 대로 바뀐 것을 확인할 수 있다.
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField("date published")
def __str__(self):
return f'제목 : {self.question_text}, 날짜 : {self.pub_date}'
필드 추가
아래와 같이 총 3개의 필드(Float, Bollean, JSON)를 추가해 주었다. 해당 필드가 추가된 것을 확인할 수 있고, 추가 정보를 입력할 수 있게 되었다.
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField("date published")
score = models.FloatField(default=0)
is_something = models.BooleanField(default=False)
json_field = models.JSONField(default=dict)
def __str__(self):
return f'제목 : {self.question_text}, 날짜 : {self.pub_date}'
장고(Django) - 쉘(Shell)
사용하기
python을 shell에서 사용하듯이 Django도 아래의 명령어를 통해 shell에서 사용 가능하다.
python manage.py shell
Shell에서의 명령
모델.objects.all()을 실행하면 해당 모델에 존재하는 요소들을 확인할 수 있다. import를 진행해 준 후에 진행해야 하며, Choice에는 아무 작업도 하지 않았기에 비어있다.
import polls.models import *
Question.objects.all()
# result
# <QuerySet [<Question: 제목 : 휴가를 어디서 보내고 싶으세요?, 날짜 : 2024-04-08 05:27:50+00:00>,
# <Question: 제목 : 가장 좋아하는 디저트는?, 날짜 : 2024-04-08 05:28:26+00:00>]>
Choice.objects.all()
# result : <QuertSet []>
Choice와 Question의 관계
Choice에는 ForeignKey를 사용하여 해당 모델 내에 선언이 되어있어 Question에 바로 접근 가능하지만, Question은 Choice에 바로 접근할 수 없다. Question은 다양한 Choice를 갖고 있으며, 접근 방법의 차이가 있다.
c = choice.objects.all()[0]
c.question.id
c.question.pub_date
...
q = Question.objects.all()[0]
q.choice_set.all() # 해당 질문의 Choice 요소들을 모두 가져옴
현재 시간 구하기
python의 datetime과의 차이는 맨 뒤의 tzinfo 정보가 더 있다는 것이다. Django에서는 datetime 대신 timezone이 사용된다는 것을 알아두자.
from django.utils import timezone
timezone.now()
# result : datetime.datetime(2024, 4, 8, 6, 16, 11, 155495, tzinfo=datetime.timezone.utc)
레코드 생성하기
Question의 레코드를 생성하는 과정이다. question_text는 "커피 vs 녹차"이며, pub_date는 timezone.now()를 사용해 현재 시간을 입력하였다. 하지만 Shell에서 실행한다고 바로 페이지에 적용되는 것이 아니라 save() 메서드를 사용해 저장할 수 있다.
from polls.models import *
# "커피 vs 녹차" 라는 내용의 새로운 Question 오브젝트를 생성하고 'q1'이라는 변수에 저장하기
q1 = Question(question_text = "커피 vs 녹차")
# tiemzone을 활용하여 새로운 오브젝트 'q1'의 생성시각을 설정하기
from django.utils import timezone
q1.pub_date = timezone.now()
# 새로운 Question 오브젝트 'q1'을 데이터베이스에 저장하기
q1.save()
자동으로 현재 시간 저장
일일이 시간을 입력해 주기엔 번거로움이 있을 것이다. 그래서 Question의 pub_date에 auto_now_add=True 인자를 주고, 생성될 때 자동으로 현재 시간으로 저장이 되도록 한다.
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'제목 : {self.question_text}, 날짜 : {self.pub_date}'
Question에 연결된 Choice 레코드 생성
choice_set.create() 메서드를 통해 바로 생성할 수도 있고, Choice 객체를 생성하여 저장하는 방식으로도 생성할 수 있다.
q3 = Question(question_text = "abc")
q3.save()
# create() 메서드를 활용하여 q3와 연결된 새로운 Choice 오브젝트를 생성하고, choice_text 필드에 값을 넣어주기
q3.choice_set.create(choice_text = "b")
# 새로운 Choice 오브젝트를 생성하고 question 필드에 q3 값을 넣어 연결하기
choice_c = Choice(choice_text='c', question=q3)
# 새로운 Choice 오브젝트를 데이터베이스에 저장하기
choice_c.save()
레코드 수정 및 삭제하기
레코드 수정
# Question 오브젝트 중 가장 마지막으로 만들어진 것을 가져오기
q = Question.objects.last()
# 해당 오브젝트의 question_text에 새로운 내용을 더해 수정하기
q.question_text = q.question_text + '???'
q.save()
레코드 삭제
# Choice 오브젝트 중 가장 마지막으로 만들어진 것을 가져오기
choice = Choice.objects.last()
# 해당 오브젝트에 연결된 Question을 통해서 choice set을 가져오기
choice.queston.choice_set.all()
# 해당 오브젝트를 삭제하기
choice.delete()
모델 필터링(Model Filtering) (1)
get() 메서드
get() 메서드를 사용하여 조건에 부합하는 오브젝트를 가져올 수 있다. __startwith를 사용하면 입력한 인자로 시작하는 오브젝트를 가져온다. 그러나 여러 개의 오브젝트를 가져오려고 하면 에거라 발생한다.
Question.objects.get(id=1)
Question.objects.get(question_text__startswith='휴가를')
Question.objects.get(pub_date__year=2024) # Error
filter() 메서드
filter() 메서드를 사용하여 조건에 부합하는 오브젝트를 가져온다. get()과 똑같은 역할을 하지만, filter는 여러 개의 오브젝트를 가져올 수 있다는 차이점이 있다. 명령어를 실행했을 때, 반환되는 요소가 2개 이상일 때 QuerySet의 형태로 반환된다.
Question.objects.filter(pub_date_year=2024)
Question.objects.filter(pub_date_year=2024).count()
QuerySet의 SQL 쿼리 살펴보기
Django는 쿼리를 직접 작성할 필요 없이 명령어를 입력하면 알아서 처리해 준다. 또한 해당 명령어의 쿼리를 직접 살펴보면서 어떻게 동작하는지 확인할 수 있다.
print(Question.objects.filter(pub_date__year=2024).query)
# result
# SELECT "polls_question"."id", "polls_question"."question_text",
# "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."pub_date"
# BETWEEN 2024-01-01 00:00:00 AND 2024-12-31 23:59:59.999999
모델 필터링(Model Filtering) (2)
다양한 모델 필터링 메서드가 존재하지만, 여기서 모두 다루지는 않는다. 아래 사이트의 Field lookups에서 다양한 메서드를 확인할 수 있다.
contains
특정 문자열이 포함되어 있는 레코드를 가져온다. 아래 코드를 실행하면 '휴가'가 포함된 question_text를 가진 레코드를 가져온다.
Question.objects.filter(question_text__contains='휴가')
gt
입력한 id보다 큰 레코드를 가져온다.
choice=Choice.objects.first()
choice.votes=5
choice.save()
Choice.objects.filter(votes__gt=0)
regex
정규 표현식을 사용하여 조건에 해당하는 오브젝트들을 필터링한다. 아래 코드는 '휴가'로 시작하고, '어디'라는 문자열이 그 이후에 등장하는 오브젝트를 가져온다. 이는 contains와 startswith를 결합하면, 같은 의미로 사용할 수 있다.
Question.objects.filter(question_text__regex=r'^휴가.*어디')
모델 관계 기반 필터링
아래의 코드는 Choice 모델에서 question_text을 기준으로 필터링을 진행한다.
Choice.objects.filter(question__question_text__startswith='휴가')
exclude() 메서드는 조건이 부합하는 오브젝트를 제외한 오브젝트를 가져온다.
Choice.objects.exclude(question__question_text__startswith='휴가')
모델 메서드
Question 모델에 하루 이내에 만들어진 오브젝트인지 판단하는 was_published_recently 메서드를 생성하였다. 또한 해당 메서드의 결괏값에 따라 제목을 다르게 출력하도록 __str__을 수정해 주었다.
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
def __str__(self):
if self.was_published_recently():
new_badge = 'NEW!!!'
else:
new_badge = ''
return f'{new_badge} 제목: {self.question_text}, 날짜: {self.pub_date}'
결론
느낀 점
페이지를 직접 만들고 조작하는 것이 어렵다고만 생각했는데, UI를 통해 생각보다 쉽게 조작할 수 있다는 것을 깨달았다. 그리고 모든 메서드 이름이나 사용법을 외울 수는 없으므로 레퍼런스를 참고하거나 검색하는 습관을 들여야 된다는 것을 다시 한번 알게 되었다.
어려웠던 점
웹 프레임워크를 직접 다뤄보는 것이 처음이었고, 공부량도 적지 않아 시간이 훌쩍 지나가는 게 느껴졌고 지치는 느낌이 들었다. 그만큼 성장하고 있다는 뜻이겠지~
참고 링크
장고걸스 튜토리얼(Django Girls Tutorial)
https://tutorial.djangogirls.org/ko/django/
'[프로그래머스] 데이터 엔지니어링 데브코스 3기 > TIL(Today I Learn)' 카테고리의 다른 글
[TIL - 13일 차] 파이썬 장고 프레임웍을 사용해서 API 서버 만들기 (3) (0) | 2024.04.10 |
---|---|
[TIL - 12일 차] 파이썬 장고 프레임웍을 사용해서 API 서버 만들기 (2) (0) | 2024.04.09 |
[TIL - 10일 차] 데이터 엔지니어링 : 파이썬으로 웹 데이터를 크롤하고 분석하기 (5) (0) | 2024.04.05 |
[TIL - 9일 차] 데이터 엔지니어링 : 파이썬으로 웹 데이터를 크롤하고 분석하기 (4) (0) | 2024.04.04 |
[TIL - 8일 차] 데이터 엔지니어링 : 파이썬으로 웹 데이터를 크롤하고 분석하기 (3) (0) | 2024.04.03 |