Create, Read, Update, Delete의 약자.
어떠한 대상을 저장하고, 저장된 대상을 가공하는 행동.
CRUD가 작동되려면 데이터베이스를 다룰 줄 알아야 함.
DBMS : 데이터베이스를 관리하는 프로그램.
오라클, MySQL 등이 있다.
RDBMS(관계형 데이터베이스)를 사용해볼 것임.
데이터를 표(테이블)처럼 활용한다는 것이 특징.
데이터베이스에 접근하고 조작할 수 있는 언어가 SQL이다.
Primary Key(기본키)
Null값일 수 없으며, 다른 값과 중복되어도 안된다.
가장 중요한 키.
Foreign Key(외래키)
다른 테이블의 데이터를 갖다 쓸 수 있게 해줌.
학생들의 수강과목은 개설과목에 있는 항목들을 사용하므로 개설과목들을 참조하는 외래키라고 한다.
ORM : Object Relational Mapping
객체를 이용한 Table 매핑
models.py 안에 class로 table을 표현.
이후에는
python3 manage.py migrate
python3 manage.py makemigrations
데이터베이스 초기화를 원하거나 처음 Migrate할때는 첫번째코드.
두번째는 변경사항을 담은 파일을 만들 때 사용한다.
자 그럼 CRUD기능을 실습하기 위해 지금부터 블로그 만들기 프로젝트를 시작한다.
늘상 하던대로 가상환경을 만들고, 가상환경을 실행하고, 장고를 설치하고, Blogproject라는 프로젝트를 만들고, Blogapp이라는 어플리케이션을 만들고, 그걸 Settings.py에 등록해주고, Blogapp에 templates 폴더를 만들어 그 안에 index.html파일을 만들어서 대충 아무 말이나 써넣고, Urls.py에서 라우팅해주고(home으로 이름도 지정했다), views.py에서 home이라는 함수를 만들었다.
이제부터가 다른 점인데, 우선적으로 Migration을 시켜줬다.
python3 manage.py migrate
다음으로, blogapp에 있는 models.py에 들어갔다.
그러고는 블로그 객체를 만들기 시작했다.
참고로 Blog(models.Model)의 의미는 models에 있는 Model을 상속해서 만들어진다는 의미이다. 참고로 상속이란 이미 구현되어있는 장고의 모델 기능을 사용한다는 뜻이다.
다음으로 title = models.CharField(max_length=200)은 최대 200자까지를 지원한다는 뜻.
나머지도 비슷한 의미이다.
class Blog(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
date = models.DateTimeField(auto_now_add=True)
다음으로 이걸 admin사이트에서 확인하려면 다음의 과정이 필요하다.
1) admin 계정생성
python3 manage.py createsuperuser
2) admin.py에 등록.
from django.contrib import admin
from .models import Blog
admin.site.register(Blog)
3) admin에서 목차에 뜨는 텍스트가 제목이도록 설정하기.
def __str__(self):
return self.title
그리고 runserver 해서 admin 사이트로 들어가면 이렇게 뜹니다.
home.html을 만든다. 앞으로 3가지 방식으로 사용자에게 입력을 받을 것이므로 a태그를 3개 만들면 된다.
<h1>my simple blog</h1>
<a href="{% url 'new' %}">HTML Form을 이용하여 새 글 작성</a><br/>
<a href="{% url 'formcreate' %}">Django Form을 이용하여 새 글 작성</a><br/>
다음은 본격적으로 사용자 입력을 받아볼 것인데, 다시 말해 블로그 글을 업로드하는 기능을 구현하기 시작한단 의미이다.
Django에서 사용자 입력을 받는 방법은 3가지이다.
1) HTML Form 이용하기
2) Django Form 이용하기
3) Django modelForm 이용하기
일단 첫 번째 방법은 다음과 같다.
new는 new.html을 화면에 띄워주는 용도니까 완성한 함수이고
create는 복잡한 기능을 가진 함수라 1단계에선 아직 미완
def new(request) :
return render(request, 'new.html')
def create(request) :
return
name 지정하는 것만 안잊으면 됨. name은 나중에 템플릿언어 써야 해서 필요함.
from django.contrib import admin
from django.urls import path
from blogapp import views
urlpatterns = [
path('admin/', admin.site.urls),
path('',views.home, name='home' ),
path('create/',views.create, name='create' ),
path('new/',views.new, name='new' ),
]
form태그를 만들고, action=''에 템플릿 언어 넣고, Method는 등록하는거니까 post.
{% csrf_token %} 이건 보안 때문에 넣는 코드.
<form action="{% url 'create' %}" method="POST">
{% csrf_token %}
<div>
<label for="title">제목</label><br/>
<input type="text" name="title" id="title">
</div>
<div>
<label for="body">본문</label><br/>
<textarea name="body" id="body" cols="30", rows="10"></textarea>
</div>
<input type="submit" value="글 생성하기">
</form>
그럼 이런 걸 만들 수 있음.
새 글 작성을 누르면 이게 뜸.
일단 redirect 기능 쓸거니까 import에 redirect 추가.
if문으로 request의 method가 post일 때만 기능을 수행하도록 함
그래서 맞다면, post라는 변수에 blog 객체를 생성한다.
그 blog의 title에는 request의 post 중 title에 해동하는 부분을 담고, 마찬가지로 본문과 시간까지 blog객체의 각각의 항목에 담는다.
마지막으로 save로 데이터베이스에 저장하고
기능을 다한 함수는 home으로 redirect 된다.
from django.shortcuts import render, redirect
def create(request) :
if(request.method == 'POST'):
post = Blog()
post.title = request.POST['title']
post.body = request.POST['body']
post.date = timezone.now()
post.save()
return redirect('home')
지금까지의 로직을 정리하자면 다음과 같다. 시간이 생기면 그림으로도 정리해 보겠다.
새 글 작성하기를 누르고 제목과 본문을 적은 후에 글 생성하기를 누르면, create url로 post요청을 보내도록 form태그를 만들어놨으니 작성한 내용을 담아 create url로 post 요청을 보내게 됨.
그럼 create url에서는 create함수가 실행되는데, create함수는 받은 request(요청)의 method가 POST인지 확인하고 만약 맞다면 post에 blog 객체를 형성한 후의 request로부터 받은 post요청에서의 각 요소들을 blog객체에 담고 데이터베이스에 저장하게 됨. 이러한 기능을 수행하고 다시 home으로 redirect하는 것까지가 create함수의 기능.
여기까지 코드를 짰다면 html에서 새 글 작성하기를 눌러 제목과 본문 내용을 입력하면 admin사이트에서 실제로 그 글이 저장된 것을 확인할 수 있다.
다음으로 두 번째 방법은 다음과 같다.
1) blogapp에 forms.py
form을 정의할 수 있는 임의의 파이썬 파일을 만든 것.
forms.py 안에는 django로부터 forms를 import하고 BlogForm이라는 클래스를 만든다. 클래스로써 BlogForm을 정의한다. 이 객체는 forms의 Form을 상속해서 만든다. title은 forms로부터 문자열형식으로 입력받을 것임을 의미, body도 forms로부터 문자열형식으로 입력받을 것이나, 그 형식은 textarea로 조금 더 넓은 것을 사용할 것임을 의미.
from django import forms
from .models import Blog
class BlogForm(forms.Form):
#내가 입력받고자 하는 값들
title = forms.CharField()
body = forms.CharField(widget=forms.Textarea)
2) views.py에서 formcreate함수를 정의.
formcreate 함수는 Django form을 이용하면 get요청과 post요청을 모두 처리할 수 있다.
<POST요청>
form이라는 변수 = BlogForm(request.POST)
form.is_valid() 유효성을 자동으로 검사해주는 메서드.
그래서 유효하다면 Post=Blog()
form이라는 변수에 Blogform()이라고 하는 아까 만들어준 클래스를 갖다 쓰고.
그 다음 코드에서 request를 form_create.html에 딕셔너리 형태로, 'form'이라는 키의 값으로 넣는다.
def formcreate(request):
if request.method == 'POST' :
#입력 내용을 데이터베이스에 저장
form = BlogForm(request.POST)
if form.is_valid():
#저장을 시행하라는 코드
post = Blog()
post.title = form.cleaned_data['title']
post.body = form.cleaned_data['body']
post.save()
return redirect('home')
else:
#입력을 받을 수 있는 HTML을 가져다주기
form = BlogForm()
return render(request, 'form_create.html', {'form':form})
3) form_create.html 코드 채우기
(1) {{form}}
views.py에서 render()의 세 번째 인자로 넘겨준 데이터는 html 내에서 {{}}에 감싸진 채로 표현될 수 있다.
변형도 가능하다.
{{form.as_table}} : table 태그로 감싸진 것과 같은 효과
{{form.as_p}} : p태그로 감싸진 것과 같은 효과
<h1>django form을 이용한 새 글 작성 페이지</h1>
<form action="" method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="새 글 생성하기">
</form>
from django.contrib import admin
from django.urls import path
from blogapp import views
urlpatterns = [
path('admin/', admin.site.urls),
path('',views.home, name='home' ),
# html form을 이용해 블로그 객체 만들기
path('create/',views.create, name='create' ),
path('new/',views.new, name='new' ),
#Django form을 이용해 블로그 객체 만들기
path('formcreate/',views.formcreate, name='formcreate' ),
#Django modelform을 이용해 블로그 객체 만들기
path('modelformcreate/',views.modelformcreate, name='modelformcreate' ),
]
from django.shortcuts import render, redirect
from .models import Blog
from django.utils import timezone
from .forms import BlogForm, BlogModelForm
def modelformcreate(request):
if request.method == 'POST' :
#입력 내용을 데이터베이스에 저장
form = BlogModelForm(request.POST)
if form.is_valid():
#저장을 시행하라는 코드
form.save()
return redirect('home')
else:
#입력을 받을 수 있는 HTML을 가져다주기
form = BlogModelForm()
return render(request, 'form_create.html', {'form':form})
class BlogModelForm(forms.ModelForm):
class Meta:
model = Blog
#만약에 다 입력하고 싶으면 fields='__all__'
fields = ['title', 'body']
<h1>django form을 이용한 새 글 작성 페이지</h1>
<form action="" method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="새 글 생성하기">
</form>
다음으로 구현해야 하는 기능은 맨 처음 페이지에 여태껏 블로그에 업로드 했던 글들의 목록을 띄우는 기능이다.
1) views.py에서 home 함수를 손본다.
# 기본 페이지를 띄우는 함수(처음 시작페이지)
def home(request) :
# 블로그 글들을 띄우는 코드가 필요함.
posts = Blog.objects.all() #블로그 객체들을 모조리 갖고와서 posts 변수에 담고
return render(request, 'index.html', {'posts':posts})
보면 posts라는 변수에 Blog.objects.all()을 통해 블로그 객체들을 모조리 가져다 담은 후, render의 세 번째 인자를 통해 ㅑIndex.html 문서에 띄울 수 있도록 준비한다.
2) index.html 수정
<h1>my simple blog</h1>
<!-- 여태껏 등록된(업로드된) 블로그 글들의 목록 -->
{{ posts }}
<a href="{% url 'new' %}">HTML Form을 이용하여 새 글 작성</a><br/>
<a href="{% url 'formcreate' %}">Django Form을 이용하여 새 글 작성</a><br/>
<a href="{% url 'modelformcreate' %}">Django ModelForm을 이용하여 새 글 작성</a><br/>
render의 세 번째 인자였던 딕셔너리를 html에 띄우기 위해 중괄호 두개를 이용해 {{ posts }}라고 작성해 본다.
그럼 위의 스샷처럼 QuerySet에 감싸져서 나온다.
원하던 모양이 아니다.
QuerySet : 데이터베이스로부터 전달받은 객체 목록
3) 장고의 템플릿 언어를 활용해 html파일에서 for 반복문을 사용
<h1>my simple blog</h1>
<!-- 여태껏 등록된(업로드된) 블로그 글들의 목록 -->
{% for post in posts %}
<h3>제목 : {{ post.title }}</h3>
<h4>작성날짜 : {{ post.date }}</h4>
{{ post.body }}
{% endfor %}
<br/><br/><br/>
<a href="{% url 'new' %}">HTML Form을 이용하여 새 글 작성</a><br/>
<a href="{% url 'formcreate' %}">Django Form을 이용하여 새 글 작성</a><br/>
<a href="{% url 'modelformcreate' %}">Django ModelForm을 이용하여 새 글 작성</a><br/>
이제야 대충 원하는 모양이 나온다.
4) 저것들을 날짜순으로 정렬하려면
애초에 블로그 객체들을 가져올 때, filter를 사용했어야 함.
def home(request) :
# 블로그 글들을 띄우는 코드가 필요함.
#posts = Blog.objects.all() #블로그 객체들을 모조리 갖고와서 posts 변수에 담는다. 이건 그냥 다 쓸어오는거고
posts = Blog.objects.filter().order_by('date') #이렇게 하면 날짜순으로 쓸어오는 것
return render(request, 'index.html', {'posts':posts})
이제 메인 페이지를 만들었으니, 상세페이지를 구현할 차례.
지금까지는 CRUD 중에 Create만 구현한 것이나 다름이 없다.
목차에 있는 글을 누르면 상세페이지로 넘어가게 만들어야 나머지 Read, Update, Delete 구현이 가능해진다.
지금 우리가 Models.py에 이렇게 구현해놓은 Blog 클래스는
이렇게 생겼다. 여기서 우리는 기본키(Primary Key)를 어떤 것으로 할 지 정해주지 않았으므로
장고는 자동으로 Id열을 만들어 그걸 기본키로 설정해 두었다. 이 ID키는 만든 순서대로 1, 2, 3... 이런식으로 만들어진다. 그러니까 첫 번째로 만들어진 블로그 객체는 1이라는 ID값을 갖는다는 의미이다. 물론 admin사이트에서 삭제한다고 해서 ID값이 땡겨지는 건 아니므로 시행착오를 거쳤으면 1, 2, 3으로 연속적이지 않을 수는 있다. 어쨌든 Primary Key인 ID값은 생성순서를 의미한다는 것.
1) urls.py에 상세페이지 Url path 지정하기
detail/뒤에는 id값이 올 자리.
path('detail/<int:blog_id>',views.detail, name='detail' ),
2) index.html 수정.
a태그로 상세페이지와 블로그 목차의 제목 연결
아까 메인페이지에 구현해두었던 블로그의 목차에서 제목을 클릭하면 상세페이지로 넘어갈 수 있도록 a태그를 활용한다.
여기서 url 'detail'은 아까 urls.py에서 상세페이지의 url 이름을 name='detail'로 설정했기 때문. 그 뒤의 Post.id는 id값도 같이 넘겨주겠다는 의미.
<a href="{% url 'detail' post.id %}"></a><h3>제목 : {{ post.title }}</h3></a>
3) 다음은 views.py에서 detail 함수 만들기
from django.shortcuts import render, redirect, get_object_or_404
def detail(request, blog_id):
# Blog_id 번째 블로그 글을 데이터베이스로부터 끌고 온 다음에
blog_detail = get_object_or_404(Blog, pk=blog_id)
# blog_id 번째 블로그 글을 detail.html로 띄워주는 코드
return render(request, 'detail.html', {'blog_detail' : blog_detail})
pk값을 이용해 특정 모델 객체 하나만 갖고오는 함수를 get_object_or_404()라고 한다. 조건에 맞는 특정 개체 하나만을 가져오는 함수라고 보면 된다.
from django.shortcuts import render, redirect, get_object_or_404
우선적으로 그 함수를 사용하기 위해 django.shortcuts라는 module에서 render함수나 redirect 함수처럼 import 해준다.
get_object_or_404() 함수에는 두 가지 인자를 넣는데, 하나는 Blog에 해당하는 객체를 가져오라는 뜻, 나머지 하나는 어떤 블로그 객체를(그러니까 어떤 Pk값을 가진 블로그 객체를) 가져올 것인지를 정해주는 역할을 한다.
blog_detail = get_object_or_404(Blog, pk=blog_id)
따라서 위의 코드는 Blog의 객체 중에 blog_id라는 pk값을 지닌 객체 하나를 가져와 blog_detail에 담는 코드.
담았으면
return render(request, 'detail.html', {'blog_detail' : blog_detail})
위의 코드로 detail.html을 띄워주되, detail.html 안에서 blog_detail 변수를 사용할 수 있도록 함.
4) detail.html 생성하기
templates에 detail.html을 생성하고,
<h1>제목</h1>
{{ blog_detail.title }}
<h2>날짜</h2>
{{ blog_detail.date }}
<h3>본문</h3>
{{ blog_detail.body }}
이렇게 간단하게 상세페이지를 구현해 주면 된다.
그럼 이런 식으로 제목을 클릭할 수가 있게 되고
누르면 이렇게 상세페이지로 들어갈 수 있게 된다.
이렇게 해서 Create와 Read까지는 구현하였다.
다음은 이 상세페이지 안에서 Update, Delete만 구현하면 끝.
이제는 create 중에 조금 더 심화 단계로, 제목이랑 본문 내용 말고 media 파일도 업로드가 가능하도록 설정한다.
static과 달리 media는 사용자가 업로드하는 파일이다.
뭐 그런 차이점이 있긴 하지만 media도 static처럼 settings.py에 들어가서 수정을 해줘야 한다.
import os
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
그리고 urls.py에 들어가서 여태까지 했던 거에 다음과 같은 코드를 덧붙인다.
from django.conf import settings
from django.conf.urls.static import static
# media 파일에 접근할 수 있는 url도 추가해주어야 함.
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
관례처럼 굳어진 거라고 딱히 설명을 해주진 않았다.
models.py에도 들어가서 Blog 클래스에 사진칸을 추가했다.
photo = models.ImageField(blank=True, null=Ture, upload_to='blog_photo')
여기서 models.ImageField()의 인자는 빈칸으로 냅둬도 된다는 의미와 media 폴더 내에 blog_photo라는 폴더가 자동으로 생성되어 그 안에 담길거라는 의미.
그리고 Models.py를 건드렸으므로 migrate를 해 주어야 한다.
그런데 하다가 오류가 좀 생겼다.
Pillow가 없으면 ImageField를 못쓴다며 깔라고 하길래
python3 -m pip install Pillow
를 입력하여 설치하고
python3 manage.py makemigrations
python3 manage.py migrate
로 성공적으로 Migrate 했다.
이렇게까지 하면 runserver 해서 admin사이트로 들어갔을 때
add blog 를 누르면
이처럼 Photo 업로드가 가능해지는 것을 볼 수 있다.
일단 사용자에게 정보를 입력받을 때, 그니까 블로그 글을 업로드하는 방법이 굳이 세가지일 필요가 없으니까 index.html에서 나머지를 주석처리하여 업로드 방법을 하나로 남겨뒀다.
그리고 모두 입력하게 all로 바꿨다. 그래야 사진도 넣고 할테니까.
그다음에는 form_create.html로 돌아와서
위와 같이 폼태그를 좀 수정했는데,
html에 파일을 업로드할 때는
enctype="multipart/form-data"
이 코드를 추가해야 한단다.(나중에 추가적으로 공부할 것)
그리고는 뭐 views.py에 들어가서 여러 가지를 수정했다.
if request.method == "POST" :
였던걸
if request.method == "POST" or request.method =="FILES" :
로 수정
form = BlogModelForm(request.POST)
얘도
form = BlogModelForm(request.POST, request.FILES)
이렇게 수정.
그러면 이제 사진 데이터도 잘 올라간다. 대충 새 글 아무거나 작성하고 사진 첨부해서 업로드 하게 되면
이렇게 뜸. 성공적으로 media에 blog_photo라는 폴더에 모이는 것을 확인할 수 있음.
그래서 이제 상세페이지에서도 사진을 확인할 수 있게끔 하려고 이렇게 작성해놨더니
정작 상세페이지 들어가보면 이렇게 뜸;;
따라서 이렇게 작성해야
요래 뜸.
이제 댓글 구현이다. 상세페이지 아래쪽 부분에 댓글을 구현하면 될 것이다. 왜냐면 댓글은 각각의 글에 종속적인 요소이기 때문이다.
일단, models.py에 댓글을 클래스로 정의해줘야 한다. 아까 Blog객체로 정의해주었던 것처럼 말이다.
그래서 이런식으로 코딩을 해 주고 나니, 뭔가 중요한걸 빼먹었다.
댓글이 이런식으로 데이터베이스에 저장이 될텐데, 이러면 댓글이 어떤 게시글에 달린 댓글인지를 모른다. 그래서 댓글 클래스는 Blog객체를 참조해야 한다. Database이론에서 배웠던 외래키를 사용한단 의미.
post = models.ForeignKey(Blog, on_delete=models.CASCADE)
요걸 추가해주면 된다.
Blog 객체를 참조하겠다는 의미, 두 번째는 참조하는 객체가 사라지면 이 객체도 사라진다는 의미.
models.py를 건드렸으니
python3 manage.py makemigrations
python3 manage.py migrate
최종적으로 models.py에 생긴 Comment 클래스는
class Comment(models.Model):
comment = models.CharField(max_length=200)
date = models.DateTimeField(auto_now_add=True)
post = models.ForeignKey(Blog, on_delete=models.CASCADE)
#어떤 게시물에 달려있는 댓글인지를 알 수 있는, 댓글이 달린 그 게시물이 쓰임
def __str__(self):
return self.title
다음으로 admin 사이트에서 댓글관리도 가능해야 하니까
admin.py로 들어가서 수정.
forms.py 들어가서 CommentForm도 만들고
urls.py들어가서
상세페이지에 댓글 입력하는 칸도 띄워야 하니까 detail.html