[Django] CRUD with view functions

문지은·2023년 4월 21일
0

Django 기초

목록 보기
7/14
post-thumbnail

이전에 익힌 QuerySet API를 통해 view 함수에서 직접 CRUD를 구현해보자.
먼저 프로젝트 이름은 crud, 애플리케이션 이름은 articles,
데이터를 처리할 Model은 다음과 같이 작성하였다.

# articles/models.py
from django.db import models

# Create your models here.
class Article(models.Model):
    title = models.CharField(max_length=30)
    content = models.TextField()

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'{self.id}번째 글 - {self.title}'

⭐️ READ - index

  • 전체 게시글 조회하기

url

# crud/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('articles/', include('articles.urls')),
]

view

# articles/view.py

from django.shortcuts import render
from .models import Article

def index(request):
    articles = Article.objects.all()
    context = {'articles':articles}
    return render(request, 'articles/index.html', context)

template

<!-- templates/articles/index.html -->

{% extends 'base.html' %}

{% block content %}
    <h1>INDEX</h1>
    <hr>
    {% for article in articles  %}
        <p>글 번호 : {{article.id}}</p>
        <p>글 제목 : {{article.title}}</p>
        <p>글 내용 : {{article.content}}</p>
        <hr>
    {% endfor %}
{% endblock content %}

⭐️ READ - detail

  • 개별 게시글 상세 페이지 제작
  • 모든 게시글 마다 뷰 함수와 템플릿 파일을 만들 수는 없다.
    • 글의 번호(pk)를 활용해서 하나의 뷰 함수와 템플릿 파일로 대응시킨다.
    • Variable Routing 활용

url

  • URL로 특정 게시글을 조회할 수 있는 번호를 받는다.
# articles/urls.py

urlpatterns = [
		...
    path('<int:pk>/', views.detail, name='detail'),
]

view

  • Article.objects.get(pk=pk)에서 오른쪽 pk는 variable routing을 톻해 받은 pk, 왼쪽 pk는 DB에 저장된 레코드의 id 컬럼을 뜻한다.
# articles/views.py

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

template

<!-- templates/articles/detail.html -->

{% extends 'base.html' %}

{% block content %}
    <h1>DETAIL</h1>
    <hr>

    <p>글 제목 : {{article.title}}</p>
    <p>글 내용 : {{article.content}}</p>
    <p>생성 시각 : {{article.created_at}}</p>
    <p>수정 시각 : {{article.updated_at}}</p>

    <a href="{% url 'articles:index'%}">목록보기</a>

{% endblock content %}

제목을 누르면 상세페이지로 이동하게 하기

{% extends 'base.html' %}

{% block content %}
    <h1>INDEX</h1>
    <a href="{% url 'articles:new' %}">글 작성하기</a>
    <hr>
    {% for article in articles  %}
        <p>글 번호 : {{article.id}}</p>
        <p><a href="{% url 'articles:detail' article.pk %}">글 제목 : {{article.title}}</a></p>
        <p>글 내용 : {{article.content}}</p>
        <hr>
    {% endfor %}
{% endblock content %}

⭐️ CREATE

  • 사용자의 입력을 받을 페이지를 렌더링 하는 함수 1개
    • new view function
  • 사용자가 입력한 데이터를 전송 받아 DB에 저장하는 함수 1개
    • create view function

New

url

# articles/urls.py

urlpatterns = [
		...
		path('new/', views.new, name='new'),
]

view

# articles/views.py

def new(request):
    return render(request, 'articles/new.html')

template

  • index 페이지에 new 페이지로 이동할 수 있는 하이퍼 링크 작성
<!-- templates/articles/index.html -->

{% extends 'base.html' %}

{% block content %}
    <h1>INDEX</h1>
    <a href="{% url 'articles:new' %}">글 작성하기</a>
    <hr>
		...
{% endblock content %}

Create

url

# articles/urls.py

urlpatterns = [
		...
		path('create/', views.create, name='create'),
]

view

# aritlcles/views.py

def create(request):
	# 입력한 데이터 가져오기
    title = request.POST.get('title')
    content = request.POST.get('content')

    # DB에 새로운 Article 저장
    # 방법 1
    Article.objects.create(title = title, content = content)

    # 방법 2
    article = Article(title=title, content=content)
    article.save()
		
	# 방법 3
	article = Article()
	article.title = title
	article.content = content
	article.save()

    return redirect('articles:index')
  • 데이터를 생성하는 3가지 방법 중 2 또는 3번째 생성 방식을 사용한다.
    • create 메서드가 더 간단해 보이지만 추후 데이터가 저장되기 전에 유효성 검사 과정을 거치게 될 예정
    • 유효성 검사가 진행된 후에 save 메서드가 호출되는 구조를 택하기 위함이다.

📍 redirect()

  • 인자에 작성된 곳으로 다시 요청을 보냄
  • 사용 가능한 인자
    1. view name (URL pattern name) return redirect(’articles:index’)
    2. absolute or relative URL return redirect(’/articles/’)

template

<!-- templates/articles/new.html -->

{% extends 'base.html' %}

{% block content %}
    <h1>글 작성하기</h1>
    <hr>

    <form action="{% url 'articles:create' %}" method="POST">
		{% csrf_token %}
        <label for ="title">제목 : </label>
        <input type="text" id="title" name="title"><br>

        <label for ="content">내용 : </label>
        <textarea name="content"></textarea><br>

        <input type="submit">

    </form>
    <hr>
    <a href="{% url 'articles:index'%}">목록보기</a>
    
{% endblock content %}
  • DB에 변경사항 발생하므로 POST method 를 사용하여 데이터를 전송한다.
    • POST method를 사용할 때에는 crsf_token이 필요하다.

📍 crsf_token 템플릿 태그

  • {% csrf_token %}
  • 해당 태그가 없다면 Django는 서버 요청에 대해 403 forbidden으로 응답
  • 템플릿에서 내부 URL로 향하는 Post form을 사용하는 경우에 사용
    • 외부 URL로 향하는 POST form에 대해서는 CSRF 토큰이 유출되어 취약성을 유발할 수 있기 때문에 사용해서는 안됨
  • csrf_token은 해당 POST 요청이 내가 보낸것 인지를 검증하는 것!

📍 403 Forbidden

  • 서버에 요청이 전달되었지만, 권한 때문에 거절되었다는 것을 의미
  • 서버에 요청은 도달했으나 서버가 접근을 거부할 때 반환됨
  • 즉, 게시글을 작성할 권한이 없다
    → Django 입장에서는 ‘작성자가 누구인지 모르기 때문에 함부로 작성할 수 없다’ 라는 의미
  • 모델(DB)을 조작하는 것은 단순 조회와 달리 최소한의 신원 확인이 필요하기 때문

⭐️ DELETE

url

  • 삭제하고자 하는 특정 글을 조회 후 삭제해야 함
    • Variable Routing 활용
# articles/urls.py

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

view

# aritlcles/views.py

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

위 코드처럼 작성하면 사용자가 template를 통해 삭제 요청을 보낸 것이 아닌 단순 url로 접속해도 게시글이 삭제되는 현상이 발생한다.

  • POST 요청에 대해서만 게시글이 삭제될 수 있도록 view 함수를 수정한다.
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)

template

  • Detail 페이지에 Template 작성하며, DB에 영향을 미치기 때문에 POST method 사용
<!-- templates/articles/detail.html -->

{% extends 'base.html' %}

{% block content %}
    <h1>DETAIL</h1>
    <hr>

    <p>글 제목 : {{article.title}}</p>
    <p>글 내용 : {{article.content}}</p>
    <p>생성 시각 : {{article.created_at}}</p>
    <p>수정 시각 : {{article.updated_at}}</p>

    <form action="{% url 'articles:delete' article.pk %}" method="POST">
        {% csrf_token %}
        <input type="submit" value="DELETE">
    </form>

    <a href="{% url 'articles:index'%}">목록보기</a>

{% endblock content %}

⭐️ Update

  • 사용자의 입력을 받을 페이지를 렌더링 하는 함수 1개
    • edit view function
  • 사용자가 입력한 데이터를 전송받아 DB에 저장하는 함수 1개
    • update view function

edit

url

# articles/urls.py

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

view

# aritlcles/views.py

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

template

  • html 태그의 value 속성을 사용해 기존에 입력되어 있던 데이터 출력
  • textarea 태그는 value 속성이 없으므로 태그 내부 값으로 작성해야 함
<!-- templates/articles/edit.html -->

{% extends 'base.html' %}

{% block content %}
<h1>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">{{article.content}}</textarea><br>
    <input type="submit">
</form>
<hr>
<a href="{% url 'articles:index' %}">목록보기</a>
{% endblock content %}
  • Edit 페이지로 이동하기 위한 하이퍼링크 작성
<!-- templates/articles/detail.html -->

{% extends 'base.html' %}

{% block content %}
    <h1>DETAIL</h1>
    <hr>

    <p>글 제목 : {{article.title}}</p>
    <p>글 내용 : {{article.content}}</p>
    <p>생성 시각 : {{article.created_at}}</p>
    <p>수정 시각 : {{article.updated_at}}</p>

    <a href="{% url 'articles:edit' article.pk %}">EDIT</a><br>

    <form action="{% url 'articles:delete' article.pk %}" method="POST">
        {% csrf_token %}
        <input type="submit" value="DELETE">
    </form>

    <a href="{% url 'articles:index'%}">목록보기</a>

{% endblock content %

Update

url

# articles/urls.py

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

view

# aritlcles/views.py

from django.shortcuts import render, redirect
from .models import Article

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)

template

<!-- templates/articles/edit.html -->

{% extends 'base.html' %}

{% block content %}
<h1>EDIT</h1>
<form action="{% url 'articles:update' article.pk %}" method="POST">
    {% csrf_token %}
		...
</form>
<hr>
<a href="{% url 'articles:index' %}">목록보기</a>
{% endblock content %}

⭐️ Handling HTTP requests

new-create, edit-update의 view 함수

  • new - create는 모두 CREATE 로직을 구현하기 위한 공통 목적
  • edit - update는 모두 UPDATE 로직을 구현하기 위한 공통 목적
  • new와 edit는 GET 요청에 대한 처리만을 진행
  • create와 update는 POST 요청에 대한 처리만을 진행

→ 하나의 view 함수에서 method에 따라 로직이 분리되도록 변경하기!

Create

  • new와 create view 함수를 합침
  • 각각의 역할은 request.method 값을 기준으로 나뉨
def create(request):
    if request.method == 'POST':
        title = request.POST.get('title')
        content = request.POST.get('content')
        article = Article(title=title, content=content)
        article.save()
        return redirect('articles:index')
    else:
        return render(request, 'articles/create.html')

Update

  • edit와 update view 함수 합침
def update(request, pk):
    if request.method == 'POST':
        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)
    else:
        article = Article.objects.get(pk=pk)
        context = {'article' : article}
        return render(request, 'articles/edit.html', context)

🔥 전체 코드 확인

profile
코드로 꿈을 펼치는 개발자의 이야기, 노력과 열정이 가득한 곳 🌈

0개의 댓글