"Object-Relational-Mapping"은 객체 지향 프로그래밍 언어를 사용하여 호환되지 않는 유형의 시스템 간에(Django - SQL) 데이터를 변환하는 프로그래밍 기술이다. 이것은 프로그래밍 언어에서 사용할 수 있는 '가상 객체 데이터베이스'를 만들어 사용한다.
우리는 DB를 객체(object)로 조작하기 위해 ORM을 사용한다.
from django.db import models
class Article(models.Model): # 장고에서 미리 정의한 Model을 상속받는다.
# ORM을 이용해서 id, title, content 컬럼을 가진 DB 모델을 생성해보자.
# id 컬럼은 자동으로 생성된다.
# CharField - 길이의 제한이 있는 varchar형 필드. (man_length가 필수 인자임)
title = models.CharField(max_length=10)
# TextField - 길이의 제한이 없는 text형 필드.
content = models.TextField()
# DateTimeFIeld - DateTime 기록 필드.
created_at = models.DateTimeField(
auto_now_add=True) # auto_now_add - 첫 저장 시 자동 기록
updated_at = models.DateTimeField(
auto_now=True) # auto_now - 저장될 때마다 자동 기록
makemigrations
migrate
sqlmigrate
showmigrations
makemigrations
python manage.py makemigrations
migrate
python manage.py migrate
sqlmigraate
python manage.py sqlmigrate 앱이름 마이그레이션번호
python manage.py sqlmigrate articles 0001
showmigrations
python manage.py showmigrations
python manage.py makemigrations
python manage.py migrate
SQLite extension 설치 - django 폴더 내 db.sqlite3 파일을 우클릭하면 open database 선택지가 생성됨 - 왼쪽 EXPLOLER에 SQLITE EXPLOLER 탭이 생겨서 그 안에서 DB 테이블 조회 가능!
장고 내장 기본 셸은 python manage.py shell
쿼리 조작을 실습하기 위해서 django third-party 라이브러리 설치 - 셸을 실행할 때 DB API 조작을 위해 필요한 것들을 자동으로 import 해줘서 편리함
django-extensions 설치
pip install ipython
- > cf) 파이썬 셸 업그레이드
pip install django-extensions
→ 장고 third-party shell extension
settings.py 안의 INSTALLED_APPS 에 추가
INSTALLED_APPS = (
...
'django_extensions',
)
python manage.py shell_plus
로 설치된 extension 실행
아래에서 살펴볼 CRUD 코드들은
python manage.py shell_plus
를 통해 실행된 django third-party shell 환경 내에서 실행된 것들이다.
# 1. 빈 게시물 인스턴스 생성
a = Ariticle() # 게시물 인스턴스 생성
a.title = 'title1' # 게시물 title 필드 값 입력
a.content = 'content1' # 게시물 content 값 입력
a.save() # 게시물을 DB에 저장
a
# <Article: Article object (None)>
# save를 하기 전에는 아직 DB에 저장된 상태가 아니며, pk값이 주어지지 않아 None으로 나타난다.
# 2. 생성자를 이용해 필드에 값이 있는 게시물 인스턴스 생성
# django에서 제공하는 Model 클래스에 정의되어 있는 생성자를 이용해 인스턴스 생성하는 방법
b = Article(title='second', content'django!!')
b
# <Article: Article object (None)>
b.save()
b
# <Article: Article object (2)>
# 3. create 이용해서 인스턴스 생성 후 바로 저장
# 클래스.objects.create() -> 인스턴스를 생성하지 않고 저장하는 방법
Article.objects.create(title='third', content='django!!!')
# <Article: Article object (3)>
# 1. all - 모든 데이터 조회
Article.objects.all()
# <QuerySet [<Article: Article object (1)>, <Article: Article object (2)>, <Article: Article object (3)>]>
# 2. first - 가장 첫번째 데이터 조회
a = Article.objects.first() # articles_article 테이블의 첫 번째 레코드 read
a.title
# 'title1'
a.content
# 'content1'
a
# <Article: Article object (1)>
a.pk
# 1
a.id
# 1
# 3. get - unique한 값(pk)을 통해 조회할 떄 사용
b = Article.objects.get(pk=2)
b
# <Article: Article object (2)>
# 해당하는 객체가 없을 경우 에러 발생
# DoesNotExist: Article matching query does not exist.
# 해당하는 객체가 여러 개일 경우 에러 발생
# ultipleObjectsReturned: get() returned more than one Article -- it returned 2!
# 4. filter - 지정된 매개변수와 일치하는 객체를 모두 포함하는 'QuerySet' 반환
# (해당하는 레코드가 단 하나일 경우에도 QuerySet 형태로 반환됨에 주의!)
# 1번 레코드와 같은 content 값을 가지는 새로운 레코드 저장
# DB에 저장과 동시에, 생성된 인스턴스를 반환한다.
Article.objects.create(title='title4', content='django!')
# <Article: Article object (4)>
Article.objects.filter(content='django!')
# <QuerySet [<Article: Article object (1)>, <Article: Article object (4)>]>
# content 필드에 '!'를 포함하는 레코드 조회
Article.objects.filter(content__contains='!')
# <QuerySet [<Article: Article object (1)>, <Article: Article object (2)>, <Article: Article object (3)>, <Article: Article object (4)>]>
`
article = Article.objects.get(pk=1)
article
# <Article: Article object (1)>
article.title
# 'first'
article.title = 'byebye'
article.title
# 'byebye'
a.save() # Update (반드시 save해야 함. 그래야만 DB에 수정사항이 반영됨)
article = Article.objects.get(pk=1)
article.delete() # 삭제
# (1, {'articles.Article': 1})
article
# <Article: Article object (None)> pk가 사라져 None으로 표시되는 것을 확인할 수 있다.
Article.objects.get(pk=1) # 실제 DB에서 pk가 1인 레코드가 사라져 오류가 남
# DoesNotExist: Article matching query does not exist.
# 이후 새로운 레코드를 저장하면?
Article.objects.create(title='test', content='test!!!')
# <Article: Article object (5)>
# 삭제된 pk값(1)을 재사용 하지 않는다. pk값 5로 저장됨
articles 앱 폴더 내의 admin.py 파일에 다음과 같이 작성한다.
from django.contrib import admin
from .models import Article
# 'admin, site에, 등록한다' 로 외우자.
admin.site.register(Article)
콘솔에서 python manage.py createsuperuser
를 통해 admin 계정을 하나 생성한다.
만든 계정이 들어갈 테이블은 어디에?? - 프로젝트에서 처음 migrate 할 때 auth_user 테이블이 생성됨. (settings.py에서 INSTALLED_APP에 기본적으로 'django.contrib.auth' 앱이 등록되어 있기 때문에 가능)
굉장히 편리하게도, 장고는 기본적으로 관리자 페이지 기능을 제공한다.
이에 필요한 'django.contrib.admin' 도 프로젝트 생성 시 자동으로 INSTALLED_APP에 등록되어 있다.
서버를 실행하고 'adimn/' url로 접속하면 다음과 같은 관리자 페이지가 나타난다.
from django.contrib import admin
from .models import Article
class ArticleAdmin(admin.ModelAdmin):
list_display = ('pk', 'title', 'content', 'created_at', 'updated_at',)
# 'admin, site에, 등록한다' 로 외우자.
admin.site.register(Article, ArticleAdmin)
현재까지 배운 Template, View, Model의 개념을 연계하여 활용해보자.
from django.urls import path
from . import views
app_name = 'articles'
urlpatterns = [
path('index/', views.index, name='index'),
]
from django.db import models
# Create your models here.
class Article(models.Model):
title = models.CharField(max_length=10)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
from django.shortcuts import render
from .models import Article
# Create your views here.
def index(request):
# 모든 게시글을 조회
articles = Article.objects.all()
context = {
'articles': articles,
}
return render(request, 'index.html', context)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
{% extends 'base.html' %}
{% block content %}
<h1>INDEX</h1>
{% for article in articles %}
<p>글 번호: {{ article.pk }}</p>
<p>글 제목: {{ article.title }}</p>
<p>글 내용: {{ article.content }}</p>
<hr>
{% endfor%}
{% endblock %}
form에서 다음과 같이 GET 메소드를 사용하고 있을 때 사용자가 submit 버튼을 누르면 새로 간 주소 맨 뒤에 '?title=입력된제목&content=입력된내용'이 표시되는 것을 확인할 수 있다.
GET 방식은 사용자가 입력한 데이터를 url에 넣어서 보낸다.
<form action="{% url 'articles:create' %}" method="GET">
<div class="mb-3">
<label for="title" class="form-label">제목</label>
<input name="title" type="text" class="form-control" id="title" placeholder="제목을 입력해주세요">
</div>
<div class="mb-3">
<label for="content" class="form-label">내용</label>
<textarea name="content" class="form-control" id="content" rows="3" placeholder="내용 입력"></textarea>
</div>
<input type="submit" class="btn btn-primary"></input>
</form>
하지만 form 태그에서 POST 메소드를 사용할 경우 아래와 같은 오류가 발생한다. (인증 실패)
CSRF 검증이 실패되었다고 나온다.
CSRF는 Cross-Site Request Fordery의 약자로, 사이트 간 요청 위조를 뜻한다.
장고에서는 이러한 공격을 막기 위해 믿을 수 있는 사이트에서 보낸 POST 요청만 처리할 수 있도록 클라이언트에게 csrf token이라는 것을 발급한다.
form 태그 안에 DTL을 활용하여 csrf_token을 발급하고 렌더링하면 form 태그 안에 hidden 타입을 가진 input 태그가 하나 생기는데, 이 안에 장고가 발급한 csrf_token이 들어있다. 이는 새로고침할 때마다 다른 값이 나타난다.
장고는 페이지를 요청받을 때마다 페이지에 각각 다른 도장을 찍어 보내는 것과 같다.
즉, 이 도장이 없거나 잘못된 도장을 가지고 있는 요청이 서버로 들어오게 될 경우 403 Forbiden 에러가 뜨면서 접근이 금지되는 것이다.