# 2. Web/Django

Django # CRUD 보충 & Http Method & Redirect

둥굴둥굴둥굴레차 2021. 4. 22. 13:43

 

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 %}