[Django Tutorial] 모델 만들기
1. 모델 설계
데이터를 저장하기 위해서는 가장 먼저 모델을 설계해야 한다. 설계에 앞서 사용자들이 게시판을 어떻게 사용할지 가정하여 나열한다.
- 게시판(게시글 목록)에는 게시글들의 목록이 나열됩니다.
- 게시글들은 제목과 작성자 표시됩니다.
- 게시글을 (클릭해서) 들어가면 게시글 상세화면으로 이동하고 제목, 내용, 작성일이 출력합니다.
- 게시글 상세화면에서 수정하기 버튼을 누르면 수정하는 화면으로 이동합니다.
- 게시글 수정화면에서 저장하기 버튼을 누르면 수정된 내용이 저장되고 게시판으로 이동합니다.
- 게시글 수정화면에서 삭제하기 버튼을 누르면 게시글이 삭제되고 게시판으로 이동합니다.
- 게시판에서 새글쓰기 버튼을 누르면 새로운 게시글을 입력할 수 있는 화면이 출력됩니다.
- 게시글을 작성하고 저장하기 버튼을 누르면 수정된 내용이 저장되고 게시판으로 이동합니다.
게시글이라는 모델을 만들고 제목, 내용, 작성자, 작성일 속성을 정의해 준다. 그러면 이 게시글들을 모두 불러와서 게시글 목록에서 보여주고, 게시글 상세화면과 수정화면에서는 해당하는 하나의 게시글만 불러와서 보여주면 된다.
2. 모델 생성
A. 모델 정의
모델은 Article이라는 이름으로 models.py에 정의합니다.
# bbs.models.py
from django.db import models
class Article(models.Model):
title = models.CharField('제목', max_length=126, null=False)
content = models.TextField('내용', null=False)
author = models.CharField('작성자', max_length=16, null=False)
created_at = models.DateTimeField('작성일', auto_now_add=True)
models 모듈은 장고의 모델과 관련된 모든 기능이 구현된 모듈입니다.
우리가 구현할 모델 클래스는 models.Model 클래스를 상속받습니다. 모델이 데이터베이스와 연결하는 모든 기능들이 구현되어 있습니다.
모델 속성은 장고 ORM에서 제공하는 기본 필드를 구현합니다. 예제에서 다루는 필드 이외에 더 많은 종류의 필드와 필드 옵션들이 있습니다.
- CharField - sql에서의 varchar 자료형으로 변환됩니다. 글자수 제한이 있는 문자열 데이터를 저장합니다
- TextField - sql에서의 text 자료형으로 변환됩니다. 길이수 제한이 없는 문자열 데이터를 저장합니다.
- DateTimeField - sql에서의 datetime 자료형으로 변환됩니다. 날짜와 시간이 utc시간으로 저장됩니다.
B. 데이터베이스 설정
정의된 모델이 실제 데이터베이스에 저장되도록 하기 위한 설정을 하기 위해 프로젝트의 settings.py파일을 수정합니다. 우리는 SQLite3을 이용해 데이터를 저장할 것이므로 설정을 그대로 내버려두면 됩니다.
# settings.py
# 생략
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # 데이터베이스는 sqlite3 사용
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # 루트 디렉토리에 db.sqlite3 파일로 데이터 저장
}
}
# 생략
데이터 베이스가 sqlite3로 설정된 상태이기 때문에 Article 모델을 sqlite3의 테이블로 생성시켜주기만 하면 됩니다.
manage.py에서 제공하는 makemigrations를 사용하면 migrations 디렉토리의 migration파일들을 비교해서 어떻게 변경됐는지 확인 후 새로운 migration 파일을 생성하여 변경된 내용을 기록합니다.
Article 모델을 새로 생성한 모델이기 때문에 비교없이 새로운 migration 파일을 생성합니다.
C. 앱 등록하기
startapp 커맨드는 앱디렉토리와 관련 파일들을 생성할 뿐 앱 관련 설정은 프로젝트에 추가하지 않기 때문에 setting.py에서 직접 앱을 등록시켜주어야 합니다.
# settings.py
# 생략
INSTALLED_APPS = [
'bbs', # 등록할 앱이름
'django.contrib.admin', # 장고 어드민 앱
'django.contrib.auth', # 장고 인증 앱
'django.contrib.contenttypes', # 다양한 종류의 모델데이터를 관리할 수 있게 도와주는 앱.
'django.contrib.sessions', # 클라이언트 정보를 세션에서 관리하도록 하는 프레임워크
'django.contrib.messages', # 컨트롤러에서 발생한 정보를 뷰에서 쉽게 접근하도록 연결하는 프레임워크
'django.contrib.staticfiles', # html, css, js 파일등의 정적파일 들을 관리해주는 프레임워크
]
# 생략
TIME_ZONE = 'Asia/Seoul' # 시간대를 서울로 변경
USE_TZ = False # 기본 시간대(UTC)를 사용하지 않겠다고 변경
# 생략
bbs를 제외한 다른 앱들은 아직 필요하지는 않지만 기본적으로 장고에서 제공하여주는 프레임워크들 입니다.
이제 다시 makemigrations 커맨드로 migration 파일을 생성하여 줍니다.
위 사진처럼 뜬다면 제대로 진행된 것입니다.
이후 bbs/migrations/0001_initial.py 파일이 생성되는데 그 파일 안에 migration 한 간단한 내용이 표시되어 있습니다.
# bbs/migrations/0001_initial.py
# Generated by Django 4.1.5 on 2023-01-07 18:54
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Article',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=126, verbose_name='제목')),
('content', models.TextField(verbose_name='내용')),
('author', models.CharField(max_length=16, verbose_name='작성자')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='작성일')),
],
),
]
bbs/migrations/0001_initial.py에서 operation 리스트를 보면 migrations.CreateModel이 추가되어있는데 여기에 생성될 테이블의 내용이 기록되어 있습니다.
id라는 필드는 장고에서 기본적으로 제공해주는 필드입니다. primary key를 설정하지 않으면 자동으로 id라는 필드를 생성하고 pk로 설정합니다. migration 파일은 가급적 수정 및 삭제를 해서는 안됩니다. migration 파일을 수정으로 변경할 경우 이후 스키마 동기화 작업에 문제가 생길 수 있습니다.
manage.py의 migrate를 사용하여 모든 migration파일을 추적 후 아직 적용하지 않은 migration을 적용시켜 줍니다.
변경된 migration 파일이 없다면 아무런 작업도 하지 않습니다.
py manage.py migrate
사진에서 우리가 만든 모델뿐만 아니라 여러 많은 모델들이 migration 되는데 장고에서 기본적으로 제공해주는 모델들입니다.
3. 장고 쉘에서 테스트
manage.py의 shell 커맨드를 실행하면 파이썬 쉘이 실행되고, 현재 장고가 실행할 때 가져오는 환경 변수들을 동일하게 가져옵니다. 이 쉘을 통해 간단하게 모델이 정상적으로 동작하는지 확인할 수 있습니다.
py manage.py shell
A. 데이터 저장
파이썬 쉘 프롬프가 출력되면 Article 모델을 불러와서 CRUD명령을 실행해볼 수 있습니다.
# 데이터 저장
>>> from bbs.models import Article
>>> article = Article.objects.create(title='How to create a article', content='1. import Article class\n2. invoke \'create\' method of Article\'s manager.', author='jongs', created_at='2023-01-07')
>>> print(article)
Article object (1)
>>> print('{} title: {}, content: {}, author: {} created_at: {}'.format(article.id, article.title, article.content, article.author, article.created_at))
1 title: How to create a article, content: 1. import Article class
2. invoke 'create' method of Article's manager., author: jongs created_at: 2023-01-07 19:14:07.023885
Article 모델을 관리하는 objects 매니저는 Article클래스가 상속받은 models.Model 클래스에 기본내장되어 있습니다. 기본 매니저를 통해 CRUD를 실행할 수 있는데 데이터 생성은 create 메소드를 이용합니다.
create 함수는 positional argument도 지원하지만 인자가 여러 개이거나 한눈에 보기 어려운 경우 keyword argument로 전달하는 것이 보기 좋습니다.
B. 데이터 표시 형식 변경
article은 생성된 데이터 객체입니다. print 함수로 출력해보면 Article object (1)이라고 출력됩니다.
string formatter를 이용해 저장된 데이터를 확인해보니 created_at 값이 입력한 값과 다르게 출력되었습니다. create_at 필드에 auto_now_add를 True로 설정하면 create 메소드가 호출될 때 항상 현재 시간으로 기록이 됩니다. 앞으로 자주 디버깅해야 할 오브젝트인데 매번 string formatter를 이용하는 게 불편합니다. 그래서 모델에 __str__메소드를 오버라이드 해줍니다.
# bbs/models.py
from django.db import models
class Article(models.Model):
title = models.CharField('타이틀', max_length=126, null=False)
content = models.TextField('내용', null=False)
author = models.CharField('작성자', max_length=16, null=False)
created_at = models.DateTimeField('작성일', auto_now_add=True)
def __str__(self):
return '[{}] {}'.format(self.id, self.title)
C. 데이터 표시 형식 변경
field가 변경된 것이 아니고 메소드만 변경된 것이니 migration을 해줄 필요는 없습니다. 다시 shell 커맨드를 실행해서 아까 전 생성한 데이터를 검색해서 created_ad 값을 변경해 줍니다.
>>> from bbs.models import Article
>>> article = Article.objects.get(id=1) # id가 1인 Article 데이터 검색. 없거나 2개 이상일 경우 에러발생
>>> print(article)
[1] How to create a article
>>> article.created_at = '2023-01-07 07:51'
>>> article.save() # 변경된 값 저장. `time formatter('%Y-%m-%d %H:%M')` 형식의 문자열은 DateTimeField에서 자동으로 시간 데이터로 변환해줍니다.
# 변경된 created_at 값을 time fomatter를 이용해 출력해보지만 에러발생
>>> article.created_at.strftime('%Y-%m-%d')
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'str' object has no attribute 'strftime'
# db로 부터 새로 검색 후 time fomatter를 이용해 출력
>>> article.refresh_from_db()
>>> article.created_at.strftime('%Y-%m-%d') # 정상출력
'2023-01-07'
모델의 데이터 검색은 기본 매니저의 get 메소드로 검색할 수 있습니다. sql의 where에 해당하는 내용을 keyword argument로 전달하면 됩니다. 만일 데이터가 아무것도 발견되지 않거나 2개이상이 발견될 경우 에러가 발생합니다. get 메소드 대신 QuerySet라는 이터레이터를 반환하는 filter메소드를 이용하면 레코드가 0개 이거나 2개 이상이더라도 오류가 발생하지 않습니다.
QuerySet 객체는 이터레이터의 모든 기능을 사용할 수도 있습니다. 특별히 첫 번째 데이터와 마지막 데이터의 경우 first, last메소드로도 접근이 가능하고 QuerySet이 검색결과가 0개인 경우 None을 반환합니다.
>>> Article.objects.filter(author='swarf00').first() # author='swarf00'인 첫번째 레코드 검색
>>> Article.objects.filter(author='swarf00').last() # author='swarf00'인 마지막 레코드 검색
>>> Article.first() # Article 테이블에서 조건없이 첫번째 레코드 검색
>>> Article.last() # Article 테이블에서 조건없이 마지막 레코드 검색
4. Admin 사이트
A. 관리자 등록하기
manage.py의 shell 커맨드도 database를 관리하기에 편리하지만 장고에서는 웹 기반의 admin 사이트를 제공합니다. 장고 ORM이 익숙하지 않거나 불편할 경우 admin사이트를 이용하면 편하게 할 수 있다.
admin 사이트는 데이터베이스 내의 각종 데이터를 접근 및 수정이 가능하기 때문에 인증기능이 필수로 구현되어 있습니다. manage.py의 createsuperuser 커맨드를 이용하면 간단하게 superuser 권한의 사용자를 생성할 수 있습니다.
py manage.py createsuperuser
위 사진처럼 진행한 후 superuser create successfully가 뜨면 admin사이트로 username과 password를 이용해서 접속할 수 있습니다.
B. Admin 사이트 접속
장고를 재시작하고 http://127.0.0.1:8000/admin으로 접속합니다.
방금 만들었던 superuser 계정으로 로그인하면 현재 admin사이트에 등록된 앱과 모델들이 나타납니다. 장고 2부터는 admin사이트도 ResponsiveWebDesigm이 지원됩니다.
로그인을 하고 admin사이트의 웰컴페이지가 나타납니다. admin 사이트에 등록된 모든 모델들이 앱별로 보입니다. 아직까지 bbs모델은 admin 사이트에 등록하지 않은 상태여서 보이지 않습니다. 기본 등록된 모델들을 빠르게 보겠습니다.
AUTHENTICATION AND AUTHORIZATION(인증과 권한) 앱에 Group, User 두 모델이 등록되어 있는 것이 확인됩니다. Recent actions라고 가장 최근 작업에 대한 기록도 출력됩니다.
Group에는 아무런 기록도 없고 User에는 아까 생성한 superuser의 정보가 보입니다.
C. Admin 사이트에 등록
Article 모델도 어드민 사이트에 등록하기 위해 bbs/admin.py를 수정합니다.
# bbs/admin.py
from django.contrib import admin
from .models import Article
admin.site.register(Article)
admin.site.register 함수를 이용하면 간단하게 Article 모델을 admin사이트에 등록할 수 있습니다.
장고를 재시작하고 다시 admin 사이트에 접속합니다.
위 사진처럼 BBS앱에 Article 모델이 추가된 걸 확인할 수 있습니다. Article 모델을 클릭하면 레코드 리스트가 보입니다.
C. Admin 사이트 커스텀마이징
하나의 데이터만 생성시켰었기 때문에 하나의 레코드만 보입니다. 데이터가 문자열 형태로 표현되는데 보기가 불편합니다. admin 사이트는 손쉽게 출력화면을 커스텀마이징 할 수 있는 방법을 제공합니다.
# bbs/admin.py
from django.contrib import admin
from .models import Article
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'author', 'date_created') # date_created는 아래 정의한 메소드
list_display_links = ('id', 'title') # 상세페이지로 이동할 수 있는 필드 리스트
def date_created(self, obj): # create_at 필드의 출력형식을 변경해주는 메소드
return obj.created_at.strftime("%Y-%m-%d")
date_created.admin_order_field = 'created_at' # date_created 컬럼 제목을 클릭시 실제 어떤 데이터를 기준으로 정렬할 지 결정
date_created.short_description = '작성일' # date_created 컬럼 제목에 보일 텍스트
이전과는 다르게 ArticleAdmin이란 클래스 생성 후 @admin.register(Article) 데코레이터로 wrapping을 했습니다.
admin.site.register 함수를 사용하는 것보다 번거롭지만 상세한 설정이 가능합니다.
list_display 속성에 필드명을 문자열의 튜플로 저장해주면 admin 사이트의 리스트에서 튜플의 순서대로 테이블의 칼럼을 생성해 줍니다.created_at 필드를 그대로 사용하면 장고의 기본 포맷대로 출력되기 때문에 date_created 함수를 생성해서 출력형식을 변경해 줬습니다.
date_created 함수는 admin 사이트에서 필드처럼 사용됩니다. 하지만 모델에 존재하는 필드가 아니기 때문에 칼럼에 대한 속성을 새로 지정해줘야 합니다.
list_display_links는 레코드의 상세내용을 확인하거나 수정하기 위해 이동할 수 있는 링크를 어떤 필드에 제공할 것인가에 대한 속성입니다. 기본은 첫 번째 필드인 id에만 링크가 걸려있지만 좀 더 클릭하기 편하게 id, title 에 클릭할 수 있도록 설정했습니다.
변경한 대로 작성일 칼럼이 추가되었고 출력형식은 %Y-%m-%d 형식으로 출력됩니다. 또한 제목을 클릭하면 작성일을 기준으로 정렬을 할 수 있습니다. 제목을 클릭하여 상세 페이지로 이동할 수 있습니다.
상세 페이지에는 작성일이 보이지 않는데 DateTimeField의 auto_now_add 속성이 True로 설정이 되면 editable 속성이 자동으로 false가 설정됩니다. editable속성을 True로 변경해주면 디테일 화면에서 확인 및 수정이 가능해집니다.
'BackEnd > Django, DRF' 카테고리의 다른 글
[Django Tutorial] Template 만들기 (0) | 2023.01.10 |
---|---|
[Django Tutorial] 뷰 만들기 (0) | 2023.01.09 |
[Django Tutorial] 가상환경, 프로젝트 만들기 (1) | 2023.01.06 |
[Project] Django allauth를 이용한 소셜 로그인 - Naver (0) | 2023.01.05 |
[Project] Django allauth를 이용한 소셜 로그인 - 기본 설정 (0) | 2023.01.05 |