base.html
<!-- crud/templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- Bootstrap CDN -->
</head>
<body>
<div class="container">
{% block content %}
{% endblock %}
</div>
<!-- Bootstrap CDN -->
</body>
</html>
# crud/settings.py
'DIRS': [BASE_DIR / 'crud' / 'templates'],
<!-- templates/articles/index.html -->
{% extends 'base.html' %}
{% block content %}
<h1>Articles</h1>
{% endblock %}
READ
게시글 전체 조회
# articles/views.py
from .models import Article
def index(request):
articles = Article.objects.all()
context = {
'articles': articles,
}
return render(request, 'articles/index.html', context)
<!--templates/articles/index.html-->
{% extends 'base.html' %}
{% block content %}
<h1>Articles</h1>
<hr>
{% for article in articles %}
<p>글 번호: {{ article.pk }}</p>
<p>글 제목: {{ article.title }}</p>
<p>글 내용: {{ article.content }}</p>
<hr>
{% endfor %}
{% endblock %}
CREATE
New
# articles/urls.py
path('new/', views.new, name='new'),
# articles/views.py
def new(request):
return render(request, 'articles/new.html')
<!-- templates/articles/new.html -->
{% extends 'base.html' %}
{% block content %}
<h1>NEW</h1>
<form action="#" method="GET">
<label for="title">Title: </label>
<input type="text" name="title"><br>
<label for="content">Content: </label>
<textarea name="content" cols="30" rows="5"></textarea><br>
<input type="submit">
</form>
<hr>
<a href="{% url 'articles:index' %}">[back]</a>
{% endblock %}
<!-- templates/articles/index.html -->
{% extends 'base.html' %}
{% block content %}
<h1>Articles</h1>
<a href="{% url 'articles:new' %}">[new]</a>
<hr>
{% endblock %}
Create
# article/urls.py
path('create/', views.create, name='create'),
def create(request):
title = request.GET.get('title')
content = request.GET.get('content')
article = Article(title=title, content=content)
article.save()
return render(request, 'articles/create.html')
<!-- templates/articles/create.html -->
{% extends 'base.html' %}
{% block content %}
<h1>성공적으로 글이 작성되었습니다.</h1>
{% endblock %}
<!-- templates/articles/index.html -->
{% extends 'base.html' %}
{% block content %}
<h1>NEW</h1>
<form action="{% url 'articles:create' %}" method="GET">
<label for="title">Title: </label>
<input type="text" name="title"><br>
<label for="content">Content: </label>
<textarea name="content" cols="30" rows="5"></textarea><br>
<input type="submit">
</form>
<hr>
<a href="{% url 'articles:index' %}">[back]</a>
{% endblock %}
게시글 정렬 순서 변경
# articles/views.py
def index(request):
# 1. articles = Article.objects.all()[::-1]
# DB로 부터 받은 쿼리셋을 이후에 파이썬이 변경 (Python이 조작)
# 2. articles = Artile.objects.order_by('-pk')
# 처음부터 내림차순 쿼리셋으로 받음 (DB가 조작)
Http Method - POST
3가지 이유에서 우리는 글을 작성할 때 GET 요청이 아닌 POST 요청을 해야 한다.
1. 사용자는 Django에게 'HTML 파일 줘(GET)' 가 아니라 '~한 레코드(글)을 생성해(POST)'
라고 요청 보내기 때문에 GET보다는 POST 요청이 맞다.
2. 데이터는 URL에 직접 노출되면 안된다. (우리가 주소창으로 접근하는 방식은 모두 GET 요청)
query의 형태를 통해 DB schema를 유추할 수 있다.
3. 모델(DB)을 건드리는 친구는 GET이 아닌 POST 요청! 왜? 중요하니까 최소한의 신원 확인이 필요하다!
POST
- 서버로 데이터를 전송할 때 사용, 서버에 변경사항을 만듦
- 따라서 요청자에 대한 최소한의 검증을 하지 않으면 부작용을 일으킬 수 있음
csrf_token을 통해서 요청자의 최소한의 신원확인 - 리소스를 생성/변경하기 위해 데이터를 HTTP body에 담아 전송
- CRUD에서 C/U/D 역할을 담당
GET
- 특정 리소스를 가져오도록 요청할 때 사용
- 반드시 데이터를 가져올 때만 사용해야 함
- DB에 변화를 주지 않음
- CRUD에서 R 역할을 담당
DB 조작(GET/POST)
- GET 요청은 DB에서 데이터를 꺼내서 가져온다. 즉, DB에 변화를 주는 게 아니다.
- 즉, GET은 누가 요청해도 어차피 정보를 조회(HTML 파일을 얻는 것)하기 때문에 문제가 되지 않음.
- POST 요청은 DB에 조작(생성/수정/삭제)를 하는 것(디비에 변화를 준다)
POST는 DB에 조작이 가해지기 때문에 요청자에 대한 최소한의 검증을 하지 않으면
아무나 DB에 접근해서 데이터에 조작을 가할 수 있다.
csrf_token을 통해서 요청자의 최소한의 신원확인을 한다.
new.html 수정
<!-- templates/articles/new.html -->
{% extends 'base.html' %}
{% block content %}
<h1>NEW</h1>
<form action="{% url 'articles:create' %}" method="POST">
{% csrf_token %}
<label for="title">Title: </label>
<input type="text" name="title"><br>
<label for="content">Content: </label>
<textarea name="content" cols="30" rows="5"></textarea><br>
<input type="submit">
</form>
<hr>
<a href="{% url 'articles:index' %}">[back]</a>
{% endblock %}
# articles/views.py
def create(request):
title = request.POST.get('title')
content = request.POST.get('content')
article = Article(title=title, content=content)
article.save()
return render(request, 'articles/index.html')
CSRF Token
- 사이트 간 요청 위조(Cross-Site-Request-Fogery)
- 웹 애플리케이션 취약점 중 하나로 사용자가 자신의 의지와 무관하게 공격자가 의도한 행동을 하여
특정 웹페이지를 보안에 취약하게 한다거나 수정, 삭제 등의 작업을 하게 만드는 공격 방법을 의미한다.
{% csrf_token %} 을 설정하면 input type hidden 으로 특정한 hash 값이 들어있다. - {% csrf_token %} 이 없다면?
- 403 forbidden 에러:
- 서버에 요청은 도달했으나 서버가 접근을 거부할 때 반환하는 HTTP 응답 코드 / 오류 코드.
서버 자체 또는 서버에 있는 파일에 접근할 권한이 없을 경우에 발생
이러한 접근을 할 수 있도록 하는 것이 {% csrf_token %} →
사내 인트라넷 서버를 사내가 아닌 밖에서 접속하려고 할 때도 해당 HTTP 응답 코드가 뜬다.
게시글 작성 후 index로 되돌리기
# articles/views.py
def create(request):
...
return render(request, 'articles/index.html')
문제점 발생
1. 글을 작성 후 index 페이지가 출력되지만 게시글이 조회되지 않음
2. URL은 여전이 create에 머물러 있음
단순히 index 페이지만 render 되었을 뿐이고 url이 돌아가지 못했기 때문
Redirect
Django shortcut function 중 하나이며 model, view name, absolute or relate URL을 인자로 받음
여기서 인자 view name은 URL pattern name으로 작성 될 수 있음
POST 요청은 HTML 문서를 렌더링 하는 것이 아니라 '~~ 좀 처리해줘(요청)'의 의미이기 때문에
요청을 처리하고 나서의 요청의 결과를 보기 위한 페이지로 바로 넘겨주는 것이 일반적이다.
# articles/views.py
from django.shortcuts import render, redirect
def create(request):
title = request.POST.get('title')
content = request.POST.get('content')
article = Article(title=title, content=content)
article.save()
return redirect('articles:index')
POST 요청으로 변경 후 변화하는 것
POST 요청을 하게 되면 form을 통해 전송한 데이터를 받을 때도 request.POST.get() 로 받아야 함
글이 작성되면 실제로 주소 창에 내가 넘긴 데이터가 나타나지 않는다. (POST 요청은 HTTP body에 데이터를 전송함)
POST는 html을 요청하는 것이 아니기 때문에 html 파일을 받아볼 수 있는 곳으로 다시 redirect 한다.
DETAIL
urls 설정
개별 게시글 상세 페이지
글의 번호(pk)를 활용해서 각각의 페이지를 따로 구현해야 함
무엇을 활용할 수 있을까? → Variable Routing
# articles/urls.py
path('<int:pk>/', views.detail, name='detail'),
# articles/views.py
def detail(request, pk):
article = Article.objects.get(pk=pk)
context = {
'article': article,
}
return render(request, 'articles/detail.html', context)
<!-- templates/articles/detail.html -->
{% extends 'base.html' %}
{% block content %}
<h2>DETAIL</h2>
<h3>{{ article.pk }} 번째 글</h3>
<hr>
<p>제목: {{ article.title }}</p>
<p>내용: {{ article.content }}</p>
<p>작성 시각: {{ article.created_at }}</p>
<p>수정 시각: {{ article.updated_at }}</p>
<hr>
<a href="{% url 'articles:index' %}">[back]</a>
{% endblock %}
index 페이지에 게시글별 detail 링크작성
<!-- templates/articles/index.html -->
{% extends 'base.html' %}
{% block content %}
<h1 class="text-center">Articles</h1>
<a href="{% url 'articles:new' %}">[new]</a>
<hr>
{% for article in articles %}
<p>글 번호: {{ article.pk }}</p>
<p>글 제목: {{ article.title }}</p>
<p>글 내용: {{ article.content }}</p>
**<a href="{% url 'articles:detail' article.pk %}">[detail]</a>**
<hr>
{% endfor %}
{% endblock %}
create 후 detail로 이동
# articles/views.py
def create(request):
title = request.POST.get('title')
content = request.POST.get('content')
article = Article(title=title, content=content)
article.save()
return redirect('articles:detail', article.pk)
DELETE
# articles/urls.py
path('<int:pk>/delete/', views.delete, name='delete'),
# articles/views.py
def delete(request, pk):
article = Article.objects.get(pk=pk)
article.delete()
return redirect('articles:index')
<!-- articles/detail.html -->
{% extends 'base.html' %}
{% block content %}
...
<form action="{% url 'articles:delete' article.pk %}" method="POST">
{% csrf_token %}
<button class="btn btn-danger">DELETE</button>
</form><br>
<a href="{% url 'articles:index' %}">[back]</a>
{% endblock %}
그래서 POST 로 요청을 받기 위해 다음과 같이 조건을 만든다.
# articles/views.py
def delete(request, pk):
article = Article.objects.get(pk=pk)
if request.method == 'POST':
article.delete()
return redirect('articles:index')
else:
return redirect('articles:detail', article.pk)
UPDATE
Edit
# articles/urls.py
path('<int:pk>/edit/', views.delete, name='edit'),
# articles/views.py
def edit(request, pk):
article = Article.objects.get(pk=pk)
context = {
'article': article,
}
return render(request, 'articles/edit.html', context)
수정은 기존에 입력 되어 있던 데이터를 보여주는 것이 좋기 때문에 html 태그의 value 속성을 사용
<!-- articles/edit.html -->
{% extends 'base.html' %}
{% block content %}
<h1 class="text-center">EDIT</h1>
<form action="#" method="POST">
{% csrf_token %}
<label for="title">Title: </label>
<input type="text" name="title" value="{{ article.title }}"><br>
<label for="content">Content: </label>
<textarea name="content" cols="30" rows="5">{{ article.content }}</textarea><br>
<input type="submit">
</form>
<hr>
<a href="{% url 'articles:index' %}">[back]</a>
{% endblock %}
detail.html에 edit 으로 가는 링크 작성
<!-- articles/detail.html -->
{% extends 'base.html' %}
{% block content %}
...
<a href="{% url 'articles:edit' article.pk %}" class="btn btn-primary">EDIT</a><br>
<form action="{% url 'articles:delete' article.pk %}" method="POST">
{% csrf_token %}
<button class="btn btn-danger">DELETE</button>
</form><br>
<a href="{% url 'articles:detail' article.pk %}">[back]</a>
{% endblock %}
Update
# articles/urls.py
path('<int:pk>/update/', views.update, name='update'),
# articles/views.py
def update(request, pk):
article = Article.objects.get(pk=pk)
article.title = request.POST.get('title')
article.content = request.POST.get('content')
article.save()
return redirect('articles:detail', article.pk)
<!-- articles/edit.html -->
{% extends 'base.html' %}
{% block content %}
<h1>EDIT</h1>
<form action="{% url 'articles:update' article.pk %}" method="POST">
{% csrf_token %}
...
<a href="{% url 'articles:index' %}">[back]</a>
{% endblock %}
'# 2. Web > Django' 카테고리의 다른 글
Django # Accounts & auth 인증 & 로그인/로그아웃 & 회원가입/탈퇴 & 비밀번호 변경 (0) | 2021.04.22 |
---|---|
Django # Static files & Media 이미지 업로드 (0) | 2021.04.22 |
Django # Form과 ModelForm 핵심 차이점 & Widgets & Allowed HTTP methods (0) | 2021.04.22 |
Django # Model이란? ORM이란? Migrations란? CRUD란? Admin Site란? (0) | 2021.03.12 |
Django # Django란? MTV패턴이란? DTL이란? URLs 유지보수란? (0) | 2021.03.08 |