logo
Search검색어를 포함하는 게시물들이 최신순으로 표시됩니다.
    Table of Contents
    [Django] CRUD까지의 전과정 정리

    이미지 보기

    [Django] CRUD까지의 전과정 정리

    • 22.03.08 작성

    • 22.03.09 수정

    • 읽는 데 25

    TOC

    1. PJT / APP 생성

    1.1. 초기설정

    BASE_DIR 생성

    'total_flow'라는 이름의 폴더를 만들었다.


    venv 설치 및 실행

    tmdgn@ASUSSH MINGW64 ~/OneDrive/바탕 화면/total_flow
    $ python -m venv venv
    
    tmdgn@ASUSSH MINGW64 ~/OneDrive/바탕 화면/total_flow
    $ source venv/scripts/activate
    
    # venv를 활성화하였어도 pip list를 입력해 venv 환경이 맞는지 재확인하자.
    (venv) 
    tmdgn@ASUSSH MINGW64 ~/OneDrive/바탕 화면/total_flow
    $ pip list
    Package    Version
    ---------- -------
    pip        21.2.4
    setuptools 58.1.0
    

    django 3.2.12. 버전 설치

    (venv) 
    tmdgn@ASUSSH MINGW64 ~/OneDrive/바탕 화면/total_flow
    $ pip install django==3.2.12
    

    1.2. PJT 생성

    (venv) 
    tmdgn@ASUSSH MINGW64 ~/OneDrive/바탕 화면/total_flow
    $ django-admin startproject crud .
    
    • crud라는 이름의 프로젝트를 만든다.
    • 끝에 .을 꼭 찍어 현재 BASE_DIR에 📂crud 및 📃manage.py가 바로 생기도록 한다.

    1.3. APP 생성 및 등록

    APP 생성

    (venv) 
    tmdgn@ASUSSH MINGW64 ~/OneDrive/바탕 화면/total_flow
    $ python manage.py startapp articles
    

    articles라는 이름의 app을 만든다.


    APP 등록

    # crud/settings.py
    
    # Application definition
    
    INSTALLED_APPS = [
        # Local
        'articles',
        
        # Built-in
        'django.contrib.admin',
        ...
        'django.contrib.staticfiles',
    ]
    
    • articles app을 settings.py의 INSTALLED_APPS에 등록해준다.
    • 반드시 APP 생성 이후에 등록해야함을 잊지 않는다.

    1.4. settings.py 추가 설정

    settings.py에 들어온 김에 필요한 설정들을 모두 해주겠다.


    BASE_DIR 설정

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [BASE_DIR / 'templates'],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]`python
    
    • TEMPLATES의 DIRS에 [BASE_DIR / 'templates'], 를 추가해준다.
    • BASE_DIR에 바로 추가한 templates 폴더에는 base.html 등 모든 app의 templates 폴더보다 먼저 조회할 파일들을 넣는다.

    Internationalization 설정

    LANGUAGE_CODE = 'ko-kr'
    
    TIME_ZONE = 'Asia/Seoul'
    
    • 언어코드를 ko-kr로 바꿔준다. 서버를 실행하면 나오는 여러 문자들을 한국어로 바꿔주는 역할을 한다.
    • TIME_ZONE을 Asia/Seoul로 바꿔준다. 시간 관련 함수에서 Seoul을 기준으로 시간을 출력해준다.

    1.5. 📂BASE_DIR 📂templates 📃base.html 생성

    📂templates 생성

    • 📂BASE_DIR에 📂templates를 추가로 생성해준다.
    • django가 templates를 조회하는 것은 이전에 settings.py에서 BASE_DIR에 경로를 이미 설정해주었다.

    📃base.html 생성

    • app에서 사용될 html 파일들의 모체가 되는 templates이다.
    • bootstrap을 사용하기 위해 bootstrap CDN 링크도 넣어주도록 한다.
    • block tag를 사용해 하위에서 extends할 부분을 정의해준다.
    • layout을 위해 container class의 div 태그 내에 block tag를 위치시킨다.
    {%- raw -%}
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
      <title>Document</title>
    </head>
    <body>
    
      <div class="container">
        {% block content %}
        {% endblock content %}
      </div>
      
      <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
    </body>
    </html>
    {% endraw -%}
    

    2. PJT urls.py 작업

    PJT의 urls.py에 APP을 include해준다.

    # 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'))
    ]
    
    
    • django는 HTTP request를 받으며 데이터의 흐름이 시작
    • crud PJT의 urls에 APP의 urls.py에 대한 경로를 incldue한다.
    • django.urls 모듈의 include 함수를 import 하는 것에 유의한다.

    3. APP 작업

    3.1. urls.py

    from django.urls import path
    from . import views
    
    app_name = 'articles'
    urlpatterns=[
        path('', views.index, name='index')
    ]
    
    • app_name은 'articles'로 둔다.
    • articles/로 url이 끝나면 articles의 main 페이지를 불러오는 index함수를 호출한다.
    • index함수는 views.py에 있기 때문에 현재 폴더라는 의미의 .에서 views 모듈을 import한다는 코드를 추가한다.
    • path name을 index로 정의한다.

    3.2. views.py

    사전 작업

    • 📂templates를 APP 폴더 내에 생성
    • 📂articles를 📂templates 폴더 내에 생성
    • 다른 APP의 r같은 파일명인 templates와의 구분을 위해 APP마다 물리적 경로를 설정하여 명시
    # articles/views.py
    
    from django.shortcuts import render
    
    def index(request):
        return render(request, 'articles/index.html')
    

    app 폴더 내의 📂templates 📂articles 📃index.html을 render하여 반환한다.


    3.3. articles/index.html

    {%- raw -%}
    <!-- articles/templates/articles/index.html -->
    {% extends 'base.html' %}
    
    {% block content %}
      <h1 class="text-center">Articles</h1>
    {% endblock content %}
    {% endraw -%}
    
    • 📂BASE_DIR 📂templates 📃base.html을 상속받기 위해 extends tag 사용
    • block tag 사용 후 html 코드 넣기
    • 📃base.html과 📃index.html의 block tag의 이름이 같아야 한다.(위에서는 content)

    3.4. Runserver 결과

    image

    서버를 실행하고 해당 url을 입력하니 articles/index.html의 템플릿이 잘 반영되었음을 확인할 수 있다.


    4. DB 설정

    4.1. models.py

    # articles/models.py
    
    from django.db import models
    
    class Article(models.Model):
        title = models.CharField(max_length=10)
        content = models.TextField()
        created_at = models.DateTimeField(auto_now_add=True)
        updated_at = models.DateTimeField(auto_now=True)
        
        def __str__(self):
            return self.title
    
    • schema를 class를 통해 정의
    • field는 class의 property를 통해 정의
    • models.--Field를 통해 datatype 설정
    • argument를 통해 옵션 및 제한사항 설정

    4.2. Migrations

    makemigrations

    $ python manage.py makemigrations
    Migrations for 'articles':
      articles\migrations\0001_initial.py
        - Create model Article
    

    Article class를 기반으로 한 현재의 migration file을 만들었다.


    migrate

    $ python manage.py migrate
    Operations to perform:
      Apply all migrations: admin, articles, auth, contenttypes, sessions
    Running migrations:
      Applying contenttypes.0001_initial... OK
      ...
      Applying auth.0012_alter_user_first_name_max_length... OK
      Applying sessions.0001_initial... OK
    

    migrate하여 DB에 반영하였다.


    migration SQL 확인

    $ python manage.py sqlmigrate articles 0001
    BEGIN;
    --
    -- Create model Article
    --
    CREATE TABLE "articles_article" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(10) NOT NULL, "content" text NOT NULL, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
    COMMIT;
    

    sqlmigrate 명령어로 articles APP의 0001번 migration에 대해 field를 SQL적으로 확인했다.


    4.3. shell_plus 사용 준비

    pip 설치

    $ pip install ipython django-extensions
    

    pip install pip1 pip2 처럼 공백으로 한 번에 여러 pip를 설치할 수 있다.


    settings.py에 App 등록

    # crud/settings.py
    
    INSTALLED_APPS = [
        # Local
        'articles',
        'django_extensions',
        ...
    ]
    
    • django_extension을 INSTALLED_APPS에 추가
    • django-extensions이 아닌 django_extensions임에 주의
    • APP 등록 후 끝에 comma(,)를 찍는 것에 주의

    shell_plus 실행

    $ python manage.py shell_plus
    
    # Shell Plus Model Imports
    from articles.models import Article
    ...
    from django.contrib.sessions.models import Session
    # Shell Plus Django Imports
    from django.core.cache import cache
    ...
    from django.db.models import Exists, OuterRef, Subquery
    
    In [1]: 
    
    • 여러 django 모듈과 함수들, 그리고 class 등을 import 해주는 강력한 기능의 shell 도구이다.
    • ipython을 이용해 우리에게 익숙한 jupyter notebook 느낌의 shell 모습을 보인다.

    4.4. DB에 예시 data 추가

    index.html에서 출력할 record들을 예시로 만드는 작업이다.

    # create 1
    In [1]: article = Article()
    
    In [2]: article.title = 'first'
    
    In [3]: article.content = 'test1'
    
    In [4]: article.save()
    
    # create 2
    In [5]: article = Article(title='second', content='test2')
    
    In [6]: article.save()
    
    # create 3
    In [7]: Article.objects.create(title='third', content='test3')
    Out[7]: <Article: third>
    

    record를 만드는 3가지 방식을 모두 이용해 총 3개의 record instance를 만들어보았다.

    image
    • 시각이 작성시각 기준 현재 새벽 3시, 즉 27시인데 created_at과 updated_at이 18시, 9시간이 차이난다.
    • Asia/Seoul은 UTC+9:00인 것을 생각하면 UTC 기준으로 record가 추가되었다.
    • DB에는 UTC 기준으로 time이 저장되고, Render할 때 TIME_ZONE 시각에 맞춰 차이를 더하고 빼주는 것

    5. DB READ

    index.html에 DB의 record들을 읽어와 rendering 해보자.


    5.1. views.py

    # articles/views.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)
    
    • .models 모듈의 Article 클래스를 import한다.
    • 이 Article 클래스의 ORM과 querySet API를 이용해 모든 record를 복사해 articles에 저장한다.
    • 이를 context에 넣어 render에 반영해준다.

    5.2. index.html

    {%- raw -%}
    {% extends 'base.html' %}
    
    {% block content %}
      <h1 class="text-center">Articles</h1>
      <hr>
      {% for article in articles %}
        <p>{{ article.pk }} | {{ article.title }}</p>
        <p>{{ article.content }}</p>
        <hr>
      {% endfor %}
    {% endblock content %}
    {% endraw -%}
    
    • django DTL의 for tag를 이용해 articles 리스트 내의 각각의 article, 즉 record 인스턴스마다 처리한다.
    • 인스턴스 property이자 record의 field인 pk, title, content를 위와 같은 형식으로 불러온다.
    • record마다 구분을 하기 위해 hr tag를 사용한 뒤, for문을 마친다.

    image

    6. Create 1 : New 페이지 생성

    article을 생성하는 페이지를 만들어보자.

    • 새로운 article을 만드는 keyword는 new이다.
    • new.html, path도 new, views함수도 new로 정해보자.

    6.1. urls.py

    from django.urls import path
    from . import views
    
    app_name = 'articles'
    urlpatterns=[
        path('', views.index, name='index'),
        path('new/', views.new, name='new'),
    ]
    

    url에 new/로 request가 들어오면 views 모듈의 new 함수를 호출한다.


    6.2. views.py

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

    new함수가 실행되면, articles/new.html을 rendering한다.


    6.3. articles/new.html

    {%- raw -%}
    {% extends 'base.html' %}
    
    {% block content %}
      <h1 class="text-center">NEW</h1>
      <hr>
      <form action="#" method="GET">
        <label for="title">제목 :</label>
        <input class="w-100 mb-5 bg-light" type="text" name="title"> <br />
        <label for="content">내용 :</label>
        <textarea class="w-100 mb-5 bg-light" name="content" rows="10"></textarea> <br />
        <button class="btn btn-primary btn-sm">완료</button>
      </form>
    {% endblock content %}
    {% endraw -%}
    
    • form tag를 이용해 제목과 내용을 입력하는 input을 마련한다.
    • label의 for와 input의 name을 통일하여 label이 input과 mapping되도록 한다.
    • textarea는 닫는 태그가 있음에 유의한다.
    • layout을 위해 bootstrap class를 일부 사용하였다.

    6.4. articles/index.html

    {%- raw -%}
    {% extends 'base.html' %}
    
    {% block content %}
      <h1 class="text-center">Articles</h1>
      <a href="{% url 'articles:new' %}">게시글 작성</a>
      <hr>
      
      {% for article in articles %}
        <p>{{ article.pk }} | {{ article.title }}</p>
        <p>{{ article.content }}</p>
        <hr>
      {% endfor %}
    {% endblock content %}
    {% endraw -%}
    
    • 메인 페이지 articles/index.html에 게시글을 만드는 페이지로 이동하는 a tag 코드를 추가하였다.
    • href는 url tag를 이용하여 'articles' APP의 'new'라는 name의 path로 지정하였다.

    6.5. 결과

    image
    • 게시글 작성 a 태그를 통해 new/로 향하는 링크가 존재함을 알 수 있다.
    • 클릭하면 다음 화면을 맞이한다.

    image

    제목과 내용을 입력하는 란이 있다.


    7. Create 2 : form 데이터 저장

    form에 데이터를 입력하여 버튼을 클릭하면 DB에 저장해보자.

    • create/ url을 사용할 것이다.

    7.1. urls.py

    from django.urls import path
    from . import views
    
    app_name = 'articles'
    urlpatterns=[
        path('', views.index, name='index'),
        path('new/', views.new, name='new'),
        path('create/', views.create, name='create'),
    ]
    

    create라는 name과 create/ url을 만나면 views.create 함수를 호출한다.


    7.2. views.py

    def create(request):
        title = request.GET.get('title')
        content = request.GET.get('content')
        
        Article.objects.create(title=title, content=content)
        
        return render(request, 'articles/new.html')
    
    • request.GET은 query의 정보를 담고있다.
    • create 함수 내에서 request.GET의 key를 이용해 title과 content의 데이터를 각각 저장한다.
    • 이를 record를 create하는 querySet API를 이용해 record를 추가해준다.
    • articles/new.html로 페이지를 이동한다.

    7.3. articles/new.py

    {%- raw -%}
    {% extends 'base.html' %}
    
    {% block content %}
      <h1 class="text-center">NEW</h1>
      <hr>
      <form action="{% url 'articles:create' %}" method="GET">
        <label for="title">제목 :</label>
        <input class="w-100 mb-5 bg-light" type="text" name="title"> <br />
        <label for="content">내용 :</label>
        <textarea class="w-100 mb-5 bg-light" name="content" rows="10"></textarea> <br />
        <a class="btn btn-light btn-sm" href="{% url 'articles:index' %}">이전</a>
        <button class="btn btn-primary btn-sm">완료</button>
      </form>
    {% endblock content %}
    {% endraw -%}
    
    • form의 action에 url을 url tag를 이용해 넣어주었다.
    • form에서 submit이 발생하면 action에 해당하는 url과 query 데이터를 server에 HTTP request로 보내는 것이다.
    • 이전 화면인 index.html로 가는 a 태그를 추가하였다.

    7.4. 결과

    image

    위와 같이 create test라는 텍스트를 각각 넣고 완료 버튼을 누른다.


    image

    index 페이지로 가면, form에 저장했던 article이 성공적으로 반영되었음을 알 수 있다.


    번외 : 정렬 순서 변경

    index 페이지의 article 순서를 최신 article 순, 즉 반대로 정렬하고 싶다!

    def index(request):
        # 1. DB로부터 받은 querySet을 views.py에서 pythonic하게 변경한다.
        articles = Article.objects.all()[::-1]
    
        # 2. DB에서 내림차순 querySet으로 DB를 불러온다.
        articles = Article.objects.order_by('-pk')
    
        ...
    
    • 2가지 방법이 가능하다.
    • 일반적으로 2번 방법이 더 효과적이다.
    • DB에서 정렬/처리해서 데이터를 가져오는 것이 훨씬 속도가 빠르다.

    8. HTTP method

    8.1. GET과 POST

    GET

    • 특정 리소스를 가져오도록 요청할 때 사용
    • 데이터를 가져올 때만 사용
    • DB에 변화를 주지 않는다.
    • CRUD에서 R 역할을 담당

    POST

    • 서버로 데이터를 전송할 때 사용
    • 리소스를 생성/변경하기 위해 데이터를 HTTP body에 담아 전송
    • 때문에 데이터 전달에 보안이 GET보다 낫다!!
    • 서버에 변경사항을 만든다.
    • CRUD에서 C/U/D 역할 담당

    8.2. CSRF에 대비

    CSRF의 자세한 내용 : [Django] CSRF와 보안


    8.3. articles/new.py

    {%- raw -%}
    <form action="{% url 'articles:create' %}" method="POST">
      {% csrf_token %}
      <label for="title">제목 :</label>
      <input class="w-100 mb-5 bg-light" type="text" name="title"> <br />
      <label for="content">내용 :</label>
      <textarea class="w-100 mb-5 bg-light" name="content" rows="10"></textarea> <br />
      <a class="btn btn-light btn-sm" href="{% url 'articles:index' %}">이전</a>
      <button class="btn btn-primary btn-sm">완료</button>
    </form>
    {% endraw -%}
    
    • form tag의 method를 기존 'GET'에서, 'POST'로 바꿔준다.
    • csrf_token 태그를 form 태그 내에 입력한다.

    8.4. articles/views.py

    def create(request):
        title = request.POST.get('title')
        content = request.POST.get('content')
        
        Article.objects.create(title=title, content=content)
        return render(request, 'articles/index.html')
    
    • 기존 GET method로 request를 보냈기 때문에 request.GET.get()을 사용
    • 이제는 POST method로 request를 보내기 때문에 request.POST.get()을 사용
    • create가 완료되면, articles/index.html로 이동하도록 return 수정

    image

    의문점 1. 왜 index 페이지에 데이터가 없는걸까?

    의문점 2. 왜 url은 아직도 /create/ 일까?

    정답 : create view 함수에서 다루고 있는 데이터만으로 index 페이지가 render되기 때문!!

    데이터도 안 주고 그대로 rendering하라는 건 도둑놈이지.


    8.5. redirect()

    • 새 URL로 요청을 다시 보낸다.
    • 인자에 따라 HttpResponseRedirect 반환
    • 브라우저는 현재 경로에 따라 전체 URL 자체를 재구성(reconstruct)

    사용 가능한 인자

    • model
    • view name : views.py에서 path 지정할 때 저장한 name
    • 상대경로 & 절대경로

    8.6. redirect() 활용 실습

    # articles/views.py
    
    from django.shortcuts import render, request
    
    def create(request):
        ...
    
        # return render(request, 'articles/index.html')
        return redirect('articles:index')
    
    • render 대신에 redirect 함수를 사용한다.
    • render처럼 django.shortcuts의 함수이므로 import에 추가해준다.
    • article APP의 path name이 'index'인 path를 재실행
    • path(..., views.index, name='index')이므로 views.index 함수가 호출되는 것!!

    9. 상세 페이지 구현 : Variable Routing

    Variable Routing을 이용해 페이지별로 상세 페이지를 구현해보자.


    9.1. urls.py

    from django.urls import path
    from . import views
    
    app_name = 'articles'
    urlpatterns=[
        ...
        path('<int:pk>/', views.detail, name='detail')
    ]
    

    url의 'articles/' 뒤에 바로 숫자가 붙는다면 이를 pk로 하여 views.detial 함수로 request와 함께 전달


    9.2. views.py

    def detail(request, pk):
        article = Article.objects.get(pk=pk)
        context = {
            'article': article,
        }
        return render(request, 'articles/detail.html', context)
    
    • variable routing을 통해 전달받은 pk를 이용해 querySet API의 get 이용
    • DB에서 해당 pk를 가진 record를 READ해서 article에 저장
    • 이를 context 넣어 articles/detail.html에 전달 후 rendering

    9.3. articles/detail.html

    {%- raw -%}
    {% extends 'base.html' %}
    
    {% block content %}
      <h1 class="text-center">DETAIL</h1>
      <hr>
      <h4>{{ article.pk }}번째 글 | {{ article.title }}</h3>
      <hr>
      <p>작성 일자 : {{ article.created_at }}</p>
      <p>수정 일자 : {{ article.updated_at }}</p>
      <p>내용 : {{ article.content }}</p>
    
      <a class="btn btn-sm btn-light" href="{% url 'articles:index' %}">이전</a>
      
    {% endblock content %}
    {% endraw -%}
    

    article의 field에 대한 데이터, 즉 property를 이용해 페이지 구성


    image

    기대했던대로 url처럼 7번 pk를 가진 게시물을 잘 렌더링하였다.


    9.4. articles/index.html

    {%- raw -%}
    {% extends 'base.html' %}
    
    {% block content %}
      <h1 class="text-center">Articles</h1>
      <a href="{% url 'articles:new' %}">게시글 작성</a>
      <hr>
      
      {% for article in articles %}
        <p>{{ article.pk }} | <a href="{% url 'articles:detail' article.pk %}">{{ article.title }}</a></p>
        <p>{{ article.content }}</p>
        <hr>
      {% endfor %}
    {% endblock content %}
    {% endraw -%}
    
    • url tag에 공백을 기준으로 다음 무언가를 넣는다.
    • 이는 url 구성에서 .../<articles:detail>/<article.pk>로 breadcrumb을 만든다.
    • 이를 이용해 index.html에서 각 article에 대한 상세 페이지 이동 링크를 걸어준다.

    image

    성공적으로 링크가 반영되었음을 알 수 있다.


    10. DELETE

    게시물을 제거하는 기능을 추가해보자.


    10.1. urls.py

    from django.urls import path
    from . import views
    
    app_name = 'articles'
    urlpatterns=[
        ...
        path('<int:pk>/delete/', views.delete, name='delete'),
    ]
    
    • delete에 대한 path를 정의
    • <int:pk>/delete/ url : 지우고자하는 article의 번호와 delete가 함께 request 된다.

    10.2. 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)
    
    • urls.py에서 variable routing을 통해 받은 pk를 parameter로 넣는다.
    • article은 해당 pk에 해당하는 DB의 record이다.
    • request의 method가 POST라면 삭제하고, 아니라면 취소해야 한다.
    • if~else 문을 활용한다.

    11. EDIT : UPDATE

    게시글을 수정할 수 있는 기능을 추가해보자.


    11.1. EDIT : urls.py

    from django.urls import path
    from . import views
    
    app_name = 'articles'
    urlpatterns=[
        ...
        path('<int:pk>/edit/', views.edit, name='edit'),
    ]
    

    delete와 마찬가지로, edit도 특정 pk의 게시물을 variable routing으로 받아온다.


    11.2. EDIT : views.py

    def edit(request, pk):
        article = Article.objects.get(pk=pk)
        if request.method=='POST':
            context = {'article': article,}
            return render(request, 'articles/edit.html', context)
        else:
            return redirect('articles:detail', article.pk)
    
    • request와 전달받은 pk를 이용한다.
    • 전달받은 pk로 DB에 조회해 해당 pk의 record를 article에 저장한다.
    • 만약 request가 POST라면 수정 페이지로 이동하고, 아니라면 해당 페이지에 남는다.
    • articles/edit.html 페이지를 재정의해줄 필요가 있다.

    11.3. EDIT : articles/edit.html

    {%- raw -%}
    {% extends 'base.html' %}
    
    {% block content %}
    <h1 class="text-center">EDIT</h1>
    <hr>
    
    <form action="#" method="POST">
      {% csrf_token %}
      <label for="title">제목 : </label>
      <input class="w-100 bg-light" type="text" name="title" value="{{ article.title }}"> <br />
      <label for="content">내용 : </label>
      <textarea class="w-100 bg-light" name="content" rows="10">{{ article.content }}</textarea> <br />
    
      <a href="{% url 'articles:detail' article.pk %}" class="btn btn-sm btn-light">이전</a>
      <input class="btn btn-sm btn-success" type="submit" value="완료">
    </form>
    
    {% endblock content %}
    {% endraw -%}
    
    • width 100%로 작성 페이지와 layout을 맞춰주었다.
    • EDIT 역시 중요한 정보이므로, form tag는 POST method를 적용
    • POST이므로 csrf_token tag 사용
    • 수정할 때 정보를 가져오면 좋으므로, article의 title, content 정보를 넣어준다.
      • input:text의 경우 value 옵션을 이용해 값을 넣어준다.
      • textarea는 value 옵션이 없고 닫는 태그가 있으므로, 사이에 내용으로 넣어준다.

    11.4. EDIT : articles/detail.html

    {%- raw -%}
    {% extends 'base.html' %}
    ...
    
      <form class="d-inline" action="{% url 'articles:edit' article.pk %}" method='POST'>
        <button class="btn btn-sm btn-success">수정</button>
        {% csrf_token %}
      </form>
    
      <form class="d-inline" action="{% url 'articles:delete' article.pk %}" method='POST'>
        <button class="btn btn-sm btn-danger">삭제</button>
        {% csrf_token %}
      </form>
    
    {% endblock content %}
    {% endraw -%}
    
    • article 상세 페이지에서 수정 페이지로 이동하도록 detail.html에 수정 버튼 추가
    • POST method 사용을 위해 form 태그와 button 클래스 사용
    • csrf_token tag 사용

    11.5. 결과

    image

    article 상세 페이지에 수정 버튼이 추가


    image
    • 수정 버튼을 클릭하면 편집 페이지로 이동
    • 기존 article 내용들이 input과 textarea에 존재

    11.6. UPDATE : urls.py

    from django.urls import path
    from . import views
    
    app_name = 'articles'
    urlpatterns=[
        ...
        path('<int:pk>/update/', views.update, name='update'),
    ]
    
    • update와 관련된 내용들을 정의
    • 특정 article에 대한 요청이므로, variable routing으로 pk 전달

    11.7. UPDATE : views.py

    def update(request, pk):
        article = Article.objects.get(pk=pk)
        if request.method=='POST':
            article.title = request.POST.get('title')
            article.content = request.POST.get('content')
            article.save()
        return redirect('articles:detail', article.pk)
    
    • DELETE와 거의 유사하다.
    • 역시 request의 method가 POST인 경우에만 수정할 수 있도록 if문을 사용한다.
    • request에 query로 들어온 수정 데이터를 처리하여 article의 property 갱신
    • article을 save()하여 DB에 저장
    • request method가 POST가 아니라면 수정 사항 없이 그대로 article 상세페이지로 이동

    11.8. 결과

    image
    • 수정사항이 잘 반영됨을 알 수 있다.
    • url에 query형식으로 넣어도 POST method가 아니기 때문에 반영되지 않는다.
    profile

    FE Developer 박승훈

    노력하는 자는 즐기는 자를 이길 수 없다