<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Ethan.Devlog</title>
        <link>https://velog.io/</link>
        <description>글로 쓰면 머리 속에 정리가 되...나?</description>
        <lastBuildDate>Mon, 11 Mar 2024 07:04:27 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Ethan.Devlog</title>
            <url>https://velog.velcdn.com/images/ethan_/profile/b0577039-fc05-4e8b-ae7e-06dd6ee38260/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Ethan.Devlog. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ethan_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[플스 포탈 5G 와이파이가 잡히지 않는 문제 해결]]></title>
            <link>https://velog.io/@ethan_/%ED%94%8C%EC%8A%A4-%ED%8F%AC%ED%83%88-5G-%EC%99%80%EC%9D%B4%ED%8C%8C%EC%9D%B4%EA%B0%80-%EC%9E%A1%ED%9E%88%EC%A7%80-%EC%95%8A%EB%8A%94-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@ethan_/%ED%94%8C%EC%8A%A4-%ED%8F%AC%ED%83%88-5G-%EC%99%80%EC%9D%B4%ED%8C%8C%EC%9D%B4%EA%B0%80-%EC%9E%A1%ED%9E%88%EC%A7%80-%EC%95%8A%EB%8A%94-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Mon, 11 Mar 2024 07:04:27 GMT</pubDate>
            <description><![CDATA[<h2 id="1-증상">1. 증상</h2>
<blockquote>
<p>플레이스테이션 포탈이 국내 정발되지 않은 현재, 해외 직구를 통해 구매하였으나 <code>5G 와이파이를 스캔하지 못하는 증상</code>이 발생함(연결이 안되는 것이 아닌, 와이파이 목록 자체에 없음)</p>
</blockquote>
<hr>
<h2 id="2-대처-실패">2. 대처 (실패)</h2>
<hr>
<h3 id="21-tp링크-공유기-구매">2.1 TP링크 공유기 구매</h3>
<blockquote>
<p>TP 링크의 <code>AX3000</code> 제품이 좋다는 평을 듣고 구매했으나, 크게 만족하지 못함</p>
</blockquote>
<hr>
<h3 id="22-tp링크-와이파이-증폭기-구매">2.2 TP링크 와이파이 증폭기 구매</h3>
<blockquote>
<p>TP 링크의 <code>AC1200</code> 와이파이 증폭기 제품이 좋다는 평을 듣고 구매했으나. 크게 만족하지 못함</p>
</blockquote>
<hr>
<h3 id="23-공유기-및-모뎀-초기화">2.3 공유기 및 모뎀 초기화</h3>
<blockquote>
<p>공유기 및 모뎀을 초기화 했으나 해결되지 않음</p>
</blockquote>
<hr>
<h3 id="24-플스-포탈-초기화">2.4 플스 포탈 초기화</h3>
<blockquote>
<p>플스 포탈을 초기화 했으나 해결되지 않음</p>
</blockquote>
<hr>
<h2 id="3-대처성공">3. 대처(성공)</h2>
<blockquote>
<p>많은 방법을 시도했지만 해결되지 않았기에 TP링크의 제품이 아닌 SK브로드밴드의 공유기를 사용하는 것으로 되돌렸고, 이 과정에서 몇 번의 시행 착오를 통해 <code>플스 포탈</code>로 SK브로드밴드의 <code>5G 와이파이를 연결</code>하는 것을 성공함.</p>
</blockquote>
<blockquote>
<p>따라서 본인이 <code>플스 포탈</code> 또는 <code>닌텐도</code>를 사용하는데 5G 와이파이가 잡히지 않는다면 <code>아래의 방법을 시도</code>해볼 수 있음</p>
</blockquote>
<hr>
<h3 id="31-무선-공유기-로그인">3.1 무선 공유기 로그인</h3>
<blockquote>
<p>사용하고 있는 인터넷의 공유기에 로그인 한다. 통신사마다 방법이 다르기 때문에 <code>구글</code>에 <code>통신사+공유기 설정</code>을 검색한다. SK 제품이라면 아래의 방법을 따른다.</p>
</blockquote>
<hr>
<h4 id="311-무선-공유기-주소-찾기">3.1.1 무선 공유기 주소 찾기</h4>
<blockquote>
<p><code>시작</code> &gt; <code>검색</code> &gt; <code>cmd</code> 입력 &gt; <code>명령 프롬프트</code> 실행 &gt; <code>ipconfig</code> 입력 후, <code>기본 게이트 웨이</code>의 숫자를 복사해 <code>인터넷 주소창</code>에 붙여넣기한 뒤 이동한다.</p>
</blockquote>
<blockquote>
<p>아래와 같은 화면(또는 비슷한 화면)이 나온다면 성공이다.
<img src="https://velog.velcdn.com/images/ethan_/post/cb63436b-6877-424c-834a-778e44eb88a6/image.png" alt=""></p>
</blockquote>
<hr>
<h4 id="312-무선-공유기-id-및-비밀번호">3.1.2 무선 공유기 ID 및 비밀번호</h4>
<blockquote>
<p>아이디와 비밀번호를 입력해야 한다. <code>ID는 admin(소문자)</code>이며 비밀번호는 <code>여섯글자_admin</code> 이며 대소문자를 구분한다. 비밀번호의 여섯글자를 찾기 위해서는 <code>공유기</code>를 직접 확인해야 한다.</p>
</blockquote>
<blockquote>
<p>옛날에는 모뎀과 공유기가 하나로 통일된 제품이 많았지만 요즘은 모뎀과 공유기를 분리한다고 한다. 구분하는 방법은 안테나의 유무다. <code>안테나가 있으면 공유기</code> <code>안테나가 없으면 모뎀</code>이다. 따라서 우리는 <code>안테나가 달린 공유기를 확인</code>해야 한다.</p>
</blockquote>
<blockquote>
<p><code>공유기</code>를 둘러보면 <code>유선 MAC</code> 정보가 있을 것이다. 그 중에서 뒤의 <code>여섯글자(.을 제외한)에 _admin을 붙인게 비밀번호다.</code> </p>
</blockquote>
<h5 id="유선-mac-예시--1234ab12cd34">유선 MAC 예시 : 1234.AB12.CD34</h5>
<h5 id="비밀번호-예시--12cd34_admin대소문자구분">비밀번호 예시 : 12CD34_admin(대소문자구분)</h5>
<hr>
<h4 id="313-와이파이-채널-변경">3.1.3 와이파이 채널 변경</h4>
<blockquote>
<p><code>일본은 와이파이 채널을 100 이하</code>로 사용하고, <code>우리나라는 와이파이 채널을 100 이상</code>으로 사용한다. 우리가 구매한 플스 포탈은 국내 정발되지 않은 일본 제품이기 때문에 우리나라 와이파이 채널을 찾아내지 못한다. <code>우리는 우리 집의 와이파이 채널을 일본처럼 100 이하의 채널로 변경</code>하면 된다.</p>
</blockquote>
<blockquote>
<p><code>ID와 비밀번호 로그인 후</code> &gt; <code>기본설정</code> &gt; <code>WIFI</code> &gt; <code>WIFI 설정</code> 메뉴로 이동한다.</p>
</blockquote>
<blockquote>
<p>채널을 <code>Auto</code>에서 <code>48</code>로 변경 후 <code>적용</code> 버튼을 클릭한다.</p>
</blockquote>
<blockquote>
<p>이제 플스 포탈에서 5G 와이파이를 마음 껏 사용하면 된다.</p>
</blockquote>
<hr>
<h2 id="4-그래도-안된다면">4. 그래도 안된다면?</h2>
<blockquote>
<p>그건 나도 모른다. 당신도 직접 찾아보고 다른 사람들을 위해 글을 작성하면 된다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[rest_framework 모듈 불러오지 못하는 문제]]></title>
            <link>https://velog.io/@ethan_/restframework-%EB%AA%A8%EB%93%88-%EB%B6%88%EB%9F%AC%EC%98%A4%EC%A7%80-%EB%AA%BB%ED%95%98%EB%8A%94-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@ethan_/restframework-%EB%AA%A8%EB%93%88-%EB%B6%88%EB%9F%AC%EC%98%A4%EC%A7%80-%EB%AA%BB%ED%95%98%EB%8A%94-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Wed, 21 Feb 2024 08:19:45 GMT</pubDate>
            <description><![CDATA[<h2 id="1-발생한-문제">1. 발생한 문제</h2>
<blockquote>
<p><code>drf</code> 를 학습하는 과정에서 rest_framework 모듈을 인식하지 못하는 문제가 발생</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ethan_/post/91e78983-6213-4986-9be3-551e9d594242/image.png" alt=""></p>
<hr>
<h2 id="2-접근">2. 접근</h2>
<h3 id="21-가상환경이-적용되었는지-확인">2.1 가상환경이 적용되었는지 확인</h3>
<blockquote>
<p>문제 없이 적용되어 있음
<img src="https://velog.velcdn.com/images/ethan_/post/207ee109-639b-464f-bee8-a4bedd1cd5ca/image.png" alt=""></p>
</blockquote>
<h3 id="22-drf가-잘-설치되어있는지-확인">2.2 drf가 잘 설치되어있는지 확인</h3>
<blockquote>
<p>설치되어 있음
<img src="https://velog.velcdn.com/images/ethan_/post/e172fc3c-d08e-4436-95c8-5ab22b54b7cd/image.png" alt=""></p>
</blockquote>
<h3 id="23-vscode에서-인터프리터를-현재-가상환경으로-설정했는지-확인">2.3 vscode에서 인터프리터를 현재 가상환경으로 설정했는지 확인</h3>
<blockquote>
<p>다른 환경으로 선택됨
<img src="https://velog.velcdn.com/images/ethan_/post/fe91e33b-1b81-4c18-b355-93c8bafe68ea/image.png" alt=""></p>
</blockquote>
<hr>
<h2 id="3-해결">3. 해결</h2>
<blockquote>
<p><code>2.3</code>의 접근을 통해 <code>vscode</code>의 <code>인터프리터</code>를 현재 가상환경으로 변경하니 문제가 해결됨
<img src="https://velog.velcdn.com/images/ethan_/post/5aed5bc1-459b-4782-975f-8f2bd0d2b56c/image.png" alt=""></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django allauth]]></title>
            <link>https://velog.io/@ethan_/Django-%EC%9C%A0%EC%A0%80-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@ethan_/Django-%EC%9C%A0%EC%A0%80-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Thu, 25 Jan 2024 08:39:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>회원 가입, 로그인 기능 등 <code>유저 기능</code>을 구현하기 위해 가상 프로젝트 <code>our_school_now_is</code> 를 시작한다.</p>
</blockquote>
<hr>
<h1 id="1-contribauth와-allauth">1. contrib.auth와 allauth</h1>
<blockquote>
<p>본 프로젝트의 유저 기능을 구현하기 위해 <code>유저 모델</code>은 <code>contrib.auth</code>의 <code>models</code>를 사용하고, 나머지 기능은 <code>allauth</code> 패키지를 통해 구현한다.</p>
</blockquote>
<blockquote>
<p><code>allauth</code> 는 <code>contrib.auth</code>가 지원하지 않는 <code>이메일 인증</code>, <code>소셜 로그인</code> 기능을 지원한다.</p>
</blockquote>
<blockquote>
<p>또한 <code>allauth</code>은 유저기능이 이미 완성되어 있지만 <code>contrib_auth</code>는 필요한 기능을 모두 직접 구현해야 한다.</p>
</blockquote>
<blockquote>
<p>따라서 <code>유저 모델</code>은 <code>contrib.auth</code>를 통해, 기능은 <code>allauth</code>를 통해 구현한다.</p>
</blockquote>
<hr>
<h1 id="2-개발환경-설정">2. 개발환경 설정</h1>
<blockquote>
<p>개발환경은 <code>&lt;dear&gt;</code> 프로젝트에 적용된 개발환경을 사용한다. 프로젝트 루트 디렉토리는 <code>Our_school_now_is</code> 이며, 생성한 앱은 <code>our_school</code> 이다.</p>
</blockquote>
<hr>
<h2 id="21-앱-등록">2.1 앱 등록</h2>
<blockquote>
<p>생성한 <code>our_school</code> 앱을 프로젝트 구성 디렉토리의 <code>settings.py</code>에 등록한다.</p>
</blockquote>
<hr>
<h2 id="22-유저-모델-정의">2.2 유저 모델 정의</h2>
<blockquote>
<p><code>User</code> 모델 클래스를 생성한다. 모델은 <code>AbstractUser</code>를 상속받는다.</p>
</blockquote>
<pre><code class="language-python">#models.py

from django.db import models
from django.contrib.auth.models import AbstractUser

# Create your models here.
class User(AbstractUser):
    pass</code></pre>
<blockquote>
<p>지금 당장은 필드를 추가하지 않기 떄문에 <code>pass</code>를 작성한다.</p>
</blockquote>
<hr>
<h2 id="23-유저-모델-등록">2.3 유저 모델 등록</h2>
<blockquote>
<p>프로젝트 구성 디렉토리의 <code>settings.py</code>에서 <code>our_school</code> 앱의 <code>User</code> 모델을 본 프로젝트의 유저 모델로 사용한다는 것을 정의한다.</p>
</blockquote>
<pre><code class="language-python">#settings.py

AUTH_USER_MODEL = &#39;our_school.User&#39;</code></pre>
<blockquote>
<p>유저 모델을 등록해야 <code>allauth</code> 패키지가 해당 모델을 사용할 수 있다.</p>
</blockquote>
<hr>
<h2 id="24-마이그레이션">2.4 마이그레이션</h2>
<blockquote>
<p>모델이 변경되었으니 <code>makemigrations</code> 과 <code>migrate</code>를 해준다.</p>
</blockquote>
<hr>
<h2 id="25-admin-등록">2.5 admin 등록</h2>
<blockquote>
<p><code>our_school</code> 앱의 <code>admin.py</code>에 <code>User</code> 모델을 등록한다.</p>
</blockquote>
<pre><code class="language-python">#admin.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User

# Register your models here.
admin.site.register(User, UserAdmin)</code></pre>
<blockquote>
<p><code>User</code> 모델을 불러오고, <code>User</code> 모델에 대한 관리자 인터페이스를 제공하는 <code>UserAdmin</code>을 불러온다. 이후 <code>User</code> 모델과, <code>UserAdmin</code> 클래스를 등록한다.</p>
</blockquote>
<blockquote>
<p><code>슈퍼유저</code> 계정을 생성한 뒤 <code>관리자 페이지</code>로 접속해보면 <code>User</code> 관리자 페이지를 확인할 수 있다.
<img src="https://velog.velcdn.com/images/ethan_/post/1153e865-7554-4040-bef2-dd66fd9eb38f/image.png" alt="">
<img src="https://velog.velcdn.com/images/ethan_/post/44a2cd69-f9d0-458e-94d3-b0b008a93237/image.png" alt=""></p>
</blockquote>
<hr>
<h2 id="26-allauth-설치">2.6 allauth 설치</h2>
<blockquote>
<p>명령어를 통해 <code>allauth</code> 를 설치한다.</p>
</blockquote>
<pre><code class="language-bash">pip install django-allauth</code></pre>
<blockquote>
<p>이후 <code>settings.py</code>에 아래의 코드를 추가한다.</p>
</blockquote>
<pre><code class="language-python">#settings.py

AUTHENTICATION_BACKENDS = [
    # Needed to login by username in Django admin, regardless of `allauth`
    &#39;django.contrib.auth.backends.ModelBackend&#39;,

    # `allauth` specific authentication methods, such as login by email
    &#39;allauth.account.auth_backends.AuthenticationBackend&#39;,
]</code></pre>
<blockquote>
<p>이후 <code>settings.py</code> 의 <code>INSTALLED_APPS</code>에 다음 항목들을 추가한다. 만약 일치하는 코드가 이미 작성되어 있다면 추가하지 않는다.</p>
</blockquote>
<pre><code class="language-python">#settings.py

INSTALLED_APPS = [
    &#39;django.contrib.sites&#39;,
    &#39;allauth&#39;,
    &#39;allauth.account&#39;,
    &#39;allauth.socialaccount&#39;,
]</code></pre>
<blockquote>
<p>이후 <code>INSTALLED_APPS</code> 하단에 다음 항목을 추가한다.</p>
</blockquote>
<pre><code class="language-python">#settings.py

SITE_ID = 1</code></pre>
<blockquote>
<p>이후 다음 코드를 추가한다.</p>
</blockquote>
<pre><code class="language-python"># settings.py

EMAIL_BACKEND = &quot;django.core.mail.backends.console.EmailBackend&quot;</code></pre>
<hr>
<h2 id="261-에러-발생">2.6.1 에러 발생</h2>
<blockquote>
<p>본인은 아래와 같은 에러가 나타났다.</p>
</blockquote>
<pre><code>Watching for file changes with StatReloader
Exception in thread django-main-thread:
Traceback (most recent call last):
  File &quot;/Users/gwon-uihyeon/.pyenv/versions/3.9.4/lib/python3.9/threading.py&quot;, line 954, in _bootstrap_inner
    self.run()
  File &quot;/Users/gwon-uihyeon/.pyenv/versions/3.9.4/lib/python3.9/threading.py&quot;, line 892, in run
    self._target(*self._args, **self._kwargs)
  File &quot;/Users/gwon-uihyeon/.pyenv/versions/django-envs/lib/python3.9/site-packages/django/utils/autoreload.py&quot;, line 64, in wrapper
    fn(*args, **kwargs)
  File &quot;/Users/gwon-uihyeon/.pyenv/versions/django-envs/lib/python3.9/site-packages/django/core/management/commands/runserver.py&quot;, line 125, in inner_run
    autoreload.raise_last_exception()
  File &quot;/Users/gwon-uihyeon/.pyenv/versions/django-envs/lib/python3.9/site-packages/django/utils/autoreload.py&quot;, line 87, in raise_last_exception
    raise _exception[1]
  File &quot;/Users/gwon-uihyeon/.pyenv/versions/django-envs/lib/python3.9/site-packages/django/core/management/__init__.py&quot;, line 394, in execute
    autoreload.check_errors(django.setup)()
  File &quot;/Users/gwon-uihyeon/.pyenv/versions/django-envs/lib/python3.9/site-packages/django/utils/autoreload.py&quot;, line 64, in wrapper
    fn(*args, **kwargs)
  File &quot;/Users/gwon-uihyeon/.pyenv/versions/django-envs/lib/python3.9/site-packages/django/__init__.py&quot;, line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File &quot;/Users/gwon-uihyeon/.pyenv/versions/django-envs/lib/python3.9/site-packages/django/apps/registry.py&quot;, line 124, in populate
    app_config.ready()
  File &quot;/Users/gwon-uihyeon/.pyenv/versions/django-envs/lib/python3.9/site-packages/allauth/account/apps.py&quot;, line 17, in ready
    raise ImproperlyConfigured(
django.core.exceptions.ImproperlyConfigured: allauth.account.middleware.AccountMiddleware must be added to settings.MIDDLEWARE</code></pre><blockquote>
<p>구글링을 통해 해결했다. <code>settings.py</code>의 <code>MIDDLEWARE</code>에 아래의 코드를 추가했다.</p>
</blockquote>
<pre><code class="language-python">#settings.py

MIDDLEWARE = [
    &#39;allauth.account.middleware.AccountMiddleware&#39;,
]</code></pre>
<hr>
<h2 id="27-url-패턴-작성">2.7 url 패턴 작성</h2>
<blockquote>
<p>프로젝트 구성 디렉토리의 <code>urls.py</code> 파일을 수정한다.</p>
</blockquote>
<blockquote>
<p>url이 비어있는 경우, allauth.urls를 참조하는 코드를 추가했다.</p>
</blockquote>
<pre><code class="language-python">#urls.py

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

urlpatterns = [
    path(&#39;admin/&#39;, admin.site.urls),
    path(&#39;&#39;, include(&quot;allauth.urls&quot;))
]</code></pre>
<hr>
<h2 id="28-마이그레이션">2.8 마이그레이션</h2>
<blockquote>
<p>에러를 방지하기 위해 다시한 번 <code>makemigrations</code> 와 <code>migrate</code>를 진행한다.</p>
</blockquote>
<hr>
<h2 id="29-결과">2.9 결과</h2>
<blockquote>
<p>개발 서버 실행 후 <code>/login</code> 에 접속하면 아래와 같이 로그인, 비밀번호 찾기, 회원가입 페이지를 확인할 수 있다.
<img src="https://velog.velcdn.com/images/ethan_/post/12a9a649-a364-44bc-bced-8e95f0bbf582/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><code>allauth</code>를 통해 생성된 것을 확인할 수 있다.</p>
</blockquote>
<hr>
<h1 id="3-회원가입-후-홈페이지로-이동">3. 회원가입 후 홈페이지로 이동</h1>
<blockquote>
<p>회원가입을 진행하면 url 상에서는 <code>/profile</code>로 이동되지만 <code>404에러</code>가 발생되는 것을 알 수 있다.</p>
</blockquote>
<blockquote>
<p>이번에는 회원가입 후 <code>홈페이지</code>를 리다이렉션 하는 기능을 알아본다.</p>
</blockquote>
<hr>
<h2 id="31-구성-디렉토리-urls-변경">3.1 구성 디렉토리 urls 변경</h2>
<blockquote>
<p>프로젝트 구성 디렉토리의 <code>urls.py</code>를 다음과 같이 수정한다.</p>
</blockquote>
<pre><code class="language-python">#urls.py

urlpatterns = [
    path(&#39;admin/&#39;, admin.site.urls),
    path(&#39;&#39;, include(&quot;our_school.urls&quot;)),  #추가
    path(&#39;&#39;, include(&quot;allauth.urls&quot;))
]</code></pre>
<blockquote>
<p>이 경우 <code>비어있는 url 접근 시</code>, 상단에 있는 <code>our_school.urls</code>를 먼저 확인한 뒤, 일치하는 url이 없다면 다시 <code>allauth.urls</code>를 확인한다. 만약 그래도 없다면 <code>404 에러</code>를 발생시킨다.</p>
</blockquote>
<hr>
<h2 id="32-앱-디렉토리-urls-생성">3.2 앱 디렉토리 urls 생성</h2>
<blockquote>
<p>다음으로 <code>our_school</code> 앱의 <code>urls.py</code> 를 생성한다.</p>
</blockquote>
<pre><code class="language-python">#ulrs.py

from django.urls import path
from . import views

urlpatterns = [
    path(&#39;&#39;, views.index, name=&quot;index&quot;),
]</code></pre>
<hr>
<h2 id="33-index-뷰-생성">3.3 index 뷰 생성</h2>
<blockquote>
<p><code>index</code> 뷰를 생성한다.</p>
</blockquote>
<pre><code class="language-python"># views.py

from django.shortcuts import render

# Create your views here.

def index(request):
    return render(request, &quot;our_school/index.html&quot;)</code></pre>
<hr>
<h2 id="34-템플릿-생성">3.4 템플릿 생성</h2>
<blockquote>
<p><code>index.html</code> 템플릿을 생성한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--index.html--&gt;

&lt;h1&gt;홈페이지&lt;/h1&gt;</code></pre>
<hr>
<h2 id="35-allauth-세팅-변경">3.5 allauth 세팅 변경</h2>
<blockquote>
<p>현재는 <code>로그인이 성공</code>하면 <code>account/profile</code>로 이동되도록 <code>기본값</code>이 설정되어 있다. <code>allauth</code>의 <code>기본값</code>을 변경하기 위해서는 <code>settings.py</code>의 <code>allauth</code> 세팅을 추가하면 된다.</p>
</blockquote>
<blockquote>
<p><code>allauth</code>의 세팅은 너무 다양해서 <a href="https://docs.allauth.org/en/latest/account/configuration.html">본 문서</a>를 참고하고, 필요한 기능을 그때 그때 찾아서 사용하면 된다.</p>
</blockquote>
<blockquote>
<p>우리가 해야할 일은 <code>로그인 성공 시</code>, <code>account/profile</code>이 아닌, <code>index(url name)</code> 로 이동하도록 변경하는 것이다. <code>settings.py</code>에서 설정하는 방법은 아래와 같다.</p>
</blockquote>
<pre><code class="language-python">#settings.py

ACCOUNT_SIGNUP_REDIRECT_URL = &quot;index&quot;
LOGIN_REDIRECT_URL = &quot;index&quot;</code></pre>
<blockquote>
<p>이렇게 하면 로그인 시, 또는 회원가입 시 <code>index(url name)</code>으로 이동하는 것으로 변경된다.</p>
</blockquote>
<hr>
<h1 id="4-유저-정보-표기">4. 유저 정보 표기</h1>
<blockquote>
<p>현재는 로그인한 유저 정보와, 로그아웃 버튼이 없다. 만들어보자.</p>
</blockquote>
<hr>
<h2 id="41-유저-데이터-접근">4.1 유저 데이터 접근</h2>
<blockquote>
<p>현재 로그인된 유저의 데이터를 <code>view</code>에서는 <code>request.user</code>로, <code>템플릿</code>에서는 <code>{{ user }}</code> 로 접근할 수 있다.</p>
</blockquote>
<blockquote>
<p><code>.is_authenticated</code> 속성을 사용하면, 현재 유저가 로그인 했는지 아닌지를 판단할 수 있다.</p>
</blockquote>
<hr>
<h2 id="42-템플릿-변경">4.2 템플릿 변경</h2>
<blockquote>
<p><code>index.html</code> 템플릿을 다음과 같이 변경한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--index.html--&gt;

&lt;h1&gt;홈페이지&lt;/h1&gt;

{% if user.is_authenticated %}
    &lt;p&gt;반갑습니다 {{ user }} 님&lt;/p&gt;
{% else %}
    &lt;p&gt;로그인이 필요합니다.&lt;/p&gt;
{% endif %}

&lt;navbar&gt;
    {% if user.is_authenticated %}
        &lt;a href=&quot;{% url &quot;account_logout&quot; %}&quot;&gt;로그아웃&lt;/a&gt;
    {% else %}
        &lt;a href=&quot;{% url &quot;account_login&quot; %}&quot;&gt;로그인&lt;/a&gt;
        &lt;a href=&quot;{% url &quot;account_signup&quot; %}&quot;&gt;회원가입&lt;/a&gt;
    {% endif %}
&lt;/navbar&gt;</code></pre>
<blockquote>
<p>살펴보면 <code>if</code>문으로 user가 로그인 한 경우와 그렇지 않은 경우를 구분했다.</p>
</blockquote>
<blockquote>
<p>또한 로그아웃, 로그인, 회원가입으로 이동하는 <code>&lt;a&gt;</code> 태그를 추가했는데, <code>allauth</code>에서 사용하는 url 정보를 확인하려면 <a href="https://github.com/pennersr/django-allauth/blob/main/allauth/account/urls.py">allauth 소스코드</a>를 참고하면 된다.</p>
</blockquote>
<hr>
<h2 id="43-로그아웃-뎁스-줄이기">4.3 로그아웃 뎁스 줄이기</h2>
<blockquote>
<p>현재 페이지는 로그아웃 버튼 클릭 시, 로그아웃 페이지로 이동한다. 로그아웃 버튼 클릭 시 바로 로그아웃되는 기능을 구현하려면 <code>allauth</code>의 세팅을 추가해야 한다.</p>
</blockquote>
<pre><code class="language-python">#settings.py

ACCOUNT_LOGOUT_ON_GET = &quot;True&quot;</code></pre>
<hr>
<h1 id="5-세션-id">5. 세션 ID</h1>
<blockquote>
<p>우리가 페이지에 로그인 하면 다른 페이지에 다녀와도 로그인 상태가 유지되는 경우가 잦다. 이와 관련하여 세션 개념을 알아본다.</p>
</blockquote>
<blockquote>
<p>클라이언트에서 로그인하면 서버에서는 <code>세션ID</code>를 생성한다. 세션ID에는 유저와 관련된 정보들이 담겨있다. 이후 서버는 세션ID를 클라이언트에게 <code>쿠키</code>를 통해 전달하는데, 이후 클라이언트의 요청은 서버로부터 생성된 세션ID를 포함하고 있어 서버는 해당 세션ID를 통해 해당 유저가 로그인된 상태임을 판단한다.</p>
</blockquote>
<blockquote>
<p><code>로그인 정보 기억하기</code> 기능은 웹 브라우저를 종료해도 <code>세션 쿠키</code>를 유지할 것인지 아닌지를 정하는 기능이다.</p>
</blockquote>
<blockquote>
<p><code>allauth</code>에서는 세션 쿠키를 유지할 것인지 아닌지를 설정하는 것 외에도 세션 쿠키의 <code>유효시간</code>을 직접 설정할 수도 있다. <code>기본값은 2주</code>이며, 설정은 <code>초 단위</code>로 가능하다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[제네릭 뷰 context 기본값]]></title>
            <link>https://velog.io/@ethan_/%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B7%B0-context-%EA%B8%B0%EB%B3%B8%EA%B0%92</link>
            <guid>https://velog.io/@ethan_/%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B7%B0-context-%EA%B8%B0%EB%B3%B8%EA%B0%92</guid>
            <pubDate>Mon, 22 Jan 2024 07:27:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>제네릭 뷰에서는 <code>context</code>로 넘겨주는 데이터의 <code>기본값</code>이 있다. 즉, 지정해주지 않아도 자동으로 넘겨주는 데이터가 있으니 코드를 더 단순하게 작성할 수 있다.</p>
</blockquote>
<hr>
<h1 id="1-listview">1. ListView</h1>
<blockquote>
<p><code>ListView</code>에서 <code>기본값</code>이 할당된 데이터를 알아본다.</p>
</blockquote>
<blockquote>
<ol>
<li><code>template_name</code>
기본값은 <code>모델명_list.html</code> 이므로 템플릿 이름이 일치한다면 삭제 가능</li>
</ol>
</blockquote>
<blockquote>
<ol start="2">
<li><code>context_object_name</code>
기본값은 <code>object_list</code>, <code>모델명_list</code> 이므로 템플릿에 <code>object_list</code> 또는 <code>모델명_list</code>로 작성되어 있다면 삭제 가능</li>
</ol>
</blockquote>
<blockquote>
<ol start="3">
<li><code>page_kwarg</code>
기본값은 <code>page</code> 이므로 템플릿에 <code>page</code>로 작성되어 있다면 삭제 가능</li>
</ol>
</blockquote>
<hr>
<h2 id="11-비교">1.1 비교</h2>
<blockquote>
<p>기존에 작성된 코드와 <code>기본값</code>을 이용해 삭제한 변경된 코드를 비교하면 다음과 같다.</p>
</blockquote>
<pre><code class="language-python">#기존
class PostListView(ListView):
    model = Post
    template_name = &quot;posts/post_list.html&quot;  #기본값은 모델명_list.html
    context_object_name = &quot;posts&quot;  #기본값은 object_list, 모델명_list
    ordering = [&#39;-dt_created&#39;]
    paginate_by = 9
    page_kwarg = &#39;page&#39;  #기본값은 page</code></pre>
<pre><code class="language-python">#변경
class PostListView(ListView):
    model = Post
    ordering = [&#39;-dt_created&#39;]
    paginate_by = 9</code></pre>
<hr>
<h1 id="2-detailview">2. DetailView</h1>
<blockquote>
<p><code>DetailView</code>에서 <code>기본값</code>이 할당된 데이터를 알아본다.</p>
</blockquote>
<blockquote>
<ol>
<li><code>template_name</code>
기본값은 <code>모델명_detail.html</code> 이므로 템플릿 이름이 일치한다면 삭제 가능</li>
</ol>
</blockquote>
<blockquote>
<ol start="2">
<li><code>pk_url_kwarg</code>
기본값은 <code>pk</code> 이므로 <code>url 패턴</code>에서 <code>&lt;int:pk&gt;</code>라면 삭제 가능</li>
</ol>
</blockquote>
<blockquote>
<ol start="3">
<li><code>context_object_name</code>
기본값은 <code>모델명</code> 이므로 템플릿에 모델명으로 작성되었다면 삭제 가능</li>
</ol>
</blockquote>
<hr>
<h2 id="21-비교">2.1 비교</h2>
<blockquote>
<p>기존에 작성된 코드와 <code>기본값</code>을 이용해 삭제한 변경된 코드를 비교하면 다음과 같다.</p>
</blockquote>
<pre><code class="language-python">#기존
class PostDetailView(DetailView):
    model = Post
    template_name = &quot;posts/post_detail.html&quot;  #기본값은 모델명_detail.html
    pk_url_kwarg = &quot;post_id&quot;  #기본값은 pk
    context_object_name = &quot;post&quot;  #기본값은 모델명</code></pre>
<pre><code class="language-python">#변경
class PostDetailView(DetailView):
    model = Post</code></pre>
<hr>
<h1 id="3-createview">3. CreateView</h1>
<blockquote>
<p><code>CreateView</code>에서 <code>기본값</code>이 할당된 데이터를 알아본다.</p>
</blockquote>
<blockquote>
<ol>
<li><code>template_name</code>
기본값은 <code>모델명_form.html</code>이므로 템플릿 이름이 일치한다면 삭제 가능</li>
</ol>
</blockquote>
<hr>
<h2 id="31-비교">3.1 비교</h2>
<blockquote>
<p>기존에 작성된 코드와 <code>기본값</code>을 이용해 삭제한 변경된 코드를 비교하면 다음과 같다.</p>
</blockquote>
<pre><code class="language-python">#기존
class PostCreateView(CreateView):
    model = Post
    form_class = PostForm
    template_name = &quot;posts/post_form.html&quot;  #기본값은 모델명_form.html

    def get_success_url(self):
        return reverse(&quot;post-detail&quot;, kwargs={&#39;post_id&#39;: self.object.id})</code></pre>
<pre><code class="language-python">#변경
class PostCreateView(CreateView):
    model = Post
    form_class = PostForm

    def get_success_url(self):
        return reverse(&quot;post-detail&quot;, kwargs={&#39;pk&#39;: self.object.id})</code></pre>
<blockquote>
<p><code>url 패턴</code>에서 상세 페이지의 <code>&lt;int:post_id&gt;를 &lt;int:pk&gt;로 변경</code>했으니 <code>kwargs=</code> 의 <code>post_id</code>를 <code>pk</code>로 변경해야 한다.</p>
</blockquote>
<hr>
<h1 id="4-updateview">4. UpdateView</h1>
<blockquote>
<p><code>UpdateView</code>에서 <code>기본값</code>이 할당된 데이터를 알아본다.</p>
</blockquote>
<blockquote>
<ol>
<li><code>template_name</code>
기본값은 <code>모델명_detail.html</code> 이므로 템플릿 이름이 일치한다면 삭제 가능</li>
</ol>
</blockquote>
<blockquote>
<ol start="2">
<li><code>pk_url_kwarg</code>
기본값은 <code>pk</code> 이므로 <code>url 패턴</code>에서 <code>&lt;int:pk&gt;</code>라면 삭제 가능</li>
</ol>
</blockquote>
<hr>
<h2 id="41-비교">4.1 비교</h2>
<blockquote>
<p>기존에 작성된 코드와 <code>기본값</code>을 이용해 삭제한 변경된 코드를 비교하면 다음과 같다.</p>
</blockquote>
<pre><code class="language-python">#기존
class PostUpdateView(UpdateView):
    model = Post
    form_class = PostForm
    template_name = &quot;posts/post_form.html&quot;  #기본값은 모델명_form.html
    pk_url_kwarg = &quot;post_id&quot;  #기본값은 pk

    def get_success_url(self):
        return reverse(&quot;post-detail&quot;, kwargs={&quot;post_id&quot;: self.object.id})</code></pre>
<pre><code class="language-python">#변경
class PostUpdateView(UpdateView):
    model = Post
    form_class = PostForm

    def get_success_url(self):
        return reverse(&quot;post-detail&quot;, kwargs={&quot;pk&quot;: self.object.id})</code></pre>
<blockquote>
<p><code>url 패턴</code>에서 수정 페이지의 <code>&lt;int:post_id&gt;를 &lt;int:pk&gt;로 변경</code>했으니 <code>kwargs=</code> 의 <code>post_id</code>를 <code>pk</code>로 변경해야 한다.</p>
</blockquote>
<hr>
<h1 id="5-deleteview">5. DeleteView</h1>
<blockquote>
<p><code>DeleteView</code>에서 <code>기본값</code>이 할당된 데이터를 알아본다.</p>
</blockquote>
<blockquote>
<ol>
<li><code>template_name</code>
기본값은 <code>모델명_detail.html</code> 이므로 템플릿 이름이 일치한다면 삭제 가능</li>
</ol>
</blockquote>
<blockquote>
<ol start="2">
<li><code>pk_url_kwarg</code>
기본값은 <code>pk</code> 이므로 <code>url 패턴</code>에서 <code>&lt;int:pk&gt;</code>라면 삭제 가능</li>
</ol>
</blockquote>
<blockquote>
<ol start="3">
<li><code>context_object_name</code>
기본값은 <code>모델명</code> 이므로 템플릿에 모델명으로 작성되었다면 삭제 가능</li>
</ol>
</blockquote>
<hr>
<h2 id="51-비교">5.1 비교</h2>
<blockquote>
<p>기존에 작성된 코드와 <code>기본값</code>을 이용해 삭제한 변경된 코드를 비교하면 다음과 같다.</p>
</blockquote>
<pre><code class="language-python">#기존
class PostDeleteView(DeleteView):
    model = Post
    template_name = &quot;posts/post_confirm_delete.html&quot;  #기본값은 모델명_confirm_delete.html
    pk_url_kwarg = &quot;post_id&quot;  #기본값은 pk
    context_object_name = &quot;post&quot;  #기본값은 모델명

    def get_success_url(self):
        return reverse(&quot;post-list&quot;)</code></pre>
<pre><code class="language-python">#변경
class PostDeleteView(DeleteView):
    model = Post

    def get_success_url(self):
        return reverse(&quot;post-list&quot;)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[제네릭 뷰 DeleteView]]></title>
            <link>https://velog.io/@ethan_/%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B7%B0-DeleteView</link>
            <guid>https://velog.io/@ethan_/%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B7%B0-DeleteView</guid>
            <pubDate>Fri, 19 Jan 2024 08:38:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>반복된 내용을 작성하기가 매우 힘이드니 약식으로 설명한다.</p>
</blockquote>
<hr>
<h2 id="deleteview를-상속하는-클래스형-뷰-생성">DeleteView를 상속하는 클래스형 뷰 생성</h2>
<pre><code class="language-python">#views.py

class PostDeleteView(DeleteView):
    model = Post
    template_name = &quot;posts/post_confirm_delete.html&quot;
    pk_url_kwarg = &quot;post_id&quot;
    context_object_name = &quot;post&quot;

    def get_success_url(self):
        return reverse(&quot;post-list&quot;)
</code></pre>
<blockquote>
<p><code>삭제 확인 페이지</code>로 이동하기 위해 <code>post_confirm_delete.html</code> <code>템플릿</code>을 렌더한다.</p>
</blockquote>
<blockquote>
<p>삭제 후 <code>포스트 목록 페이지</code>로 이동한다.</p>
</blockquote>
<hr>
<h2 id="url-패턴-변경">url 패턴 변경</h2>
<pre><code class="language-python">#urls.py

urlpatterns = [
    # path(&#39;&#39;, views.index),
    path(&#39;&#39;, views.PostListView.as_view(), name=&quot;post-list&quot;),
    path(&#39;posts/new/&#39;, views.PostCreateView.as_view(), name=&quot;post-create&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/&#39;, views.PostDetailView.as_view(), name=&quot;post-detail&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/edit/&#39;, views.PostUpdateView.as_view(), name=&quot;post-update&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/delete/&#39;, views.PostDeleteView.as_view(), name=&quot;post-delete&quot;),
]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[제네릭 뷰 UpdateView]]></title>
            <link>https://velog.io/@ethan_/%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B7%B0-UpdateView</link>
            <guid>https://velog.io/@ethan_/%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B7%B0-UpdateView</guid>
            <pubDate>Fri, 19 Jan 2024 08:30:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><code>제네릭 뷰</code>와 관련된 이전 포스팅에서 반복했던 내용이기 때문에 간단하게 작성한다.</p>
</blockquote>
<hr>
<h1 id="1-updateview를-상속받는-클래스형-뷰-생성">1. UpdateView를 상속받는 클래스형 뷰 생성</h1>
<hr>
<h2 id="11-기존-함수형-뷰-post_update">1.1 기존 함수형 뷰 post_update</h2>
<blockquote>
<p>기존에 작성한 <code>함수형 뷰</code>는 아래와 같다. 주석처리 한다.</p>
</blockquote>
<pre><code class="language-python">#views.py

# def post_update(request, post_id):
#     post = get_object_or_404(Post, id=post_id)
#     if request.method == &quot;POST&quot;:
#         post_form = PostForm(request.POST, instance=post)
#         if post_form.is_valid():
#             post_form.save()
#             return redirect(&quot;post-detail&quot;, post_id=post.id)
#     else:
#         post_form = PostForm(instance=post)
#     context = {&quot;post_form&quot;: post_form}
#     return render(request, &quot;posts/post_form.html&quot;, context)</code></pre>
<hr>
<h2 id="12-변경된-클래스형-뷰-postupdateview">1.2 변경된 클래스형 뷰 PostUpdateView</h2>
<blockquote>
<p><code>UpdateView</code> 를 상속받는 클래스형 뷰 PostUpdateView를 작성한다.</p>
</blockquote>
<pre><code class="language-python">#views.py &gt; PostUpdateView

class PostUpdateView(UpdateView):
    model = Post
    form_class = PostForm
    template_name = &quot;posts/post_form.html&quot;
    pk_url_kwarg = &quot;post_id&quot;

    def get_success_url(self):
        return reverse(&quot;post-detail&quot;, kwargs={&quot;post_id&quot;: self.object.id})</code></pre>
<blockquote>
<p><code>포스트 수정</code> 페이지는 수정 후 해당 포스트의 <code>상세 페이지</code>로 이동하기 때문에 <code>get_success_url</code> 함수를 작성한다.</p>
</blockquote>
<hr>
<h2 id="13-url-패턴-변경">1.3 url 패턴 변경</h2>
<blockquote>
<p>url 패턴에서 post_update를 PostUpdateView로 변경해야 한다.</p>
</blockquote>
<pre><code class="language-python">#urls.py

urlpatterns = [
    # path(&#39;&#39;, views.index),
    path(&#39;&#39;, views.PostListView.as_view(), name=&quot;post-list&quot;),
    path(&#39;posts/new/&#39;, views.PostCreateView.as_view(), name=&quot;post-create&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/&#39;, views.PostDetailView.as_view(), name=&quot;post-detail&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/edit/&#39;, views.PostUpdateView.as_view(), name=&quot;post-update&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/delete/&#39;, views.post_delete, name=&quot;post-delete&quot;),
]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[제네릭 뷰 DetailView]]></title>
            <link>https://velog.io/@ethan_/%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B7%B0-DetailView</link>
            <guid>https://velog.io/@ethan_/%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B7%B0-DetailView</guid>
            <pubDate>Fri, 19 Jan 2024 08:19:32 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>장고의 <code>DetailView</code> 제네릭 뷰를 상속받는 상속 페이지를 구현하는 방법을 알아본다.</p>
</blockquote>
<hr>
<h1 id="1-detailview를-상속받는-클래스형-뷰-생성">1. DetailView를 상속받는 클래스형 뷰 생성</h1>
<hr>
<h2 id="11-기존-함수형-뷰-post_detail">1.1 기존 함수형 뷰 post_detail</h2>
<blockquote>
<p>기존에 작성된 <code>함수형 뷰</code> <code>post_detail</code>은 아래와 같다. 주석처리 해준다.</p>
</blockquote>
<pre><code class="language-python">#views.py

# def post_detail(request, post_id):
#     post = get_object_or_404(Post, id=post_id)
#     context = {&quot;post&quot;: post}
#     return render(request, &#39;posts/post_detail.html&#39;, context=context)</code></pre>
<hr>
<h2 id="12-변경된-클래스형-뷰-postdetailview">1.2 변경된 클래스형 뷰 PostDetailView</h2>
<blockquote>
<p>DetailView를 상속받는 클래스형 뷰를 생성한다.</p>
</blockquote>
<pre><code class="language-python">#views.py &gt; PostDetailView

class PostDetailView(DetailView):
    model = Post
    template_name = &quot;posts/post_detail.html&quot;
    pk_url_kwarg = &quot;post_id&quot;
    context_object_name = &quot;post&quot;</code></pre>
<blockquote>
<p>접근할 <code>모델</code>을 정의하고, 렌더할 <code>템플릿</code>을 정의하고, url로부터 전달받는 <code>인자</code>를 정의하고, 데이터에 접근하기 위해 사용될 <code>이름</code>을 정의한다.</p>
</blockquote>
<blockquote>
<p>마찬가지로 <code>context_object_name</code> 변수에 지정한 <code>값</code>을 이용해 <code>템플릿</code>에서 해당 <code>값</code>을 통해 데이터에 접근할 수 있다.</p>
</blockquote>
<blockquote>
<p>또한 <code>DetailView</code>를 상속받기 위해서는 해당 클래스를 불러와야 한다.</p>
</blockquote>
<hr>
<h2 id="13-url-패턴-변경">1.3 url 패턴 변경</h2>
<blockquote>
<p>url 패턴에서 post_detail을 PostDetailView로 변경해야 한다.</p>
</blockquote>
<pre><code class="language-python">#urls.py

urlpatterns = [
    # path(&#39;&#39;, views.index),
    path(&#39;&#39;, views.PostListView.as_view(), name=&quot;post-list&quot;),
    path(&#39;posts/new/&#39;, views.PostCreateView.as_view(), name=&quot;post-create&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/&#39;, views.PostDetailView.as_view(), name=&quot;post-detail&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/edit/&#39;, views.post_update, name=&quot;post-update&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/delete/&#39;, views.post_delete, name=&quot;post-delete&quot;),
]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[제네릭 뷰 ListView]]></title>
            <link>https://velog.io/@ethan_/%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B7%B02</link>
            <guid>https://velog.io/@ethan_/%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B7%B02</guid>
            <pubDate>Fri, 19 Jan 2024 07:53:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이전 포스팅에서는 <code>게시글 작성 페이지</code>를 <code>CreateView</code>를 통해 구현하는 방법을 배웠다. 본 포스팅에서는 <code>게시글 목록 페이지</code>를 <code>ListView</code>를 통해 구현하는 방법을 알아본다.</p>
</blockquote>
<h1 id="1-listview를-상속받은-클래스형-뷰-생성">1. ListView를 상속받은 클래스형 뷰 생성</h1>
<hr>
<h2 id="11-기존-함수형-뷰-post_list">1.1 기존 함수형 뷰 post_list</h2>
<blockquote>
<p>기존에 작성된 함수형 뷰는 아래와 같다. 주석처리 해준다.</p>
</blockquote>
<pre><code class="language-python">#views.py

# def post_list(request):
#     posts = Post.objects.all().order_by(&quot;-dt_modified&quot;)  #생성일 기준 내림차순으로 변경
#     page = request.GET.get(&quot;page&quot;, 1)  #GET 요청 방식일 때 url에서 로컬 호스트/ 다음 page에 해당하는 값을 가져오고 값이 없다면 1을 기본값으로 함
#     paginator = Paginator(posts, 9)  #posts를 페이지 당 9개씩 호출
#     page_obj = paginator.get_page(page)  #가져온 page 값에 해당하는 페이지의 데이터를 불러온다
#     context = {&quot;page_obj&quot;: page_obj}
#     return render(request, &#39;posts/post_list.html&#39;, context=context)</code></pre>
<blockquote>
<p>다시봐도 상당히 복잡하다.</p>
</blockquote>
<hr>
<h2 id="12-변경된-클래스형-뷰-postlistview">1.2 변경된 클래스형 뷰 PostListView</h2>
<blockquote>
<p><code>ListView</code> 제네릭 뷰를 상속받은 클래스를 생성한다.</p>
</blockquote>
<pre><code class="language-python">#views.py

class PostListView(ListView):
    model = Post
    template_name = &quot;posts/post_list.html&quot;
    context_object_name = &quot;posts&quot;
    ordering = [&quot;-dt_created&quot;]
    paginate_by = 9
    page_kwarg = &#39;page&#39;</code></pre>
<blockquote>
<p>접근할 <code>모델</code>을 정의하고, 렌더할 <code>템플릿</code>을 정의하고, context로 전달할 <code>객체 이름</code>을 정의하고, 정렬 기준을 <code>오름차순</code>으로 정의하고, 페이지 당 표기되는 <code>게시글 수</code>를 정의하고, 현재 페이지를 조회하기 위해 전달하는 <code>이름</code>을 정의한다.</p>
</blockquote>
<blockquote>
<p>기존처럼 복잡한 구조가 아닌, 변수에 대한 값만 정해주면 훨씬 간단하게 작성할 수 있다.</p>
</blockquote>
<hr>
<h3 id="121-주의할-점">1.2.1 주의할 점</h3>
<blockquote>
<p><code>ListView</code>를 상속받기 위해서는 <code>import</code> 해야한다.</p>
</blockquote>
<blockquote>
<p><code>ListView</code>는 <code>context</code>에 각 페이지에 해당하는 데이터를 전달할 때, 데이터의를 <code>page_obj</code>라는 <code>키</code>의 <code>값</code>으로 전달한다.</p>
<blockquote>
<p>따라서 <code>템플릿</code>에서는 <code>page_obj</code>를 통해 각 페이지에 해당하는 데이터에 접근할 수 있다.</p>
<blockquote>
<p>여기서 <code>context_object_name</code> 변수에 지정하는 <code>값(여기서는 posts)</code>을 통해서도 접근할 수 있다.</p>
</blockquote>
</blockquote>
</blockquote>
<blockquote>
<p><code>page_kwarg</code>는 현재 페이지를 어떤 <code>값</code>으로 조회하는지를 전달한다.</p>
<blockquote>
<p>이해를 위해 <code>템플릿</code>의 <code>처음으로</code> 버튼을 살펴보면 <code>&lt;a class=&quot;able&quot; href=&quot;?page=1&quot;&gt;&amp;lt&amp;lt&lt;/a&gt;</code> 태그로 작성되어 있다.</p>
</blockquote>
<blockquote>
<p>여기서 url을 살펴보면 <code>?page=1</code>로 작성된 것을 확인할 수 있는데 이는 1번 페이지로 이동하는 것을 말한다.</p>
</blockquote>
<blockquote>
<p>만약 <code>템플릿</code>에 작성된 <code>page</code> 이름이 <code>pages</code>로 변경되는 것과 같이 <code>page_kwarg</code>에 지정한 값과 다르다면, 원하는 페이지로 이동하지 못한다.</p>
</blockquote>
</blockquote>
<hr>
<h2 id="13-템플릿-수정">1.3 템플릿 수정</h2>
<blockquote>
<p>데이터에 접근하기 위한 <code>템플릿 변수</code>를 <code>page_obj</code> 또는 <code>context_object_name</code>에 지정한 값 <code>posts</code> 둘 중 하나를 사용해야 하는데, <code>&lt;dear&gt;</code> 프로젝트는 이미 <code>page_obj</code>로 사용하고 있기 때문에 변경하지 않아도 된다. </p>
</blockquote>
<pre><code class="language-html">&lt;!--post_list.html--&gt;

{% block content %}
    &lt;div class=&quot;btn_post&quot;&gt;
        &lt;a href={% url &quot;post-create&quot; %}&gt;기록하기&lt;/a&gt;
    &lt;/div&gt;
    {% if page_obj %}
        &lt;div class=&quot;post_container&quot;&gt;
            {% for post in page_obj.object_list %}
                &lt;div class=&quot;post&quot;&gt;&lt;a href=&quot;{% url &quot;post-detail&quot; post.id %}&quot;&gt;
                    &lt;h2 class=&quot;title&quot;&gt;{{post.title}}&lt;/h2&gt;
                    &lt;p class=&quot;date&quot;&gt;{{post.dt_created|date:&quot;Y년 m월 d일 H시 i분&quot;}}&lt;/p&gt;
                    &lt;p class=&quot;text&quot;&gt;{{post.content}}&lt;/p&gt;
                &lt;/a&gt;&lt;/div&gt;
            {% endfor %}
        &lt;/div&gt;

        &lt;div class=&quot;pagination&quot;&gt;
            {% if page_obj.has_previous %}
                &lt;a class=&quot;able&quot; href=&quot;?page=1&quot;&gt;&amp;lt&amp;lt&lt;/a&gt;
            {% else %}
                &lt;a class=&quot;disable&quot; onclick=&quot;return false;&quot; href=&quot;#&quot;&gt;&amp;lt&amp;lt&lt;/a&gt;
            {% endif %}

            {% if page_obj.has_previous %}
                &lt;a class=&quot;able&quot; href=&quot;?page={{ page_obj.previous_page_number }}&quot;&gt;&amp;lt&lt;/a&gt;
            {% else %}
                &lt;a class=&quot;disable&quot; onclick=&quot;return false;&quot; href=&quot;#&quot;&gt;&amp;lt&lt;/a&gt;
            {% endif %}


            {% for page_number in page_obj.paginator.page_range %}
                {% if page_number == page_obj.number %}
                    &lt;a class=&quot;disable&quot; id=&quot;current&quot; onclick=&quot;return false;&quot; href=&quot;#&quot;&gt;{{ page_number }}&lt;/a&gt;
                {% elif page_obj.number &lt;= 3 and page_number &lt;= 5 %}
                    &lt;a class=&quot;able&quot; href=&quot;?page={{ page_number }}&quot;&gt;{{ page_number }}&lt;/a&gt;
                {% elif page_obj.number &gt; page_obj.paginator.num_pages|add:-3 and page_number &gt;= page_obj.paginator.num_pages|add:-4 %}
                    &lt;a class=&quot;able&quot; href=&quot;?page={{ page_number }}&quot;&gt;{{ page_number }}&lt;/a&gt;
                {% elif page_number &gt;= page_obj.number|add:-2 and page_number &lt;= page_obj.number|add:2 %}
                    &lt;a class=&quot;able&quot; href=&quot;?page={{ page_number }}&quot;&gt;{{ page_number }}&lt;/a&gt;
                {% endif %}
            {% endfor %}


            {% if page_obj.has_next %}
            &lt;a class=&quot;able&quot; href=&quot;?page={{ page_obj.next_page_number }}&quot;&gt;&amp;gt&lt;/a&gt;
            {% else %}
            &lt;a class=&quot;disable&quot; onclick=&quot;return false;&quot; href=&quot;#&quot;&gt;&amp;gt&lt;/a&gt;
            {% endif %}

            {% if page_obj.has_next %}
            &lt;a class=&quot;able&quot; href=&quot;?page={{ page_obj.paginator.num_pages }}&quot;&gt;&amp;gt&amp;gt&lt;/a&gt;
            {% else %}
            &lt;a class=&quot;disable&quot; onclick=&quot;return false;&quot; href=&quot;#&quot;&gt;&amp;gt&amp;gt&lt;/a&gt;
            {% endif %}
        &lt;/div&gt;

        {% else %}
        &lt;div class=&quot;blank&quot;&gt;
            &lt;p&gt;
                등록된 게시글이 없습니다.&lt;br&gt;
                게시글을 등록해보세요.
            &lt;/p&gt;
        &lt;/div&gt;
    {% endif %}

{% endblock content %}</code></pre>
<hr>
<h2 id="14-url-패턴-변경">1.4 url 패턴 변경</h2>
<blockquote>
<p>url 패턴에서 <code>post_list</code>를 <code>PostListView</code>로 변경해야 한다.</p>
</blockquote>
<pre><code class="language-python">urls.py

urlpatterns = [
    # path(&#39;&#39;, views.index),
    path(&#39;&#39;, views.PostListView.as_view(), name=&quot;post-list&quot;),
    path(&#39;posts/new/&#39;, views.PostCreateView.as_view(), name=&quot;post-create&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/&#39;, views.PostDetailView.as_view(), name=&quot;post-detail&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/edit/&#39;, views.post_update, name=&quot;post-update&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/delete/&#39;, views.post_delete, name=&quot;post-delete&quot;),
]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[제네릭 뷰 CreateView]]></title>
            <link>https://velog.io/@ethan_/%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B7%B0</link>
            <guid>https://velog.io/@ethan_/%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B7%B0</guid>
            <pubDate>Fri, 19 Jan 2024 07:12:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><code>클래스형 뷰</code>가 개발자들이 자주 쓰는 기능들을 <code>상위 클래스</code>로 <code>상속</code>받아 사용한다면, 제네릭 뷰는 <code>상속</code>받을 수 있는 <code>이미 만들어진 상위 클래스</code>를 말한다.</p>
</blockquote>
<blockquote>
<p><code>CRUD</code>에서 <code>Read</code>에 해당하는 클래스형 함수를 만들 때, 모델에 접근해 데이터를 불러오는 코드를 직접 작성하지 않고도 <code>ListView</code> 또는 <code>DetailView</code> 제네릭 뷰를 상속받으면 접근할 <code>모델</code>과 렌더할 <code>템플릿</code> 만 작성하면 된다.</p>
</blockquote>
<hr>
<h1 id="1-postcreateview를-제네릭-뷰를-사용해서-변경">1. PostCreateView를 제네릭 뷰를 사용해서 변경</h1>
<blockquote>
<p>이전 포스팅에 작성된 <code>클래스형 함수</code> <code>PostCreateView</code>는 장고의 <code>View</code>를 상속받아 작성됐다. <code>제네릭 뷰</code>를 상속받아 더 간편하게 <code>CRUD</code>의 <code>CREATE</code> 기능을 구현하는 방법을 알아본다.</p>
</blockquote>
<hr>
<h2 id="11-postcreateview-비교">1.1 PostCreateView 비교</h2>
<blockquote>
<p>기존에 작성된 코드와 <code>제네릭뷰</code>(CreateView)를 상속받아 작성된 코드를 비교해보자.</p>
</blockquote>
<blockquote>
<p>기존에 작성된 코드는 아래와 같다.</p>
</blockquote>
<pre><code class="language-python"># class PostCreateView(View):
#     def get(self, request):
#         post_form = PostForm()
#         context = {&quot;post_form&quot;: post_form}
#         return render(request, &#39;posts/post_form.html&#39;, context=context)

#     def post(self, request):
#         post_form = PostForm(request.POST)
#         if post_form.is_valid():
#             new_post = post_form.save()
#             return redirect(&quot;post-detail&quot;, post_id=new_post.id)
#         else:
#             context = {&quot;post_form&quot;: post_form}
#             return render(request, &#39;posts/post_form.html&#39;, context=context)</code></pre>
<blockquote>
<p><code>제네릭 뷰</code>(CreateView)를 상속받아 다시 작성된 코드는 아래와 같다.</p>
</blockquote>
<pre><code class="language-python">class PostCreateView(CreateView):
    model = Post
    form_class = PostForm
    template_name = &quot;posts/post_form.html&quot;

    def get_success_url(self):
        return reverse(&quot;post-detail&quot;, kwargs={&#39;post_id&#39;: self.object.id})</code></pre>
<blockquote>
<p>아래의 코드는 접근할 <code>모델</code>, 사용할 <code>폼</code>, 렌더할 <code>템플릿</code>, 그리고 게시글 생성 후 이동할 <code>url</code>만 작성했지만 기능은 일치한다.</p>
</blockquote>
<blockquote>
<p>그 이유는 상속받은 <code>CreateView</code> 제네릭 뷰에서 다른 내용들을 포함하고 있기 때문이다.</p>
</blockquote>
<hr>
<h3 id="111-주의할-점">1.1.1 주의할 점</h3>
<blockquote>
<p><code>CreateView</code>는 사용할 <code>폼</code>을 템플릿에 전달할 때 <code>context</code>에 <code>{&quot;form&quot;: {사용할 폼}}</code>을 전달한다. 따라서 기존에 <code>context</code>로 전달하는 <code>폼</code>의 <code>키</code>가 다른 이름이라면 <code>템플릿에서 변경</code>해야 한다.</p>
<blockquote>
<p><code>&lt;dear&gt;</code> 프로젝트에서는 기존에는 <code>{&quot;post_form&quot;:PostForm}</code>으로 전달 했는데 <code>키</code>가 <code>post_form</code> 에서 <code>form</code> 으로 변경되어 전달되기 때문에 <code>템플릿</code>에서 해당 부분만 변경하면 된다.</p>
</blockquote>
</blockquote>
<blockquote>
<p><code>reverse</code> 함수는 <code>redirect</code>과는 다르게 url을 문자열로 생성하고 반환하는 역할만 한다. <code>CreateView</code> 제네릭 뷰에서 이미 해당 url로 이동하는 기능을 내장하고 있기 때문에 <code>reverse</code>를 사용하는 것 같다.</p>
<blockquote>
<p>정확한 이해를 위해 각종 블로그를 찾아봤는데 <code>redirect</code>는 url 경로가 변경되면 변경된 경로를 모두 찾아 수정해야하고 <code>reverse</code>는 <code>name</code> 속성을 이용해 경로를 수정하지 않아도 된다는 내용이 많았다.</p>
</blockquote>
<blockquote>
<p>그러나 <code>redirect</code>도 마찬가지로 <code>name</code> 속성을 이용하면 url 경로를 하드코딩하지 않아도 되기 때문에 변경된 경로를 모두 찾아 수정할 필요가 없지 않나 라는 의문이 들었다.</p>
</blockquote>
<blockquote>
<p>해당 의문에 대한 명확한 답을 찾게된다면 다시 정리해서 수정하겠다.</p>
</blockquote>
</blockquote>
<hr>
<h2 id="12-url-패턴-변경">1.2 url 패턴 변경</h2>
<blockquote>
<p>url 패턴에서 <code>post_create</code>를 <code>PostCreateView</code>로 변경해야 한다.</p>
</blockquote>
<pre><code class="language-python">urls.py

urlpatterns = [
    # path(&#39;&#39;, views.index),
    path(&#39;&#39;, views.PostListView.as_view(), name=&quot;post-list&quot;),
    path(&#39;posts/new/&#39;, views.PostCreateView.as_view(), name=&quot;post-create&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/&#39;, views.PostDetailView.as_view(), name=&quot;post-detail&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/edit/&#39;, views.post_update, name=&quot;post-update&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/delete/&#39;, views.post_delete, name=&quot;post-delete&quot;),
]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[클래스형 뷰]]></title>
            <link>https://velog.io/@ethan_/%ED%81%B4%EB%9E%98%EC%8A%A4%ED%98%95-%EB%B7%B0</link>
            <guid>https://velog.io/@ethan_/%ED%81%B4%EB%9E%98%EC%8A%A4%ED%98%95-%EB%B7%B0</guid>
            <pubDate>Fri, 19 Jan 2024 05:27:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>지금까지는 <code>함수형 뷰</code>를 사용했다. <code>함수형 뷰</code>는 모든 로직을 직접 구현하기 때문에 직관적이지만, <code>클래스형 뷰</code>를 사용하면 이미 작성된 <code>상위 클래스</code>를 상속받을 수 있어 더 간편하고 빠르게 뷰를 작성할 수 있다.</p>
</blockquote>
<blockquote>
<p>우리는 이미 클래스를 사용했다. <code>&lt;dear&gt;</code> 프로젝트의 <code>Post 모델</code>을 살펴보면 다음과 같다.</p>
</blockquote>
<pre><code class="language-python">#posts/models.py &gt; Post

class Post(models.Model):
    title = models.CharField(max_length=50, unique=True, error_messages={&quot;unique&quot;:&quot;이미 같은 제목의 게시글이 존재합니다&quot;})
    content = models.TextField(validators=[MinLengthValidator(10, &quot;10자 이상 입력해주세요&quot;),
                                           validate_symbols])
    dt_created = models.DateTimeField(verbose_name=&quot;Date Created&quot;, auto_now_add=True)
    dt_modified = models.DateTimeField(verbose_name=&quot;Date Modified&quot;, auto_now=True)

    def __str__(self):
        return self.title</code></pre>
<blockquote>
<p><code>Post 모델</code>은 장고의 <code>modles.Model</code> 클래스를 상속받아 사용하기 때문에 관련된 기능을 직접 구현하지 않아도 된다. 또한 <code>__str__</code> 함수를 클래스 안에서 구현한다.</p>
</blockquote>
<blockquote>
<p>장고는 많은 개발자들이 자주 사용하는 여러 기능들을 클래스로 제공한다. <code>CRUD</code>와 같은 복잡한 기능들을 <code>상위 클래스</code>로 <code>상속</code>받으면 뷰를 더 쉽고 간편하게 작성할 수 있다.</p>
</blockquote>
<hr>
<h1 id="1-함수형-뷰를-클래스형-뷰로-변경">1. 함수형 뷰를 클래스형 뷰로 변경</h1>
<blockquote>
<p>기존에 작성한 <code>post_create</code> 뷰를 <code>클래스형 뷰</code>로 작성하는 방법을 알아본다. 이를 위해 기존 코드를 주석처리 한다. </p>
</blockquote>
<pre><code class="language-python">#views.py &gt; post_create

# def post_create(request):
#     if request.method ==&quot;POST&quot;:
#         post_form = PostForm(request.POST)
#         if post_form.is_valid():
#             new_post = post_form.save()
#             return redirect(&quot;post-detail&quot;, post_id=new_post.id)
#         else:
#             context = {&quot;post_form&quot;: post_form}
#     else:
#         post_form = PostForm()
#         context = {&quot;post_form&quot;: post_form}
#     return render(request, &#39;posts/post_form.html&#39;, context=context)</code></pre>
<hr>
<h2 id="11-클래스형-뷰-작성">1.1 클래스형 뷰 작성</h2>
<blockquote>
<p>기존의 <code>함수형 뷰</code>를 <code>클래스형 뷰</code>로 변경하기 위해서는 장고의 <code>뷰 클래스</code>를 <code>상위 클래스</code>로 <code>상속</code> 받아야 한다. 따라서 장고의 <code>뷰 클래스</code>를 불러온다.</p>
</blockquote>
<pre><code class="language-python">#views.py

from django.views import View</code></pre>
<blockquote>
<p>이후 <code>class</code>를 생성하는데, <code>파스칼케이스 (Pascal Case)</code> 관례를 따라서 언더바 없이 <code>단어의 첫 글자를 대문자</code>로 표기한다.</p>
</blockquote>
<pre><code class="language-python">#views.py &gt; PostCreateView

class PostCreateView(View):
    def get(self, request):
        post_form = PostForm()
        context = {&quot;post_form&quot;: post_form}
        return render(request, &#39;posts/post_form.html&#39;, context=context)

    def post(self, request):
        post_form = PostForm(request.POST)
        if post_form.is_valid():
            new_post = post_form.save()
            return redirect(&quot;post-detail&quot;, post_id=new_post.id)
        else:
            context = {&quot;post_form&quot;: post_form}
            return render(request, &#39;posts/post_form.html&#39;, context=context)</code></pre>
<blockquote>
<p>코드를 살펴보면 장고의 <code>View 클래스</code>를 상위 클래스로 두어 상속받는다.</p>
</blockquote>
<blockquote>
<p>또한 <code>함수형 뷰</code>와는 달리 <code>if</code>문으로 요청방식이 <code>POST</code>일 때와 <code>GET</code>일 때를 구분하지 않아도 된다. </p>
</blockquote>
<blockquote>
<p>이는 상위 클래스인 장고의 <code>View</code>에서 요청이 들어오면 요청과 같은 이름의 함수를 실행하는 함수를 상속받았기 때문이다.</p>
</blockquote>
<hr>
<h2 id="12-url-패턴-변경">1.2 url 패턴 변경</h2>
<blockquote>
<p>url 패턴에서 <code>post_create</code> 뷰를 불러오는 방식을 PostCreateView를 불러오는 방식으로 변경한다. </p>
</blockquote>
<blockquote>
<p>이때 클래스형 뷰는 함수형 부를 불러오는 방법과는 다르게 뒤에 <code>.as_view()</code>를 사용해야 한다.</p>
</blockquote>
<pre><code class="language-python">#urls.py

urlpatterns = [
    # path(&#39;&#39;, views.index),
    path(&#39;&#39;, views.post_list, name=&quot;post-list&quot;),
    path(&#39;posts/new/&#39;, views.PostCreateView.as_view(), name=&quot;post-create&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/&#39;, views.post_detail, name=&quot;post-detail&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/edit/&#39;, views.post_update, name=&quot;post-update&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/delete/&#39;, views.post_delete, name=&quot;post-delete&quot;),
]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[페이지 네이션]]></title>
            <link>https://velog.io/@ethan_/%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%84%A4%EC%9D%B4%EC%85%98</link>
            <guid>https://velog.io/@ethan_/%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%84%A4%EC%9D%B4%EC%85%98</guid>
            <pubDate>Thu, 18 Jan 2024 16:05:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>페이지 네이션을 만드는 방법을 알아본다.</p>
</blockquote>
<hr>
<h1 id="1-views-수정">1. views 수정</h1>
<blockquote>
<p>먼저 뷰를 수정해야 한다.</p>
</blockquote>
<hr>
<h2 id="11-모듈-불러오기">1.1 모듈 불러오기</h2>
<blockquote>
<p>장고의 <code>페이지네이터</code>를 불러온다.</p>
</blockquote>
<pre><code class="language-python">#views.py

from django.core.paginator import Paginator</code></pre>
<hr>
<h2 id="12-post_list-수정하기">1.2 post_list 수정하기</h2>
<blockquote>
<p><code>post_list</code> 함수를 수정한다.</p>
</blockquote>
<pre><code class="language-python">views.py &gt; post_list


def post_list(request):
    posts = Post.objects.all().order_by(&quot;-dt_modified&quot;)
    page = request.GET.get(&quot;page&quot;, 1)
    paginator = Paginator(posts, 9)
    page_obj = paginator.get_page(page)
    context = {&quot;page_obj&quot;: page_obj}
    return render(request, &#39;posts/post_list.html&#39;, context=context)</code></pre>
<blockquote>
<p>요청으로부터 <code>page 파라미터</code>에 인자를 넘겨받아 <code>변수 page</code>에 지정한다. 만약 넘겨받은 인자가 없다면 1로 간주한다.</p>
</blockquote>
<pre><code class="language-python">    page = request.GET.get(&quot;page&quot;, 1)</code></pre>
<blockquote>
<p><code>변수 posts</code>의 데이터를 한 페이지 당 9개로 구분하고 <code>변수 paginator</code>에 지정한다.</p>
</blockquote>
<pre><code class="language-python">    paginator = Paginator(posts, 9)</code></pre>
<blockquote>
<p><code>변수 paginator</code> 를 통해 전체 데이터를 전체 데이터를 한 페이지 당 N개의 데이터로 나누고 <code>변수 page</code>와 일치하는 페이지 번호의 데이터들을 불러온 뒤 <code>변수 page_obj</code>에 지정한다.</p>
</blockquote>
<pre><code class="language-python">    page_obj = paginator.get_page(page)</code></pre>
<blockquote>
<blockquote>
<p>풀어서 설명하자면 아래와 같다.
<code>page_obj</code>는
<code>1. paginator를 통해 전체 데이터를 한 페이지 당 9개의 데이터로 구분 한 뒤</code>,
<code>2. 템플릿으로부터 파라미터로 전달받은 page의 값이 2라면</code>,
<code>3. 2페이지에 포함된 10번부터 18번까지의 데이터들을 호출한다.</code></p>
</blockquote>
</blockquote>
<blockquote>
<p><code>context로 page_ojb</code>를 넘겨준다.</p>
</blockquote>
<pre><code class="language-python">    context = {&quot;page_obj&quot;: page_obj}</code></pre>
<blockquote>
<p>요청과 context를 전달하면서 템플릿을 렌더한다.</p>
</blockquote>
<pre><code class="language-python">    return render(request, &#39;posts/post_list.html&#39;, context=context)</code></pre>
<hr>
<h2 id="13-템플릿-수정">1.3 템플릿 수정</h2>
<blockquote>
<p>우선 데이터를 호출하는 코드 중 기존 <code>context</code>로 넘겨받던 <code>posts</code>를 <code>page_obj</code> 로 변경했기 때문에 해당 부분을 변경해준다.</p>
</blockquote>
<blockquote>
<p>그런데 <code>for</code> 문에서 <code>page_obj</code>만 단독으로 사용하면 전체 게시글 개수, 전체 페이지 수, 이전 페이지 및 다음 페이지 존재 등은 확인할 수 있지만 데이터에는 접근할 수 없다. 따라서 <code>페이지 내에 있는 데이터에 접근</code>하기 위해서는 <code>.object_list</code>를 붙여줘야 한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_list.html--&gt;

    &lt;div class=&quot;btn_post&quot;&gt;
        &lt;a href={% url &quot;post-create&quot; %}&gt;기록하기&lt;/a&gt;
    &lt;/div&gt;
    {% if page_obj %}
        &lt;div class=&quot;post_container&quot;&gt;
            {% for post in page_obj.object_list %}
                &lt;div class=&quot;post&quot;&gt;&lt;a href=&quot;{% url &quot;post-detail&quot; post.id %}&quot;&gt;
                    &lt;h2 class=&quot;title&quot;&gt;{{post.title}}&lt;/h2&gt;
                    &lt;p class=&quot;date&quot;&gt;{{post.dt_created}}&lt;/p&gt;
                    &lt;p class=&quot;text&quot;&gt;{{post.content}}&lt;/p&gt;
                &lt;/a&gt;&lt;/div&gt;
            {% endfor %}
        &lt;/div&gt;</code></pre>
<hr>
<h3 id="131-페이지네이션-코드">1.3.1 페이지네이션 코드</h3>
<blockquote>
<p>이후 페이지네이션을 표기하는 코드를 작성한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_list.html--&gt;

        &lt;div class=&quot;pagination&quot;&gt;
            {% if page_obj.has_previous %}
                &lt;a class=&quot;able&quot; href=&quot;?page=1&quot;&gt;&amp;lt&amp;lt&lt;/a&gt;
            {% else %}
                &lt;a class=&quot;disable&quot; onclick=&quot;return false;&quot; href=&quot;#&quot;&gt;&amp;lt&amp;lt&lt;/a&gt;
            {% endif %}

            {% if page_obj.has_previous %}
                &lt;a class=&quot;able&quot; href=&quot;?page={{ page_obj.previous_page_number }}&quot;&gt;&amp;lt&lt;/a&gt;
            {% else %}
                &lt;a class=&quot;disable&quot; onclick=&quot;return false;&quot; href=&quot;#&quot;&gt;&amp;lt&lt;/a&gt;
            {% endif %}


            {% for page_number in page_obj.paginator.page_range %}
                {% if page_number == page_obj.number %}
                    &lt;a class=&quot;disable&quot; id=&quot;current&quot; onclick=&quot;return false;&quot; href=&quot;#&quot;&gt;{{ page_number }}&lt;/a&gt;
                {% elif page_obj.number &lt;= 3 and page_number &lt;= 5 %}
                    &lt;a class=&quot;able&quot; href=&quot;?page={{ page_number }}&quot;&gt;{{ page_number }}&lt;/a&gt;
                {% elif page_obj.number &gt; page_obj.paginator.num_pages|add:-3 and page_number &gt;= page_obj.paginator.num_pages|add:-4 %}
                    &lt;a class=&quot;able&quot; href=&quot;?page={{ page_number }}&quot;&gt;{{ page_number }}&lt;/a&gt;
                {% elif page_number &gt;= page_obj.number|add:-2 and page_number &lt;= page_obj.number|add:2 %}
                    &lt;a class=&quot;able&quot; href=&quot;?page={{ page_number }}&quot;&gt;{{ page_number }}&lt;/a&gt;
                {% endif %}
            {% endfor %}


            {% if page_obj.has_next %}
            &lt;a class=&quot;able&quot; href=&quot;?page={{ page_obj.next_page_number }}&quot;&gt;&amp;gt&lt;/a&gt;
            {% else %}
            &lt;a class=&quot;disable&quot; onclick=&quot;return false;&quot; href=&quot;#&quot;&gt;&amp;gt&lt;/a&gt;
            {% endif %}

            {% if page_obj.has_next %}
            &lt;a class=&quot;able&quot; href=&quot;?page={{ page_obj.paginator.num_pages }}&quot;&gt;&amp;gt&amp;gt&lt;/a&gt;
            {% else %}
            &lt;a class=&quot;disable&quot; onclick=&quot;return false;&quot; href=&quot;#&quot;&gt;&amp;gt&amp;gt&lt;/a&gt;
            {% endif %}
        &lt;/div&gt;</code></pre>
<blockquote>
<p>무척 어려워 보이지만 전체적인 맥락을 파악하면 이해할 수 있다.</p>
</blockquote>
<blockquote>
<p>우리가 페이지네이션에서 만들어야할 것은 아래와 같다.</p>
<blockquote>
<ol>
<li>처음으로</li>
<li>이전으로</li>
<li>현재 페이지 기준 근접한 페이지</li>
<li>다음으로</li>
<li>마지막으로</li>
</ol>
</blockquote>
</blockquote>
<hr>
<h3 id="132-처음-이전-다음-마지막-버튼">1.3.2 처음, 이전, 다음, 마지막 버튼</h3>
<blockquote>
<p>먼저 <code>처음으로</code>와 <code>이전으로</code>를 살펴본다.</p>
</blockquote>
<pre><code class="language-html">{% if page_obj.has_previous %}
    &lt;a class=&quot;able&quot; href=&quot;?page=1&quot;&gt;&amp;lt&amp;lt&lt;/a&gt;
{% else %}
    &lt;a class=&quot;disable&quot; onclick=&quot;return false;&quot; href=&quot;#&quot;&gt;&amp;lt&amp;lt&lt;/a&gt;
{% endif %}
&gt;
{% if page_obj.has_previous %}
    &lt;a class=&quot;able&quot; href=&quot;?page={{ page_obj.previous_page_number }}&quot;&gt;&amp;lt&lt;/a&gt;
{% else %}
    &lt;a class=&quot;disable&quot; onclick=&quot;return false;&quot; href=&quot;#&quot;&gt;&amp;lt&lt;/a&gt;
{% endif %}</code></pre>
<blockquote>
<p><code>처음으로</code> 버튼은 <code>if</code>문을 사용해
<code>1. 이전 페이지가 있다면</code> <code>page 파라미터</code>에 <code>1</code>을 인자로 전달하는 <code>&lt;a&gt;</code> 태그를 생성
<code>2. 이전 페이지가 없다면</code> 클릭해도 <code>반응이 없는 &lt;a&gt;</code> 태그를 생성한다.</p>
</blockquote>
<blockquote>
<p><code>이전으로</code> 버튼은 <code>if</code>문을 사용해
<code>1. 이전 페이지가 있다면</code> <code>page 파라미터</code>에 <code>이전 페이지의 page 번호</code>를 인자로 전달하는 <code>&lt;a&gt;</code> 태그를 생성
<code>2. 이전 페이지가 없다면</code> 클릭해도 <code>반응이 없는 &lt;a&gt;</code>태그를 생성한다.</p>
</blockquote>
<blockquote>
<p><code>다음으로</code>와 <code>마지막으로</code> 버튼을 이와 비슷하니 따로 설명하지 않겠다.</p>
</blockquote>
<hr>
<h3 id="133-현재-페이지-기준-근접한-페이지">1.3.3 현재 페이지 기준 근접한 페이지</h3>
<blockquote>
<p>이 부분은 <code>page_obj</code>에 사용된 <code>paginator</code>를 통해 페이지의 <code>전체 범위</code>를 반복해 <code>page_number</code>를 찾는다.</p>
</blockquote>
<pre><code class="language-html">{% for page_number in page_obj.paginator.page_range %}
    {% if page_number == page_obj.number %}
        &lt;a class=&quot;disable&quot; id=&quot;current&quot; onclick=&quot;return false;&quot; href=&quot;#&quot;&gt;{{ page_number }}&lt;/a&gt;
    {% elif page_obj.number &lt;= 3 and page_number &lt;= 5 %}
        &lt;a class=&quot;able&quot; href=&quot;?page={{ page_number }}&quot;&gt;{{ page_number }}&lt;/a&gt;
    {% elif page_obj.number &gt; page_obj.paginator.num_pages|add:-3 and page_number &gt;= page_obj.paginator.num_pages|add:-4 %}
        &lt;a class=&quot;able&quot; href=&quot;?page={{ page_number }}&quot;&gt;{{ page_number }}&lt;/a&gt;
    {% elif page_number &gt;= page_obj.number|add:-2 and page_number &lt;= page_obj.number|add:2 %}
        &lt;a class=&quot;able&quot; href=&quot;?page={{ page_number }}&quot;&gt;{{ page_number }}&lt;/a&gt;
    {% endif %}
{% endfor %}</code></pre>
<blockquote>
<p>첫번 째 <code>if</code>문은 현재 반복중인 페이지가 현재 페이지와 일치하면 클릭해도 반응없는 <code>&lt;a&gt;</code> 태그를 생성하기 위함이다.</p>
</blockquote>
<blockquote>
<p>나머지 <code>elif</code>문은 현재 페이지에 따라서 <code>총 5개의 인접한 페이지만 호출</code>되도록 제한하는 내용이다.</p>
</blockquote>
<blockquote>
<p>그 외 중앙정렬, 폰트, 호버 등 간단한 css를 적용하기 위해 <code>&lt;a&gt;</code>태그별로 <code>클래스</code>를 지정했다.
<img src="blob:https://velog.io/0d74c8a7-20df-4ff1-a0c1-66a1ef114ff1" alt="업로드중.."></p>
</blockquote>
<hr>
<blockquote>
<p>페이지네이션은 직접 완벽하게 만들기에는 많이 복잡하다. 웬만하면 남이 작성한 코드를 사용하자.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[더미데이터 유효성 검증]]></title>
            <link>https://velog.io/@ethan_/%EB%8D%94%EB%AF%B8%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%A6%9D</link>
            <guid>https://velog.io/@ethan_/%EB%8D%94%EB%AF%B8%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%A6%9D</guid>
            <pubDate>Thu, 18 Jan 2024 06:41:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><code>데이터 시딩</code>을 통해 더미데이터를 추가했지만, <code>유효성</code>이 관리되지 않는다. 따라서 본 포스팅은 <code>&lt;dear&gt;</code> 프로젝트에 추가된 더미데이터를 유효성 검증에 통과된 데이터로 변경하는 방법을 알아본다.</p>
</blockquote>
<h1 id="1-더미데이터-확인">1. 더미데이터 확인</h1>
<blockquote>
<p><code>데이터 시딩</code>을 통해 생성된 데이터 중, <code>유효성</code> 검증에 통과하지 못한 데이터는 다음과 같다.</p>
</blockquote>
<blockquote>
<p>특수문자 <code>&amp;</code>이 포함됨
<img src="https://velog.velcdn.com/images/ethan_/post/4fc45ada-0867-4f1d-911d-b0dfdbf8b129/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>이미지에는 보이지 않지만 수정일이 생성일보다 빠름
<img src="https://velog.velcdn.com/images/ethan_/post/efcbdc1f-2145-4d47-80ef-a22db6a5964e/image.png" alt=""></p>
</blockquote>
<hr>
<h1 id="2-py-파일-및-함수-작성">2. py 파일 및 함수 작성</h1>
<blockquote>
<p>수정일이 생성일보다 빠른 게시글을 찾아 <code>수정일</code>을 <code>현재 시간</code>으로 변경하고</p>
</blockquote>
<blockquote>
<p>내용에 <code>&amp;</code>가 포함된 게시글을 찾아 <code>&amp;</code>를 삭제하는 파이썬 파일을 작성한다.</p>
</blockquote>
<blockquote>
<p>파일 이름은 <code>validate_data</code>로 생성했다.</p>
</blockquote>
<pre><code class="language-python">#posts &gt; validate_data.py

from .models import Post

def validate_post():
    posts = Post.objects.all()

    for post in posts:
        if &quot;&amp;&quot; in post.content:
            print(post.id, &quot;번 글에 &amp;가 있음&quot;)
            post.content = post.content.replace(&quot;&amp;&quot;, &quot; &quot;)
            post.save()

        if post.dt_modified &lt; post.dt_created:
            print(post.id, &quot;번 글의 수정일이 생성일보다 과거임&quot;)
            post.save()</code></pre>
<hr>
<h2 id="21-각-코드의-의미">2.1 각 코드의 의미</h2>
<blockquote>
<p><code>Post 모델</code>을 불러온다.</p>
</blockquote>
<pre><code class="language-python">from .models import Post</code></pre>
<blockquote>
<p><code>validate_post</code> 이름의 함수를 작성한다.</p>
</blockquote>
<pre><code class="language-python">def validate_post():</code></pre>
<blockquote>
<p><code>변수 posts</code>에 <code>Post 모델의 모든 데이터</code>를 지정한다.</p>
</blockquote>
<pre><code class="language-python">    posts = Post.objects.all()</code></pre>
<blockquote>
<p><code>posts</code>에 담긴 데이터를 <code>post</code> 라는 이름으로 하나씩 불러온다.</p>
</blockquote>
<pre><code class="language-python">    for post in posts:</code></pre>
<blockquote>
<p>만약 <code>&amp;</code> 특수문자가 <code>post</code>의 <code>content 필드</code>에 있다면 해당 <code>post</code>의 <code>id</code>를 호출한다. 그리고 해당 <code>post</code>의 <code>content 필드</code>에서 <code>&amp;</code>를 <code></code>로 대체한다. 이후 <code>post</code>를 저장한다.</p>
</blockquote>
<pre><code class="language-python">        if &quot;&amp;&quot; in post.content:
            print(post.id, &quot;번 글에 &amp;가 있음&quot;)
            post.content = post.content.replace(&quot;&amp;&quot;, &quot; &quot;)
            post.save()</code></pre>
<blockquote>
<p>만약 <code>post</code>의 <code>dt_modified 필드</code>의 값이 <code>de_created</code>보다 낮다면, 해당 <code>post</code>의 <code>id</code>를 호출한다. 그리고 해당 <code>post</code>를 저장한다. </p>
</blockquote>
<pre><code class="language-python">        if post.dt_modified &lt; post.dt_created:
            print(post.id, &quot;번 글의 수정일이 생성일보다 과거임&quot;)
            post.save()</code></pre>
<blockquote>
<blockquote>
<p>(<code>dt_modified</code>는 <code>auto_now</code> 옵션을 <code>True</code>로 정의했기 때문에 저장하면 현재시간으로 변경된다.)</p>
</blockquote>
</blockquote>
<hr>
<h1 id="3-shell-환경에서-파일-사용">3. shell 환경에서 파일 사용</h1>
<blockquote>
<p>작성된 파일을 <code>shell</code> 환경에서 사용한다.</p>
</blockquote>
<pre><code class="language-bash">python manage.py shell</code></pre>
<blockquote>
<p>작성된 파일의 <code>validate_post</code> 함수를 불러온다.</p>
</blockquote>
<pre><code class="language-shell">from posts.validate_data import validate_post</code></pre>
<blockquote>
<p>불러온 함수를 사용한다.</p>
</blockquote>
<pre><code class="language-shell">validate_post()</code></pre>
<hr>
<h2 id="31-콘솔-결과">3.1 콘솔 결과</h2>
<p><img src="https://velog.velcdn.com/images/ethan_/post/ffce3006-2099-426e-b678-e24bd2e0a9d9/image.png" alt=""></p>
<hr>
<h2 id="32-서버-결과">3.2 서버 결과</h2>
<blockquote>
<p><code>&amp;</code> 특수문자가 모두 제거됨
<img src="https://velog.velcdn.com/images/ethan_/post/71de476f-adcc-400c-b69d-7b142dcfdf99/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>수정일이 현재시간으로 변경됨
<img src="https://velog.velcdn.com/images/ethan_/post/58b64f96-5385-415c-9ccb-32c25f391f9a/image.png" alt=""></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터 추출과 삽입]]></title>
            <link>https://velog.io/@ethan_/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B6%94%EC%B6%9C%EA%B3%BC-%EC%82%BD%EC%9E%85</link>
            <guid>https://velog.io/@ethan_/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B6%94%EC%B6%9C%EA%B3%BC-%EC%82%BD%EC%9E%85</guid>
            <pubDate>Wed, 17 Jan 2024 05:45:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><code>&lt;dear&gt;</code>프로젝트에서 더미 데이터를 삽입하려 한다. 데이터를 삽입하는 것을 <code>seeding</code> 이라고 한다. 이번에는 데이터를 시딩하는 두 가지 방법을 알아본다.</p>
</blockquote>
<h1 id="1-loaddata를-통한-시딩">1. loaddata를 통한 시딩</h1>
<blockquote>
<p><code>loaddata</code>는 <code>미리 작성된 데이터 파일</code>을 통해 데이터를 시딩하는 방법이다.</p>
</blockquote>
<blockquote>
<p>데이터 파일은 <code>json</code> 또는 <code>xml</code> 의 형식을 사용할 수 있다.</p>
</blockquote>
<blockquote>
<p>본 포스팅에서는 <code>json</code> 형식의 파일을 통해 <code>loaddata</code>하는 방법을 알아본다.</p>
</blockquote>
<hr>
<h2 id="11-json-파일-추출">1.1 json 파일 추출</h2>
<blockquote>
<p><code>json</code>은 데이터를 표현하기 위해 사용하는 대표적인 포맷이다. 파이썬의 사전형 자료와 유사하며, 데이터를 <code>키와 값</code>을 쌍으로 이루어 표현한다.</p>
</blockquote>
<blockquote>
<p>우리 프로젝트의 데이터가 <code>json</code> 형식으로 저장되면 어떤 모습일지는 데이터를 추출해보면 된다.</p>
</blockquote>
<pre><code>python manage.py dumpdata posts --indent=2 &gt; posts_data.json</code></pre><blockquote>
<p>명령어를 살펴보면 <code>posts 앱</code>의 데이터를 <code>posts_data.json 파일</code>로 저장한다는 의미이며 <code>indent</code>는 각 코드의 <code>들여쓰기</code>를 의미한다. <code>indent</code>를 사용하면 데이터가 보기 좋게 정렬된다.</p>
</blockquote>
<pre><code class="language-python">#posts_data.json

[
{
  &quot;model&quot;:&quot;posts.post&quot;,
  &quot;pk&quot;: 1,
  &quot;title&quot;: abc1,
  &quot;content&quot;: abcdefg,
}
]</code></pre>
<hr>
<h2 id="12-json-파일-수정">1.2 json 파일 수정</h2>
<blockquote>
<p>추출한 <code>json</code> 파일의 형식에 맞게 데이터를 추가한다.</p>
</blockquote>
<pre><code class="language-python">#posts_data.json

[
{
  &quot;model&quot;:&quot;posts.post&quot;,
  &quot;pk&quot;: 1,
  &quot;title&quot;: abc1,
  &quot;content&quot;: abcdefg,
},
{
  &quot;model&quot;:&quot;posts.post&quot;,
  &quot;pk&quot;: 2,
  &quot;title&quot;: abc2,
  &quot;content&quot;: abcdefg,
}
]</code></pre>
<hr>
<h2 id="13-json-파일을-데이터-베이스에-적용">1.3 json 파일을 데이터 베이스에 적용</h2>
<blockquote>
<p>수정한 <code>json</code>을 데이터베이스에 적용한다.</p>
</blockquote>
<pre><code class="language-shell">python manage.py loaddata posts_data.json</code></pre>
<blockquote>
<p><code>loaddata</code>를 사용하면 <code>posts_data.json</code> 파일을 데이터베이스에 적용한다. 추가되는 것이 아닌, 기존의 데이터를 <code>덮어쓰는 것</code>을 주의해야 한다.</p>
</blockquote>
<blockquote>
<p>어떤 모델의 데이터베이스에 적용되는지는 <code>json</code> 파일의 <code>model 키</code>의 <code>값</code> 에 따라 결정된다. 따라서 하나의 <code>json</code> 파일에서 <code>여러 모델</code>의 데이터베이스를 추가할 수 있다.</p>
</blockquote>
<hr>
<h1 id="2-django-seed를-통한-시딩">2. django-seed를 통한 시딩</h1>
<blockquote>
<p>만약 생성해야 할 데이터가 많다면, <code>dumpdata</code>를 통해 <code>json</code> 파일을 추출하고, 해당 <code>json</code> 파일에 데이터를 복사한 뒤, 복사한 데이터 수 만큼 <code>pk</code>값을 모두 변경해야 한다. 이런 번거로움을 <code>django-seed</code>를 통해 해결할 수 있다.</p>
</blockquote>
<hr>
<h2 id="21-django-seed-설치">2.1 django-seed 설치</h2>
<blockquote>
<p><code>django-seed</code>를 설치한다. <code>&lt;dear&gt;</code> 프로젝트의 가상환경은 <code>django 2.2</code> 버전을 사용하고 있어, 해당 버전과 호환되는 <code>django_seed 0.2.2</code>를 설치한다.</p>
</blockquote>
<pre><code class="language-bash">pip install django-seed==0.2.2</code></pre>
<hr>
<h2 id="22-django-seed-앱-등록">2.2 django-seed 앱 등록</h2>
<blockquote>
<p><code>django-seed</code>를 사용하기 위해 <code>프로젝트 구성 디렉토리</code>의 <code>settings.py</code>에 등록한다. 주의할 점은 하이픈이 아닌 언더바를 넣어 <code>django_seed</code>로 등록해야 한다.</p>
</blockquote>
<pre><code class="language-python">#settings.py &gt; INSTALLED_APPS

INSTALLED_APPS = [
    &#39;django.contrib.admin&#39;,
    &#39;django.contrib.auth&#39;,
    &#39;django.contrib.contenttypes&#39;,
    &#39;django.contrib.sessions&#39;,
    &#39;django.contrib.messages&#39;,
    &#39;django.contrib.staticfiles&#39;,
    &#39;posts&#39;,
    &#39;django_seed&#39;  #하이픈이 아닌 언더바
]
</code></pre>
<hr>
<h2 id="23-seed-명령어-사용">2.3 seed 명령어 사용</h2>
<blockquote>
<p>시딩을 위한 준비가 끝났으니 seed 명령어를 통해 10개의 데이터를 추가해본다.</p>
</blockquote>
<pre><code class="language-bash">python manage.py seed posts --number=10</code></pre>
<blockquote>
<p><code>posts</code>는 앱 이름이며 <code>number</code>는 생성할 데이터를 의미한다.</p>
</blockquote>
<blockquote>
<p><code>seed</code> 명령어는 지정한 앱의 <code>모든 모델</code>의 데이터를 지정한 데이터 수 만큼 생성한다.</p>
</blockquote>
<blockquote>
<p>또한 <code>seed</code> 명령어는 <code>유효성 검증</code>을 무시한 채로 데이터를 생성하므로, 유효성 검증과 관련해서는 <code>직접 데이터를 관리</code>해야 한다.</p>
</blockquote>
<hr>
<h3 id="231-특정-모델의-데이터만-생성">2.3.1 특정 모델의 데이터만 생성</h3>
<blockquote>
<p>특정 모델의 데이터만 생성하고 싶다면 <code>--seeder</code> 옵션을 사용해 <code>모델 이름</code>을 입력하면 된다.</p>
</blockquote>
<blockquote>
<p>아래의 경우 <code>posts</code> 앱의 <code>Post</code> 모델에 5개의 데이터를 생성한다.</p>
</blockquote>
<pre><code class="language-bash">python manage.py seed posts --number=5 --seeder Post</code></pre>
<hr>
<h3 id="232-특정-필드에-특정-데이터를-지정">2.3.2 특정 필드에 특정 데이터를 지정</h3>
<blockquote>
<p>특정 모델의 데이터를 생성하면서, 특정 필드의 값을 지정하고 싶다면</p>
<blockquote>
<p><code>--seeder</code> 옵션에서 <code>모델이름</code>에 <code>.필드이름</code> 붙이고 <code>&quot;</code>로 감싼 형태로 입력한다.</p>
</blockquote>
<blockquote>
<p>그리고 원하는 값을 <code>&quot;</code>로 감싼 형태로 입력한다.</p>
</blockquote>
</blockquote>
<blockquote>
<p>아래의 경우 <code>posts</code> 앱의 <code>Post</code> 모델에 <code>title</code> 필드의 값이 <code>dummy title</code>인 5개의 데이터를 생성한다.</p>
</blockquote>
<pre><code class="language-bash">python manage.py seed posts --number=5 --seeder &quot;Post.title&quot; &quot;dummy title&quot;</code></pre>
<hr>
<h3 id="232-여러-필드에-특정-데이터를-지정">2.3.2 여러 필드에 특정 데이터를 지정</h3>
<blockquote>
<p>특정 모델의 데이터를 생성하면서, 여러 필드의 값을 지정하고 싶다면 <code>--seeder</code> 옵션을 추가하기만 하면 된다.</p>
</blockquote>
<blockquote>
<p>아래의 경우 <code>posts</code> 앱의 <code>Post</code> 모델에 <code>title</code> 필드의 값이 <code>dummy title</code>이며, <code>content</code> 필드의 값이 <code>dummy content</code>인 5개의 데이터를 생성한다.</p>
</blockquote>
<pre><code class="language-bash">python manage.py seed posts --number=5 --seeder &quot;Post.title&quot; &quot;dummy title&quot; --seeder &quot;Post.content&quot; &quot;dummy content&quot;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[get_object_or_404]]></title>
            <link>https://velog.io/@ethan_/getobjector404</link>
            <guid>https://velog.io/@ethan_/getobjector404</guid>
            <pubDate>Wed, 17 Jan 2024 04:20:21 GMT</pubDate>
            <description><![CDATA[<h1 id="1-postobjectsgetid-post_id">1. Post.objects.get(id= post_id)</h1>
<blockquote>
<p><code>&lt;Dear&gt;</code> 프로젝트에서 기존에 작성된 뷰는 게시글을 불러올 때 <code>Post 모델</code>에서 <code>id</code>가 <code>post_id</code>와 일치하는 게시글을 불러왔다.</p>
</blockquote>
<blockquote>
<p>그러나 <code>post_id</code>와 일치하는 게시글이 없는 경우 <code>에러 페이지</code>를 렌더했다. </p>
</blockquote>
<blockquote>
<p>이 경우 유저는 잘못된 접근인지, 서버 오류인지를 구분하기가 어려웠다.</p>
</blockquote>
<h1 id="2-get_object_or_404">2. get_object_or_404()</h1>
<blockquote>
<p>따라서 모델의 데이터를 불러오고, 해당 데이터가 없는 경우에는 <code>404 에러</code>를 불러오는 <code>get_object_or_404</code>를 알아본다. </p>
</blockquote>
<blockquote>
<p>우선 장고 숏컷 모듈에서 해당 함수를 불러온다.</p>
</blockquote>
<pre><code class="language-python">
from django.shortcuts import get_object_or_404</code></pre>
<blockquote>
<p>이후 기존에 사용했던 Post.objects.get(id = post_id)를 변경한다. 그리고 <code>변수 post</code>에 지정한다.</p>
</blockquote>
<blockquote>
<p><code>파라미터</code>에는 <code>참조할 모델 클래스</code>(Post)와 <code>조건</code>(id=post_id)을 <code>인자</code>로 넘겨준다.</p>
</blockquote>
<pre><code class="language-python">
post = get_object_or_404(Post, id = post_id)</code></pre>
<blockquote>
<p>이 경우 <code>post_id</code>와 일치하는 <code>id</code>를 가진 데이터를 <code>Post 모델</code>에서 찾아보고</p>
</blockquote>
<blockquote>
<p>해당 데이터가 있다면 <code>변수 post</code>에 지정하지만</p>
</blockquote>
<blockquote>
<p>해당 데이터가 없다면 <code>404 에러</code>를 불러온다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[모델의 데이터가 없을 때]]></title>
            <link>https://velog.io/@ethan_/%EB%AA%A8%EB%8D%B8%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0%EA%B0%80-%EC%97%86%EC%9D%84-%EB%95%8C</link>
            <guid>https://velog.io/@ethan_/%EB%AA%A8%EB%8D%B8%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0%EA%B0%80-%EC%97%86%EC%9D%84-%EB%95%8C</guid>
            <pubDate>Wed, 17 Jan 2024 04:03:52 GMT</pubDate>
            <description><![CDATA[<h1 id="1-모델의-데이터가-비어있는-경우">1. 모델의 데이터가 비어있는 경우</h1>
<blockquote>
<p>모델의 데이터가 비어있는 경우 빈 페이지를 보여주기 보다는, 데이터가 비어있음을 안내해야 한다.</p>
</blockquote>
<h2 id="11-if문을-통한-구분">1.1 if문을 통한 구분</h2>
<blockquote>
<p>if문을 통해 작성된 html을 살펴본다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_list.html--&gt;

{% block content %}
    &lt;div class=&quot;btn_post&quot;&gt;
        &lt;a href={% url &quot;post-create&quot; %}&gt;기록하기&lt;/a&gt;
    &lt;/div&gt;
    {% if posts %}
        &lt;div class=&quot;post_container&quot;&gt;
            {% for post in posts %}
                &lt;div class=&quot;post&quot;&gt;&lt;a href=&quot;{% url &quot;post-detail&quot; post.id %}&quot;&gt;
                    &lt;h2 class=&quot;title&quot;&gt;{{post.title}}&lt;/h2&gt;
                    &lt;p class=&quot;date&quot;&gt;{{post.dt_created}}&lt;/p&gt;
                    &lt;p class=&quot;text&quot;&gt;{{post.content}}&lt;/p&gt;
                &lt;/a&gt;&lt;/div&gt;
            {% endfor %}
        &lt;/div&gt;
    {% else %}
        &lt;div class=&quot;blank&quot;&gt;
            &lt;p&gt;
                등록된 게시글이 없습니다.&lt;br&gt;
                게시글을 등록해보세요.
            &lt;/p&gt;
        &lt;/div&gt;
    {% endif %}

{% endblock content %}</code></pre>
<blockquote>
<p><code>템플릿 태그 if</code> 를 활용</p>
</blockquote>
<blockquote>
<p>posts가 있다면, 각 포스트의 내용을 불러오고</p>
</blockquote>
<blockquote>
<p>posts가 없다면 <code>&lt;p&gt;</code>태그로 등록된 게시글이 없음을 안내한다.</p>
</blockquote>
<blockquote>
<p>여기서 <code>posts</code>는 <code>view</code>를 통해 <code>전달받은 데이터</code>를 말한다.</p>
</blockquote>
<hr>
<h2 id="12-결과">1.2 결과</h2>
<p><img src="https://velog.velcdn.com/images/ethan_/post/a98f471d-8062-42a2-8c8a-dda0e75a03f6/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django Form]]></title>
            <link>https://velog.io/@ethan_/Django-Form</link>
            <guid>https://velog.io/@ethan_/Django-Form</guid>
            <pubDate>Tue, 09 Jan 2024 11:04:34 GMT</pubDate>
            <description><![CDATA[<h1 id="1-폼이란">1. 폼이란?</h1>
<hr>
<h2 id="11-폼이란">1.1 폼이란?</h2>
<blockquote>
<p>웹 서비스에서 사용자가 데이터를 입력하고 서버에 전송하기 위한 방식을 말한다.</p>
</blockquote>
<blockquote>
<p>CRUD 에서 <code>Read</code> 는 데이터베이스에서 데이터를 가져와서 템플릿으로 렌더해 사용자에게 전달하면 된다. </p>
</blockquote>
<blockquote>
<p><code>Create</code> 는 이와 반대로 사용자의 입력을 받고 서버로 전송해야 하는데 이때 사용되는 것이 <code>form</code>이다.</p>
</blockquote>
<hr>
<h2 id="12-폼-예시">1.2 폼 예시</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/ethan_/post/0e292584-03e0-41ce-ad17-ec2450cae04f/image.png" alt=""></p>
</blockquote>
<pre><code class="language-html">&lt;form action=&quot;complate/&quot; method=&quot;post&quot;&gt;
  &lt;label for=&quot;name&quot;&gt;이름&lt;/label&gt;
  &lt;input type=&quot;text&quot; id=&quot;name&quot; name=&quot;name&quot;/&gt;
  &lt;label for=&quot;nickname&quot;&gt;별명&lt;/label&gt;
  &lt;input type=&quot;text&quot; id=&quot;nickname&quot; name=&quot;nickname&quot;/&gt;
  &lt;input typq=&quot;submit&quot; value=&quot;저장&quot;/&gt;
&lt;/form&gt;</code></pre>
<hr>
<h1 id="2-폼-처리-과정">2. 폼 처리 과정</h1>
<blockquote>
<p>폼을 이해하기 위해 폼이 어떻게 처리되는지 살펴본다.</p>
</blockquote>
<hr>
<h2 id="21-method">2.1 Method</h2>
<blockquote>
<p>클라이언트가 서버에 보내는 모든 <code>request</code>는 종류에 따라 구분되며, 가장 많이 사용되는 것이 <code>GET</code>과 <code>POST</code> 방식이다.</p>
</blockquote>
<hr>
<h3 id="211-get">2.1.1 GET</h3>
<blockquote>
<p><code>GET</code>은 서버에 데이터를 조회하기 위한 메서드이다.</p>
</blockquote>
<blockquote>
<p>GET은 요청에 포함하고 싶은 데이터가 있을 때 필요한 데이터를 url에 담아서 요청한다.</p>
<blockquote>
<p>#예시
<code>http://dear.com/profile?name=홍길동&amp;email=gildong@dear.com</code></p>
</blockquote>
<blockquote>
<p>예시에서 <code>dear.com/profile</code> 까지는 <code>url</code>이고 <code>?</code>다음 문자열이 필요한 데이터를 표현하는 <code>쿼리 스트링</code>이다.</p>
</blockquote>
<blockquote>
<p>위 url은 <code>이름</code>이 <code>홍길동</code>이고 <code>email</code>이 <code>gildong@dear.com</code>인 유저의 프로필을 조회한다고 보면 된다.</p>
</blockquote>
</blockquote>
<blockquote>
<p>정리하자면 <code>쿼리 스트링</code>은 url의 마지막 <code>?</code> 다음에 표현되며 <code>키</code>와 <code>밸류</code>의 쌍으로 데이터를 표시하고 각각의 데이터는 <code>&amp;</code> 으로 구분한다.</p>
</blockquote>
<blockquote>
<p>그러나 민감한 데이터가 url에 노출된 채로 서버로 전송될 수 있기 때문에 <code>POST</code>의 방식을 더 많이 사용한다.</p>
</blockquote>
<hr>
<h3 id="212-post">2.1.2 POST</h3>
<blockquote>
<p><code>POST</code> 방식에서는 필요한 데이터를 url이 아닌, 클라이언트와 서버가 통신하는 메세지 내부에 담기 떄문에 데이터 노출 우려가 적다.</p>
</blockquote>
<hr>
<h3 id="213-간단히">2.1.3 간단히</h3>
<blockquote>
<p>간단히 말해서 서버의 데이터를 조회만 하는 것이라면 <code>GET</code> 방식이고, 서버의 데이터를 <code>변경</code>(생성, 수정, 삭제)하는 것은 <code>POST</code> 방식이다. </p>
</blockquote>
<hr>
<h2 id="22-폼의-처리-과정">2.2 폼의 처리 과정</h2>
<blockquote>
<p>폼의 처리 과정을 단계별로 구분하면 다음과 같다.</p>
</blockquote>
<hr>
<h3 id="221-클라이언트의-요청">2.2.1 클라이언트의 요청</h3>
<blockquote>
<p>클라이언트가 폼을 작성하기 위해 서버에 폼 양식을 요청한다.</p>
</blockquote>
<blockquote>
<p>이미 서버에서 가지고 있는 폼 양식을 조회하는 것이므로 <code>GET</code> 방식으로 요청한다.</p>
</blockquote>
<blockquote>
<p>이 때 서버가 폼 양식을 전달하는데, 데이터가 폼과 연결되어 있지 않기 때문에 이를 <code>언바운드 폼</code>이라 한다.</p>
</blockquote>
<blockquote>
<p>아래 처럼 비어 있는 폼을 생각하면 된다.
<img src="https://velog.velcdn.com/images/ethan_/post/df1e76ce-08e9-42fa-9091-9c20b43fa071/image.png" alt=""></p>
</blockquote>
<hr>
<h3 id="222-사용자의-데이터-전송">2.2.2 사용자의 데이터 전송</h3>
<blockquote>
<p>사용자가 데이터를 입력하고 서버에 전송한다.
<img src="https://velog.velcdn.com/images/ethan_/post/f9051e19-d04b-46ce-b3a1-d86983af37d5/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><code>method</code> 에 작성된 방식으로 <code>action</code>에 해당하는 url로 데이터를 전송한다.</p>
</blockquote>
<pre><code class="language-html">&lt;form action=&quot;complate/&quot; method=&quot;post&quot;&gt;
  &lt;label for=&quot;name&quot;&gt;이름&lt;/label&gt;
  &lt;input type=&quot;text&quot; id=&quot;name&quot; name=&quot;name&quot;/&gt;
  &lt;label for=&quot;nickname&quot;&gt;별명&lt;/label&gt;
  &lt;input type=&quot;text&quot; id=&quot;nickname&quot; name=&quot;nickname&quot;/&gt;
  &lt;input typq=&quot;submit&quot; value=&quot;저장&quot;/&gt;
&lt;/form&gt;</code></pre>
<blockquote>
<p>여기서는 <code>POST</code> 방식으로 <code>로컬호스트:8000/complate</code> 에 입력된 <code>이름</code>과 <code>별명</code>을 전송한다.</p>
</blockquote>
<hr>
<h3 id="223-바인딩">2.2.3 바인딩</h3>
<blockquote>
<p>서버에서 전달받은 데이터와 폼을 합쳐서 하나의 형태로 만들며 이를 바인딩이라 하며, 데이터와 합쳐진 폼을 바운드 폼이라 한다.
<img src="https://velog.velcdn.com/images/ethan_/post/7c6f1bde-5a87-4be9-b43e-41dd1f770e34/image.png" alt=""></p>
</blockquote>
<hr>
<h3 id="224-바운드-폼-확인">2.2.4 바운드 폼 확인</h3>
<blockquote>
<p>바운드 폼의 데이터가 유효하지 않은 경우, 사용자에게 다시 폼을 입력하도록 안내한다.
<img src="https://velog.velcdn.com/images/ethan_/post/34d253f8-deb2-4d37-9900-550ddc7ec404/image.png" alt=""></p>
</blockquote>
<hr>
<h3 id="225-로직-수행">2.2.5 로직 수행</h3>
<blockquote>
<p>바운드폼의 데이터가 유효하다면 서버의 로직을 수행한다.</p>
</blockquote>
<blockquote>
<p>데이터를 가공하거나, 수정하거나, 저장한다.
<img src="https://velog.velcdn.com/images/ethan_/post/40f014f3-c496-4a84-8e37-56b2e4a614ac/image.png" alt=""></p>
</blockquote>
<hr>
<h3 id="226-응답">2.2.6 응답</h3>
<blockquote>
<p>로직 수행이 끝났다면 새로운 페이지를 응답으로 돌려준다.
<img src="https://velog.velcdn.com/images/ethan_/post/286561e8-bd1f-4331-a958-616705803391/image.png" alt=""></p>
</blockquote>
<hr>
<h1 id="3-폼-구조">3. 폼 구조</h1>
<blockquote>
<p>폼의 구조는 <code>&lt;form&gt;</code> 태그 안에 <code>&lt;label&gt;</code> 태그와 <code>&lt;input&gt;</code> 태그로 구성된다.</p>
</blockquote>
<hr>
<h2 id="31-form-태그">3.1 form 태그</h2>
<blockquote>
<p>폼 태그는 데이터를 어디로 전송해야 하는지 url 경로를 작성하는 <code>action</code> 속성과 어떤 방식으로 요청을 보내는지를 정의하는 <code>method</code> 속성이 있다.</p>
</blockquote>
<pre><code class="language-html">&lt;form action=&quot;complate/&quot; method=&quot;post&quot;&gt;</code></pre>
<blockquote>
<p>만약 <code>action</code> 속성과 <code>method</code> 속성을 입력하지 않으면 <code>action</code> 은 <code>현재 url</code>, <code>method</code> 는 <code>GET</code> 방식으로 설정된다. 즉, <code>form에 입력된 데이터</code>를 가지고 <code>현재 url</code>을 다시 불러온다.</p>
</blockquote>
<hr>
<h2 id="32-label-태그">3.2 label 태그</h2>
<blockquote>
<p>레이블 태그는 특정 인풋 태그와 연결하기 위한 <code>for</code> 속성이 있다.</p>
</blockquote>
<blockquote>
<p>레이블 태그의 <code>for</code> 속성에 입력되는 값과, 인풋 태그의 <code>id</code> 속성에 입력되는 값이 일치하는 해당 레이블 태그와 인풋 태그가 연결된다.</p>
</blockquote>
<pre><code class="language-html">  &lt;label for=&quot;name&quot;&gt;이름&lt;/label&gt;
  &lt;input type=&quot;text&quot; id=&quot;name&quot; name=&quot;name&quot;/&gt;</code></pre>
<blockquote>
<p>만약 레이블 태그의 for 속성과 인풋 태그의 id 속성을 적고싶지 않다면 레이블 태그로 인풋 태그를 감싼 형태를 만들면 된다</p>
</blockquote>
<pre><code class="language-html">  &lt;label&gt;이름
      &lt;input type=&quot;text&quot; name=&quot;name&quot;/&gt;
  &lt;/label&gt;</code></pre>
<p>다만 css 적용을 위해서는 id 속성을 작성해야 한다.</p>
<hr>
<h2 id="33-input-태그">3.3 input 태그</h2>
<blockquote>
<p>id 속성은 위에서 설명했으니 넘어가고 <code>name</code> 속성과 <code>type</code> 속성만 알아본다.</p>
</blockquote>
<blockquote>
<p><code>name</code> 속성은 폼에 입력한 데이터를 서버로 전송할 때, 서버에서 각가의 데이터를 구분할 수 있게 한다. <code>name</code> 속성이 없는 인풋 태그는 값이 서버로 전달되지 않는다.</p>
</blockquote>
<blockquote>
<p><code>type</code> 속성은 입력되는 유형을 지정하는 속성이다. <code>type</code> 속성에 따라 입력하는 <code>위젯</code>이 달라진다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[무료 html 템플릿 사이트]]></title>
            <link>https://velog.io/@ethan_/%EB%AC%B4%EB%A3%8C-html-%ED%85%9C%ED%94%8C%EB%A6%BF-%EC%82%AC%EC%9D%B4%ED%8A%B8</link>
            <guid>https://velog.io/@ethan_/%EB%AC%B4%EB%A3%8C-html-%ED%85%9C%ED%94%8C%EB%A6%BF-%EC%82%AC%EC%9D%B4%ED%8A%B8</guid>
            <pubDate>Mon, 08 Jan 2024 06:43:43 GMT</pubDate>
            <description><![CDATA[<p>무료로 반응형 html 템플릿을 제공하는 사이트</p>
<blockquote>
<p><a href="https://html5up.net/">https://html5up.net/</a>
<a href="https://colorlib.com/">https://colorlib.com/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django Model API]]></title>
            <link>https://velog.io/@ethan_/Django-Model-API</link>
            <guid>https://velog.io/@ethan_/Django-Model-API</guid>
            <pubDate>Fri, 05 Jan 2024 08:37:24 GMT</pubDate>
            <description><![CDATA[<p>장고의 Model을 통해서 데이터베이스의 데이터를 Queryset으로 불러올 수 있다. 여기서 우리는 Model API를 통해 Queryset을 원하는 조건으로 불러올 수 있다.</p>
<hr>
<h2 id="1-queryset-api">1. Queryset API</h2>
<hr>
<h3 id="11-all">1.1. all()</h3>
<blockquote>
<p>해당 모델 테이블의 <code>모든 데이터</code> 조회</p>
</blockquote>
<pre><code class="language-shell">all()
&gt;
#예시
Post.objects.all()</code></pre>
<hr>
<h3 id="12-filter">1.2. filter()</h3>
<blockquote>
<p><code>특정 조건</code>에 맞는 <code>모든 데이터</code> 조회</p>
</blockquote>
<pre><code class="language-shell">filter()
&gt;
#예시
Post.objects.filter({필드}{필드조건옵션}={조건})</code></pre>
<hr>
<h3 id="13-exclude">1.3. exclude()</h3>
<blockquote>
<p><code>특정 조건을 제외</code>한 <code>모든 데이터</code> 조회</p>
</blockquote>
<pre><code class="language-shell">exclude()
&gt;
#예시
Post.objects.exclude({필드}{필드조건옵션}{조건})</code></pre>
<hr>
<h3 id="14-order_by">1.4. order_by()</h3>
<blockquote>
<p><code>특정 조건으로 정렬</code>된 데이터 조회
기본값은 내림차순 / -를 붙이면 오름차순</p>
</blockquote>
<pre><code class="language-shell">order_by()
&gt;
#예시
Post.objects.order_by({필드})
Post.objects.order_by({-필드})</code></pre>
<hr>
<h3 id="15-values">1.5. values()</h3>
<blockquote>
<p>Queryset에 있는 <code>모든 모델 데이터</code>의 <code>모든 정보</code>를 사전형으로 갖는 리스트로 반환</p>
</blockquote>
<pre><code class="language-shell">values()
&gt;
#예시
Post.objects.all().values()</code></pre>
<hr>
<h3 id="16-get">1.6. get()</h3>
<blockquote>
<p><code>조건</code>에 맞는 <code>하나의 데이터</code> 조회</p>
</blockquote>
<pre><code class="language-shell">get()
&gt;
#예시
Post.objects.get({조건})</code></pre>
<hr>
<h3 id="17-create">1.7. create()</h3>
<blockquote>
<p><code>하나의 데이터</code>를 <code>생성</code>하고 해당 모델 데이터를 반환</p>
</blockquote>
<pre><code class="language-shell">create()
&gt;
#예시
Post.objects.create({필드}={값})</code></pre>
<hr>
<h3 id="18-get_or_create">1.8. get_or_create()</h3>
<blockquote>
<p><code>조건</code>에 맞는 <code>데이터를 조회</code>, <code>해당 데이터가 없다면</code> <code>새로 생성</code> 후 새로 생성된 모델 데이터를 반환</p>
</blockquote>
<pre><code class="language-shell">get_or_create()
&gt;
#예시
Post.objects.get_or_create({필드}={값})</code></pre>
<hr>
<h3 id="19-latest">1.9. latest()</h3>
<blockquote>
<p>주어진 필드 기준으로 <code>가장 최신</code>의 모델 데이터를 반환</p>
</blockquote>
<pre><code class="language-shell">latest()
&gt;
#예시
Post.objects.latest({필드})</code></pre>
<hr>
<h3 id="110-first">1.10. first()</h3>
<blockquote>
<p>Queryset의 <code>가장 첫번 째</code> 모델 데이터를 반환, 만약 정렬하지 않은 Queryset인 경우 pk를 기준으로 정렬 후 반환</p>
</blockquote>
<pre><code class="language-shell">first()
&gt;
#예시
Post.objects.order_by({필드}).first()</code></pre>
<hr>
<h3 id="111-last">1.11. last()</h3>
<blockquote>
<p>Queryset의 <code>가장 마지막</code> 모델 데이터를 반환</p>
</blockquote>
<pre><code class="language-shell">last()
&gt;
#예시
Post.objects.order_by({필드}).last()</code></pre>
<hr>
<h3 id="112-exists">1.12. exists()</h3>
<blockquote>
<p>Queryset에 데이터가 있다면 True 반환</p>
</blockquote>
<pre><code class="language-shell">exists()
&gt;
#예시
Post.objects.get({조건}).exists()</code></pre>
<hr>
<h3 id="113-count">1.13. count()</h3>
<blockquote>
<p>Queryset의 <code>데이터 개수</code>를 <code>정수</code>로 반환</p>
</blockquote>
<pre><code class="language-shell">count()
&gt;
#예시
Post.objects.all().count()</code></pre>
<hr>
<h3 id="114-update">1.14. update()</h3>
<blockquote>
<p>데이터를 수정할 때 사용하며, <code>여러 데이터 또는 필드</code>를 한 번에 <code>수정</code>할 수 있음
수정 후 수정된 데이터의 개수를 정수로 반환</p>
</blockquote>
<pre><code class="language-shell">update()
&gt;
#예시
Post.objects.filter({조건}).update({변경내용})</code></pre>
<hr>
<h2 id="2-필드-조건-옵션">2. 필드 조건 옵션</h2>
<hr>
<h3 id="21-__contains">2.1. __contains</h3>
<blockquote>
<p><code>대소문자를 구분</code>하여 <code>문자열 포함</code> 여부 확인</p>
</blockquote>
<pre><code class="language-shell">#예시
Post.objects.get({필드}__contains={값})</code></pre>
<hr>
<h3 id="22-__icontains">2.2. __icontains</h3>
<blockquote>
<p><code>대소문자를 구분하지 않고</code> <code>문자열 포함</code> 여부 확인</p>
</blockquote>
<pre><code class="language-shell">#예시
Post.objects.get({필드}__icontains={값})</code></pre>
<hr>
<h3 id="23-__in">2.3. __in</h3>
<blockquote>
<p>반복 가능한 객체 안에서의 포함 여부를 확인</p>
</blockquote>
<pre><code class="language-shell">#예시
Post.objects.filter({필드}__in=[{값1}, {값2}, {값3}])</code></pre>
<hr>
<h3 id="24-__gt">2.4. __gt</h3>
<blockquote>
<p><code>초과</code> 여부 확인</p>
</blockquote>
<pre><code class="language-shell">#예시
Post.objects.filter({필드}__gt={값})</code></pre>
<hr>
<h3 id="25-__gte">2.5. __gte</h3>
<blockquote>
<p><code>이상</code> 여부 확인</p>
</blockquote>
<pre><code class="language-shell">#예시
Post.objects.filter({필드}__gte={값})</code></pre>
<hr>
<h3 id="26-__lt">2.6. __lt</h3>
<blockquote>
<p><code>미만</code> 여부 확인</p>
</blockquote>
<pre><code class="language-shell">#예시
Post.objects.filter({필드}__lt={값})</code></pre>
<hr>
<h3 id="27-__lte">2.7. __lte</h3>
<blockquote>
<p><code>이하</code> 여부 확인</p>
</blockquote>
<pre><code class="language-shell">#예시
Post.objects.filter({필드}__lte={값})</code></pre>
<hr>
<h3 id="28-__startswith">2.8. __startswith</h3>
<blockquote>
<p><code>대소문자를 구분</code>하여 <code>해당 문자열로 시작</code>하는지 여부 확인</p>
</blockquote>
<pre><code class="language-shell">#예시
Post.objects.filter({필드}__startswith={값})</code></pre>
<hr>
<h3 id="29-__istartswith">2.9. __istartswith</h3>
<blockquote>
<p><code>대소문자를 구분하지 않고</code> <code>해당 문자열로 시작</code>하는지 여부 확인</p>
</blockquote>
<pre><code class="language-shell">#예시
Post.objects.filter({필드}__istartswith={값})</code></pre>
<hr>
<h3 id="210-__endswith">2.10. __endswith</h3>
<blockquote>
<p><code>대소문자를 구분</code>하여 <code>해당 문자열로 끝</code>나는지 여부 확인</p>
</blockquote>
<pre><code class="language-shell">#예시
Post.objects.filter({필드}__endswith={값})</code></pre>
<hr>
<h3 id="211-__iendswith">2.11. __iendswith</h3>
<blockquote>
<p><code>대소문자를 구분하지 않고</code> <code>해당 문자열로 끝</code>나는지 여부 확인</p>
</blockquote>
<pre><code class="language-shell">#예시
Post.objects.filter({필드}__iendswith={값})</code></pre>
<hr>
<h3 id="212-__range">2.12. __range</h3>
<blockquote>
<p>range로 제시하는 <code>범위 내에 포함</code>되는지 확인</p>
</blockquote>
<pre><code class="language-shell">#예시
Post.objects.filter({필드}__range={시작값}, {끝값})</code></pre>
<hr>
<h3 id="213-__isnull">2.13. __isnull</h3>
<blockquote>
<p>해당 필드가 <code>Null</code> 인지 여부를 확인</p>
</blockquote>
<pre><code class="language-shell">#예시
Post.objects.filter({필드}__isnull=True)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[<Dear> 프로젝트]]></title>
            <link>https://velog.io/@ethan_/Dear-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@ethan_/Dear-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Thu, 04 Jan 2024 09:25:41 GMT</pubDate>
            <description><![CDATA[<h1 id="1-프로젝트-개요">1. 프로젝트 개요</h1>
<p>Django CRUD를 학습하면서, Dear 프로젝트를 만들어 볼 것이다. </p>
<p><img src="https://velog.velcdn.com/images/ethan_/post/342a689a-0a74-4523-a611-18b7c0f54adc/image.png" alt=""></p>
<hr>
<h2 id="11-dear의-의미">1.1 Dear의 의미</h2>
<blockquote>
<p><strong>Dear</strong>은 Daily Events and Affective Reactions의 약어다.</p>
</blockquote>
<hr>
<h2 id="12-주요-특징">1.2 주요 특징</h2>
<blockquote>
<p><strong>서비스의 주요 특징</strong>은 사용자가 오늘 하루 있었던 일과 관련된 <code>경험 태그</code> 하나와 그로 인해 느낀 <code>감정 태그</code> 하나를 선택하여 글을 작성하고, 다른 사람들이 작성한 글 중, 태그가 일치하는 글을 볼 수 있는 것이다.</p>
</blockquote>
<hr>
<h2 id="13-기대-효과">1.3 기대 효과</h2>
<blockquote>
<p><strong>Dear</strong>은 일상의 사건과 감정을 중심으로 하는 소셜 네트워크로서, 사용자들이 자신의 경험을 기반으로 비슷한 경험을 한 사용자를 찾아 서로의 이야기에 공감하며 상호작용하는 공간을 제공할 수 있다.</p>
</blockquote>
<hr>
<h1 id="2프로젝트-생성-및-설정">2.프로젝트 생성 및 설정</h1>
<h2 id="21-가상환경-설정">2.1 가상환경 설정</h2>
<blockquote>
<p>Dear 프로젝트를 생성하기 전, <code>가상환경 설정</code>을 위해 django 디렉토리를 하나 생성해준다.</p>
</blockquote>
<pre><code class="language-bash">mkdir django</code></pre>
<blockquote>
<p>django 디렉토리에 기존에 생성해둔 <code>django-envs</code> 가상환경을 적용해준다</p>
</blockquote>
<pre><code class="language-bash">cd django
pyenv local django-envs</code></pre>
<hr>
<h2 id="22-장고-프로젝트-생성">2.2 장고 프로젝트 생성</h2>
<blockquote>
<p>Dear 프로젝트를 생성한다.</p>
</blockquote>
<pre><code class="language-bash">django-admin startproject dear</code></pre>
<blockquote>
<p>프로젝트 루트로 이동 후 vscode를 실행한다.</p>
</blockquote>
<pre><code class="language-bash">cd dear
code .</code></pre>
<hr>
<h2 id="23-프로젝트-초기-설정">2.3 프로젝트 초기 설정</h2>
<blockquote>
<p><code>settings.py</code>에서 타임존을 한국 시간으로 변경한다.</p>
</blockquote>
<pre><code class="language-python">#settings.py

TIME_ZONE = &#39;Asia/Seoul&#39;</code></pre>
<hr>
<h2 id="24-앱-생성">2.4 앱 생성</h2>
<blockquote>
<p>posts 앱을 생성한다. 터미널에서 명령어를 입력한다.</p>
</blockquote>
<pre><code class="language-bash">python manage.py startapp posts</code></pre>
<blockquote>
<p>앱을 생성한 뒤에는 <code>settings.py</code>에서 등록해야 한다.</p>
</blockquote>
<pre><code class="language-python">#settings.py

INSTALLED_APPS = [
    &#39;django.contrib.admin&#39;,
    &#39;django.contrib.auth&#39;,
    &#39;django.contrib.contenttypes&#39;,
    &#39;django.contrib.sessions&#39;,
    &#39;django.contrib.messages&#39;,
    &#39;django.contrib.staticfiles&#39;,
    &#39;posts&#39;  #등록
]</code></pre>
<blockquote>
<p>위를 살펴보면 posts 말고도 장고가 기본적으로 제공하는 앱들이 있다. 해당 앱들이 필요로 하는 데이터 구조를 생성하기 위해서 <code>migrate</code> 명령어를 입력한다.</p>
</blockquote>
<pre><code class="language-bash">python manage.py migrate</code></pre>
<blockquote>
<p>명령어를 입력하면 <code>db.sqlite3</code> 파일이 생성된 것을 확인할 수 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ethan_/post/46247153-ff35-4577-9cbe-6bcd6bf7bc77/image.png" alt=""></p>
<hr>
<h2 id="25-개발서버-실행">2.5 개발서버 실행</h2>
<blockquote>
<p>개발 서버를 실행하고 서버가 정상적으로 동작하는지 확인한다.</p>
</blockquote>
<pre><code class="language-bash">python manage.py runserver</code></pre>
<blockquote>
<p>아래와 같이 잘 동작하는 것을 알 수 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ethan_/post/49e5f8dc-0037-4aa1-9d0b-8559492a011c/image.png" alt=""></p>
<hr>
<h1 id="3-url-구조-작성">3. URL 구조 작성</h1>
<blockquote>
<p>Dear의 url 구조는 다음과 같다.</p>
</blockquote>
<ol>
<li>홈페이지</li>
<li>내가 작성한 포스트 목록 (R)</li>
<li>오늘 내가 작성한 포스트와 태그가 일치하는 포스트 목록 (R)</li>
<li>포스트 상세 (R)</li>
<li>포스트 작성 (C)</li>
<li>포스트 수정 (U)</li>
<li>포스트 삭제 (D)</li>
</ol>
<blockquote>
<p>다만 현재 학습하는 토픽에서는  기본적인 포스팅 구조만을 배우고 있기에 하단의 url 구조를 따르고, 추후 변경하는 것을 목표로 한다.</p>
</blockquote>
<ol>
<li>홈페이지</li>
<li>전체 포스트 목록 (R)</li>
<li>포스트 상세 (R)</li>
<li>포스트 작성 (C)</li>
<li>포스트 수정 (U)</li>
<li>포스트 삭제 (D)</li>
</ol>
<hr>
<h2 id="31-프로젝트-구성-디렉토리-url-패턴-작성">3.1 프로젝트 구성 디렉토리 URL 패턴 작성</h2>
<blockquote>
<p>프로젝트 구성 디렉토리의 <code>urls.py</code> 를 작성한다.</p>
</blockquote>
<pre><code class="language-python">#dear/urls.py

from django.contrib import admin
from django.urls import path, include  #include 추가

urlpatterns = [
    path(&#39;admin/&#39;, admin.site.urls),
    path(&#39;&#39;, include(&#39;posts.urls&#39;)),  #작성
]
</code></pre>
<blockquote>
<p>이렇게 작성하면 로컬호스트:8000으로 접속하거나 로컬호스트:8000/빈url로 접속하면 가장 먼저 <code>posts</code> 앱의 <code>urls</code>를 확인하게 된다.</p>
</blockquote>
<hr>
<h2 id="32-posts-앱-url-패턴-작성">3.2 posts 앱 URL 패턴 작성</h2>
<blockquote>
<p>posts 앱에 urls가 없으니 새로 생성하고 코드를 작성해 준다.</p>
</blockquote>
<pre><code class="language-python">#posts/urls.py

from django.urls import path  #작성
from . import views  #작성

urlpatterns = [
    path(&#39;&#39;, views.index),
    path(&#39;posts/&#39;, views.post_list),
    path(&#39;posts/new&#39;, views.post_create),
    path(&#39;posts/&lt;int:post_id&gt;&#39;, views.post_detail),
    path(&#39;posts/&lt;int:post_id&gt;/edit&#39;, views.post_update),
    path(&#39;posts/&lt;int:post_id&gt;/delete&#39;, views.post_delete),
]
</code></pre>
<hr>
<h3 id="321-각-코드의-의미">3.2.1 각 코드의 의미</h3>
<blockquote>
<p>기본적으로 로컬호스트:8000/ 으로 접속하면 장고는 프로젝트 구성 디렉토리의 url 패턴에 따라 posts 앱의 url 패턴을 확인한다.</p>
</blockquote>
<blockquote>
<p><code>path(&#39;&#39;, views.index)</code> 는 <code>로컬호스트:8000/{빈 url}</code>로 접속하면 views의 index 함수를 확인하라는 내용이다.</p>
</blockquote>
<blockquote>
<p><code>path(&#39;posts/&#39;, views.post_list)</code>는 <code>로컬호스트:8000/posts</code>로 접속하면 views의 post_list 함수를 확인하라는 내용이다.</p>
</blockquote>
<blockquote>
<p><code>path(&#39;posts/new&#39;, views.post_create)</code>는 <code>로컬호스트:8000/posts/new</code>로 접속하면 views의 post_create 함수를 확인하라는 내용이다.</p>
</blockquote>
<blockquote>
<p><code>path(&#39;posts/&lt;int:post_id&gt;&#39;, views.post_detail)</code>는 <code>로컬호스트:8000/posts/{url}</code>로 접속하면 {url}을 정수로 보고 post_id에 담아서 views의 post_detail 함수를 확인하라는 내용이다.</p>
</blockquote>
<blockquote>
<p><code>path(&#39;posts/&lt;int:post_id&gt;/edit&#39;, views.post_update)</code>는 <code>로컬호스트:8000/posts/{url}/edit</code>으로 접속하면 {url}을 정수로 보고 post_id에 담아서 views의 post_update 함수를 확인하라는 내용이다.</p>
</blockquote>
<blockquote>
<p><code>path(&#39;posts/&lt;int:post_id&gt;/delete&#39;, views.post_delete)</code>는 <code>로컬호스트:8000/posts/{url}/delete</code>로 접속하면 {url}을 정수로 보고 post_id에 담아서 views의 post_delete 함수를 확인하라는 내용이다.</p>
</blockquote>
<hr>
<h3 id="322-url-패턴-주석처리">3.2.2 URL 패턴 주석처리</h3>
<blockquote>
<p>현재는 각 url 패턴과 매칭되는 views가 정의되지 않았으니 개발 서버를 실행하면 에러가 날 것이다. 따라서 url 패턴을 주석처리 해둔다.</p>
</blockquote>
<pre><code class="language-python">from django.urls import path
from . import views

urlpatterns = [
    # path(&#39;&#39;, views.index),
    # path(&#39;posts/&#39;, views.post_list),
    # path(&#39;posts/new&#39;, views.post_create),
    # path(&#39;posts/&lt;int:post_id&gt;&#39;, views.post_detail),
    # path(&#39;posts/&lt;int:post_id&gt;/edit&#39;, views.post_update),
    # path(&#39;posts/&lt;int:post_id&gt;/delete&#39;, views.post_delete),
]</code></pre>
<hr>
<h1 id="4-model-정의">4. model 정의</h1>
<h2 id="41-post-모델-정의">4.1 Post 모델 정의</h2>
<blockquote>
<p>데이터베이스에 접근하기 위해서는 model을 정의해줘야 한다. posts 앱 디렉토리의 <code>models.py</code>를 작성한다.</p>
</blockquote>
<pre><code class="language-python">#posts/models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=50)
    content = models.TextField()
    dt_created = models.DateTimeField(verbose_name=&quot;Date Created&quot;, auto_now_add=True)
    dt_modified = models.DateTimeField(verbose_name=&quot;Date Modified&quot;, auto_now=True)

    def __str__(self):
        return self.title</code></pre>
<hr>
<h3 id="411-각-코드의-의미">4.1.1 각 코드의 의미</h3>
<blockquote>
<p><code>from django.db import models</code> 
장고 프레임워크의 db 모듈에서 models 클래스를 불러온다</p>
</blockquote>
<blockquote>
<p><code>class Post(models.Model):</code>
Post 라는 이름의 모델 클래스를 생성한다.</p>
</blockquote>
<blockquote>
<p><code>title = models.CharField(max_length=50)</code>
Post 모델에 최대 길이가 50인 문자열 컬럼 title을 추가한다.</p>
</blockquote>
<blockquote>
<p><code>content = models.TextField()</code>
Post 모델에 최대 길이 제한이 없는 문자열 컬럼 content를 추가한다.</p>
</blockquote>
<blockquote>
<p><code>dt_created = models.DateTimeField(verbose_name=&quot;Date Created&quot;, auto_now_add=True)</code>
Post 모델에 Date Created라는 별명을 가졌으며 인스턴스 생성 시 생성 시간이 자동으로 입력되는 컬럼 dt_created를 추가한다.</p>
</blockquote>
<blockquote>
<p><code>dt_modified = models.DateTimeField(verbose_name=&quot;Date Modified&quot;, auto_now=True)</code>
Post 모델에 Date Modified라는 별명을 가졌으며 인스턴스 변경 시 변경 시간이 자동으로 입력되는 컬럼 dt_modified를 추가한다.</p>
</blockquote>
<blockquote>
<p>마지막 던더 str 함수는 title을 문자열로 반환하는 역할을 한다.
shell 환경에서 데이터를 불러오면 각 데이터의 title을 표기한다.</p>
</blockquote>
<pre><code class="language-python">def __str__(self):
    return self.title</code></pre>
<hr>
<h2 id="42-migration-생성-및-mirgrate">4.2 migration 생성 및 mirgrate</h2>
<blockquote>
<p>모델이 생성되거나 변경되면 <code>makemigrations</code>으로 새로운 migrations 파일을 생성하고 <code>migrate</code>를 통해 실제 데이터베이스에 적용해야 한다. 터미널에서 아래의 명령어를 입력한다.</p>
</blockquote>
<pre><code class="language-bash">python manage.py makemigrations  #migration 파일 생성
python manage.py migrate  #데이터베이스에 적용
</code></pre>
<hr>
<h1 id="5-더미데이터">5. 더미데이터</h1>
<h2 id="51-더미데이터-생성">5.1 더미데이터 생성</h2>
<blockquote>
<p>더미데이터를 생성하는 방법을 알아보자</p>
</blockquote>
<blockquote>
<p><code>shell</code> 환경을 불러온다.</p>
</blockquote>
<pre><code class="language-bash">python manage.py shell  #shell 환경을 불러오는 명령어</code></pre>
<blockquote>
<p>모델의 <code>Post</code> 클래스를 불러온다.</p>
</blockquote>
<pre><code class="language-bash">from posts.models import Post  #posts 디렉토리의 모델 파일에서 Post 클래스를 불러온다</code></pre>
<blockquote>
<p>데이터가 있는지 확인하기 위해 모든 데이터를 불러온다.</p>
</blockquote>
<pre><code class="language-bash">Post.objects.all()  #모든 데이터를 불러오는 명령어</code></pre>
<blockquote>
<p>아직 생성된 데이터가 없으니 데이터를 수동으로 생성해본다.</p>
</blockquote>
<pre><code class="language-shell">Post.objects.create(title = &quot;기분 좋은 날&quot;,content = &quot;오늘은 평소와 달리 기분 좋은 아침을 맞이했다.&quot;)</code></pre>
<blockquote>
<p>데이터가 생성 되었다면 다음과 같이 출력될 것이다. 
<img src="https://velog.velcdn.com/images/ethan_/post/3d302e95-91bc-49d3-ab63-25c21f2bc3c6/image.png" alt=""></p>
<blockquote>
<p>출력 결과에 위처럼 데이터의 <code>title</code>이 호출되는 이유는 <code>던더 str</code> 함수에서 self.title을 반환하기 때문이다</p>
</blockquote>
</blockquote>
<pre><code class="language-python">def __str__(self):
    return self.title</code></pre>
<blockquote>
<p>생성된 데이터를 자세히 살펴보기 위해 아래의 명령어를 실행하면</p>
</blockquote>
<pre><code class="language-shell">Post.objects.all().values()  #모델의 모든 데이터를 모든 컬럼의 값을 포함하여 호출</code></pre>
<blockquote>
<p>다음과 같이 출력된다.
<img src="https://velog.velcdn.com/images/ethan_/post/89a83b9b-13f4-4fcf-872c-09dd90b3a1a8/image.png" alt=""></p>
<blockquote>
<p>Post 클래스의 <code>dt_created</code>와 <code>dt_modified</code> 컬럼은 <code>auto_now</code>와 <code>auto_now_add</code> 속성을 사용했기 때문에 직접 입력하지 않아도 <code>자동</code>으로 생성되는 것을 확인할 수 있다.</p>
</blockquote>
</blockquote>
<hr>
<h2 id="52-더미데이터-수정">5.2 더미데이터 수정</h2>
<blockquote>
<p>이번에는 더미데이터를 수정하는 방법을 알아보자.</p>
</blockquote>
<blockquote>
<p>데이터 수정을 위해서는 먼저 데이터를 불러와야 한다. 따라서 post라는 변수에 id가 1인 객체를 지정한다.</p>
</blockquote>
<pre><code class="language-shell">post = Post.objects.get(id=1)  #id가 1인 객체를 post 변수에 삽입</code></pre>
<blockquote>
<p>post.title을 입력해 현재 title을 확인한다.</p>
</blockquote>
<pre><code class="language-shell">post.title  #post변수의 안쪽 속성에 접근해 title 컬럼의 값을 확인</code></pre>
<p><img src="https://velog.velcdn.com/images/ethan_/post/3ca914bd-47cc-4bd3-9959-924a1af98095/image.png" alt=""></p>
<blockquote>
<p>이제 post 변수에 담긴 데이터의 title을 변경해보자.</p>
</blockquote>
<pre><code class="language-shell">post.title = &quot;기분이 너어무 좋은 날&quot;</code></pre>
<blockquote>
<p>post.title로 확인해보니 변경이 완료됐다.
<img src="https://velog.velcdn.com/images/ethan_/post/2277b3ff-16de-40b4-86bc-0348bc1520fb/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>데이터베이스에서도 변경되었는지 확인해보자.</p>
</blockquote>
<pre><code class="language-shell">Post.objects.all()</code></pre>
<blockquote>
<p>변경되지 않았다. 여전히 &quot;기분 좋은 날&quot;로 나타난다.
<img src="https://velog.velcdn.com/images/ethan_/post/2f303238-dbfc-4ed4-b180-4a13351265bf/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>그 이유는 저장을 하지 않았기 때문이다. </p>
</blockquote>
<pre><code class="language-shell">post.save()  #데이터 변경내용 저장</code></pre>
<blockquote>
<p>저장하고 다시 확인해보면 <code>title</code>과 <code>dt_modified</code>가 변경된 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/ethan_/post/9592f68b-30b3-40ca-b067-29a80e743ffc/image.png" alt=""></p>
</blockquote>
<hr>
<h3 id="521-dt_created와-dt_modified가-안맞는다">5.2.1 dt_created와 dt_modified가 안맞는다?</h3>
<blockquote>
<p>분명 <code>settings.py</code>의 타임존을 Asia/Seoul 로 변경했는데 왜 모델 인스턴스의 시간은 <code>UTC</code>로 표기가 되는 것일까?</p>
</blockquote>
<blockquote>
<p>이는 <code>settings.py</code>의 <code>USE_TZ</code>와 관련있다. 장고는 <code>USE_TZ = True</code>인 경우 설정한 타임존이 템플릿과 폼에만 영향을 끼친다. 따라서 <code>USE_TZ = False</code>로 변경하면 해결된다.</p>
</blockquote>
<hr>
<h2 id="53-관리자-페이지에서-데이터-수정">5.3 관리자 페이지에서 데이터 수정</h2>
<blockquote>
<p>이번에는 shell 환경이 아닌 관리자 페이지에서 데이터를 수정해보자.</p>
</blockquote>
<hr>
<h3 id="531-관리자-페이지에-모델-등록">5.3.1 관리자 페이지에 모델 등록</h3>
<blockquote>
<p><code>admin.py</code>에 Post 모델을 등록한다.</p>
</blockquote>
<pre><code class="language-python">#posts/admin.py
&gt;
from django.contrib import admin
from .models import Post  #모델에서 Post 클래스를 불러온다
&gt;
# Register your models here.
admin.site.register(Post)  #Post를 관리자 페이지에 등록한다</code></pre>
<hr>
<h3 id="532-관리자-계정-생성">5.3.2 관리자 계정 생성</h3>
<blockquote>
<p>shell 환경을 종료한다.</p>
</blockquote>
<pre><code class="language-shell">exit()  #shell 환경 종료</code></pre>
<blockquote>
<p>관리자 계정을 생성한다.</p>
</blockquote>
<pre><code class="language-bash">python manage.py createsuperuser  #관리자 계정 생성</code></pre>
<hr>
<h3 id="533-관리자-페이지-접속">5.3.3 관리자 페이지 접속</h3>
<blockquote>
<p>개발서버를 실행한다.</p>
</blockquote>
<pre><code class="language-bash">python manage.py runserver</code></pre>
<blockquote>
<p>로컬호스트:8000/admin으로 접속한다.</p>
</blockquote>
<blockquote>
<p>관리자로 로그인하면 Posts가 생성된 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/ethan_/post/cae21e3f-5408-41a0-84b6-c931948b0377/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>Posts를 클릭하면 shell 환경으로 작성한 데이터를 확인할 수 있다.
<img src="https://velog.velcdn.com/images/ethan_/post/8bda08f8-3274-4830-85da-05f3de19d6b3/image.png" alt=""></p>
</blockquote>
<hr>
<h1 id="6-포스트-목록-페이지-만들기">6. 포스트 목록 페이지 만들기</h1>
<hr>
<h2 id="61-url-패턴-주석-해제">6.1 url 패턴 주석 해제</h2>
<blockquote>
<p>posts 앱 디렉토리의 <code>urls.py</code>의 <code>url 패턴</code> 중 <code>포스트 목록 페이지</code>에 해당하는 코드를 <code>주석 해제</code> 한다.</p>
</blockquote>
<pre><code class="language-python">from django.urls import path
from . import views

urlpatterns = [
    # path(&#39;&#39;, views.index),
    path(&#39;posts/&#39;, views.post_list),  #주석 해제
    # path(&#39;posts/new&#39;, views.post_create),
    # path(&#39;posts/&lt;int:post_id&gt;&#39;, views.post_detail),
    # path(&#39;posts/&lt;int:post_id&gt;/edit&#39;, views.post_update),
    # path(&#39;posts/&lt;int:post_id&gt;/delete&#39;, views.post_delete),
]</code></pre>
<hr>
<h2 id="62-viewspy-작성">6.2 views.py 작성</h2>
<blockquote>
<p>로컬호스트:8000/posts/ 경로로 접속 시 매칭되는 views의 <code>post_list</code> 함수를 작성한다.</p>
</blockquote>
<pre><code class="language-python">from django.shortcuts import render

# Create your views here.

def post_list(request):
    return render(request, &#39;posts/post_list.html&#39;)</code></pre>
<blockquote>
<p><code>post_list</code> 함수는 요청이 들어오면 posts 앱 디렉토리의 <code>post_list.html</code> 템플릿을 렌더한다.</p>
</blockquote>
<hr>
<h2 id="63-post_listhtml-템플릿-작성">6.3 post_list.html 템플릿 작성</h2>
<h3 id="631-templates-디렉토리-생성">6.3.1 templates 디렉토리 생성</h3>
<blockquote>
<p>먼저 templates 디렉토리를 생성한다. templates과 정적 파일은 <code>샌드위치 구조</code>의 디렉토리를 생성해야 한다.
<img src="https://velog.velcdn.com/images/ethan_/post/2aece7c8-2ebc-43c3-b33d-b226446742e6/image.png" alt=""></p>
</blockquote>
<hr>
<h3 id="632-basehtml-템플릿-생성">6.3.2 base.html 템플릿 생성</h3>
<blockquote>
<p>자주 사용되며 변경되지 않는 내용은 부모 템플릿을 통해 상속받도록 <code>temlpates/posts</code>에 <code>base.html</code>을 생성하고 작성한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!-- base.html --&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
    &lt;head&gt;
        &lt;meta charset=&quot;UTF-8&quot;&gt;
        &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
        &lt;title&gt;Dear :: Daily Events and Affective Reactions&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        {% block post_container %}  &lt;!--상속받지 않는 부분--&gt;
          {% endblock post_container %}
    &lt;/body&gt;
&lt;/html&gt;</code></pre>
<blockquote>
<p>상속받지 않는 부분은 <code>block</code> 영역을 지정하고 <code>post_container</code>로 명명한다.</p>
</blockquote>
<hr>
<h3 id="633-post_listhtml-템플릿-생성">6.3.3 post_list.html 템플릿 생성</h3>
<blockquote>
<p>base.html을 상속받는 <code>post_list.html</code>을 생성하고 내용을 작성한다.</p>
</blockquote>
<pre><code class="language-html">{% extends &quot;./base.html&quot; %}  &lt;!--부모 템플릿 상속--&gt;

{% block post_container %}  &lt;!--상속받지 않는 영역--&gt;
    &lt;h1&gt;글 목록 페이지입니다.&lt;/h1&gt;
{% endblock post_container %}</code></pre>
<blockquote>
<p>base.html을 상속받는 <code>extends</code> 템플릿 태그를 작성하고, container 블록에 &amp;lth1&amp;gt 태그를 입력한다.</p>
</blockquote>
<blockquote>
<p>개발 서버를 실행하고 접속하면 다음과 같이 나타난다.
<img src="https://velog.velcdn.com/images/ethan_/post/859ee259-3a73-4775-aed7-6cc30cfd84c2/image.png" alt=""></p>
</blockquote>
<hr>
<h2 id="64-view에-데이터를-불러오는-로직-추가">6.4 view에 데이터를 불러오는 로직 추가</h2>
<blockquote>
<p>post_list 함수에 Post 모델을 통해 데이터를 가져오는 로직을 추가한다.</p>
</blockquote>
<pre><code class="language-python"># views.py

from django.shortcuts import render
from .models import Post

# Create your views here.

def post_list(request):
    posts = Post.objects.all()
    context = {&quot;posts&quot; : posts}
    return render(request, &#39;posts/post_list.html&#39;, context=context)</code></pre>
<h3 id="641-각-코드의-의미">6.4.1 각 코드의 의미</h3>
<blockquote>
<p>현재 디렉토리의 <code>models</code> 파일에서 <code>Post</code> 모델을 불러온다.</p>
</blockquote>
<pre><code class="language-python">from .models import Post</code></pre>
<blockquote>
<p><code>posts</code> 변수에 <code>Post 모델의 모든 데이터</code>를 불러오고, <code>context</code> 변수에 사전형으로 담아서 post_list.html 템플릿을 렌더할 때 함께 전달한다.</p>
</blockquote>
<pre><code class="language-python">def post_list(request):
    posts = Post.objects.all()
    context = {&quot;posts&quot; : posts}
    return render(request, &#39;posts/post_list.html&#39;, context=context)</code></pre>
<hr>
<h2 id="65-post_list-템플릿-수정">6.5 post_list 템플릿 수정</h2>
<blockquote>
<p>view로부터 context로 전달받은 데이터를 호출할 수 있도록 <code>템플릿 태그</code>를 사용해 템플릿을 수정한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_list.html--&gt;

{% extends &quot;./base.html&quot; %}

{% block post_container %}
    &lt;h1&gt;글 목록 페이지입니다.&lt;/h1&gt;
    &lt;table&gt;
        &lt;tr&gt;
            &lt;td&gt;제목&lt;/td&gt;
            &lt;td&gt;작성일&lt;/td&gt;
            &lt;td&gt;수정일&lt;/td&gt;
        &lt;/tr&gt;
        {% for post in posts %}
            &lt;tr&gt;
                &lt;td&gt;{{post.title}}&lt;/td&gt;
                &lt;td&gt;{{post.dt_created}}&lt;/td&gt;
                &lt;td&gt;{{post.dt_modified}}&lt;/td&gt;
            &lt;/tr&gt;
        {% endfor %}

    &lt;/table&gt;
{% endblock post_container %}</code></pre>
<blockquote>
<p>각 게시글이 쉽게 구분될 수 있도록 <code>테이블 태그</code>를 사용했다.</p>
</blockquote>
<hr>
<h3 id="651-각-코드의-의미">6.5.1 각 코드의 의미</h3>
<blockquote>
<p><code>for 템플릿 태그</code>를 사용해 <code>사전형</code>으로 전달받은 데이터를 post 이름으로 안쪽 속성에 접근해 <code>title</code>, <code>de_created</code>, <code>dt_modified</code>를 각 &amp;lttd&amp;gt 태그에 작성하는 것을 반복한다.</p>
</blockquote>
<pre><code class="language-html">{% for post in posts %}
    &lt;tr&gt;
        &lt;td&gt;{{post.title}}&lt;/td&gt;
        &lt;td&gt;{{post.dt_created}}&lt;/td&gt;
        &lt;td&gt;{{post.dt_modified}}&lt;/td&gt;
    &lt;/tr&gt;
{% endfor %}</code></pre>
<hr>
<h3 id="652-결과">6.5.2 결과</h3>
<blockquote>
<p>개발 서버를 실행하고 접속해보면 아래와 같이 호출된다.
<img src="https://velog.velcdn.com/images/ethan_/post/20e79f0c-d12f-4e5a-b000-b13b6e2b2968/image.png" alt=""></p>
</blockquote>
<hr>
<h1 id="7-포스트-상세-페이지-만들기">7. 포스트 상세 페이지 만들기</h1>
<hr>
<h2 id="71-url-패턴-설정">7.1 url 패턴 설정</h2>
<blockquote>
<p>기존에 작성한 <code>urls.py</code>에서 포스트 상세 페이지에 대한 url 패턴을 주석해제 한다.</p>
</blockquote>
<pre><code class="language-python">#posts/urls.py

from django.urls import path
from . import views

urlpatterns = [
    # path(&#39;&#39;, views.index),
    path(&#39;posts/&#39;, views.post_list),
    # path(&#39;posts/new&#39;, views.post_create),
    path(&#39;posts/&lt;int:post_id&gt;&#39;, views.post_detail),  #주석 해제
    # path(&#39;posts/&lt;int:post_id&gt;/edit&#39;, views.post_update),
    # path(&#39;posts/&lt;int:post_id&gt;/delete&#39;, views.post_delete),
]</code></pre>
<hr>
<h2 id="72-post_detail-뷰-작성">7.2 post_detail 뷰 작성</h2>
<blockquote>
<p><code>post_detail</code> 뷰를 작성한다.</p>
</blockquote>
<pre><code class="language-python">#posts/views.py

from django.shortcuts import render
from .models import Post

# Create your views here.

def post_list(request):
    posts = Post.objects.all()
    context = {&quot;posts&quot; : posts}
    return render(request, &#39;posts/post_list.html&#39;, context=context)


def post_detail(request, post_id):  #작성
    post = Post.objects.get(id=post_id)
    context = {&quot;post&quot;: post}
    return render(request, &#39;posts/post_detail.html&#39;, context=context)
</code></pre>
<hr>
<h3 id="721-각-코드의-의미">7.2.1 각 코드의 의미</h3>
<blockquote>
<p><code>post_detail</code> 함수는 request와 <code>post_id</code>를 파라미터로 받는다. <code>post_id</code>는  <code>posts/{url}</code>인 경우 <code>{url}</code>을 <code>정수</code>로 보고 <code>post_id</code>라는 이름으로 넘겨받는 것으로 url 패턴에서 정의했다.</p>
</blockquote>
<pre><code class="language-python">def post_detail(request, post_id): </code></pre>
<blockquote>
<p>post 변수에 Post 모델에서 post_id와 일치하는 값의 id를 지닌 데이터를 지정하고 context에 사전형으로 지정한다.</p>
</blockquote>
<pre><code class="language-python">    post = Post.objects.get(id=post_id)
    context = {&quot;post&quot;: post}</code></pre>
<blockquote>
<p><code>context</code>와 <code>post_detail.html</code> 템플릿을 렌더해 반환한다.</p>
</blockquote>
<pre><code class="language-python">    return render(request, &#39;posts/post_detail.html&#39;, context)</code></pre>
<hr>
<h2 id="73-post_detailhtml-템플릿-생성">7.3 post_detail.html 템플릿 생성</h2>
<blockquote>
<p><code>post_detail.html</code> 파일을 생성하고 작성한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_detail.html--&gt;
{% extends &quot;./base.html&quot; %}

{% block post_container %}
&lt;h2&gt;{{post.title}}&lt;/h2&gt;
&lt;div&gt;작성일: {{post.dt_created}}&lt;/div&gt;
&lt;hr&gt;
&lt;div&gt;{{post.content}}&lt;/div&gt;
&lt;hr&gt;
{% endblock post_container %}</code></pre>
<hr>
<h3 id="731-각-코드의-의미">7.3.1 각 코드의 의미</h3>
<blockquote>
<p>먼저 부모 템플릿을 상속받는 <code>extends</code> 템플릿 태그를 작성한다.</p>
</blockquote>
<pre><code class="language-html">{% extends &quot;./base.html&quot; %}</code></pre>
<blockquote>
<p>부모 템플릿을 상속받지 않는 <code>post_container</code> 블록 영역을 추가한다.</p>
</blockquote>
<pre><code class="language-html">{% block post_container %}
{% endblock post_container %}</code></pre>
<blockquote>
<p><code>템플릿 변수</code>를 사용해 <code>view</code>로 부터 전달받은 <code>post</code>의 안쪽 속성에 접근한다.</p>
</blockquote>
<pre><code class="language-html">{% block post_container %}
&lt;h2&gt;{{post.title}}&lt;/h2&gt;
&lt;div&gt;작성일: {{post.dt_created}}&lt;/div&gt;
&lt;hr&gt;
&lt;div&gt;{{post.content}}&lt;/div&gt;
&lt;hr&gt;
{% endblock post_container %}</code></pre>
<hr>
<h3 id="732-결과">7.3.2 결과</h3>
<blockquote>
<p>개발 서버를 실행하고 <code>posts/3</code> 으로 접속해보면 아래와 같이 출력된다.
<img src="https://velog.velcdn.com/images/ethan_/post/ce8c678f-fdba-450e-b000-a8d10ccc0a09/image.png" alt=""></p>
</blockquote>
<hr>
<h3 id="733-줄바꿈">7.3.3 줄바꿈</h3>
<blockquote>
<p>그런데 문제가 있다. 생성한 데이터의 내용 중 엔터가 줄바꿈으로 인식되지 않는다.</p>
</blockquote>
<blockquote>
<p>그 이유는 장고에서는 엔터를 <code>\n</code>로 저장하지만 html에서는 <code>&lt;br&gt;</code> 태그로 저장하기 때문이다. 따라서 <code>\n</code>을 <code>&lt;br&gt;</code>로 변경하는 작업이 필요하다.</p>
</blockquote>
<blockquote>
<p><code>post_detail.html</code> 템플릿에서 <code>post.content</code> 변수에 <code>linebreaksbr</code> 템플릿 필터를 작성하면 된다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_detail.html--&gt;
{% extends &quot;./base.html&quot; %}
&gt;
{% block post_container %}
&lt;h2&gt;{{post.title}}&lt;/h2&gt;
&lt;div&gt;작성일: {{post.dt_created}}&lt;/div&gt;
&lt;hr&gt;
&lt;div&gt;{{post.content|linebreaksbr}}&lt;/div&gt;  &lt;!--작성--&gt;
&lt;hr&gt;
{% endblock post_container %}</code></pre>
<blockquote>
<p>작성하면 아래와 같이 장고의 <code>\n</code> 이 html의 <code>&lt;br&gt;</code>로 변경된 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/ethan_/post/40c9d5eb-f096-4f05-bdea-f2038e13d53e/image.png" alt=""></p>
</blockquote>
<hr>
<h1 id="8-목록-페이지와-상세-페이지-연결">8. 목록 페이지와 상세 페이지 연결</h1>
<blockquote>
<p>포스트 목록 페이지와 포스트 상세 페이지를 만들었다. 현재는 포스트 상세 페이지로 이동하려면 <code>posts/{url}</code>을 통해 url을 직접 입력해 이동하는 방법만 있다. 포스트 목록 페이지와 상세 페이지를 연결해보자.</p>
</blockquote>
<hr>
<h2 id="81-목록-페이지에서-상세-페이지로-연결">8.1 목록 페이지에서 상세 페이지로 연결</h2>
<blockquote>
<p><code>post_list.html</code> 템플릿에서 포스트 제목에 <code>&lt;a&gt;</code>태그를 추가한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_list.html--&gt;
{% extends &quot;./base.html&quot; %}

{% block post_container %}
    &lt;h1&gt;글 목록 페이지입니다.&lt;/h1&gt;
    &lt;table&gt;
        &lt;tr&gt;
            &lt;td&gt;제목&lt;/td&gt;
            &lt;td&gt;작성일&lt;/td&gt;
            &lt;td&gt;수정일&lt;/td&gt;
        &lt;/tr&gt;
        {% for post in posts %}
            &lt;tr&gt;
                &lt;td&gt;&lt;a href=&quot;/posts/{{post.id}}&quot;&gt;{{post.title}}&lt;/a&gt;&lt;/td&gt;
                &lt;td&gt;{{post.dt_created}}&lt;/td&gt;
                &lt;td&gt;{{post.dt_modified}}&lt;/td&gt;
            &lt;/tr&gt;
        {% endfor %}

    &lt;/table&gt;
{% endblock post_container %}</code></pre>
<hr>
<h3 id="811-각-코드의-의미">8.1.1 각 코드의 의미</h3>
<blockquote>
<p><code>&lt;td&gt;</code> 태그 안에 <code>&lt;a&gt;</code> 태그가 있고 <code>&lt;a&gt;</code> 태그의 경로는 <code>posts/{{post.id}}</code>이며 대체 텍스트는 <code>{{post.title}}</code> 이다.</p>
</blockquote>
<pre><code class="language-html">&lt;td&gt;&lt;a href=&quot;/posts/{{post.id}}&quot;&gt;{{post.title}}&lt;/a&gt;&lt;/td&gt;</code></pre>
<blockquote>
<p>포스트 목록 페이지를 렌더할 때 url로 부터 <code>posts/{url}</code>의 <code>{url}</code>을 정수로 보고 <code>post_id</code>로 전달받고 view는 <code>post_id</code>를 이용해 각각의 포스트의 <code>title</code>, <code>dt_created</code>, <code>de_modified</code> 데이터에 접근해 출력할 수 있다.</p>
</blockquote>
<blockquote>
<p>따라서 <code>post.id</code>로 각 포스트의 <code>id</code> 데이터에 접근할 수 있고, 이를 통해 <code>&lt;a&gt;</code> 태그의 경로를 <code>posts/{각 포스트의 id}</code>로 지정해 제목을 클릭하면 각 포스트의 <code>상세 페이지로 연결</code>할 수 있다.</p>
</blockquote>
<hr>
<h3 id="812-결과">8.1.2 결과</h3>
<blockquote>
<p>포스트 목록 페이지에서 포스트 제목을 클릭하면
<img src="https://velog.velcdn.com/images/ethan_/post/5c821d72-6bd5-4c58-9223-282263fb05f6/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><code>posts/{포스트의 id}</code>로 이동되며
<img src="https://velog.velcdn.com/images/ethan_/post/0966300a-869c-427f-8d85-8ff1b54e2397/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>포스트의 상세 페이지를 확인할 수 있다.
<img src="https://velog.velcdn.com/images/ethan_/post/226536b1-e1ab-44c8-b374-203aa5c735c3/image.png" alt=""></p>
</blockquote>
<hr>
<h2 id="82-상세-페이지에서-목록-페이지로-연결">8.2 상세 페이지에서 목록 페이지로 연결</h2>
<blockquote>
<p><code>post_detail.html</code> 템플릿에 <code>&lt;a&gt;</code> 태그를 추가한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_detail.html--&gt;

{% extends &quot;./base.html&quot; %}

{% block post_container %}
    &lt;h2&gt;{{post.title}}&lt;/h2&gt;
    &lt;div&gt;작성일: {{post.dt_created}}&lt;/div&gt;
    &lt;hr&gt;
    &lt;div&gt;{{post.content|linebreaksbr}}&lt;/div&gt;
    &lt;hr&gt;
    &lt;a href=&quot;/posts/&quot;&gt;돌아가기&lt;/a&gt;  &lt;!--추가--&gt;
{% endblock post_container %}</code></pre>
<blockquote>
<p><code>&lt;a&gt;</code>태그에 <code>/posts/</code> 경로를 입력하면 클릭 시 포스트 목록 페이지로 이동한다.</p>
</blockquote>
<hr>
<h1 id="9-url에-이름-붙이기">9. url에 이름 붙이기</h1>
<blockquote>
<p>목록 페이지에서 상세 페이지로 이동하는 url은 <code>/posts/{{post.id}}</code> 로 정의되어 있고, 상세 페이지에서 목록 페이지로 이동하는 url은 <code>/posts/</code> 로 정의되어 있다.</p>
</blockquote>
<blockquote>
<p>문제는 url의 구조 또는 url 패턴이 변경되는 경우, 많은 수정이 필요하다는 것이다. 이번에는 하드코딩으로 인한 유지보수의 어려움을 해결하기 위해 url에 이름을 붙인다.</p>
</blockquote>
<hr>
<h2 id="91-url-이름-붙이기">9.1 url 이름 붙이기</h2>
<blockquote>
<p>urls.py 에서 url 패턴을 살펴보자</p>
</blockquote>
<pre><code class="language-python">#posts/urls.py

from django.urls import path
from . import views

urlpatterns = [
    # path(&#39;&#39;, views.index),
    path(&#39;posts/&#39;, views.post_list),
    # path(&#39;posts/new/&#39;, views.post_create),
    path(&#39;posts/&lt;int:post_id&gt;/&#39;, views.post_detail),
    # path(&#39;posts/&lt;int:post_id&gt;/edit/&#39;, views.post_update),
    # path(&#39;posts/&lt;int:post_id&gt;/delete/&#39;, views.post_delete),
]</code></pre>
<blockquote>
<p><code>name</code> 속성을 추가해 각 url에 이름을 붙여보자.</p>
</blockquote>
<pre><code class="language-python">#posts/urls.py

from django.urls import path
from . import views
from django.urls import path
from . import views

urlpatterns = [
    # path(&#39;&#39;, views.index),
    path(&#39;posts/&#39;, views.post_list, name=&quot;post-list&quot;),  #name 속성 추가
    # path(&#39;posts/new/&#39;, views.post_create),
    path(&#39;posts/&lt;int:post_id&gt;/&#39;, views.post_detail, name=&quot;post-detail&quot;),  # name 속성 추가
    # path(&#39;posts/&lt;int:post_id&gt;/edit/&#39;, views.post_update),
    # path(&#39;posts/&lt;int:post_id&gt;/delete/&#39;, views.post_delete),
]</code></pre>
<hr>
<h2 id="92-하드코딩된-부분-수정">9.2 하드코딩된 부분 수정</h2>
<blockquote>
<p>하드코딩된 url 경로를 템플릿 태그 <code>{% url {url name} {인자} %}</code>를 이용해 수정한다.</p>
</blockquote>
<hr>
<h3 id="921-post_listhtml-템플릿-수정">9.2.1 post_list.html 템플릿 수정</h3>
<blockquote>
<p>기존 <code>&lt;a&gt;</code>태그에서는 포스트 상세 페이지 경로를 <code>/posts/{{post.id}}</code>로 설정했다. </p>
</blockquote>
<blockquote>
<p>템플릿 태그 <code>url</code>에 포스트 상세 페이지의 <code>url name</code>을 경로로 입력한 뒤, 한 칸 띄어서 <code>post.id</code> 인자를 넘겨준다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_list.html--&gt;
{% extends &quot;./base.html&quot; %}

{% block post_container %}
    &lt;h1&gt;글 목록 페이지입니다.&lt;/h1&gt;
    &lt;table&gt;
        &lt;tr&gt;
            &lt;td&gt;제목&lt;/td&gt;
            &lt;td&gt;작성일&lt;/td&gt;
            &lt;td&gt;수정일&lt;/td&gt;
        &lt;/tr&gt;
        {% for post in posts %}
            &lt;tr&gt;
                &lt;td&gt;&lt;a href={% url &quot;post-detail&quot; post.id %}&gt;{{post.title}}&lt;/a&gt;&lt;/td&gt;  &lt;!--수정--&gt;
                &lt;td&gt;{{post.dt_created}}&lt;/td&gt;
                &lt;td&gt;{{post.dt_modified}}&lt;/td&gt;
            &lt;/tr&gt;
        {% endfor %}

    &lt;/table&gt;
{% endblock post_container %}</code></pre>
<hr>
<h3 id="922-post_detailhtml-템플릿-수정">9.2.2 post_detail.html 템플릿 수정</h3>
<blockquote>
<p>기존 <code>&lt;a&gt;</code>태그에서는 포스트 목록 경로를 <code>/posts/</code> 로 설정했다.</p>
</blockquote>
<blockquote>
<p>템플릿 태그 <code>url</code>에 포스트 목록 페이지의 <code>url name</code>을 경로로 입력한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_detail.html--&gt;

{% extends &quot;./base.html&quot; %}

{% block post_container %}
    &lt;h2&gt;{{post.title}}&lt;/h2&gt;
    &lt;div&gt;작성일: {{post.dt_created}}&lt;/div&gt;
    &lt;hr&gt;
    &lt;div&gt;{{post.content|linebreaksbr}}&lt;/div&gt;
    &lt;hr&gt;
    &lt;a href={% url &quot;post-list&quot; %}&gt;돌아가기&lt;/a&gt;  &lt;!--수정--&gt;
{% endblock post_container %}</code></pre>
<hr>
<h3 id="923-결과">9.2.3 결과</h3>
<blockquote>
<p>기존에 하드코딩된 방식과 똑같이 작동한다. 추후 url이 변경되는 경우 하드코딩된 방식은 연관된 모든 파일을 수정해야 하지만, <code>url name</code> 속성을 입력한 방식은 연관된 파일을 수정하지 않아도 된다.</p>
</blockquote>
<hr>
<h1 id="10-디자인-입히기">10. 디자인 입히기</h1>
<blockquote>
<p>html과 css를 직접 작성하지 않고, 다른 사람이 작성한 템플릿을 프로젝트에 적용하는 방법을 알아본다.</p>
</blockquote>
<hr>
<h2 id="101-부모-템플릿-확인">10.1 부모 템플릿 확인</h2>
<blockquote>
<p><code>base.html</code>은 다음과 같이 작성되어 있다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--base.html--&gt;

{% load static %}
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;title&gt;DEAR :: Daily Events and Affective Reactions&lt;/title&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    {% block css %}
    {% endblock css %}
    &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot;
    href=&quot;https://fonts.googleapis.com/earlyaccess/nanummyeongjo.css&quot;&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div id=&quot;warp&quot;&gt;
        &lt;div id=&quot;header&quot;&gt;
            &lt;div id=&quot;nav&quot;&gt;
                &lt;div class=&quot;logo&quot;&gt;
                    &lt;a href=&quot;[A]&quot;&gt;&lt;img src=&quot;{% static &#39;posts/images/logo.png&#39; %}&quot;&gt;&lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            {% block post_header %}
            {% endblock post_header %}
        &lt;/div&gt;
        {% block logo_text %}
        {% endblock logo_text %}
        &lt;div id=&quot;content&quot;&gt;
            &lt;div class=&quot;container&quot;&gt;
                {% block content %}
                {% endblock content %}
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div id=&quot;footer&quot;&gt;
            &lt;div class=&quot;footer&quot;&gt;
                &lt;p&gt;Costory&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<hr>
<h3 id="1011-block-영역">10.1.1 block 영역</h3>
<blockquote>
<p>부모 템플릿의 <code>block</code> 영역은 총 4개로 구분된다.</p>
</blockquote>
<blockquote>
<p><code>&lt;head&gt;</code> 태그 안에 작성된 <code>block css</code> 는 자식 템플릿 별로 다른 <code>css</code> 파일을 적용하기 위한 영역이다. </p>
</blockquote>
<pre><code class="language-html">{% block css %}
{% endblock css %}</code></pre>
<blockquote>
<p><code>&lt;body&gt;</code> 태그 안에 작성된 <code>block header</code> 은 포스트 상세 페이지의 제목을 입력하기 위한 영역이다.</p>
</blockquote>
<pre><code class="language-html">{% block post_header %}
{% endblock post_header %}</code></pre>
<blockquote>
<p><code>&lt;body&gt;</code> 태그 안에 작성된 <code>block logo_text</code> 는 포스트 목록 페이지의 상단 로고를 작성하기 위한 영역이다.</p>
</blockquote>
<pre><code class="language-html">{% block logo_text %}
{% endblock logo_text %}</code></pre>
<blockquote>
<p><code>&lt;body&gt;</code> 태그 안에 작성된 <code>block content</code> 는 포스트 목록 페이지의 각 포스트를 보여주기 위한 영역이다.</p>
</blockquote>
<pre><code class="language-html">{% block content %}
{% endblock content %}</code></pre>
<hr>
<h2 id="102-부모-템플릿-수정">10.2 부모 템플릿 수정</h2>
<blockquote>
<p>부모 템플릿의 상단 로고를 클릭하면 이동하는 url이 비어있다. 하드코딩이 아닌, <code>url name</code> 속성을 입력한다. dear의 메인페이지는 포스트 목록 페이지니까 <code>url 템플릿 태그</code>에 포스트 목록 페이지의 <code>name 속성</code>인 <code>post-list</code>를 입력한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--base.html--&gt;

{% load static %}
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;title&gt;DEAR :: Daily Events and Affective Reactions&lt;/title&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    {% block css %}
    {% endblock css %}
    &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot;
    href=&quot;https://fonts.googleapis.com/earlyaccess/nanummyeongjo.css&quot;&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div id=&quot;warp&quot;&gt;
        &lt;div id=&quot;header&quot;&gt;
            &lt;div id=&quot;nav&quot;&gt;
                &lt;div class=&quot;logo&quot;&gt;
                    &lt;a href={% url &quot;post-list&quot; %}&gt;&lt;img src=&quot;{% static &#39;posts/images/logo.png&#39; %}&quot;&gt;&lt;/a&gt;  &lt;!--수정--&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            {% block post_header %}
            {% endblock post_header %}
        &lt;/div&gt;
        {% block logo_text %}
        {% endblock logo_text %}
        &lt;div id=&quot;content&quot;&gt;
            &lt;div class=&quot;container&quot;&gt;
                {% block content %}
                {% endblock content %}
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div id=&quot;footer&quot;&gt;
            &lt;div class=&quot;footer&quot;&gt;
                &lt;p&gt;Costory&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<hr>
<h2 id="103-post_listhtml-확인">10.3 post_list.html 확인</h2>
<blockquote>
<p><code>post_list.html</code> 은 다음과 같이 작성되어 있다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_list.html--&gt;

{% block css %}
    &lt;link rel=&quot;stylesheet&quot; href=&quot;./css/post_list.css&quot;&gt;
{% endblock css %}

{% block logo_text %}
    &lt;div id=&quot;header&quot;&gt;
        &lt;div class=&quot;container&quot;&gt;
            &lt;h1&gt;&lt;img src=&quot;{% static &#39;posts/images/headertxt.png&#39; %}&quot;&gt;&lt;/h1&gt;
        &lt;/div&gt;
    &lt;/div&gt;
{% endblock logo_text %}


{% block content %}
    &lt;div class=&quot;post_container&quot;&gt;

    &lt;/div&gt;
{% endblock content %}</code></pre>
<hr>
<h2 id="104-post_listhtml-수정">10.4 post_list.html 수정</h2>
<blockquote>
<p>우선 부모 템플릿을 상속받는 템플릿 태그와 정적파일을 불러오는 템플릿 태그를 작성한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_list.html--&gt;

{% extends &quot;./base.html&quot; %}  &lt;!--작성--&gt;
{% load static %}  &lt;!--작성--&gt;

{% block css %}
    &lt;link rel=&quot;stylesheet&quot; href=&quot;./css/post_list.css&quot;&gt;
{% endblock css %}

{% block logo_text %}
    &lt;div id=&quot;header&quot;&gt;
        &lt;div class=&quot;container&quot;&gt;
            &lt;h1&gt;&lt;img src=&quot;{% static &#39;posts/images/headertxt.png&#39; %}&quot;&gt;&lt;/h1&gt;
        &lt;/div&gt;
    &lt;/div&gt;
{% endblock logo_text %}


{% block content %}
    &lt;div class=&quot;post_container&quot;&gt;

    &lt;/div&gt;
{% endblock content %}</code></pre>
<blockquote>
<p>다음은 하드 코딩된 css 경로를 정적파일 경로로 수정한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_list.html--&gt;

{% extends &quot;./base.html&quot; %}  
{% load static %}  

{% block css %}
    &lt;link rel=&quot;stylesheet&quot; href=&quot;{% static &quot;posts/css/post_list.css&quot; %}&quot;&gt;  &lt;!--수정--&gt;
{% endblock css %}

{% block logo_text %}
    &lt;div id=&quot;header&quot;&gt;
        &lt;div class=&quot;container&quot;&gt;
            &lt;h1&gt;&lt;img src=&quot;{% static &#39;posts/images/headertxt.png&#39; %}&quot;&gt;&lt;/h1&gt;
        &lt;/div&gt;
    &lt;/div&gt;
{% endblock logo_text %}


{% block content %}
    &lt;div class=&quot;post_container&quot;&gt;

    &lt;/div&gt;
{% endblock content %}</code></pre>
<blockquote>
<p>다음은 <code>block content</code> 영역이다. 여기에는 각 포스트의 <code>제목</code>, <code>작성일</code>, <code>본문의 일부</code>를 표기해야 한다. css를 반영할 수 있는 class를 포함한 코드를 추가하면 다음과 같다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_list.html--&gt;

{% extends &quot;./base.html&quot; %}
{% load static %}

{% block css %}
    &lt;link rel=&quot;stylesheet&quot; href=&quot;{% static &quot;posts/css/post_list.css&quot; %}&quot;&gt;
{% endblock css %}

{% block logo_text %}
    &lt;div id=&quot;header&quot;&gt;
        &lt;div class=&quot;container&quot;&gt;
            &lt;h1&gt;&lt;img src=&quot;{% static &#39;posts/images/headertxt.png&#39; %}&quot;&gt;&lt;/h1&gt;
        &lt;/div&gt;
    &lt;/div&gt;
{% endblock logo_text %}


{% block content %}
    &lt;div class=&quot;post_container&quot;&gt;
        &lt;div class=&quot;post&quot;&gt;&lt;a href=&quot;[A] 개별 포스트로 이동하는 URL&quot;&gt;  &lt;!----&gt;
            &lt;h2 class=&quot;title&quot;&gt;[B] 글 목록에서 보여질 제목&lt;/h2&gt;
            &lt;p class=&quot;date&quot;&gt;[C] 글 목록에서 보여질 작성일&lt;/p&gt;
            &lt;p class=&quot;text&quot;&gt;[D] 글 목록에서 보여질 본문 100자&lt;/p&gt;
        &lt;/a&gt;&lt;/div&gt;
    &lt;/div&gt;
{% endblock content %}</code></pre>
<blockquote>
<p>각 포스트는 클릭 시 해당 포스트의 상세 페이지로 이동하는 기능을 해야 하니, <code>&lt;a&gt;</code> 태그로 감싼다. </p>
</blockquote>
<blockquote>
<p>우리는 이전 과정에서 for 문을 사용해 posts에 담긴 각각의 게시글을 post로 생성했다. 이를 변경된 디자인 템플릿에도 적용될 수 있도록 작성한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_list.html--&gt;

{% extends &quot;./base.html&quot; %}
{% load static %}

{% block css %}
    &lt;link rel=&quot;stylesheet&quot; href=&quot;{% static &quot;posts/css/post_list.css&quot; %}&quot;&gt;
{% endblock css %}

{% block logo_text %}
    &lt;div id=&quot;header&quot;&gt;
        &lt;div class=&quot;container&quot;&gt;
            &lt;h1&gt;&lt;img src=&quot;{% static &#39;posts/images/headertxt.png&#39; %}&quot;&gt;&lt;/h1&gt;
        &lt;/div&gt;
    &lt;/div&gt;
{% endblock logo_text %}


{% block content %}
    &lt;div class=&quot;post_container&quot;&gt;
        {% for post in posts %}
            &lt;div class=&quot;post&quot;&gt;&lt;a href=&quot;{% url &quot;post-detail&quot; post.id %}&quot;&gt;
                &lt;h2 class=&quot;title&quot;&gt;{{post.title}}&lt;/h2&gt;
                &lt;p class=&quot;date&quot;&gt;{{post.dt_created}}&lt;/p&gt;
                &lt;p class=&quot;text&quot;&gt;{{post.content}}&lt;/p&gt;
            &lt;/a&gt;&lt;/div&gt;
        {% endfor %}
    &lt;/div&gt;
{% endblock content %}</code></pre>
<hr>
<h2 id="105-post_detailhtml-수정">10.5 post_detail.html 수정</h2>
<blockquote>
<p>이전 과정과 크게 다르지 않다. 부모 템플릿을 상속받는 템플릿 태그와 정적 파일을 불러오는 템플릿 태그를 작성한다.</p>
</blockquote>
<pre><code class="language-html">
&lt;!--post_detail.html--&gt;

{% extends &quot;./base.html&quot; %}
{% load static %}</code></pre>
<blockquote>
<p><code>css</code>를 불러오는 <code>css 블럭</code>을 작성한다.</p>
</blockquote>
<pre><code class="language-html">
&lt;!--post_detail.html--&gt;

{% extends &quot;./base.html&quot; %}
{% load static %}

{% block css %}
    &lt;link rel=&quot;stylesheet&quot; href=&quot;{% static &quot;posts/css/post_detail.css&quot; %}&quot;&gt;
{% endblock css %}</code></pre>
<blockquote>
<p><code>post_header 블럭</code>을 작성한다. </p>
</blockquote>
<pre><code class="language-html">
&lt;!--post_detail.html--&gt;

{% extends &quot;./base.html&quot; %}
{% load static %}

{% block css %}
    &lt;link rel=&quot;stylesheet&quot; href=&quot;{% static &quot;posts/css/post_detail.css&quot; %}&quot;&gt;
{% endblock css %}

{% block post_header %}
    &lt;div class=&quot;container&quot;&gt;
        &lt;h1 class=&quot;title&quot;&gt;{{post.title}}&lt;/h1&gt;
        &lt;p class=&quot;date&quot;&gt;{{post.dt_created}}&lt;/p&gt;
    &lt;/div&gt;
{% endblock post_header %}</code></pre>
<blockquote>
<p><code>content 블럭</code>을 작성한다.</p>
</blockquote>
<pre><code class="language-html">
&lt;!--post_detail.html--&gt;

{% extends &quot;./base.html&quot; %}
{% load static %}

{% block css %}
    &lt;link rel=&quot;stylesheet&quot; href=&quot;{% static &quot;posts/css/post_detail.css&quot; %}&quot;&gt;
{% endblock css %}

{% block post_header %}
    &lt;div class=&quot;container&quot;&gt;
        &lt;h1 class=&quot;title&quot;&gt;{{post.title}}&lt;/h1&gt;
        &lt;p class=&quot;date&quot;&gt;{{post.dt_created}}&lt;/p&gt;
    &lt;/div&gt;
{% endblock post_header %}</code></pre>
<hr>
<h2 id="105-결과">10.5 결과</h2>
<blockquote>
<p>포스트 목록 페이지는 다음과 같이 출력된다.
<img src="https://velog.velcdn.com/images/ethan_/post/dfaa8112-dd38-435a-a4f7-f6c17f347cd7/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>포스트 상세 페이지는 다음과 같이 출력된다.
<img src="https://velog.velcdn.com/images/ethan_/post/8624e477-369d-40d2-95c0-e7281d472202/image.png" alt=""></p>
</blockquote>
<hr>
<h1 id="11-포스트-작성-페이지">11. 포스트 작성 페이지</h1>
<blockquote>
<p>포스트를 작성하고 저장하는 페이지를 만든다.</p>
</blockquote>
<hr>
<h2 id="111-formpy-생성-및-작성">11.1 form.py 생성 및 작성</h2>
<blockquote>
<p>포스트 작성 페이지를 만들기 위해 posts 앱 디렉토리에 <code>forms.py</code> 파일을 생성한다.</p>
</blockquote>
<blockquote>
<p>장고의 <code>forms 모듈</code>을 불러온다.</p>
</blockquote>
<pre><code class="language-python">#forms.py

from django import forms</code></pre>
<blockquote>
<p><code>PostForm</code> 클래스를 만들고 <code>forms</code>의 <code>Form</code> 클래스를 부모클래스로 두어 <code>상속</code>받는다.</p>
</blockquote>
<pre><code class="language-python">from django import forms

class PostForm(forms.form):</code></pre>
<blockquote>
<p><code>PostForm</code> 클래스의 내용을 입력한다.</p>
</blockquote>
<pre><code class="language-python">from django import forms

class PostForm(forms.form):
    title = forms.CharField(max_length=50, label=&quot;제목&quot;)
    content = forms.CharField(label=&quot;내용&quot;, widget=forms.Textarea)</code></pre>
<blockquote>
<p>기본적으로 폼 클래스는 모델 클래스와 작성 방식이 유사하다. 하나하나 살펴보자.</p>
</blockquote>
<blockquote>
<p><code>title</code> 필드는 forms 모듈의 <code>CharField</code>이며 <code>최대 길이</code>는 50이고 <code>필드 이름</code>은 제목이다.</p>
</blockquote>
<pre><code class="language-python">title = forms.CharField(max_length=50, label=&quot;제목&quot;)</code></pre>
<blockquote>
<p><code>content</code> 필드는 forms 모듈의 <code>CharField</code> 이며 <code>필드 이름</code> 은 내용이다. <code>위젯 스타일</code>은 forms 모듈의 Textarea로 설정했다.</p>
</blockquote>
<pre><code class="language-python">content = forms.CharField(label=&quot;내용&quot;, widget=forms.Textarea)</code></pre>
<blockquote>
<blockquote>
<p><code>Charfield</code>는 기본적으로 한 줄 입력의 위젯을 제공하는데 <code>Textarea</code> 위젯을 지정하면 여러 줄 입력의 위젯으로 변경된다.</p>
</blockquote>
</blockquote>
<hr>
<h2 id="112-url-패턴-주석-해제">11.2 url 패턴 주석 해제</h2>
<blockquote>
<p>포스트 작성 페이지의 url 주석을 해제하고 <code>url name</code> 을 입력한다.</p>
</blockquote>
<pre><code class="language-python">#urls.py

from django.urls import path
from . import views

urlpatterns = [
    # path(&#39;&#39;, views.index),
    path(&#39;posts/&#39;, views.post_list, name=&quot;post-list&quot;),
    path(&#39;posts/new/&#39;, views.post_create, name=&quot;post-create&quot;),  #수정
    path(&#39;posts/&lt;int:post_id&gt;/&#39;, views.post_detail, name=&quot;post-detail&quot;),
    # path(&#39;posts/&lt;int:post_id&gt;/edit/&#39;, views.post_update),
    # path(&#39;posts/&lt;int:post_id&gt;/delete/&#39;, views.post_delete),
]</code></pre>
<hr>
<h2 id="113-post_create-뷰-작성">11.3 post_create 뷰 작성</h2>
<blockquote>
<p>url 경로와 매칭되는 view 함수를 작성한다.</p>
</blockquote>
<blockquote>
<p>생성한 forms 파일의 PostForm 클래스를 불러온다.</p>
</blockquote>
<pre><code class="language-python">#views.py

from django.shortcuts import render
from .models import Post
from .forms import PostForm  #PostForm 클래스 불러오기

# Create your views here.

def post_list(request):
    posts = Post.objects.all()
    context = {&quot;posts&quot; : posts}
    return render(request, &#39;posts/post_list.html&#39;, context=context)


def post_detail(request, post_id):
    post = Post.objects.get(id=post_id)
    context = {&quot;post&quot;: post}
    return render(request, &#39;posts/post_detail.html&#39;, context=context)</code></pre>
<blockquote>
<p>post_create 함수를 작성한다.</p>
</blockquote>
<pre><code class="language-python">#views.py

from django.shortcuts import render
from .models import Post
from .forms import PostForm

# Create your views here.

def post_list(request):
    posts = Post.objects.all()
    context = {&quot;posts&quot; : posts}
    return render(request, &#39;posts/post_list.html&#39;, context=context)


def post_detail(request, post_id):
    post = Post.objects.get(id=post_id)
    context = {&quot;post&quot;: post}
    return render(request, &#39;posts/post_detail.html&#39;, context=context)

def post_create(request):  #작성
    post_form = PostForm()
    context = {&quot;post_form&quot;: post_form}
    return render(request, &#39;posts/post_form.html&#39;, context=context)</code></pre>
<hr>
<h3 id="1131-각-코드의-의미">11.3.1 각 코드의 의미</h3>
<blockquote>
<p><code>post_form 변수</code>에 <code>PostForm</code>을 <code>빈 양식</code>으로 지정한다.</p>
</blockquote>
<pre><code class="language-python">posts_form = PostForm()</code></pre>
<blockquote>
<p><code>context</code> 변수에 post_form <code>키</code>와 post_form <code>밸류</code>를 쌍으로 저장한다. </p>
</blockquote>
<pre><code class="language-python">context = {&quot;post_form&quot;: post_form}</code></pre>
<blockquote>
<p><code>context 변수</code>를 context 인자로 담아서 <code>posts/post_form.html</code> 페이지를 렌더한다.</p>
</blockquote>
<hr>
<h2 id="114-post_formhtml-템플릿-생성">11.4 post_form.html 템플릿 생성</h2>
<blockquote>
<p><code>post_form.html</code> 템플릿을 생성하고 내용을 작성한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_form.html--&gt;

&lt;form&gt;
    {{post_form}}
    &lt;input type=&quot;submit&quot; value=&quot;전송&quot;&gt;
&lt;/form&gt;</code></pre>
<blockquote>
<p><code>Post_Form 클래스</code>를 만들었고 views의 <code>post_create</code> 함수에서 이를 템플릿으로 전달했으니 <code>&lt;form&gt;</code> 태그 안에 템플릿 변수인 <code>{{post_form}}</code>을 입력하면 폼 형식이 렌더된다. 추가적으로 전송 버튼만 <code>&lt;input&gt;</code> 태그로 작성한다.</p>
</blockquote>
<blockquote>
<p>결과는 다음과 같다.
<img src="https://velog.velcdn.com/images/ethan_/post/e7e76a87-e0b4-4efe-a247-29a8b4219ef7/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><code>{{post_form}}</code> 템플릿 변수에 .as_p를 입력하면 <code>&lt;p&gt;</code> 태그의 형태로 변환된다. </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ethan_/post/3cf24d2e-9421-4dbe-9694-8ce335da768f/image.png" alt=""></p>
<blockquote>
<p>이 밖에도 <code>리스트 형태</code>(as_ul), <code>테이블 형태</code>(as_table)도 가능하다. 본 프로젝트는 미리 작성된 css 스타일을 적용하기 위해 <code>리스트 형태</code>로 변환한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_form.html--&gt;

&lt;form&gt;
    {{post_form.as_ul}}  &lt;!--작성--&gt;
    &lt;input type=&quot;submit&quot; value=&quot;전송&quot;&gt;
&lt;/form&gt;</code></pre>
<blockquote>
<p>다음은 <code>&lt;form&gt;</code> 태그의 속성을 작성한다. <code>method</code> 속성은 <code>post</code>로 작성하고 <code>action</code> 속성은 작성하지 않는다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_form.html--&gt;

&lt;form method=&quot;post&quot;&gt;  &lt;!--작성--&gt;
    {{post_form.as_ul}}
    &lt;input type=&quot;submit&quot; value=&quot;전송&quot;&gt;
&lt;/form&gt;</code></pre>
<blockquote>
<p>이렇게 하면 <code>action</code> 속성을 작성하지 않았기 때문에  <code>post</code> 방식으로 <code>현재 url</code>로 데이터를 전송한다.</p>
</blockquote>
<blockquote>
<p>다음으로는 <code>csrf_token</code> 템플릿 태그를 추가한다. 이 템플릿 태그는 <code>교차 사이트 위조 검증</code>으로 &lt;요청하지 않은 요청&gt;을 마치 &lt;요청한 것 처럼 위조&gt;하는 것을 <code>방지</code>하는 기술이다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_form.html--&gt;

&lt;form method=&quot;post&quot;&gt;{% csrf_token %} &lt;!--csrf토큰--&gt;
    {{post_form.as_ul}}
    &lt;input type=&quot;submit&quot; value=&quot;전송&quot;&gt;
&lt;/form&gt;</code></pre>
<hr>
<h2 id="115-view-작성">11.5 view 작성</h2>
<blockquote>
<p>뷰를 수정해야하는데, 기존에 작성된 뷰를 살펴보면 다음과 같다.</p>
</blockquote>
<pre><code class="language-python">#views.py

def post_create(request):
    post_form = PostForm()
    context = {&quot;post_form&quot;: post_form}
    return render(request, &#39;posts/post_form.html&#39;, context=context)</code></pre>
<blockquote>
<p>우리가 해야할 것은 두 가지다. </p>
<blockquote>
<p>하나는 <code>posts/new</code> 로 처음 접속 했을 때는 폼을 띄우는 것이고,</p>
</blockquote>
<blockquote>
<p>다른 하나는 <code>posts/new</code> 에서 데이터를 입력하고 전송 버튼을 눌렀을 때 <code>입력된 데이터로 새 게시글을 저장</code>한 다음, <code>해당 게시글의 상세 페이지로 이동</code>하는 것이다.</p>
</blockquote>
</blockquote>
<blockquote>
<p>이는 <code>if</code> 문과 <code>else</code> 문으로 구분해서 작성할 수 있다. 작성된 코드는 다음과 같다.</p>
</blockquote>
<pre><code class="language-python">#views.py

from django.shortcuts import render, redirect
from .models import Post
from .forms import PostForm

# Create your views here.

def post_list(request):
    posts = Post.objects.all()
    context = {&quot;posts&quot; : posts}
    return render(request, &#39;posts/post_list.html&#39;, context=context)


def post_detail(request, post_id):
    post = Post.objects.get(id=post_id)
    context = {&quot;post&quot;: post}
    return render(request, &#39;posts/post_detail.html&#39;, context=context)

def post_create(request):
    if request.method ==&quot;POST&quot;:
        title = request.POST[&quot;title&quot;]
        content = request.POST[&quot;content&quot;]
        new_post = Post(
            title = title,
            content = content
        )
        new_post.save()
        return redirect(&quot;post-detail&quot;, post_id=new_post.id)
    else:
        post_form = PostForm()
        context = {&quot;post_form&quot;: post_form}
        return render(request, &#39;posts/post_form.html&#39;, context=context)</code></pre>
<blockquote>
<p>우리는 여기서 post_create 함수만 살펴본다. </p>
</blockquote>
<pre><code class="language-python">#views.py &gt; post_create

def post_create(request):
    if request.method ==&quot;POST&quot;:
        title = request.POST[&quot;title&quot;]
        content = request.POST[&quot;content&quot;]
        new_post = Post(
            title = title,
            content = content
        )
        new_post.save()
        return redirect(&quot;post-detail&quot;, post_id=new_post.id)
    else:
        post_form = PostForm()
        context = {&quot;post_form&quot;: post_form}
        return render(request, &#39;posts/post_form.html&#39;, context=context)</code></pre>
<blockquote>
<p>우선 render 함수 외에 redirect 함수를 사용하기 때문에 redirect 함수를 불러온다.</p>
</blockquote>
<pre><code class="language-python">from django.shortcuts import render, redirect</code></pre>
<hr>
<h3 id="1151-if문">11.5.1 if문</h3>
<blockquote>
<p>먼저 if문으로 작성된 코드를 살펴본다.</p>
</blockquote>
<pre><code class="language-python">if request.method ==&quot;POST&quot;:
        title = request.POST[&quot;title&quot;]
        content = request.POST[&quot;content&quot;]
        new_post = Post(
            title = title,
            content = content
        )
        new_post.save()
        return redirect(&quot;post-detail&quot;, post_id=new_post.id)</code></pre>
<blockquote>
<p>만약 <code>요청 방식</code>이 <code>POST</code>라면:</p>
</blockquote>
<pre><code class="language-python">if request.method ==&quot;POST&quot;:</code></pre>
<p><code>변수 title</code>에 <code>POST 요청이 담고 있는 데이터</code> 중 <code>키</code>를 <code>title</code>로 하는 <code>값</code>을 지정한다.</p>
<pre><code class="language-python">title = request.POST[&quot;title&quot;]</code></pre>
<p><code>변수 content</code>에 <code>POST 요청이 담고 있는 데이터</code> 중 <code>키</code>를 <code>content</code>로 하는 <code>값</code>을 지정한다.</p>
<pre><code class="language-python">content = request.POST[&quot;content&quot;]</code></pre>
<p><code>변수 new_post</code>에 <code>Post 모델</code>을 사용해 <code>title 필드</code>에는 <code>변수 title</code>에 저장한 값을 넣고, <code>content 필드</code>에는 <code>변수 content</code>에 저장한 값을 넣는다.</p>
<pre><code class="language-python">new_post = Post(
            title = title,
            content = content
        )</code></pre>
<p><code>new_post</code>를 <code>데이터베이스</code>에 저장한다.</p>
<pre><code class="language-python">new_post.save()</code></pre>
<p>그리고 <code>redirect</code> 함수를 사용해 <code>post-detail</code>(상세 페이지) url로 이동한다. 그런데 <code>post-detail</code>(상세 페이지)는 <code>&lt;int:post_id&gt;</code>를 필요로 하니, <code>post_id</code>에 방금 생성한 <code>new_post의 id</code>를 전달한다.</p>
<pre><code class="language-python">return redirect(&quot;post-detail&quot;, post_id=new_post.id)</code></pre>
<blockquote>
<p>간단히 설명하면, posts/new에 접속해 폼에 데이터를 입력하고 전송 버튼을 누르면 입력된 데이터로 게시글을 생성하고, 해당 게시글의 상세 페이지로 이동하는 로직이다.</p>
</blockquote>
<hr>
<h3 id="1152-else문">11.5.2 else문</h3>
<blockquote>
<p><code>else</code>문으로 작성된 코드를 살펴본다.</p>
</blockquote>
<pre><code class="language-python">else:
        post_form = PostForm()
        context = {&quot;post_form&quot;: post_form}
        return render(request, &#39;posts/post_form.html&#39;, context=context)</code></pre>
<blockquote>
<p>만약 그렇지 않으면 (여기서는 요청 방식이 POST가 아니라면, 즉 <code>요청 방식</code>이 <code>GET</code>이라면)</p>
</blockquote>
<pre><code class="language-python">else:</code></pre>
<p><code>변수 post_form</code>에 <code>PostForm</code> 폼을 지정한다.</p>
<pre><code class="language-python">post_form = PostForm()</code></pre>
<p>그리고 <code>변수 context</code>에 post_form을 지정한다.</p>
<pre><code class="language-python">context = {&quot;post_form&quot;: post_form}</code></pre>
<p>렌더 함수를 사용해 <code>posts/post_form.html</code>을 렌더하고, <code>파라미터 context의 인자</code>로 <code>변수 context</code>를 전달한다.</p>
<blockquote>
<p>간단히 설명하면, <code>posts/new</code> url에 처음 접속(GET)하면 <code>forms.py</code>의 <code>PostForm</code> 클래스를 포함하는 <code>게시글 작성 페이지</code>로 이동하는 로직이다.</p>
</blockquote>
<hr>
<h2 id="115-글쓰기-버튼-생성">11.5 글쓰기 버튼 생성</h2>
<blockquote>
<p><code>form</code>을 통해 게시글 작성 페이지를 만들었으니, <code>게시글 목록 페이지</code>에서 <code>글쓰기 버튼</code>을 추가한다.</p>
</blockquote>
<blockquote>
<p><code>post_list.html</code>의 <code>content 블럭</code>에 <code>&lt;a&gt;</code> 태그를 추가한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_list.html--&gt;

{% extends &quot;./base.html&quot; %}
{% load static %}

{% block css %}
    &lt;link rel=&quot;stylesheet&quot; href=&quot;{% static &quot;posts/css/post_list.css&quot; %}&quot;&gt;
{% endblock css %}

{% block logo_text %}
    &lt;div id=&quot;header&quot;&gt;
        &lt;div class=&quot;container&quot;&gt;
            &lt;h1&gt;&lt;img src=&quot;{% static &#39;posts/images/headertxt.png&#39; %}&quot;&gt;&lt;/h1&gt;
        &lt;/div&gt;
    &lt;/div&gt;
{% endblock logo_text %}


{% block content %}
    &lt;div class=&quot;btn_post&quot;&gt;
        &lt;a href={% url &quot;post-create&quot; %}&gt;기록하기&lt;/a&gt;  &lt;!--추가--&gt;
    &lt;/div&gt;
    &lt;div class=&quot;post_container&quot;&gt;
        {% for post in posts %}
            &lt;div class=&quot;post&quot;&gt;&lt;a href=&quot;{% url &quot;post-detail&quot; post.id %}&quot;&gt;
                &lt;h2 class=&quot;title&quot;&gt;{{post.title}}&lt;/h2&gt;
                &lt;p class=&quot;date&quot;&gt;{{post.dt_created}}&lt;/p&gt;
                &lt;p class=&quot;text&quot;&gt;{{post.content}}&lt;/p&gt;
            &lt;/a&gt;&lt;/div&gt;
        {% endfor %}
    &lt;/div&gt;
{% endblock content %}</code></pre>
<blockquote>
<p><code>btn_post</code> <code>&lt;div&gt;</code>태그로 감싼 이유는 <code>css 파일을 적용</code>하기 위함이다.</p>
</blockquote>
<hr>
<h1 id="12-포스트-작성-페이지-모델-폼">12. 포스트 작성 페이지 (모델 폼)</h1>
<blockquote>
<p>#11. 포스트 작성 페이지에서 폼을 활용해 사용자가 입력한 데이터를 기반으로 새로운 게시글을 생성하는 기능을 구현했다.</p>
</blockquote>
<blockquote>
<p>이번에는 장고의 <code>모델 폼</code> 기능을 활용해 폼을 더욱 간편하게 만드는 방법을 알아본다.</p>
</blockquote>
<hr>
<h2 id="121-formspy-수정">12.1 forms.py 수정</h2>
<blockquote>
<p>기존에 작성된 폼은 다음과 같다.</p>
</blockquote>
<pre><code class="language-python">#forms.py

from django import forms

class PostForm(forms.Form):
    title = forms.CharField(max_length=50, label=&quot;제목&quot;)
    content = forms.CharField(label=&quot;내용&quot;, widget=forms.Textarea)</code></pre>
<blockquote>
<p>모델 폼을 활용하기 위한 코드는 다음과 같다.</p>
</blockquote>
<pre><code class="language-python">#forms.py

from django import forms
from .models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = [&quot;title&quot;, &quot;content&quot;]</code></pre>
<blockquote>
<p><code>모델 폼</code>은 모델을 기반으로 폼을 간편하게 생성할 수 있는 기능이다.</p>
</blockquote>
<blockquote>
<p>먼저 모델을 불러와야 한다.</p>
</blockquote>
<pre><code class="language-python">from .models import Post</code></pre>
<blockquote>
<p><code>forms.ModelForm</code> 클래스를 상속받는 <code>PostForm</code> 클래스를 생성한다.</p>
</blockquote>
<pre><code class="language-python">class PostForm(forms.ModelForm):</code></pre>
<p><code>Post</code> 모델을 참고해 <code>title 필드</code>와 <code>content 필드</code>를 입력받는 폼을 생성한다.</p>
<pre><code class="language-python">class Meta:
    model = Post  #Post 모델을 참고
    fields = [&quot;title&quot;, &quot;content&quot;]  #폼을 통해 입력받을 필드를 추가</code></pre>
<p>만약 필드가 너무 많아 일일이 입력하기가 힘들다면 <code>모든 필드</code>를 추가하는 방법이 있다.</p>
<pre><code class="language-python">    fields = &quot;__all__&quot;</code></pre>
<hr>
<h2 id="122-viewspy-수정">12.2 views.py 수정</h2>
<blockquote>
<p>기존에 작성한 <code>post_create</code> 함수를 살펴본다.</p>
</blockquote>
<pre><code class="language-python">def post_create(request):
    if request.method ==&quot;POST&quot;:
        title = request.POST[&quot;title&quot;]
        content = request.POST[&quot;content&quot;]
        new_post = Post(
            title = title,
            content = content
        )
        new_post.save()
        return redirect(&quot;post-detail&quot;, post_id=new_post.id)
    else:
        post_form = PostForm()
        context = {&quot;post_form&quot;: post_form}
        return render(request, &#39;posts/post_form.html&#39;, context=context)</code></pre>
<blockquote>
<p><code>if</code>문을 모델폼에 맞게 변경한다.</p>
</blockquote>
<pre><code class="language-python">def post_create(request):
    if request.method ==&quot;POST&quot;:
        post_form = PostForm(request.POST)
        new_post = post_form.save()
        return redirect(&quot;post-detail&quot;, post_id=new_post.id)</code></pre>
<hr>
<h3 id="1221-각-코드의-의미">12.2.1 각 코드의 의미</h3>
<blockquote>
<p>변경된 코드의 의미는 다음과 같다.</p>
</blockquote>
<blockquote>
<p>만약 요청 방식이 POST라면</p>
</blockquote>
<pre><code class="language-python">    if request.method ==&quot;POST&quot;:</code></pre>
<blockquote>
<p>요청에 포함되어 있는 데이터(폼에 입력되고 전송된)를 PostForm과 바인딩하고 변수 post_form에 지정한다.</p>
</blockquote>
<pre><code class="language-python">        post_form = PostForm(request.POST)</code></pre>
<blockquote>
<p>변수 post_form에 지정된 바운드폼을 데이터베이스에 저장한 뒤, new_post에 지정한다.</p>
</blockquote>
<pre><code class="language-python">        new_post = post_form.save()</code></pre>
<blockquote>
<p>new_post의 상세 페이지로 이동한다.</p>
</blockquote>
<pre><code class="language-python">        return redirect(&quot;post-detail&quot;, post_id=new_post.id)</code></pre>
<blockquote>
<p>앞으로는 더 간단한 방법인 모델 폼을 사용하도록 하자.</p>
</blockquote>
<hr>
<h1 id="13-유효성-검증">13. 유효성 검증</h1>
<blockquote>
<p>이번에는 모델폼으로 작성하는 데이터의 <code>유효성</code>을 검증하는 방법을 알아본다.</p>
</blockquote>
<blockquote>
<p>유효성을 검증하는 방법은 <code>두 가지</code>가 있다. <code>모델 필드 또는 폼 필드</code>를 통한 유효성 검증 방법과 <code>Validator</code>를 통한 유효성 검증 방법이 있다.</p>
</blockquote>
<hr>
<h2 id="131-필드를-통한-유효성-검증">13.1 필드를 통한 유효성 검증</h2>
<blockquote>
<p>우리는 모델 폼을 사용하기 때문에 폼 필드가 아닌, <code>모델 필드</code>를 통해 <code>유효성을 검증</code>하는 방법을 알아본다. </p>
</blockquote>
<hr>
<h3 id="1311-modelspy-수정">13.1.1 models.py 수정</h3>
<blockquote>
<p>만약, <code>게시글의 제목은 중복될 수 없음</code> 이라는 정책이 있다면, 우리는 게시글 작성 페이지에서 게시글을 생성하기 전, 제목에 입력된 데이터가 기존에 생성된 게시글의 제목과 중복되지 않는지 유효성을 검사해야 한다.</p>
</blockquote>
<blockquote>
<p>그러기 위해서 <code>Post 모델</code>의 <code>title 필드</code>에 <code>unique 옵션</code>을 추가한다.</p>
</blockquote>
<pre><code class="language-python">from django.db import models

# Create your models here.

class Post(models.Model):
    title = models.CharField(max_length=50, unique=True)
    content = models.TextField()
    dt_created = models.DateTimeField(verbose_name=&quot;Date Created&quot;, auto_now_add=True)
    dt_modified = models.DateTimeField(verbose_name=&quot;Date Modified&quot;, auto_now=True)

    def __str__(self):
        return self.title</code></pre>
<blockquote>
<p>중복된 제목으로 인해 유효성 검증에 실패하면 유저에게 보여줄 에러메세지를 변경하는 <code>error_messages 옵션</code>을 추가한다.</p>
</blockquote>
<pre><code class="language-python">from django.db import models

# Create your models here.

class Post(models.Model):
    title = models.CharField(max_length=50, unique=True, error_messages={&quot;unique&quot;:&quot;이미 같은 제목의 게시글이 존재합니다&quot;})
    content = models.TextField()
    dt_created = models.DateTimeField(verbose_name=&quot;Date Created&quot;, auto_now_add=True)
    dt_modified = models.DateTimeField(verbose_name=&quot;Date Modified&quot;, auto_now=True)

    def __str__(self):
        return self.title</code></pre>
<hr>
<h3 id="1312-viewspy-수정">13.1.2 views.py 수정</h3>
<blockquote>
<p>post_create 뷰에서 유효성 검증을 통과한 경우와 그렇지 않은 경우에 따른 로직을 구분한다.</p>
</blockquote>
<pre><code class="language-python">#views.py &gt; post_create

def post_create(request):
    if request.method ==&quot;POST&quot;:
        post_form = PostForm(request.POST)
        if post_form.is_valid():
            new_post = post_form.save()
            return redirect(&quot;post-detail&quot;, post_id=new_post.id)
        else:
            context = {&quot;post_form&quot;: post_form}
    else:
        post_form = PostForm()
        context = {&quot;post_form&quot;: post_form}
    return render(request, &#39;posts/post_form.html&#39;, context=context)</code></pre>
<blockquote>
<p>변경된 코드의 의미는 다음과 같다.</p>
</blockquote>
<blockquote>
<p>만약 요청 방식이 <code>POST</code> 라면</p>
</blockquote>
<pre><code class="language-python">    if request.method ==&quot;POST&quot;:</code></pre>
<blockquote>
<p><code>PostForm 폼</code>과 요청이 담고있는 데이터를 바인딩 하고 <code>변수 post_form</code>에 지정한다.</p>
</blockquote>
<pre><code class="language-python">        post_form = PostForm(request.POST)</code></pre>
<blockquote>
<p>만약 <code>post_form</code>이 유효성 검증에 통과하면</p>
</blockquote>
<pre><code class="language-python">        if post_form.is_valid():</code></pre>
<blockquote>
<p><code>post_form</code>을 기반으로 게시글을 생성하고 <code>변수 new_post</code>에 지정한다.</p>
</blockquote>
<pre><code class="language-python">            new_post = post_form.save()</code></pre>
<blockquote>
<p>그리고 해당 게시글의 상세 페이지로 이동한다.</p>
</blockquote>
<pre><code class="language-python">            return redirect(&quot;post-detail&quot;, post_id=new_post.id)</code></pre>
<blockquote>
<p>만약 <code>post_form</code>이 유효성 검증에 실패하면</p>
</blockquote>
<pre><code class="language-python">        else:</code></pre>
<blockquote>
<p><code>변수 context</code>에 <code>post_form</code>을 지정한다.</p>
</blockquote>
<pre><code class="language-python">            context = {&quot;post_form&quot;: post_form}</code></pre>
<blockquote>
<blockquote>
<p>이때, <code>else</code>문과 <code>if</code>문 밖으로 이동해 마지막 <code>return</code>문으로 이동한다. 결과적으로는 <code>post_form</code>(입력한 데이터)를 <code>유지</code>한 채로 <code>post_form.html</code>을 다시 렌더한다.</p>
</blockquote>
<p>만약 요청 방식이 <code>POST</code>가 아니라면,</p>
</blockquote>
<pre><code class="language-python">    else:</code></pre>
<blockquote>
<p><code>변수 post_form</code>에 <code>비어있는</code> <code>Post_form 폼</code>을 지정한다.</p>
</blockquote>
<pre><code class="language-python">        post_form = PostForm()</code></pre>
<blockquote>
<p><code>변수 context</code>에 <code>post_form을</code> 지정한다.</p>
</blockquote>
<pre><code class="language-python">        context = {&quot;post_form&quot;: post_form}</code></pre>
<blockquote>
<p><code>context</code>에 담긴 데이터를 가지고 <code>post_form.html</code>을 렌더한다.</p>
</blockquote>
<pre><code class="language-python">    return render(request, &#39;posts/post_form.html&#39;, context=context)</code></pre>
<hr>
<h2 id="132-bulit-in-validator를-통한-유효성-검증">13.2 bulit-in validator를 통한 유효성 검증</h2>
<blockquote>
<p>만약 보다 복잡한 유효성 검증이 필요한 경우, 장고에서 제공하는 유효성 검증인 <code>validator</code>를 통한 유효성 검증을 방법을 선택할 수 있다.</p>
</blockquote>
<blockquote>
<p><code>validator</code>는 장고에서 제공하는 <code>bulit-in validator</code>와 직접 구현한 <code>validator</code>가 있는데, 여기서는 전자를 사용할 것이다.</p>
</blockquote>
<hr>
<h3 id="1321-django-built-in-validators">13.2.1 django built-in validators</h3>
<blockquote>
<p>[django built-in validators] (<a href="https://docs.djangoproject.com/en/5.0/ref/validators/)%EC%97%90">https://docs.djangoproject.com/en/5.0/ref/validators/)에</a> 접속하면 다양한 밸리데이터를 찾을 수 있다.</p>
</blockquote>
<hr>
<h3 id="1322-modelspy-수정">13.2.2 models.py 수정</h3>
<blockquote>
<p><code>게시글 내용</code>에 <code>최소길이제한</code> 유효성 검증을 적용해본다.</p>
</blockquote>
<blockquote>
<p><code>djnago.core.validators</code> 모듈의 <code>최소길이제한</code> 클래스를 불러온 뒤, <code>content 필드</code>에 적용할 <code>최소길이</code>와 <code>에러메세지</code>를 입력한다.</p>
</blockquote>
<blockquote>
<p>밸리데이터를 추가하기 위해서는 <code>리스트 형식</code>으로 작성해야 한다.</p>
</blockquote>
<pre><code class="language-python"># models.py

from django.db import models
from django.core.validators import MinLengthValidator  #최소길이제한 클래스

# Create your models here.

class Post(models.Model):
    title = models.CharField(max_length=50, unique=True, error_messages={&quot;unique&quot;:&quot;이미 같은 제목의 게시글이 존재합니다&quot;})
    content = models.TextField(validators=[MinLengthValidator(10, &quot;10자 이상 입력해주세요&quot;)])  #입력
    dt_created = models.DateTimeField(verbose_name=&quot;Date Created&quot;, auto_now_add=True)
    dt_modified = models.DateTimeField(verbose_name=&quot;Date Modified&quot;, auto_now=True)

    def __str__(self):
        return self.title</code></pre>
<blockquote>
<p>개발서버를 실행한 결과 아래와 같이 잘 적용되는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/ethan_/post/7227ace3-fba4-4e0c-995f-b80a46a52be8/image.png" alt=""></p>
</blockquote>
<hr>
<h2 id="133-직접-만든-validator">13.3 직접 만든 validator</h2>
<blockquote>
<p>이번에는 validator를 직접 만드는 방법을 알아본다.</p>
</blockquote>
<hr>
<h3 id="1331-validatorspy-생성-및-작성">13.3.1 validators.py 생성 및 작성</h3>
<blockquote>
<p>validators.py을 생성하고 ValidationError 객체를 불러온 뒤, 함수를 작성한다.</p>
</blockquote>
<pre><code class="language-python">#validators.py

from django.core.exceptions import ValidationError


def validate_symbols(value):
    if (&quot;@&quot; in value) or (&quot;#&quot; in value):
        raise ValidationError(&quot;&#39;@&#39;와 &#39;#&#39;은 포함될 수 없습니다.&quot;, code=&quot;symbol_error&quot;)</code></pre>
<blockquote>
<p><code>ValidationError</code>는 유효성 검증 에러를 일으키는 객체다.</p>
</blockquote>
<pre><code class="language-python">from django.core.exceptions import ValidationError</code></pre>
<blockquote>
<p><code>validate_symbols</code> 함수는 <code>if</code>문을 사용해 <code>@ 또는 #이 포함</code>되면 유효성검증 에러를 발생시킨다.</p>
</blockquote>
<pre><code class="language-python">def validate_symbols(value):
    if (&quot;@&quot; in value) or (&quot;#&quot; in value):
        raise ValidationError(&quot;&#39;@&#39;와 &#39;#&#39;은 포함될 수 없습니다.&quot;, code=&quot;symbol_error&quot;)</code></pre>
<hr>
<h3 id="1332-modelspy-수정">13.3.2 models.py 수정</h3>
<blockquote>
<p>작성한 <code>validate_symbols</code>를 <code>Post 모델</code>의 <code>content 필드</code>에 적용한다.</p>
</blockquote>
<pre><code class="language-python">#models.py

from django.db import models
from django.core.validators import MinLengthValidator
from .validators import validate_symbols  #밸리데이터 불러오기

# Create your models here.

class Post(models.Model):
    title = models.CharField(max_length=50, unique=True, error_messages={&quot;unique&quot;:&quot;이미 같은 제목의 게시글이 존재합니다&quot;})
    content = models.TextField(validators=[MinLengthValidator(10, &quot;10자 이상 입력해주세요&quot;),
                                           validate_symbols])  #밸리데이터 적용
    dt_created = models.DateTimeField(verbose_name=&quot;Date Created&quot;, auto_now_add=True)
    dt_modified = models.DateTimeField(verbose_name=&quot;Date Modified&quot;, auto_now=True)

    def __str__(self):
        return self.title</code></pre>
<blockquote>
<p>개발서버를 실행한 결과 잘 작동되는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/ethan_/post/34d4b114-026e-45c5-ae46-cd0410f5de93/image.png" alt=""></p>
</blockquote>
<hr>
<h1 id="14-폼-구조-변경하기">14. 폼 구조 변경하기</h1>
<blockquote>
<p>지금의 폼 구조는 이쁘지가 않다. css를 적용하기 전 폼의 구조를 변경하는 방법을 알아본다.</p>
</blockquote>
<hr>
<h2 id="141-post_formhtml-템플릿-수정">14.1 post_form.html 템플릿 수정</h2>
<blockquote>
<p>현재는 <code>PostForm 폼</code>을 <code>템플릿 태그 {{post_form}}</code> 으로 일괄적으로 불러온다. 각 폼의 인풋을 별도로 불러오는 코드로 변경해 본다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_form.html--&gt;

&lt;form method=&quot;post&quot;&gt;{% csrf_token %}
    &lt;h3&gt;제목&lt;/h3&gt;
    &lt;p&gt;{{post_form.title}}&lt;/p&gt;
    &lt;h3&gt;내용&lt;/h3&gt;
    &lt;p&gt;{{post_form.content}}&lt;/p&gt;
    &lt;input type=&quot;submit&quot; value=&quot;전송&quot;&gt;
&lt;/form&gt;</code></pre>
<blockquote>
<p>코드를 살펴보면 폼의 안쪽 속성으로 필드에 접근해서 <code>&lt;p&gt;</code> 태그로 감싼 형태로 작성했다. 결과는 아래와 같다.
<img src="https://velog.velcdn.com/images/ethan_/post/8e1d1c4f-3efb-4784-b7ba-e31fb0452b1f/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>좀 더 깔끔한 모습으로 변했다. 그러나 <code>에러메세지</code>가 호출되지 않는다. 기존에는 <code>템플릿 태그 {{post_form}}</code>로 폼을 일괄적으로 호출해서 에러메세지가 포함되었다. 이번에는 <code>에러메세지도 별도로 호출</code>하는 코드를 작성한다. </p>
</blockquote>
<pre><code class="language-html">&lt;!--post_form.html--&gt;

&lt;form method=&quot;post&quot;&gt;{% csrf_token %}
    &lt;h3&gt;제목&lt;/h3&gt;
    &lt;p&gt;{{post_form.title}}&lt;/p&gt;
    {% for error in post_form.title.errors %}
        &lt;p&gt;{{error}}&lt;/p&gt;
    {% endfor %}
    &lt;h3&gt;내용&lt;/h3&gt;
    &lt;p&gt;{{post_form.content}}&lt;/p&gt;
    {% for error in post_form.content.errors %}
        &lt;p&gt;{{error}}&lt;/p&gt;
    {% endfor %}
    &lt;input type=&quot;submit&quot; value=&quot;전송&quot;&gt;
&lt;/form&gt;</code></pre>
<blockquote>
<p><code>에러메세지</code>가 여러 개일 수 있으니 <code>for</code>문을 활용해 각 필드의 에러 중, 각각의 에러를 호출한다. 에러는 <code>.errors</code> 로 접근할 수 있다.</p>
</blockquote>
<blockquote>
<p>개발서버를 실행한 결과 에러메세지가 잘 호출되는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/ethan_/post/aa0cacfc-b941-477b-841a-c27f9ecdc05c/image.png" alt=""></p>
</blockquote>
<hr>
<h1 id="15-폼-디자인-적용">15. 폼 디자인 적용</h1>
<blockquote>
<p>폼 디자인을 적용하는 방법을 단계별로 알아본다.</p>
</blockquote>
<hr>
<h2 id="151-formcss">15.1 form.css</h2>
<blockquote>
<p><code>정적파일 디렉토리</code>에 <code>form.css</code> 파일을 생성하고 작성한다.</p>
</blockquote>
<pre><code class="language-css">/*form.css*/

.error {
    color: red;
}</code></pre>
<hr>
<h2 id="152-post_formhtml">15.2 post_form.html</h2>
<blockquote>
<p><code>css</code>를 적용하기 위해 <code>부모 템플릿을 상속</code>받는 <code>템플릿 태그</code>와 <code>정적파일</code>을 불러오는 <code>템플릿 태그</code>를 작성한다.</p>
</blockquote>
<blockquote>
<p>그리고 <code>css 블럭 영역</code>에 방금 생선한 <code>form.css</code> 파일을 적용한다.</p>
</blockquote>
<blockquote>
<p>다음으로 <code>&lt;form&gt;</code> 태그를 <code>content 블럭 영역</code>으로 감싼다.</p>
</blockquote>
<blockquote>
<p>마지막으로 에러메세지를 담고있는 <code>&lt;p&gt;</code> 태그에 <code>error 클래스</code>를 적용한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_form.html--&gt;

{% extends &quot;./base.html&quot; %}
{% load static %}

{% block css %}
    &lt;link rel=&quot;stylesheet&quot; href=&quot;{% static &quot;posts/css/form.css&quot; %}&quot;&gt;
{% endblock css %}

{% block content %}
    &lt;form method=&quot;post&quot;&gt;{% csrf_token %}
        &lt;h3&gt;제목&lt;/h3&gt;
        &lt;p&gt;{{post_form.title}}&lt;/p&gt;
        {% for error in post_form.title.errors %}
            &lt;p class=&quot;error&quot;&gt;{{error}}&lt;/p&gt;
        {% endfor %}
        &lt;h3&gt;내용&lt;/h3&gt;
        &lt;p&gt;{{post_form.content}}&lt;/p&gt;
        {% for error in post_form.content.errors %}
            &lt;p class=&quot;error&quot;&gt;{{error}}&lt;/p&gt;
        {% endfor %}
        &lt;input type=&quot;submit&quot; value=&quot;전송&quot;&gt;
    &lt;/form&gt;
{% endblock content %}</code></pre>
<blockquote>
<p>결과는 아래와 같다.
<img src="https://velog.velcdn.com/images/ethan_/post/8ae52f48-9193-42a7-8fbf-d31dd5190340/image.png" alt=""></p>
</blockquote>
<hr>
<h2 id="153-인풋-디자인-적용">15.3 인풋 디자인 적용</h2>
<blockquote>
<p><code>에러메세지</code>의 경우 <code>클래스</code>를 지정해서 해당 클래스에 <code>css</code>를 적용할 수 있었다.</p>
</blockquote>
<blockquote>
<p>그러나 <code>인풋</code>의 경우 <code>필드별로 장고가 지정한 위젯</code>을 사용하기 때문에 <code>장고 공식 문서</code>에서 <code>해당 필드의 기본 위젯</code>이 무엇인지 확인한 뒤, <code>해당 위젯에 클래스를 지정</code>한 뒤, 해당 클래스에 <code>css</code>를 적용해야 한다.</p>
</blockquote>
<hr>
<h3 id="1531-charfield의-위젯-변경">15.3.1 CharField의 위젯 변경</h3>
<blockquote>
<p>장고 공식문서를 통해 확인한 결과 <code>CharField</code>의 기본 위젯은 <code>TextInput</code>이다. </p>
</blockquote>
<blockquote>
<p><code>forms.py</code>의 <code>Meta 클래스</code>에서 <code>widgets</code>은 <code>필드를 키</code>로 하고 <code>값으로 위젯</code> 이름을 입력하면, <code>다른 위젯</code>을 기본 위젯으로 사용할 수 있다. 또는 지금처럼 <code>직접 기본 위젯에 접근</code>해야 할 때 사용할 수 있다.</p>
</blockquote>
<pre><code class="language-python">#forms.py

from django import forms
from .models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = [&quot;title&quot;, &quot;content&quot;]
        widgets = {&quot;title&quot;: forms.TextInput(attrs={&quot;class&quot;: &quot;title&quot;, &quot;placeholder&quot;: &quot;제목이 입력하세요&quot;,}), 
                &quot;content&quot;: forms.Textarea(attrs={&quot;placeholder&quot;: &quot;내용을 입력하세요&quot;})}</code></pre>
<blockquote>
<p>위 코드를 확인해보면 <code>Meta 클래스</code>에 <code>widgets</code>을 두고 <code>title 필드</code>의 위젯을 <code>TextInput</code>으로 지정하고 <code>attrs</code>로 해당 위젯의 <code>속성</code>에 접근해 <code>title 클래스</code>를 지정했다.</p>
</blockquote>
<blockquote>
<p>또한 <code>title 필드</code>와 <code>content 필드</code>의 <code>placeholder</code>를 지정했다.</p>
</blockquote>
<blockquote>
<p>이후 <code>form.css</code> 에서 <code>title 클래스</code>에 대한 <code>css</code>를 선언한다.</p>
</blockquote>
<pre><code class="language-css">/*form.css*/

.error {
    color: red;
}

.title {
    width: 400px;
}</code></pre>
<hr>
<h1 id="16-폼-css-적용">16. 폼 css 적용</h1>
<blockquote>
<p>이미 작성되어 있는 css를 적용하는 방법을 알아본다.</p>
</blockquote>
<hr>
<h2 id="161-css-파일-살펴보기">16.1 css 파일 살펴보기</h2>
<blockquote>
<p>전달 받은 css는 다음과 같다.</p>
</blockquote>
<pre><code class="language-css">/*post_form.css*/

h1,h2,h3,p,a,ul,li{
    margin: 0;
    padding: 0;
    font-family : &#39;NanumMyeongjo&#39;;
    font-weight: 200;
    list-style:  none;
    text-decoration: none; 
    outline: none;
    color: black;
}
a:hover,a:active,button:hover {
    text-decoration: none;
}
html{
    position: relative;
    min-height: 100%;
    margin: 0;
}
body {
    min-height: 100%;
    margin: 0;
    padding: 0;
    background: #2b2a2e;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: auto;
}
img {
    margin: 0;
    padding: 0;
    border: 0;
}

#nav{
    height: 60px;
}
#header{
    margin: 20px 0 20px;
}
#content{
    margin-bottom: 300px;
}
#footer{
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 75px;
    background: rgba(41, 41, 44, 0.7);
}
.container{
    width: 800px;
    margin: 0 auto;
}

.logo{
    width: 65%;
    min-width: 899px;
    margin: 0 auto;
    padding-top: 17px;
}
.logo img{
    width: 88px;
}
h1{
    text-align: center;
    font-size: 32px;
    letter-spacing: -2px;
    color: #fff;
}
.error{
    text-align: right;
    color: red;
    padding-right: 20px;
    padding-bottom: 20px;
}
.submit{
    margin-bottom: 27px;
    text-align: right;
}
input[type=&quot;submit&quot;]{
    padding: 10px 43px 11px 19px; 
    font-size: 15px;
    font-family : &#39;NanumMyeongjo&#39;;
    color: #fff;
    border: 1px solid #5b595f;
    border-radius: 1px;
    letter-spacing: -0.35px;
    background: url(../images/pen.svg)no-repeat 89px 11px/16px;
    cursor: pointer;
}
input[type=&quot;submit&quot;]:hover{
    color: #ccc;
    transition: all 0.2s;
}
.editor{
    background: #f9f9f9;
}
input[type=&quot;text&quot;]{
    display: block;
    width: 100%;
    padding: 35px 33px 0 30px;
    border: none;
    font-size: 19px;
    font-family : &#39;NanumMyeongjo&#39;;
    background-color: transparent;
    box-sizing: border-box;
}
textarea{
    width: 100%;
    height: 500px;
    padding: 33px 33px 30px 30px;
    border: none;
    font-size: 15px;
    line-height: 1.73;
    font-family : &#39;NanumMyeongjo&#39;;
    background-color: transparent;
    resize: none;
    box-sizing: border-box;
}
textarea,input:focus{
    outline: none;
}
textarea::placeholder{
    color: #a9abb7;
    letter-spacing: -0.44px;
}
input::placeholder{
    color: #a9abb7;
    letter-spacing: -0.44px;
}
.btn_back a{
    display: inline-block;
    margin-top: 30px;
    padding: 10px 19px 11px;
    color: #e38917;
    font-size: 15px;
    border: 1px solid #e38917;
}
.btn_back a:hover{
    color: #f2ad56;
    transition: all .2s;
}
.footer{
    width: 65%;
    min-width: 899px;
    margin: 0 auto;
}
.footer p{
    text-align: right;
    font-size: 20px;
    line-height: 75px;
    font-weight: 800;
    letter-spacing: -0.85px;
    color: #53534f;
}</code></pre>
<hr>
<h2 id="162-post_formhtml-수정">16.2 post_form.html 수정</h2>
<blockquote>
<p>작성된 css 적용을 위해 html 템플릿을 수정한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_form.html--&gt;

{% extends &quot;./base.html&quot; %}
{% load static %}

{% block css %}
    &lt;link rel=&quot;stylesheet&quot; href=&quot;{% static &quot;posts/css/post_form.css&quot; %}&quot;&gt;  &lt;!--css 파일 이름 변경--&gt;
{% endblock css %}

{% block post_header %}  &lt;!--글 쓰기 버튼을 post_header 블럭 영역에 추가--&gt;
    &lt;div class=&quot;container&quot;&gt;  &lt;!--글 쓰기 버튼 div 태그로 감싸고 클래스로 지정--&gt;
        &lt;h1&gt;글쓰기&lt;/h1&gt;
    &lt;/div&gt;
{% endblock post_header %}

{% block content %}
    &lt;form method=&quot;post&quot;&gt;{% csrf_token %}
        &lt;div class=&quot;submit&quot;&gt;
            &lt;input type=&quot;submit&quot; value=&quot;작성 완료&quot;&gt;
        &lt;/div&gt;
        &lt;div class=&quot;editor&quot;&gt; &lt;!--작성 영역을 div 태그로 감싸고 editor 클래스로 지정 --&gt;
                &lt;p&gt;{{post_form.title}}&lt;/p&gt;
                {% for error in post_form.title.errors %}
                &lt;p class=&quot;error&quot;&gt;{{error}}&lt;/p&gt;
                {% endfor %}
                &lt;p&gt;{{post_form.content}}&lt;/p&gt;
                {% for error in post_form.content.errors %}
                &lt;p class=&quot;error&quot;&gt;{{error}}&lt;/p&gt;
                {% endfor %}
        &lt;/div&gt;
    &lt;/form&gt;
    &lt;div class=&quot;btn_back&quot;&gt;  &lt;!--목록으로 이동하는 div 태그 생성 후, 클래스 추가--&gt;
        &lt;a href=&quot;{% url &quot;post-list&quot; %}&quot;&gt;목록으로&lt;/a&gt;  &lt;!--a 태그 작성--&gt;
    &lt;/div&gt;
{% endblock content %}</code></pre>
<hr>
<h1 id="17-포스트-수정-페이지">17. 포스트 수정 페이지</h1>
<blockquote>
<p>기존에 작성된 게시글을 수정하는 페이지를 만들어 본다. 수정 페이지는 <code>폼 페이지에 기존에 작성된 내용</code>을 불러오는 형태로 만들면 될 것 같다.</p>
</blockquote>
<hr>
<h2 id="171-url-패턴-주석-해제">17.1 url 패턴 주석 해제</h2>
<blockquote>
<p><code>urls.py</code> 에서 주석 처리된 url 패턴을 <code>주석 해제</code>하고 <code>name 속성</code>에 <code>post-update</code>를 작성한다. </p>
</blockquote>
<pre><code class="language-python">#urls.py

from django.urls import path
from . import views

urlpatterns = [
    # path(&#39;&#39;, views.index),
    path(&#39;posts/&#39;, views.post_list, name=&quot;post-list&quot;),
    path(&#39;posts/new/&#39;, views.post_create, name=&quot;post-create&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/&#39;, views.post_detail, name=&quot;post-detail&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/edit/&#39;, views.post_update, name=&quot;post-update&quot;),  #주석 해제 및 name 속성 작성
    # path(&#39;posts/&lt;int:post_id&gt;/delete/&#39;, views.post_delete),
]</code></pre>
<hr>
<h2 id="172-상세-페이지에서-수정하기-버튼-추가">17.2 상세 페이지에서 수정하기 버튼 추가</h2>
<blockquote>
<p>상세 페이지에서 게시글을 수정할 수 있는 <code>&lt;a&gt;</code> 태그를 추가한다.</p>
</blockquote>
<blockquote>
<p><code>css 적용</code>을 위해 두 개의 <code>&lt;div&gt;</code> 태그로 감싸고 클래스를 지정한다.</p>
</blockquote>
<blockquote>
<p>포스트의 <code>id</code>를 넘겨준다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_detail.html &gt; block content--&gt;

{% block content %}
    &lt;div class=&quot;content&quot;&gt;
        &lt;p class=&quot;text&quot;&gt;{{post.content|linebreaksbr}}&lt;/p&gt;
        &lt;p class=&quot;date_md&quot;&gt;수정일 &lt;span&gt;{{post.dt_modified}}&lt;/span&gt;&lt;/p&gt;
    &lt;/div&gt;
        &lt;div class=&quot;btn&quot;&gt;
            &lt;div class=&quot;btn_list&quot;&gt;
                &lt;a href=&quot;{% url &quot;post-list&quot; %}&quot;&gt;스토리 목록으로&lt;/a&gt;
            &lt;/div&gt;
            &lt;div class=&quot;right_btn&quot;&gt;  &lt;!--추가--&gt;
                &lt;div class=&quot;btn_modify&quot;&gt;  &lt;!--추가--&gt;
                    &lt;a href=&quot;{% url &quot;post-update&quot; post.id %}&quot;&gt;수정하기&lt;/a&gt;  &lt;!--추가--&gt;
                &lt;/div&gt;  &lt;!--추가--&gt;
            &lt;/div&gt;  &lt;!--추가--&gt;
    &lt;/div&gt;
{% endblock content %}</code></pre>
<hr>
<h2 id="173-뷰-작성">17.3 뷰 작성</h2>
<blockquote>
<p><code>views.py</code>에서 <code>post_update</code> 함수를 추가한다.</p>
</blockquote>
<pre><code class="language-python">#views.py &gt; post_update

def post_update(request, post_id):
    post = Post.objects.get(id=post_id)
    if request.method == &quot;POST&quot;:
        post_form = PostForm(request.POST, instance=post)
        if post_form.is_valid():
            post_form.save()
            return redirect(&quot;post-detail&quot;, post_id=post.id)
    else:
        post_form = PostForm(instance=post)
    context = {&quot;post_form&quot;: post_form}
    return render(request, &quot;posts/post_form.html&quot;, context)</code></pre>
<hr>
<h3 id="1731-각-코드의-의미">17.3.1 각 코드의 의미</h3>
<blockquote>
<p>post_update 함수를 생성하는데, 요청과, post_id를 인자로 받는 파라미터를 추가한다.</p>
</blockquote>
<pre><code class="language-python">def post_update(request, post_id):</code></pre>
<blockquote>
<p><code>Post 모델</code>에서 <code>post_id로 전달 받은 인자</code>와 <code>동일한 값의 id</code>를 지닌 데이터를 불러와 <code>변수 post</code>에 지정한다.</p>
</blockquote>
<pre><code class="language-python">    post = Post.objects.get(id=post_id)</code></pre>
<blockquote>
<p>만약 요청 방식이 <code>POST</code>라면</p>
</blockquote>
<pre><code class="language-python">    if request.method == &quot;POST&quot;:</code></pre>
<blockquote>
<p><code>폼 PostForm</code>에 <code>POST에 담긴 데이터</code>를 <code>바인딩</code>하고, 새 인스턴스가 아닌 <code>post로 지정된 인스턴스</code>에 매칭시킨 후 <code>변수 post_form</code>에 지정한다.</p>
</blockquote>
<pre><code class="language-python">        post_form = PostForm(request.POST, instance=post)</code></pre>
<blockquote>
<p>만약 <code>변수 post_form</code>의 데이터가 유효성 검증을 통과하면</p>
</blockquote>
<pre><code class="language-python">        if post_form.is_valid():</code></pre>
<blockquote>
<p><code>post_form</code> 을 데이터 베이스에 저장한다. </p>
</blockquote>
<pre><code class="language-python">            post_form.save()
</code></pre>
<blockquote>
<p>상세 페이지로 이동하는데, 상세 페이지에서는 포스트의 id를 필요로 하기 때문에 <code>post_id</code>에 다시 <code>post.id</code>를 담아서 넘겨준다.</p>
</blockquote>
<pre><code class="language-python">            return redirect(&quot;post-detail&quot;, post_id=post.id)</code></pre>
<blockquote>
<p>만약 요청 방식이 <code>POST</code>가 아니라면, </p>
</blockquote>
<pre><code class="language-python">    else:</code></pre>
<blockquote>
<p><code>변수 post_form</code>에 <code>폼 PostForm</code>을 지정하는데, 새 인스턴스가 아닌, <code>post로 지정된 인스턴스</code>에 매칭시킨다.</p>
</blockquote>
<pre><code class="language-python">        post_form = PostForm(instance=post)</code></pre>
<blockquote>
<p><code>변수 context</code>에 <code>post_form</code>을 지정한다.</p>
</blockquote>
<pre><code class="language-python">    context = {&quot;post_form&quot;: post_form}</code></pre>
<blockquote>
<p><code>요청</code>과 <code>context</code>를 담아서 <code>post_form.html</code> 템플릿을 렌더한다.</p>
</blockquote>
<pre><code class="language-python">    return render(request, &quot;posts/post_form.html&quot;, context)</code></pre>
<hr>
<h1 id="18-포스트-삭제">18. 포스트 삭제</h1>
<blockquote>
<p>포스트를 삭제하는 기능을 구현한다.</p>
</blockquote>
<hr>
<h2 id="181-url-패턴-주석-해제">18.1 url 패턴 주석 해제</h2>
<blockquote>
<p><code>url 패턴의 주석을 해제</code>하고 <code>name 속성</code>을 작성한다.</p>
</blockquote>
<pre><code class="language-python">#urls.py

from django.urls import path
from . import views

urlpatterns = [
    # path(&#39;&#39;, views.index),
    path(&#39;posts/&#39;, views.post_list, name=&quot;post-list&quot;),
    path(&#39;posts/new/&#39;, views.post_create, name=&quot;post-create&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/&#39;, views.post_detail, name=&quot;post-detail&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/edit/&#39;, views.post_update, name=&quot;post-update&quot;),
    path(&#39;posts/&lt;int:post_id&gt;/delete/&#39;, views.post_delete, name=&quot;post-delete),  # 주석 해제
]</code></pre>
<hr>
<h2 id="182-상세-페이지에서-삭제하기-버튼-추가">18.2 상세 페이지에서 삭제하기 버튼 추가</h2>
<blockquote>
<p>상세 페이지에 <code>&lt;a&gt;</code> 태그를 작성해 삭제하기 버튼을 추가한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_detail.html &gt; block content--&gt;

{% block content %}
    &lt;div class=&quot;content&quot;&gt;
        &lt;p class=&quot;text&quot;&gt;{{post.content|linebreaksbr}}&lt;/p&gt;
        &lt;p class=&quot;date_md&quot;&gt;수정일 &lt;span&gt;{{post.dt_modified}}&lt;/span&gt;&lt;/p&gt;
    &lt;/div&gt;
        &lt;div class=&quot;btn&quot;&gt;
            &lt;div class=&quot;btn_list&quot;&gt;
                &lt;a href=&quot;{% url &quot;post-list&quot; %}&quot;&gt;스토리 목록으로&lt;/a&gt;
            &lt;/div&gt;
            &lt;div class=&quot;right_btn&quot;&gt;
                &lt;div class=&quot;btn_modify&quot;&gt;
                    &lt;a href=&quot;{% url &quot;post-update&quot; post.id %}&quot;&gt;수정하기&lt;/a&gt;
                &lt;/div&gt;
                &lt;div class=&quot;btn_delete&quot;&gt; &lt;!--추가--&gt;
                    &lt;a href=&quot;{% url &quot;post-delete&quot; post.id %}&quot;&gt;삭제하기&lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
    &lt;/div&gt;
{% endblock content %}</code></pre>
<hr>
<h2 id="183-뷰-작성">18.3 뷰 작성</h2>
<blockquote>
<p>포스트를 삭제하는 뷰를 작성한다.</p>
</blockquote>
<pre><code class="language-python">#views.py

def post_delete(request, post_id):
    post = Post.objects.get(id=post_id)
    post.delete()
    return redirect(&quot;post-list&quot;)</code></pre>
<blockquote>
<p>url로부터 전달받은 id에 해당하는 <code>데이터를 삭제하는 코드</code>다. 삭제 후에는 목록 페이지로 리다이렉트 한다.</p>
</blockquote>
<hr>
<h1 id="19-삭제하기-컨펌-페이지">19. 삭제하기 컨펌 페이지</h1>
<blockquote>
<p>삭제하기 버튼 클릭 시, 컨펌 없이 바로 삭제가 된다면 유저 경험 측면에서 좋지 않다. 따라서 <code>삭제하기 컨펌 페이지</code>를 만드는 방법을 알아본다.</p>
</blockquote>
<hr>
<h2 id="191-삭제하기-컨펌-페이지-템플릿-작성">19.1 삭제하기 컨펌 페이지 템플릿 작성</h2>
<blockquote>
<p>삭제하기 버튼을 클릭하면 이동되는 템플릿을 작성한다.</p>
</blockquote>
<pre><code class="language-html">&lt;!--post_confirm_delete.html--&gt;

{% extends &#39;./base.html&#39; %}
{% load static %}

{% block css %}
    &lt;link rel=&quot;stylesheet&quot; href=&quot;{% static &#39;posts/css/post_confirm_delete.css&#39; %}&quot;&gt;
{% endblock css %}

{% block content %}
&lt;div class=&quot;confirm&quot;&gt;
    &lt;p class=&quot;title&quot;&gt;[{{post.title}}]&lt;/p&gt;
    &lt;p&gt;삭제하시겠습니까?&lt;/p&gt;
    &lt;form method=&quot;POST&quot;&gt;{% csrf_token %}
        &lt;div class=&quot;confirm_btn&quot;&gt;
            &lt;input type=&quot;submit&quot; value=&quot;삭제하기&quot;&gt;
            &lt;a href=&quot;{% url &quot;post-detail&quot; post.id %}&quot;&gt;돌아가기&lt;/a&gt;
        &lt;/div&gt;
    &lt;/form&gt;
&lt;/div&gt;
{% endblock content %}</code></pre>
<hr>
<h2 id="192-삭제하기-컨펌-페이지-css-작성">19.2 삭제하기 컨펌 페이지 css 작성</h2>
<blockquote>
<p>css는 <code>강의에서 전달받은 css</code> 파일에 개인적으로 적용하고 싶은 부분을 추가했다.</p>
</blockquote>
<pre><code class="language-css">/*post_confirm_delete.css*/

h1,h2,h3,p,a,ul,li{
    margin: 0;
    padding: 0;
    font-family : &#39;NanumMyeongjo&#39;;
    font-weight: 200;
    list-style:  none;
    text-decoration: none; 
    outline: none;
    color: black;
}
a:hover,a:active,button:hover {
    text-decoration: none;
}
html{
    position: relative;
    min-height: 100%;
    margin: 0;
}
body {
    min-height: 100%;
    margin: 0;
    padding: 0;
    background: #2b2a2e;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: auto;
}
img {
    margin: 0;
    padding: 0;
    border: 0;
}

#nav{
    height: 60px;
}
#content{
    margin-top: 160px;
    margin-bottom: 300px;
}
#footer{
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 75px;
    background: rgba(41, 41, 44, 0.7);
}
.container{
    width: 800px;
    margin: 0 auto;
}

.logo{
    width: 65%;
    min-width: 899px;
    margin: 0 auto;
    padding-top: 17px;
}
.logo img{
    width: 88px;
}
.confirm{
    padding: 108px 0 124px;
    text-align: center;
    background: #fff;
}
p{
    font-size: 17px;
    color: #575362;
    letter-spacing: -0.35px;
}
p.title{
    padding-bottom: 8px;
    font-size: 20px;
    font-weight: bold;
    color: #333236;
    letter-spacing: -0.45px;
}
.confirm_btn{
    padding-top: 46px;
}
.confirm_btn ul li{
    display: inline-block;
}
.confirm_btn input{

    display: inline-block;
    padding: 10px 19px 11px;
    font-size: 15px;
    color: #f6f6f6;
    background: red;
    border: none;
}
.confirm_btn a{
    display: inline-block;
    margin-left: 18px;
    padding: 10px 19px 11px;
    font-size: 15px;
    color: #69666d;
    background: #f6f6f6;
    border: none;
}

.confirm_btn a:hover{
    color: #95929a;
    transition: all .2s;
}

.confirm_btn input:hover{
    color: red;
    background: #f6f6f6;
    transition: all .2s;
}
.footer{
    width: 65%;
    min-width: 899px;
    margin: 0 auto;
}
.footer p{
    text-align: right;
    font-size: 20px;
    line-height: 75px;
    font-weight: 800;
    letter-spacing: -0.85px;
    color: #53534f;
}</code></pre>
<hr>
<h2 id="193-뷰-작성">19.3 뷰 작성</h2>
<blockquote>
<p>기존에 작성한 <code>post_delete</code> 함수를 수정한다.</p>
</blockquote>
<pre><code class="language-python">#views.py &gt; post_delete

def post_delete(request, post_id):
    post = Post.objects.get(id=post_id)
    if request.method == &quot;POST&quot;:
        post.delete()
        return redirect(&quot;post-list&quot;)
    else:
        context = {&quot;post&quot;: post}
        return render(request, &quot;posts/post_confirm_delete.html&quot;, context=context)</code></pre>
<blockquote>
<p>요청 방식이 <code>POST</code>인 경우 게시글을 삭제하고 포스트 목록 페이지로 리다이렉트 한다.</p>
</blockquote>
<blockquote>
<p>요청 방식이 <code>GET</code>인 경우 삭제하기 컨펌 템플릿을 렌더한다.</p>
</blockquote>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django 배포 준비]]></title>
            <link>https://velog.io/@ethan_/Django-%EB%B0%B0%ED%8F%AC-%EC%A4%80%EB%B9%84</link>
            <guid>https://velog.io/@ethan_/Django-%EB%B0%B0%ED%8F%AC-%EC%A4%80%EB%B9%84</guid>
            <pubDate>Thu, 04 Jan 2024 08:22:15 GMT</pubDate>
            <description><![CDATA[<p>장고 프로젝트를 배포하기 위해 준비해야하는 내용을 알아본다.</p>
<hr>
<h3 id="디버그-모드-끄기">디버그 모드 끄기</h3>
<p>디버그 모드를 키면 에러 발생 시 자세한 에러 내용을 페이지에 표시한다. 너무 자세한 내용이 표시되면 보안에 문제가 될 수 있으므로, 디버그 모드를 종료해야 한다.</p>
<blockquote>
<p><code>프로젝트 구성 디렉토리</code>로 이동해 <code>settings.py</code>로 이동한다.</p>
</blockquote>
<blockquote>
<p>디버그 모드를 false로 변경한다.</p>
</blockquote>
<pre><code class="language-python">#settings.py

DEBUG = False  #True에서 False로 변경</code></pre>
<hr>
<h3 id="허용-호스트-설정">허용 호스트 설정</h3>
<p>다음으로 허용하는 호스트를 설정해야 한다. </p>
<blockquote>
<p>디버그 모드와 마찬가지로 <code>settings.py</code>에서 설정한다.</p>
</blockquote>
<blockquote>
<p>ALLOWED_HOSTS = []는 로컬 호스트, 내 컴퓨터에서만 접속할 수 있는 환경이다</p>
</blockquote>
<pre><code class="language-python">#settings.py

ALLOWED_HOSTS = [&#39;.pythonanuwhere.com&#39;]  #파이썬 애니웨어를 통해 배포하는 경우

ALLOWED_HOSTS = []  #로컬 호스트에서만 허용하는 경우</code></pre>
<hr>
<h3 id="정적파일을-한-곳으로-정리하기">정적파일을 한 곳으로 정리하기</h3>
<blockquote>
<p>마찬가지로 <code>setting.py</code> 에서 설정한다.</p>
</blockquote>
<blockquote>
<p>STATIC_URL 항목 하단에 아래의 코드를 입력한다.</p>
</blockquote>
<pre><code class="language-python">#settings.py

STATIC_URL = &#39;/static/&#39;
STATIC_ROOT = os.path.join(BASE.DIR, &#39;static&#39;)
</code></pre>
<blockquote>
<p><code>STATIC_ROOT = os.path.join(BASE.DIR, &#39;static&#39;)</code>는 <code>현재 파일</code>(setting.py)의 <code>BASE.DIR</code>(최상위 디렉토리 ,즉 루트 디렉토리)에 <code>static 디렉토리</code>를 <code>지정</code>해 모든 정적 파일을 이 경로에 복사하겠다는 뜻이다.</p>
</blockquote>
<blockquote>
<p>모든 정적 파일을 복사할 경로를 지정한 것이기에 다음 명령어를 입력해야 모든 정적파일이 실제로 복사된다.</p>
</blockquote>
<pre><code class="language-bash">python manage.py collectstatic  #모든 정적파일을 지정한 디렉토리로 복사하는 명령어</code></pre>
<hr>
<h3 id="배포-준비는-끝났는데">배포 준비는 끝났는데...</h3>
<p>배포 준비는 모두 끝났다. 배포는 각 호스팅 서비스에 맞춰서 진행하면 된다. 본인은 아직 모든 강의를 끝내지 않았기에 호스팅 서비스를 정하지 못했다.</p>
]]></description>
        </item>
    </channel>
</rss>