Web/Django

django_02_crud

5_ssssseung 2021. 3. 11. 22:11

02_django_crud

02_django_crud

템플릿 폴더 구조 및 url 분리

  • articles/urls.py 파일 생성
  • 프로젝트 폴더 url 설정

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'],

기본 페이지 설정

# articles/urls.py

from django.urls import path
from . import views

app_name = 'articles'
urlpatterns = [
    path('', views.index, name='index'),
]


# articles/views.py

def index(request):
    return render(request, 'articles/index.html')
<!-- 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 %}

게시글 정렬 순서 변경

  • #1 - DB로 부터 받은 쿼리셋을 이후에 파이썬이 변경 (Python이 조작)
  • #2- 처음부터 내림차순 쿼리셋으로 받음 (DB가 조작)
# articles/views.py

def index(request):
    # 1. articles = Article.objects.all()[::-1]
    # 2. articles = Artile.objects.order_by('-pk')  



Http Method - POST

https://developer.mozilla.org/ko/docs/Web/HTTP/Methods/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

https://ko.wikipedia.org/wiki/%EC%82%AC%EC%9D%B4%ED%8A%B8%EA%B0%84%EC%9A%94%EC%B2%AD_%EC%9C%84%EC%A1%B0

  • 사이트 간 요청 위조(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'),

views 설정

# articles/views.py

def detail(request, pk):
    article = Article.objects.get(pk=pk)
    context = {
        'article': article,
    }
    return render(request, 'articles/detail.html', context)

templates 설정

<!-- 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

urls 설정

# articles/urls.py

path('<int:pk>/delete/', views.delete, name='delete'),

views 설정

# articles/views.py

def delete(request, pk):
    article = Article.objects.get(pk=pk)
    article.delete()
    return redirect('articles:index')

templates 설정

<!-- 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

urls 설정

# articles/urls.py

path('<int:pk>/edit/', views.delete, name='edit'),

views 설정

# articles/views.py

def edit(request, pk):
    article = Article.objects.get(pk=pk)
    context = {
        'article': article,
    }
    return render(request, 'articles/edit.html', context)

templates 설정

  • 수정은 기존에 입력 되어 있던 데이터를 보여주는 것이 좋기 때문에 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

urls 설정

# articles/urls.py

path('<int:pk>/update/', views.update, name='update'),

views 설정

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

'Web > Django' 카테고리의 다른 글

django_05_auth  (0) 2021.03.23
django_04_static_media_files  (0) 2021.03.18
django_03_form  (0) 2021.03.17
django_01_model  (0) 2021.03.11
django_00_Intro  (0) 2021.03.11