2022. 1. 5. 13:37ㆍ강의 정리/Django Models
장고(Django)를 배우기 시작한 입문자이시거나, 또는 배우고 싶은 생각이 있으신 분은 위 출처의 강의를 적극 추천드립니다!!!
애플리케이션의 다양한 데이터 저장방법
- 데이터베이스 : RDBMS, NoSQL 등
- 파일 : 로컬, 외부 정적 스토리지(나스, 아마존 s3, 구글 스토리지)
- 캐시서버 : memcached, redis 등
데이터베이스와 SQL
데이터베이스의 종류
- RDBMS (관계형 데이터베이스 관리 시스템) ex) PostgreSQL, MySQL -> MariaDB에 대해서 알고 싶다면
- https://blog.naver.com/yardyard
- NoSQL ex) MongoDB, Cassandra, CounchDB, Google BigTable
데이터베이스에 쿼리하기 위한 언어 -> SQL
- 쿼리 : 쿼리는 웹 서버에 특정한 정보를 보여달라는 웹 클라이언트 요청(주로 문자열을 기반으로 한 요청이다)에 의한 처리이다. 출처 : http://www.terms.co.kr/query.htm
같은 작업을 하더라도 상대적으로 보다 적은 수의 SQL, 보다 높은 성능의 SQL을 추구해야한다.
직접 SQL을 만들기도 하지만, ORM(object-relational mapping)을 통해서 SQL을 생성 / 실행하기도 한다.
-> 생산성있게 서비스를 만들어갈 수 있다.
중요한 것은 ORM을 사용하더라도, 내가 작성했던 ORM 코드를 통해 어떤 SQL이 어떻게 실행되고, 작동되는지에 대해서 이해하고 최적화할 수 있어야 한다. -> django-debug-toolbar 적극 활용
장고 ORM인 모델은 RDB만을 지원함.
- RDB(Relational Database)란 관계형 데이타 모델에 기초를 둔 데이타베이스입니다. 관계형 데이타 모델이란 데이타를 구성하는데 필요한 방법 중 하나로 모든 데이타를 2차원의 테이블 형태로 표현해줍니다. 관계형 데이타 모델의 개념은 표현 개체의 외부개념 관례를 적용한 것으로, 데이타 간의 상관관계에서 개체간의 관계를 표현한 것이라고 할 수 있습니다.
출처: https://jwprogramming.tistory.com/52 [개발자를 꿈꾸는 프로그래머]
Django의 강점은 Model과 Form이다.
- Model과 Form을 통해 수월한 데이터베이스의 처리를 할 수 있다.
- SQL을 직접 실행할수도있지만, 가능하면 ORM을 써야한다.
Django Model
- 장고 내장 ORM이다.
- 데이터베이스 테이블과 파이썬 클래스를 1 : 1로 매핑해준다.
- 모델 클래스명은 단수형으로 지정 ex) Posts (X) Post (O) #클래스이기에 반드시 첫글자가 대문자 CamelCase로 네이밍
- 매핑되는 모델 클래스는 DB 테이블 필드 내역이 일치해야 한다.
- Model을 만들기 전에, 반드시 서비스에 맞는 데이터베이스 설계를 해야한다.
- Model은 데이터베이스의 영역이므로 관계형 데이터베이스 학습도 필요하다.
Model의 예시 코드
from django.db import models
# Create your models here.
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now_add=True)
모델 활용 순서
Django model을 통해, 데이터베이스 형상을 관리할 경우
- 모델 클래스 작성
- 모델 클래스로부터 마이그레이션 파일 생성 -> makemigrations 명령
- 마이그레이션 파일을 데이터베이스에 적용 -> migrate 명령
- 모델 활용
Django 외부에서, 데이터베이스 형상을 관리할 경우 = 이미 데이터베이스를 구축해 놓은 경우
- 데이터베이스로부터 모델 클래스 소스 생성 -> inspectdb 명령
- 모델 활용
모델명과 DB 테이블명
- DB 테이블명 : default(기본 값) "앱이름_모델명"
- ex) blog앱 : Post 모델 -> "blog_post", Comment 모델 -> "blog_comment"
Bouns
- app을 추가할 때마다 프로젝트의 settings.py에 추가해주어야 함
-
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'inflearn', 'instagram', ]
- app을 추가하고, urls.py를 생성하면, 프로젝트의 urls.py에 추가해주어야한다.
-
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('inflearn/', include('inflearn.urls')), path('instagram/', include('instagram.urls')), ]
- 위 두 작업이 마무리되었다면, model을 만들 차례이다.
-
from django.db import models # Create your models here. class Post(models.Model): message = models.TextField() created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now_add=True)
- model 작업도 마무리 되었다면 makemigrations 과 migrate 를 수행해준다.
- -> sqlmigrate는 실제로 데이터베이스에 들어가는 쿼리를 볼 수 있다.
- dbshell은 sqlite와 연결을 해주어서 sql 명령문을 파이썬에서 실행할 수 있도록 해준다.
기본 지원되는 모델필드 타입 # 굵은 색이 사용할 타입들
- Primary Key : AutoField(숫자가 1씩 자동으로 증가되는 필드 ex) id 값), BigAutoField
- 문자열 : CharField, TextField, SlugField
- 날짜 / 시간 : DateField, TimeField, DateTimeField, DurationField
- 참 / 거짓 : BooleanField, NullBooleanField
- 숫자 : IntegerField, FloatField
- 파일 : FileField, ImageField, BinaryField, FilePathField
- 이메일 : EmailField
- URL : URLField
- UUID : UUIDField
- 아이피 : GenericlPAddressField
- 다양한 커스텀 필드들 : https://django-model-utils.readthedocs.io/en/latest/fields.html
https://docs.djangoproject.com/ko/4.0/ref/models/fields/#field-types
모델필드들은 DB필드타입을 반영
- Varchar 필드타입 -> CharField, SlugField, URLField, EmailField 등
파이썬 데이터타입과 데이터베이스 데이터타입을 매핑
- AutoField -> int
- BinaryField -> bytes
- BooleanField -> bool
- CharField / SlugField / URLField / EmailField -> str
- 같은 모델필드라 할지라도, DB에 따라 다른 타입이 될 수도 있다. #DB에 따라 지원하는 기능이 모두 다르기 때문
자주 쓰는 필드 공통 옵션
- blank : 장고 단에서 validation시에 empty 허용 여부 (디폴트: False) # 빈문자열 허용 여부
-
message = models.TextField(blank=False)
- Validators : Validators(유효성 검사)를 수행할 함수를 다수 지정 # 모델 필드에 따라 고유한 Validators들이 등록 (ex- 이메일만 받기)
- null (DB 옵션) : null 허용 여부 (디폴트: False)
- db_index (DB 옵션) : 인덱스 필드 여부 (디폴트: False)
- default : 디폴트 값 지정, 혹은 값을 리턴해줄 함수 지정 # 사용자에게 디폴트 값을 제공하고자 할 때
- unique (DB 옵션) : 현재 테이블 내에서 유일성 여부 (디폴트: False)
- choices : select 박스 소스로 사용
- verbose_name : 필드 레이블, 미지정시 필드명이 사용
- help_text : 필드 입력 도움말
- allow_unicode : True로 하면 한글 입력을 가능케 해주는 필드
- auto_now, auto_now_add : Ture일시 필드 생성시 자동으로 생성되게 함 (디폴트: False)
설계한 데이터베이스 구조에 따라, 최대한 필드타입을 타이트하게 지정해주는 것이 입력값 오류를 막을 수 있음.
- blank / null 지정은 최소화 해야한다. -> manage.py inspect 명령을 통해 생성된 모델 코드는 초안이다.
- validators들이 다양하게 or 타이트하게 지정됩니다.
- 필요하다면, validators들을 추가로 타이트하게 지정해주어야 한다.
- 프론트엔드에서의 유효성 검사는 사용자 편의를 위해서 수행, 백엔드에서의 유효성 검사는 필수
- 직접 유효성 로직을 만들지 말고. 이미 잘 구성된 Features들을 가져다 쓰자. 장고의 Form / Model을 통해 지원되며, django-rest-framework의 Serializer를 통해서도 지원된다.
validator (유효성 검사)
유효성 검사 만들기 : https://roseline124.github.io/django/2019/04/10/pickmeal-validators.html
https://docs.djangoproject.com/en/4.0/ref/validators/
ORM은 SQL 쿼리를 만들어주는 역할인 뿐, 보다 성능 높은 애플리케이션을 위해서는 사용하려는 데이터베이스에 대한 깊은 이해가 필요하다!!!
쉘 : 파이썬 코드를 입력받고 그에 따른 실행결과를 반환하는 프로그램을 말한다.
>>>python manage.py shell
쉘에서 django Class 불러오기
>>>from instagram.models import Post
Model Manager
- 데이터베이스 질의 인터페이스를 제공
- 디폴트 Manager로서 ModelCls.objects가 제공
# SELECT * FROM app_module;
ModelCls.objects.all()
# SELECT * FROM app_module ORDER BT id DESC LIMIT 10;
ModelCls.objects.all().order_by('-id')[:10]
# INSERT INTO app_model (title) VALUES ("New Title");
ModelCls.objects.create(title="New Title")
>>> Post.objects.all()
<QuerySet [<Post: 나는 1번째 글~!>, <Post: 나는 2번째 글~!>, <Post: 나는 3번째 글~!>]>
#SELECT * FROM Post; 와 같다.
>>> Post.objects.all().order_by('pk')[:2]
<QuerySet [<Post: 나는 1번째 글~!>, <Post: 나는 2번째 글~!>]>
# SELECT * FROM Post ORDER BY pk DESC LIMIT 2; 와 같다.
#무조건 정렬을 지정해주는 것이 좋다.
# for문도 사용 가능하다.
>>> for i in Post.objects.all():
... print(i.pk, i.message, i.created_at)
...
1 나는 1번째 글~! 2021-12-27 08:42:36.006373+00:00
2 나는 2번째 글~! 2021-12-27 08:42:37.397372+00:00
3 나는 3번째 글~! 2021-12-27 08:42:51.021367+00:00
4 madebyShell 2021-12-29 05:17:08.607695+00:00
>>> Post.objects.create(message="madebyShell")
<Post: madebyShell>
# INSERT INTO Post (message) VALUES ("madebyShell"); 과 같다
QuerySet
- SQL을 생성해주는 인터페이스 ex) >>> Post.objects.all()
- 순회 가능한 객체
- Model Manager를 통해, 해당 Model에 대한 QuerySet을 획득 #즉 파이썬 코드를 SQL문법으로 번역해준다.
QuerySet은 Chaining을 지원 #각 함수들이 연결되었다 하여서 Chaining이라 부른다.
Post.obejcts.all().filter(...).exclude(...).filter(...) -> QuerySet
QuerySet은 Lazy한 특성
- QuerySet을 만드는 동안에는 DB접근을 하지 않습니다.
- 실제로 데이터가 필요한 시점에 접근을 합니다.
- So 데이터가 필요한 시점이란??
- QuerySet
- print(QuerySet)
- list(QuerySet)
- for instance in QuerySet: print(instance)
https://tutorial.djangogirls.org/ko/django_orm/
다양한 조회요청 방법 #SELECT SQL 생성
조건을 추가한 Queryset, 획득할 준비
>>> Post.objects.all().filter(message__contains='글')
<QuerySet [<Post: 나는 1번째 글~!>, <Post: 나는 2번째 글~!>, <Post: 나는 3번째 글~!>]>
>>> Post.objects.all().exclude(message__contains='글')
<QuerySet [<Post: madebyShell>]>
# __contains는 해당 내용이 포함된 객체들을 다 반환하고, __exclude는 해당 내용이 포함되지않은 객체들을 다
반환한다.
특정 모델 객체 1개 획득을 시도
>>> Post.objects.all()[1]
<Post: 나는 2번째 글~!>
# 모델 객체 혹은 예외 발생 (IndexError)
>>> Post.objects.all().get(pk='2')
<Post: 나는 2번째 글~!>
.get()은 하나의 row만 불러올 수 있다.
# 모델 객체 혹은 예외 발생 (DoesNotExist , MultipleObjectsReturned)
>>> Post.objects.all().first()
<Post: 나는 1번째 글~!>
>>> Post.objects.all().last()
<Post: madebyShell>
# 모델 객체 혹은 None 반환
#__lte는 less than과 같다.
>>> Post.objects.all().filter(pk__lte=2)
<QuerySet [<Post: 나는 1번째 글~!>, <Post: 나는 2번째 글~!>]>
# and와 or을 이용한 QuerySet 또한 가능하다.
and
from django.db.models import Q
>>> Post.objects.all().exclude(Q(pk__lte=2) & Q(message__contains='글'))
<QuerySet [<Post: 나는 3번째 글~!>, <Post: madebyShell>]>
>>> Post.objects.all().exclude(pk__lte=2, message__contains='글')
<QuerySet [<Post: 나는 3번째 글~!>, <Post: madebyShell>]>
or
>>> Post.objects.all().exclude(Q(pk__lte=2) | Q(message__contains='글'))
<QuerySet [<Post: madebyShell>]>
필드 타입별 다양한 조건 매칭
숫자 / 날짜 / 시간 필드
- 필드명__lt = 조건값 -> 필드명 < 조건값 #less than
- 필드명__lte = 조건값 -> 필드명 <= 조건값 #less than eqaul
- 필드명__gt = 조건값 -> 필드명 > 조건값 #greater than
- 필드명__gte = 조건값 -> 필드명 >= 조건값 #greater than eqaul
문자열 필드
- 필드명__startswith = 조건값 -> 필드명 LIKE "조건값%"
- 필드명__isstartswith = 조건값 -> 필드명 ILIKE "조건값%"
- 필드명__endswith = 조건값 -> 필드명 LIKE "%조건값"
- 필드명__iendswith = 조건값 -> 필드명 ILIKE "%조건값"
- 필드명__contains = 조건값 -> 필드명 LIKE "%조건값%"
- 필드명__icontains = 조건값 -> 필드명 ILIKE "%조건값%"
정렬 조건 추가 #SELECT 쿼리에 "ORDER BY" 추가
- 정렬 조건을 추가하지 않으면 일관된 순서를 보장받을 수 없음
- DB에서 다수 필드에 대한 정렬을 지원
하지만, 가급적 단일 필드로 하는 것이 성능에 이익
시간순 / 역순 정렬이 필요할 경우, id 필드를 활용해볼 수 있음. # id는 만들어진 순서에 따라 정해지기 때문에
- 정렬 조건을 지정하는 2가지 방법
1. (추천) 모델 클래스의 Meta 속성으로 ordering 설정 : list로 지정
.1 : Django-extension 설치 후 settings.py의 INSTALLED_APPS에 등록
#shell-plus라는 명령어를 통해서 쉘에 접근할 수 있게 됌.
python manage.py shell_plus --print-sql
--print -sql을 통해서 sql의 SELECT 형식으로 출력이 된다.
>>> Post.objects.all()
SELECT "instagram_post"."id",
"instagram_post"."message",
"instagram_post"."photo",
"instagram_post"."is_public",
"instagram_post"."created_at",
"instagram_post"."updated_at"
FROM "instagram_post"
LIMIT 21
.2 : models.py에 Class Meta를 만들어준다.
class Meta:
ordering = ['-id']
# ordering에 저장된대로 정렬이 되어서 출력된다.
>>> from instagram.models import Post
>>> Post.objects.all()
SELECT "instagram_post"."id",
"instagram_post"."message",
"instagram_post"."photo",
"instagram_post"."is_public",
"instagram_post"."created_at",
"instagram_post"."updated_at"
FROM "instagram_post"
ORDER BY "instagram_post"."id" DESC
LIMIT 21
Execution time: 0.002002s [Database: default]
<QuerySet [<Post: madebyShell>, <Post: 나는 3번째 글~!>, <Post: 나는 2번째 글~!>, <Post: 나는 1번째 글~!>]>
ORDER BY "instagram_post"."id" DESC
<QuerySet [<Post: madebyShell>, <Post: 나는 3번째 글~!>, <Post: 나는 2번째 글~!>, <Post: 나는 1번째 글~!>]>
. QuerySet 코드에서 직접 order_by를 지정하면 이는 무시된다.
2. 모든 QuerySet에 order_by(...)에 지정
QuerySet은 슬라이싱을 통한 범위 조건을 추가할 수 있다.
str / list / tuple에서의 슬라이싱과 거의 유사하나, 역순 슬라이싱은 지원하지 않음.
cuz 데이터베이스에서 지원하지 않기 때문
객체[start:stop:step]
OFFSET -> start
LIMIT -> stop - start
step은 쿼리에 대응되지 않기에 사용 비추천 # step을 사용하는 순간 list로 반환이 됨.
>>> Post.objects.all()[1:4:2]
SELECT "instagram_post"."id",
"instagram_post"."message",
"instagram_post"."photo",
"instagram_post"."is_public",
"instagram_post"."created_at",
"instagram_post"."updated_at"
FROM "instagram_post"
ORDER BY "instagram_post"."id" DESC
LIMIT 3
OFFSET 1
Execution time: 0.000000s [Database: default]
[<Post: 나는 3번째 글~!>, <Post: 나는 1번째 글~!>]
# Django-extension 공식 문서
https://django-extensions.readthedocs.io/en/latest/
ORM은 어디까지나, SQL 생성을 도와주는 라이브러리이지, ORM이 DB에 대한 모든 것을 알아서 처리해주지는 않는다.그러므로 보다 성능 높은 애플리케이션을 만들고자 한다면, 사용할 DB엔진과 SQL에 대해 보다 높은 이해가 필요하다.
RDBMS에서의 관계 예시 #설계하기 나름임
1 : N 관계 -> models.ForeignKey로 표현
- 1명의 유저(User)가 쓰는 다수의 포스팅(Post) # 다수측(Post)에 ForeignKey
- 1명의 유저(User)가 쓰는 다수의 댓글(Comment) # 다수측(Comment)에 ForeignKey
- 1개의 포스팅(Post)에 다수의 댓글(Comment) # 다수측(Comment)에 ForeignKey
1 : 1 관계 -> models.OneToOneField로 표현
- 1명의 유저(User)는 1개의 프로필(Profile) # 관계를 User측이나 Profile측 아무 곳에 정의 해도된다. 그러나 보통 Profile 측에서 User에 대한 관계를 정의한다.
- Django에서는 "auth"라는 앱에서 User라는 모델을 지원해준다. 물론 커스텀 User 모드를 만들 수는 있다.
M : N 관계 -> models.ManyToManyField로 표현
- 1개의 포스팅(Post)에는 다수의 태그(Tag)
- 1개의 태그(Tag)에는 다수의 포스팅(Post)
ForeignKey
쉽게 설명하면, 테이블 내의 열 중 다른 테이블의 기본키를 참조하는 열을 말한다.
1 : N 관계에서 N측에 명시 ex) Post : Comment, User : Post, User : Comment
# 그러나 이러한 것이 꼭 지켜야하는 것은 아니고, 개발하는 서비스에 맞춰 관계 방식을 변경할 수 있다.
ForeignKey(to, on_delete) #인자 옵션
to : 대상모델 (1:N에서 1측에 있는 모델)
- 클래스를 직접 지정하거나, 클래스명을 문자열로 지정. 자기 참조는 "self" 지정
on_delete : Record 삭제시 (1 : N의 관계에서 1측에 있는 레코드가 삭제될 때 N측에 속한 1측 레코드들을 어떻게 처리할지에 대한 ) Rule
- CASCADE : FK로 참조하는 다른 모델의 Record도 삭제 # 디폴트 값
- PROTECT : ProtectedError를 발생시키며, 삭제 방지
- SET_NULL : null로 대체, 필드에 null=True 옵션 필수
- SET_DEFAULT : 디폴트 값으로 대체. 필드에 디폴트 값 지정 필수
- SET : 대체할 값이나 함수 지정. 함수의 경우 호출하여 리턴값을 사용
- DO_NOTHING : 어떠한 액션 X. DB에 따라 오류가 발생할 수도 있음
class Post(models.Model):
message = models.TextField(blank=False)
photo = models.ImageField(blank=True, upload_to='instagram/post/%Y/%m/%d')
is_public = models.BooleanField(default=False, verbose_name='공개여부')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE) # post_id 라는 필드가 생성이 됨
message = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now_add=True)
# 1 : N의 관계에서 실제 N측에는 어떤 값이 저장되냐면 관계를 정의할 때는 실제 각각의 Model에는 pk(id)값이 있음. 1 : N의 관계의 예를 들어서 Post : Comment의 관계에서는 1에 속해있는 Post의 pk를 N에 속해있는 Comment에 저장한다. 그 때 저장할 필드명을 임의로 정할 수 있는데 "지정한 변수_id"라는 값으로 저장된다.
ex)post = models.ForeignKey(Post, on_delete=models.CASCADE) python manage.py sqlmigrate instagram 0004 CREATE INDEX "instagram_comment_post_id_41735a7d" ON "instagram_comment" ("post_id");
# 다른 앱의 model 또한 관계를 표현할 수 있다.
post = models.ForeignKey('instagram/Post', on_delete=models.CASCADE)
올바른 User 모델 지정
# models.py에서 User 모델을 지정할 수 있게된다.
form django.conf import settings
author = models.ForeignKey(settings.AUTH_USER_MODEL , on_delete=models.CASCADE)
장고 기본의 django.contrib.auth 앱에서는 User 모델을 기본 제공해주고 있습니다. 이 User 모델이 장고 모든 인증의 중심이 됩니다. (세션 인증, JWT 인증 등)
django.contrib.auth 앱에서 제공되는 User 모델이라서 미리 만들어진 모델인데요. 프로젝트에 따라 User 모델을 우리가 만든 장고앱의 User 모델로 변경하고 싶을 수 있습니다. 그때 myapp 장고앱의 CustomUser 모델을 사용코자 한다면, settings 에서 AUTH_USER_MODEL 값으로 "myapp.CustomUser" 문자열을 지정하면 됩니다.
이제 프로젝트에서 사용하는 유저 모델이 다른 앱의 모델로 변경될 수 있음을 아셨을 것입니다. 그런데 이 유저 모델에 대한 외래키를 지정코자 한다면, 유저 모델이 바뀔 수 있기에, 하드 코딩으로 해당 모델 클래스를 임포트해서 지정할 수는 없습니다. 유연하지 못한 대처인 것이죠.
외래키는 모델 클래스를 지정할 수도 있지만, "앱이름.모델명" 과 같은 문자열로도 지정할 수 있습니다. 그래서 settings.AUTH_USER_MODEL 을 지정해줄 수 있습니다. 이렇게 지정하시면 현재 어떤 앱의 모델을 유저 모델로 사용하는 지 신경쓰지 않고, 외래키를 지정할 수 있게 되는 것입니다.
FK(외래키)에서의 reverse_name # reverse 접근 시의 속성명 : 디폴트 -> "모델명소문자_set"
related_name="instagram_author_set")
# reverse란 1 : N의 관계에서 1이 N에 접근하는 것이다.
디폴트는 1 : N의 관계에서 1측에서 사용하는 것이다.
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
class Post(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
message = models.TextField()
>>> comment.post
>>> post.comment_set.all() <=> Comment.objects.filter(post=post)
중요 !!!
reverse_name 디폴트 명은 앱이름 고려 X, 모델명만 고려하기에 충돌이 일어날 수 있음
ex)
blog 앱 Post모델, author = FK(User)
shop앱 Post모델, author = FK(User)
이름이 충돌이 난다면, makemigrations 명령이 실패한다.
충돌을 피하는 방법 # 외래키를 지정할 때 커스텀 지정을 해야한다.
1. 어느 한 쪽의 FK에 대해, reverse_name을 포기 -> related_name = '+' #
2. 어느 한 쪽의 (혹은 모두) FK의 reverse_name을 변경
-1 : FK(User, ..., related_name="blog_post_set")
-2 : FK(User, ..., related_name="shop_post_set")
ForeignKey.limit_choices_to 옵션
Form을 통한 Choice 위젯에서 선택항목 제한 가능. # 즉 선택 할 모델들의 조건을 달 수 있음. ex) 사진이 있는 글만 선택 가능etc...
- dict / Q 객체를 통한 지정 : 일괄 지정
- dict / Q 객체를 리턴하는 함수 지정 : 매번 다른 조건 지정가능
post = models.ForeignKey(Post, on_delete=models.CASCADE, limit_choices_to={'is_public': True}) # post_id 필드가 생성
ManyToManyField에서도 지원
OneToOneField
- 1 : 1 관계에서 어느 쪽이라도 가능 #USER : PROFILE
# 그러나 USER를 다루는 auth앱은 변경할 수 없기에 PROFILE에 정의를 한다.
ForeignKey(unique=True)와 유사하지만, reverse 차이 #unique=True는 테이블 내에 유일 값을 의미함
- User : Profile을 FK(외래키)로 지정한다면 객체를 접근할 때-> profile.user_set.first() -> user
- User : Profile을 020(OneToOneField)로 지정한다면 객체를 접근할 때 -> profile.user -> user
OneToOneField(to, on_delete) #인자 옵션
to : 대상모델 (1 : 1에서 USER이 있는 모델)
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
# User는 auth에 자동 설정 되있기 때문에 User와 Profile을 다룰 때 위 코드는 거의 고정이다.
on_delete : Record 삭제시 (1 : 1의 관계에서 USER에 있는 레코드가 삭제될 때 PROFILE측에 속한 USER측 레코드들을 어떻게 처리할지에 대한 ) Rule
- CASCADE : FK로 참조하는 다른 모델의 Record도 삭제 # 디폴트 값
- PROTECT : ProtectedError를 발생시키며, 삭제 방지
- SET_NULL : null로 대체, 필드에 null=True 옵션 필수
- SET_DEFAULT : 디폴트 값으로 대체. 필드에 디폴트 값 지정 필수
- SET : 대체할 값이나 함수 지정. 함수의 경우 호출하여 리턴값을 사용
- DO_NOTHING : 어떠한 액션 X. DB에 따라 오류가 발생할 수도 있음
#accounts/models.py
from django.conf import settings
from django.db import models
# Create your models here.
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
address = models.CharField(max_length=100)
zipcode = models.CharField(max_length=6) # validators=[])를 통해서 숫자만 쓰일 수 있게 유효성 검사를 할 수 있다.
#accounts/admin.py
from django.contrib import admin
from .models import Profile
# Register your models here.
@admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
pass
# User와 Profile은 OneToOneField이기에 한 유저는 한개의 Profile만 생성할 수 있다!!!!
OneToOne에서의 즉 related_name
# reverse 접근 시의 속성명 : 디폴트 -> "모델명소문자"
related_name="author")
# 아래 코드는 모델의 데이터에 접근하기 위한 기본 코드이다.
from django.contrib.auth import get_user_model
# 유저 모델을 변수에 넘겨줌
User = get_user_model()
# 유저의 모든 객체를 변수에 넘겨줌
user = User.objects.first()
# 프로필이 언제 만들어지냐면 유저가 만들어지면 Django의 시그널(일종의 이벤트 핸들러같은 개념인데 User 모델이 Save, 생성되면 호출되는 함수를 지정할 수 있다.) 에서 프로필이 자동 생성되도록 로직으로 유저와 프로필이 항상 존재함을 나타내는 방법으로 많이 구현한다.
https://dgkim5360.tistory.com/entry/django-signal-example
↑관계형 데이터베이스 (mariaDB)에 대해서 자세히 알고싶다면???
https://blog.naver.com/yardyard/222555167886
-MYSQL에서 DATABASE를 표현하는 다른 말로, 스키마라는 표현을 사용함.
Migrations란?
모델의 변경내역을 "데이터베이스 스키마"로 반영시키는 효율적인 방법을 제공
# Migrations을 통해서 우리는 모델만 개발하고 데이터베이스에 자동으로 반영시킬 수 있다.
관련 명령
- 마이그레이션 파일 생성 # 순서 1
>>> python manage.py makemigrations <앱이름>
- 지정 데이터베이스에 마이그레이션 적용 # 순서 3
>>> python manage.py migrate <앱이름>
- 마이그레이션 적용 현황 출력 # 중간 중간 틈틈히
>>> python manage.py showmigrations <앱이름>
출력된 코드
C:\Users\yardy\Desktop\DjangoPractice\dongbaek>python manage.py showmigrations
accounts
[X] 0001_initial
[X] 0002_alter_profile_user
admin
[X] 0001_initial
[X] 0002_logentry_remove_auto_add
[X] 0003_logentry_add_action_flag_choices
auth
[X] 0001_initial
[X] 0002_alter_permission_name_max_length
[X] 0003_alter_user_email_max_length
[X] 0004_alter_user_username_opts
[X] 0005_alter_user_last_login_null
[X] 0006_require_contenttypes_0002
[X] 0007_alter_validators_add_error_messages
[X] 0008_alter_user_username_max_length
[X] 0009_alter_user_last_name_max_length
[X] 0010_alter_group_name_max_length
[X] 0011_update_proxy_permissions
[X] 0012_alter_user_first_name_max_length
church
(no migrations)
contenttypes
[X] 0001_initial
[X] 0002_remove_content_type_name
inflearn
[X] 0001_initial
instagram
[X] 0001_initial
[X] 0002_post_is_public
[X] 0003_post_photo
[X] 0004_auto_20211231_1531
[X] 0005_auto_20211231_1722
[X] 0006_auto_20220103_1516
sessions
[X] 0001_initial
- 지정 마이그레이션의 SQL 내역 출력 # 순서 2
>>> python manage.py sqlmigrate <앱이름> <마이그레이션-이름>
- 장고의 전체 기본 앱들에 대해서 마이그레이션 수행
>>>python manage.py migrate
Migrations 파일
데이터베이스에 어떤 변화를 가하는 Operation들을 나열
- 기본 Operation : 테이블 생성 / 삭제, 필드 추가 / 삭제 등
- 커스텀 Operation : 파이썬 / SQL Operation # ex)데이터 마이그레이션 등 Migration에 커스텀 과정을 넣을 수 있다.
대개 모델로부터 자동 생성 -> makemigrations 명령
- 모델 참조없이 빈 마이그레이션 파일 만들어서 직접 채워넣기도 함.
주의) 같은 Migration 파일이라 할지라도, DB종류에 따라 다른 SQL이 생성됩니다.
- 모든 데이터베이스 엔진들이 같은 기능을 제공하지는 않는다.
- ex) SQLite DB에서는 기존 테이블에 컬럼 추가가 지원되지 않음.
ex) dongbaek/accounts/migrations/0001_initial.py
# Generated by Django 3.2.10 on 2022-01-03 04:50
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Profile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('address', models.CharField(max_length=100)),
('zipcode', models.CharField(max_length=6)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
Migrations 파일 생성 및 적용 과정
0. 앱/models.py
- 모델 변경 # makemigrations 명령
1. 마이그레이션 파일들 생성
- 필히, 생성된 마이그레이션 파일 내역 확인 + sqlmigrate 명령으로 SQL 확인
- 즉 나의 의도에 맞게 생성이 되었는지 확인
2. 데이터베이스 서버
- migrate 명령으로 DB에 적용
언제 makemigrations를 하는 가?
모델 필드 관련된 어떠한 변경이라도 발생 시에 마이그레이션 파일 생성
- 실제로 DB Shceme에 가해지는 변화가 없더라도 수행
마이그레이션 파일은 모델의 변경내역을 누적하는 역할
- 적용된 마이그레이션 파일은 절대 삭제하면 안됨
- 마이그레이션 파일이 너무 많아질 경우, squashmigrations 명령으로 다수의 마이그레이션 파일을 통합할 수 있다.
마이그레이션 Migrate ( 정 / 역 방향 )
정방향 (1->2->3->4->5)
역방향(5->4->3->2->1)
python manage.py migrate <앱이름>
- 미적용 <마이그레이션-파일>부터 <최근-마이그레이션-파일>까지 정방향으로 순차적으로 수행
python manage.py migrate <앱이름> <마이그레이션-이름>
- 지정된 <마이그레이션-이름>이 현재 적용된 마이그레이션보다
- 이후라면, 정방향으로 순차적으로 지정된 마이그레이션까지 정방향 수행
- 이전이라면, 역방향으로 순차적으로 지정 마이그레이션 이전까지 역방향 수행
즉 현재의 migration 위치를 파악하는게 중요~!!!! # showmigrations 명령
ex) 1~7번까지의 migration 파일이 있을 때 현재가 5번 파일인데 3번 파일을 지정하면 5번 취소, 4번 취소, 하여 역방향으로 3번으로 가게 됨
코드로 설명하면
>>> python manage.py migrate <app> 0003_a.py
0007_a.py
0006_a.py
0005_a.py (현재 위치) ↓ 지정된 위치로 역방향 이동
0004_a.py ↓
0003_a.py (지정된 위치)
0002_a.py
0001_a.py
ex) 정방향 예시로 아래 코드를 예로 들면
>>> python manage.py migrate <app> 0005_a.py
0007_a.py
0006_a.py
0005_a.py (지정된 위치) 지정된 위치로 정방향 이동
0004_a.py ↑
0003_a.py (현재 위치) ↑
0002_a.py
0001_a.py
마이그레이션 이름 지정
전체 이름(파일명)을 지정하지 않더라도, 1개를 판별할 수 있는 일부만 지정해도 OK
migrations/000_1_initial.py
migrations/000_2_created_field.py
migrations/000_2_update_field.py
Shell
>>> python manage.py migrate blog 000 # FAIL 3개 파일에 매칭
>>> python manage.py migrate blog 100 # FAIL 매칭되는 파일이 없음
>>> python manage.py migrate blog 0001 # OK
>>> python manage.py migrate blog 0002 # FAIL 2개 파일에 매칭
>>> python manage.py migrate blog 0002_c # OK
>>> python manage.py migrate blog 0002_create # OK
>>> python manage.py migrate blog 0002_update # OK
>>> python manage.py migrate blog zero # 앱의 모든 마이그레이션을 rollback
새로운 필드가 필수필드라면?
필수필드 여부 : blank / null 옵션이 모두 False 일 때 (디폴트)
makemigrations 명령을 수행할 때, 기존 Record들에 어떤 값을 채워넣을 지 묻습니다.
- 지금 그 값을 입력하겠다. # 반드시 필드 타입에 맞게 값을 채워넣어야 한다.
- 명령 수행을 중단
C:\Users\yardy\Desktop\DjangoPractice\dongbaek>python manage.py makemigrations inflearn
You are trying to add a non-nullable field 'author' to post without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>> 1
Migrations for 'inflearn':
inflearn\migrations\0002_post_author.py
- Add field author to post
# dongbaek/inflearn/models.py
from django.conf import settings
from django.db import models
from django.conf import settings
from django.db.models.deletion import CASCADE
# Create your models here.
class Post(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=CASCADE, related_name="inflearn_author_set")
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now_add=True)
협업 Tip
절대 하지 말아야 할 일
- 팀원 각자가 마이그레이션 파일을 생성 -> 충돌 발생
추천 ) 마이그레이션 파일 생성은 1명이 전담해서 생성
- 생성한 마이그레이션 파일을 버전관리에 넣고, 다른 팀원들은 이를 받아서 migrate만 수행
개발 시에 "서버에 아직 반영하지 않은" 마이그레이션을 다수 생성했었다면?
- 이를 그래도 서버에 반영 ( migrate ) 하지말고,
- 하나의 마이그레이션으로 합쳐서 적용하기를 권장
방법 1) 서버로의 미적용 마이그레이션들을 모두 롤백 -> 롤백된 마이그레이션들을 모두 제거 -> 새로이 마이그레이션 파일 생성
방법 2) 미적용 마이그레이션들을 하나로 합치기 -> squashmigrations 명령
'강의 정리 > Django Models' 카테고리의 다른 글
마이그레이션을 통한 데이터베이스 스키마 관리 (0) | 2022.01.04 |
---|---|
관계를 표현하는 모델 필드 #OneToOneField (0) | 2022.01.03 |
관계를 표현하는 모델 필드 #ForeignKey (0) | 2021.12.31 |
Django-debug-toolbar를 통한 SQL 디버깅 (0) | 2021.12.30 |
동백 // QuerySet에 정렬 조건 추가 (0) | 2021.12.30 |