<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jurin.log</title>
        <link>https://velog.io/</link>
        <description>anaooauc1236@naver.com</description>
        <lastBuildDate>Sun, 13 Oct 2024 07:00:26 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jurin.log</title>
            <url>https://images.velog.io/images/jusung-c/profile/1a14db44-3b1e-4381-93e0-e0f47d6ac99a/캡처.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jurin.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jusung-c" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[ㅅㄷㄴㅅ]]></title>
            <link>https://velog.io/@jusung-c/%E3%85%85%E3%84%B7%E3%84%B4%E3%85%85</link>
            <guid>https://velog.io/@jusung-c/%E3%85%85%E3%84%B7%E3%84%B4%E3%85%85</guid>
            <pubDate>Sun, 13 Oct 2024 07:00:26 GMT</pubDate>
            <description><![CDATA[<p>ㅅㄷㄴㅅ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[콘텐츠 편집 기능 - Photo 앱]]></title>
            <link>https://velog.io/@jusung-c/%EC%BD%98%ED%85%90%EC%B8%A0-%ED%8E%B8%EC%A7%91-%EA%B8%B0%EB%8A%A5-Photo-%EC%95%B1</link>
            <guid>https://velog.io/@jusung-c/%EC%BD%98%ED%85%90%EC%B8%A0-%ED%8E%B8%EC%A7%91-%EA%B8%B0%EB%8A%A5-Photo-%EC%95%B1</guid>
            <pubDate>Tue, 21 Dec 2021 13:49:41 GMT</pubDate>
            <description><![CDATA[<p>Album 및 Photo 2개의 테이블 각각의 콘텐츠 편집 기능을 구현한다. 이전의 Add와 Change 기능 구현과 비슷하지만 Album과 Photo 테이블이 1:N 관계로 연결되어 있는 점을 고려해야 한다.</p>
<h3 id="url-설계">URL 설계</h3>
<p><img src="https://images.velog.io/images/jusung-c/post/4384c1d8-2c7b-4d59-927d-b1acd597fbfe/image.png" alt=""></p>
<h3 id="모델-코딩">모델 코딩</h3>
<p>Album 및 Photo 테이블에 owner 필드를 추가해준다.</p>
<pre><code class="language-py">owner = models.ForeignKey(&#39;auth.User&#39;, on_delete=models.CASCADE, 
                              verbose_name=&#39;OWNER&#39;, blank=True, null=True)
</code></pre>
<p>Album과 User 테이블 간 관계 및 Photo와 User 테이블 간 관계는 모두 N:1 관계이므로, ForeignKey로 표현한다.</p>
<h4 id="모델을-변경했으므로-db에-반영">모델을 변경했으므로 DB에 반영</h4>
<h3 id="urlconf-코딩">URLconf 코딩</h3>
<pre><code class="language-py">    # /photo/album/add/
    path(&#39;album/add/&#39;, views.AlbumPhotoCV.as_view(), name=&#39;album_add&#39;),

    # /photo/album/change/
    path(&#39;album/change/&#39;, views.AlbumChangeLV.as_view(), name=&#39;album_change&#39;),

    # /photo/album/99/update/
    path(&#39;album/&lt;int:pk&gt;/update/&#39;, views.AlbumPhotoUV.as_view(), name=&#39;album_update&#39;),

    # /photo/album/99/delete/
    path(&#39;album/&lt;int:pk&gt;/delete/&#39;, views.AlbumDelV.as_view(), name=&#39;album_delete&#39;),

    # /photo/photo/add/
    path(&#39;photo/add/&#39;, views.PhotoCV.as_view(), name=&#39;photo_add&#39;),

    # /photo/photo/change/
    path(&#39;photo/change/&#39;, views.PhotoChangeLV.as_view(), name=&#39;photo_change&#39;),

    # /photo/photo/99/update/
    path(&#39;photo/&lt;int:pk&gt;/update/&#39;, views.PhotoUV.as_view(), name=&#39;photo_update&#39;),

    # /photo/photo/99/delete/
    path(&#39;photo/&lt;int:pk&gt;/delete/&#39;, views.PhotoDelV.as_view(), name=&#39;photo_delete&#39;),</code></pre>
<p>11장의 urls.py 파일과 거의 동일하지만 AlbumPhotoCV, AlbumPhotoUV 뷰가 인라인 모델 폼을 처리하는 뷰라는 점이 다르다. 즉 앨범과 사진을 한꺼번에 처리할 수 있는 폼을 출력하는 뷰이다. </p>
<h3 id="뷰-코딩">뷰 코딩</h3>
<p>11장에서는 모델폼을 사용하니까 따로 폼을 정의할 필요가 없었는데 이번엔 폼셋을 정의해야 해서 forms.py를 코딩해야 한다.</p>
<h4 id="formspy">forms.py</h4>
<pre><code class="language-py">from django.forms import inlineformset_factory

from photo.models import Album, Photo

PhotoInlineFormSet = inlineformset_factory(Album, Photo,
                                           fields=[&#39;image&#39;, &#39;title&#39;, &#39;description&#39;],
                                           extra=2)
</code></pre>
<p>URLconf에서 정의한 뷰를 코딩한다.</p>
<p><img src="https://images.velog.io/images/jusung-c/post/1c1d826f-b997-457b-b951-771a5eb9f569/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/185f0b3a-d530-4717-b9ad-aa4813d79949/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/dfd3277b-1af6-483b-826e-4f9da7e5e512/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/e94e32d0-312d-44db-96fb-a6bdf4540050/image.png" alt=""></p>
<h3 id="템플릿-코딩하기">템플릿 코딩하기</h3>
<p>뷰 정의 시 template_name 속성을 지정하지 않았으므로 디폴트 템플릿명을 사용한다. </p>
<ul>
<li>메뉴 수정을 위한 base.html</li>
<li>Photo 모델에 대한 템플릿</li>
<li>인라인 모델 폼셋이 들어 있는 Album 모델에 대한 템플릿 </li>
</ul>
<h4 id="basehtml">base.html</h4>
<p>Add - Album, Photo 링크 추가
Change - Album, Photo 링크 추가</p>
<p>CreateView와 UpdateView는 템플릿명이 같으므로 같은 링크 추가</p>
<pre><code class="language-html">&lt;a class=&quot;dropdown-item&quot; href=&quot;{% url &#39;photo:album_add&#39;%}&quot;&gt;Album&lt;/a&gt;
&lt;a class=&quot;dropdown-item&quot; href=&quot;{% url &#39;photo:photo_add&#39;%}&quot;&gt;Photo&lt;/a&gt;</code></pre>
<h4 id="photophoto_formhtml">photo/photo_form.html</h4>
<p>Photo 레코드를 생성 or 수정하기 위한 폼을 보여주는 화면</p>
<ul>
<li><p>앨범을 선택하는 드롭다운 박스 위젯 - Album과 Photo 테이블이 1:N 관계이므로 Photo 테이블에 대한 폼인데도 Album 테이블의 레코드를 선택할 수 있도록 해준다.</p>
</li>
<li><p>사진 레코드를 생성하는 폼이지만 사진이 소속될 앨범을 새로 생성할 수 있도록 해준다. </p>
</li>
</ul>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% load widget_tweaks %}

{% block title %}photo_form.html{% endblock %}

{% block content %}
    &lt;h1&gt;Photo Create/Update - {{ user }}&lt;/h1&gt;
    &lt;p class=&quot;font-italic&quot;&gt;This is a creation or update form for your photo.&lt;/p&gt;

{% if form.errors %}
    &lt;div class=&quot;alert alert-danger&quot;&gt;
        &lt;div class=&quot;font-weight-bold&quot;&gt;
            Wrong! Please correct the error(s) below.
        &lt;/div&gt;
        {{ form.errors }}
    &lt;/div&gt;
{% endif %}

{% if form.is_multipart %}
    &lt;form enctype=&quot;multipart/form-data&quot; action=&quot;&quot; method=&quot;post&quot; class=&quot;card pt-3&quot;&gt;
{% else %}
    &lt;form action=&quot;.&quot; method=&quot;post&quot; class=&quot;card pt-3&quot;&gt;
{% endif %}
{% csrf_token %}
    &lt;div class=&quot;form-group row&quot;&gt;
        {{ form.album|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
        &lt;div class=&quot;col-sm-2&quot;&gt;
            {{ form.album|add_class:&quot;form-control&quot; }}
        &lt;/div&gt;
        &lt;div class=&quot;col-sm-2 my-auto&quot;&gt;
            &lt;a href=&quot;{% url &#39;photo:album_add&#39; %}&quot; class=&quot;btn btn-outline-primary btn-sm&quot;&gt;
                Add Album
            &lt;/a&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class=&quot;form-group row&quot;&gt;
        {{ form.title|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
        &lt;div class=&quot;col-sm-5&quot;&gt;
            {{ form.title|add_class:&quot;form-control&quot;|attr:&quot;autofocus&quot; }}
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class=&quot;form-group row&quot;&gt;
        {{ form.image|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
        &lt;div class=&quot;col-sm-5&quot;&gt;
            {{ form.image|add_class:&quot;form-control-file&quot; }}
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class=&quot;form-group row&quot;&gt;
        {{ form.description|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
        &lt;div class=&quot;col-sm-8&quot;&gt;
            {{ form.description|add_class:&quot;form-control-file&quot;|attr:&quot;rows:3&quot; }}
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class=&quot;form-group&quot;&gt;
        &lt;div class=&quot;offset-sm-2 col-sm-5&quot;&gt;
            &lt;input type=&quot;submit&quot; value=&quot;Submit&quot; class=&quot;btn btn-info&quot;/&gt;
        &lt;/div&gt;
    &lt;/div&gt;

&lt;/form&gt;
{% endblock %}</code></pre>
<p>post_form.html 파일과 거의 같고 업로드 기능이 들어 있다는 점만 다르다. 사진이나 일반 파일을 업로드 하는 경우에는 enctype 속성을 multipart/form-data로 지정해야 한다.</p>
<p>is_multipart() 메소드는 폼이나 폼셋을 미리 체크해 multipart 인코딩이 필요한지 여부를 알려준다. 반환값이 True면 enctype=multipart/form-data로 지정해야 한다. 여기서는 폼에 이미지 필드가 있으니까 True를 반환한다.</p>
<p><img src="https://images.velog.io/images/jusung-c/post/ca08d499-2c69-4463-8eaa-5a240c4df142/image.png" alt=""></p>
<h4 id="photophoto_change_listhtml">photo/photo_change_list.html</h4>
<p>Photo 테이블의 레코드를 변경하기 위해 기존 레코드의 리스트를 보여주는 화면</p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}photo_change_list.html{% endblock %}

{% block content %}
    &lt;h1&gt;
        Photo Change - {{ user }}
    &lt;/h1&gt;
    &lt;br&gt;

    &lt;table class=&quot;table table-striped table-bordered table-condensed&quot;&gt;

        &lt;thead&gt;
            &lt;tr class=&quot;table-info&quot;&gt;
                &lt;th&gt;Album&lt;/th&gt;
                &lt;th&gt;Title&lt;/th&gt;
                &lt;th&gt;Description&lt;/th&gt;
                &lt;th&gt;Owner&lt;/th&gt;
                &lt;th&gt;Update&lt;/th&gt;
                &lt;th&gt;Delete&lt;/th&gt;
            &lt;/tr&gt;
        &lt;/thead&gt;

        &lt;tbody&gt;
        {% for item in object_list %}
            &lt;tr&gt;
                &lt;td&gt;{{ item.album }}&lt;/td&gt;
                &lt;td&gt;{{ item.title }}&lt;/td&gt;
                &lt;td&gt;{{ item.description }}&lt;/td&gt;
                &lt;td&gt;{{ item.owner }}&lt;/td&gt;
                &lt;td&gt;&lt;a href=&quot;{% url &#39;photo:photo_update&#39; item.id %}&quot;&gt;Update&lt;/a&gt;&lt;/td&gt;
                &lt;td&gt;&lt;a href=&quot;{% url &#39;photo:photo_delete&#39; item.id %}&quot;&gt;Delete&lt;/a&gt;&lt;/td&gt;
            &lt;/tr&gt;
        {% endfor %}
        &lt;/tbody&gt;
    &lt;/table&gt;

{% endblock %}</code></pre>
<h4 id="photophoto_confirm_deletehtml">photo/photo_confirm_delete.html</h4>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}photo_confirm_delete.html{% endblock %}

{% block content %}
    &lt;h1&gt;
        Photo Delete
    &lt;/h1&gt;
    &lt;br&gt;

    &lt;form action=&quot;.&quot; method=&quot;post&quot;&gt;
        {% csrf_token %}
        &lt;p&gt;Are you sure you want to delete &quot;{{ object }}&quot; ?&lt;/p&gt;
        &lt;input type=&quot;submit&quot; value=&quot;Confirm&quot; class=&quot;btn btn-danger btn-sm&quot; /&gt;
    &lt;/form&gt;

&lt;/div&gt;
{% endblock %}</code></pre>
<h4 id="photoalbum_formhtml">photo/album_form.html</h4>
<p>Album 레코드를 수정하기 위한 폼을 보여주는 화면
<img src="https://images.velog.io/images/jusung-c/post/e91b430a-50f4-47c4-a244-8b8e87dd72c2/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/b70c59ce-6950-4599-acff-85cc5d5f17ef/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/7358a97b-f9fb-4edf-9b57-52455e74b3c3/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/9d54fb20-0c55-41fd-aade-e26c03f0d8db/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/86fb57e8-90e2-4bf7-b044-e0e41acf37e6/image.png" alt=""></p>
<h4 id="photoalbum_change_listhtml">photo/album_change_list.html</h4>
<p>Album 테이블의 레코드를 변경하기 위해 기존 레코드의 리스트를 보여주는 화면. </p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}album_change_list.html{% endblock %}

{% block content %}
    &lt;h1&gt;
        Album Change - {{ user }}
    &lt;/h1&gt;
    &lt;br&gt;

    &lt;table class=&quot;table table-striped table-bordered table-condensed&quot;&gt;

        &lt;thead&gt;
            &lt;tr class=&quot;table-info&quot;&gt;
                &lt;th&gt;Name&lt;/th&gt;
                &lt;th&gt;Description&lt;/th&gt;
                &lt;th&gt;Owner&lt;/th&gt;
                &lt;th&gt;Update&lt;/th&gt;
                &lt;th&gt;Delete&lt;/th&gt;
            &lt;/tr&gt;
        &lt;/thead&gt;

        &lt;tbody&gt;
        {% for item in object_list %}
            &lt;tr&gt;
                &lt;td&gt;{{ item.name }}&lt;/td&gt;
                &lt;td&gt;{{ item.description }}&lt;/td&gt;
                &lt;td&gt;{{ item.owner }}&lt;/td&gt;
                &lt;td&gt;&lt;a href=&quot;{% url &#39;photo:album_update&#39; item.id %}&quot;&gt;Update&lt;/a&gt;&lt;/td&gt;
                &lt;td&gt;&lt;a href=&quot;{% url &#39;photo:album_delete&#39; item.id %}&quot;&gt;Delete&lt;/a&gt;&lt;/td&gt;
            &lt;/tr&gt;
        {% endfor %}
        &lt;/tbody&gt;
    &lt;/table&gt;

{% endblock %}</code></pre>
<h4 id="photoalbum_confirm_deletehtml">photo/album_confirm_delete.html</h4>
<p>Album 테이블의 레코드를 삭제하기 전 확인하는 화면</p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}album_confirm_delete.html{% endblock %}

{% block content %}
    &lt;h1&gt;
        Album Delete 
        &lt;small class=&quot;font-italic&quot;&gt;
            (including related photos)
        &lt;/small&gt;
    &lt;/h1&gt;
    &lt;br&gt;

    &lt;form action=&quot;.&quot; method=&quot;post&quot;&gt;
        {% csrf_token %}
        &lt;p&gt;Are you sure you want to delete &quot;{{ object }}&quot; ?&lt;/p&gt;
        &lt;input type=&quot;submit&quot; value=&quot;Confirm&quot; class=&quot;btn btn-danger btn-sm&quot; /&gt;
    &lt;/form&gt;

&lt;/div&gt;
{% endblock %}</code></pre>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(실전편) - 김석훈님</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[콘텐츠 편집 기능(Bookmark, Blog)]]></title>
            <link>https://velog.io/@jusung-c/%EC%BD%98%ED%85%90%EC%B8%A0-%ED%8E%B8%EC%A7%91-%EA%B8%B0%EB%8A%A5Bookmark-Blog</link>
            <guid>https://velog.io/@jusung-c/%EC%BD%98%ED%85%90%EC%B8%A0-%ED%8E%B8%EC%A7%91-%EA%B8%B0%EB%8A%A5Bookmark-Blog</guid>
            <pubDate>Tue, 21 Dec 2021 09:03:13 GMT</pubDate>
            <description><![CDATA[<p>각 앱의 DB에 들어 있는 레코드들을 장고에서는 콘텐츠라고 지칭하는데 이를 Admin 사이트에서 관리자만이 콘텐츠를 생성, 변경할 수 있었다. 일반 사용자들도 콘텐츠를 생성 및 변경할 수 있도록 한다.</p>
<p>but, 콘텐츠 생성 및 변경하는 권한을 모든 사용자에게 부여해서는 안된다. </p>
<ul>
<li>콘텐츠에 대한 열람은 모든 사용자가 가능하다.</li>
<li>콘텐츠를 새로 생성하는 것은 로그인한 사용자만 가능하다. - [Add] 메뉴 </li>
<li>콘텐츠를 수정, 삭제하는 작업은 그 콘텐츠를 생성한 사용자만 가능하다. - [Change] 메뉴</li>
</ul>
<h3 id="테이블-설계">테이블 설계</h3>
<p>콘텐츠에 대한 소유자를 확인해야 하므로 각 콘텐츠 테이블별로 소유자 필드가 필요하다. </p>
<p>Bookmark 및 Post 테이블에 owner 필드(User 테이블에 대한 외래 키)가 추가되도록 설계한다.</p>
<p>필드명: owner
타입: ForeignKey(User)
제약 조건: Null</p>
<h3 id="url-설계">URL 설계</h3>
<ul>
<li>콘텐츠 생성(add)</li>
<li>변경(change)</li>
<li>대상 리스트</li>
<li>수정(update)</li>
<li>삭제(delete)</li>
</ul>
<h3 id="model-코딩하기">Model 코딩하기</h3>
<h4 id="bookmarkmodelspy-수정">bookmark/models.py 수정</h4>
<pre><code class="language-py">    owner = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
</code></pre>
<p>로그인한 사용자는 여러 개의 북마크를 생성할 수 있으므로 Bookmark와 User 테이블 사이는 N:1 관계이므로 외래 키로 표현한다. </p>
<p>기존 레코드가 존재하는 상태에서 owner 필드를 추가하는 것이기 때문에 owner 필드는 Null 값을 가질 수 있도록 해야 한다. 폼에서도 owner 필드는 입력하지 않아도 되도록 blank=True로 설정했다.</p>
<h4 id="blogmodelspy-수정">blog/models.py 수정</h4>
<p><img src="https://images.velog.io/images/jusung-c/post/9c873051-3fdf-40b9-83c7-f465559cb675/image.png" alt=""></p>
<p>모델의 필드가 추가되었으므로 변경 사항을 DB에 저장해준다.</p>
<h3 id="urlconf-코딩">URLconf 코딩</h3>
<h4 id="bookmarkurlspy">bookmark/urls.py</h4>
<pre><code class="language-py">    # /bookmark/add/
    path(&#39;add/&#39;, views.BookmarkCreateView.as_view(), name=&quot;add&quot;),

    # /bookmark/change/
    path(&#39;change/&#39;, views.BookmarkChangeLV.as_view(), name=&quot;change&quot;),

    # /bookmark/99/update
    path(&#39;&lt;int:pk&gt;/update/&#39;, views.BookmarkUpdateView.as_view(), name=&quot;update&quot;),

    # /bookmark/99/delete
    path(&#39;&lt;int:pk&gt;/delete/&#39;, views.BookmarkDeleteView.as_view(), name=&quot;delete&quot;),</code></pre>
<h4 id="blogurlspy">blog/urls.py</h4>
<pre><code class="language-py">    # /blog/add/
    path(&#39;add/&#39;, views.PostCreateView.as_view(), name=&quot;add&quot;),

    # /blog/change/
    path(&#39;change/&#39;, views.PostChangeLV.as_view(), name=&quot;change&quot;),

    # /blog/99/update
    path(&#39;&lt;int:pk&gt;/update/&#39;, views.PostUpdateView.as_view(), name=&quot;update&quot;),

    # /blog/99/delete
    path(&#39;&lt;int:pk&gt;/delete/&#39;, views.PostDeleteView.as_view(), name=&quot;delete&quot;),</code></pre>
<h3 id="view-코딩">View 코딩</h3>
<p>URLconf에서 지정한 뷰를 코딩해준다. </p>
<h4 id="bookmarkviewspy">bookmark/views.py</h4>
<p><img src="https://images.velog.io/images/jusung-c/post/7db6b035-4a39-407b-b514-0265814922a7/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/40b64164-e70e-4f26-9f04-94952e6746d8/image.png" alt=""></p>
<h4 id="blogviewspy">blog/views.py</h4>
<pre><code class="language-py">class PostCreateView(LoginRequiredMixin, CreateView):
    model = Post
    fields = [&#39;title&#39;, &#39;slug&#39;, &#39;description&#39;, &#39;content&#39;, &#39;tags&#39;]
    initial = {&#39;slug&#39;: &#39;auto-filling-do-not-input&#39;}
    # fields = [&#39;title&#39;, &#39;description&#39;, &#39;content&#39;, &#39;tags&#39;]
    success_url = reverse_lazy(&#39;blog:index&#39;)

    def form_valid(self, form):
        form.instance.owner = self.request.user
        return super().form_valid(form)


class PostChangeLV(LoginRequiredMixin, ListView):
    template_name = &#39;blog/post_change_list.html&#39;

    def get_queryset(self):
        return Post.objects.filter(owner=self.request.user)


class PostUpdateView(OwnerOnlyMixin, UpdateView):
    model = Post
    fields = [&#39;title&#39;, &#39;slug&#39;, &#39;description&#39;, &#39;content&#39;, &#39;tags&#39;]
    success_url = reverse_lazy(&#39;blog:index&#39;)


class PostDeleteView(OwnerOnlyMixin, DeleteView):
    model = Post
    success_url = reverse_lazy(&#39;blog:index&#39;)</code></pre>
<p>PostCreateView 클래스의 initual로 폼의 slug 입력 항목에 대한 초기값을 지정한다. slug 필드는 title 필드로부터 자동으로 채워지는 필드로 models.py 파일의 Post 모델 정의에 있는 save() 함수에서 수행된다.</p>
<p>slug 필드를 처리하는 또 다른 방법은 fields 속성에서 제외해 폼에 나타나지 않도록 하는 방법으로 폼에는 보이지 않지만 Post 모델의 save() 함수에 의해 테이블의 레코드에 자동으로 채워진다.</p>
<h4 id="bookmarkappviewspy">BookMarkApp/views.py</h4>
<p>OwnerOnlyMixin 클래스: 콘텐츠의 소유자인지를 판별</p>
<p><img src="https://images.velog.io/images/jusung-c/post/4bceba5c-4b68-4e81-a6b8-c88163441733/image.png" alt=""></p>
<h3 id="템플릿-코딩">템플릿 코딩</h3>
<p>편집용 제네릭 뷰를 상속받아 뷰를 작성할 경우 template_name 속성으로 템플릿명을 지정하지 않으면 장고에서 정의한 디폴트 템플릿 명을 사용한다.</p>
<p><img src="https://images.velog.io/images/jusung-c/post/3fd3049a-97ab-4910-86b9-b7ad199f018e/image.png" alt=""></p>
<h4 id="basehtml">base.html</h4>
<ul>
<li><p>[Add]와 [Change] 메뉴 수정</p>
<pre><code class="language-html">
                  &lt;li class=&quot;nav-item dropdown mx-1 btn btn-primary&quot;&gt;
                      &lt;a class=&quot;nav-link dropdown-toggle text-white&quot; href=&quot;#&quot; data-bs-toggle=&quot;dropdown&quot;&gt;Add&lt;/a&gt;
                      &lt;div class=&quot;dropdown-menu&quot;&gt;
                          &lt;a class=&quot;dropdown-item&quot; href=&quot;{% url &#39;bookmark:add&#39; %}&quot;&gt;Bookmark&lt;/a&gt;
                          &lt;a class=&quot;dropdown-item&quot; href=&quot;{% url &#39;blog:add&#39; %}&quot;&gt;Post&lt;/a&gt;
                          &lt;div class=&quot;dropdown-divider&quot;&gt;&lt;/div&gt;
                          &lt;a class=&quot;dropdown-item&quot; href=&quot;&quot;&gt;Album&lt;/a&gt;
                          &lt;a class=&quot;dropdown-item&quot; href=&quot;&quot;&gt;Photo&lt;/a&gt;
                      &lt;/div&gt;
                  &lt;/li&gt;

                  &lt;li class=&quot;nav-item dropdown mx-1 btn btn-primary&quot;&gt;
                      &lt;a class=&quot;nav-link dropdown-toggle text-white&quot; href=&quot;#&quot; data-bs-toggle=&quot;dropdown&quot;&gt;Change&lt;/a&gt;
                      &lt;div class=&quot;dropdown-menu&quot;&gt;
                          &lt;a class=&quot;dropdown-item&quot; href=&quot;{% url &#39;bookmark:change&#39; %}&quot;&gt;Bookmark&lt;/a&gt;
                          &lt;a class=&quot;dropdown-item&quot; href=&quot;{% url &#39;blog:change&#39; %}&quot;&gt;Post&lt;/a&gt;
                          &lt;div class=&quot;dropdown-divider&quot;&gt;&lt;/div&gt;
                          &lt;a class=&quot;dropdown-item&quot; href=&quot;&quot;&gt;Album&lt;/a&gt;
                          &lt;a class=&quot;dropdown-item&quot; href=&quot;&quot;&gt;Photo&lt;/a&gt;
                      &lt;/div&gt;
                  &lt;/li&gt;</code></pre>
</li>
</ul>
<h4 id="bookmarkbookmark_formhtml">bookmark/bookmark_form.html</h4>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}bookmark_detail.html{% endblock %}

{% block content %}
    &lt;h1&gt;
        Bookmark Create/Update - {{ user }}
    &lt;/h1&gt;
    &lt;p class=&quot;font-italic&quot;&gt;This is a creation or update form for your bookmark.&lt;/p&gt;

    {% if form.errors %}
    &lt;div class=&quot;alert alert-danger&quot;&gt;
        &lt;div class=&quot;font-weight-bold&quot;&gt;Wrong! Please correct the error(s) below.&lt;/div&gt;
        {{ form.errors }}
    &lt;/div&gt;
    {% endif %}

    &lt;form action=&quot;.&quot; method=&quot;post&quot; class=&quot;card pt-3&quot;&gt;
        {% csrf_token %}

        &lt;div class=&quot;col-sm-5&quot;&gt;
            {{ form.title|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}

            &lt;div class=&quot;col-sm-5&quot;&gt;
                {{ form.title|add_class:&quot;form-control&quot;|attr:&quot;auto:&quot;autofocus&quot; }}
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;div class=&quot;form-group row&quot;&gt;
            {{ form.url|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
            &lt;div class=&quot;col-sm-5&quot;&gt;
                {{ form.url|add_class:&quot;form-control&quot; }}
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;div class=&quot;form-group&quot;&gt;
            &lt;div class=&quot;offset-sm-2 col-sm-5&quot;&gt;
                &lt;input type=&quot;submit&quot; value=&quot;Submit&quot; class=&quot;btn btn-info&quot;/&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/form&gt;
{% endblock %}</code></pre>
<p>login.html과 거의 동일하다. 중요한 차이점은 login.html 파일의 경우 AuthenticationForm을 사용했지만 bookmark_form.html 파일의 form 변수는 Bookmark 모델을 사용해 장고가 내부적으로 만든 폼 객체라는 점이다. </p>
<p>즉, CreateView와 UpdateView는 Bookmark 모델에 대한 ModelForm을 스스로 만들고 사용한다는 점을 유의해야 한다.</p>
<h4 id="bookmarkbookmark_change_listhtml">bookmark/bookmark_change_list.html</h4>
<p>Bookmark 테이블의 레코드를 변경하기 위해 기존 레코드의 리스트를 보여주는 템플릿이다.</p>
<ul>
<li>테이블에 부트스트랩 클래스 적용 ( 테이블 셀에 대한 테두리, 줄 간격, 번갈아 가면서 줄마다 음영 표시)</li>
<li>테이블 제목은 primary 색으로</li>
<li>object_list의 각 항목을 순회하면 테이블의 데이터 행 추가</li>
</ul>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}bookmark_change_list.html{% endblock %}

{% block content %}
    &lt;h1&gt;
        Bookmark Change - {{ user }}
    &lt;/h1&gt;
    &lt;br&gt;

    &lt;table class=&quot;table table-bordered table-condensed table-striped&quot;&gt;

        &lt;thead&gt;
            &lt;tr class=&quot;table-primary&quot;&gt;
                &lt;th&gt;Title&lt;/th&gt;
                &lt;th&gt;Url&lt;/th&gt;
                &lt;th&gt;Owner&lt;/th&gt;
                &lt;th&gt;Update&lt;/th&gt;
                &lt;th&gt;Delete&lt;/th&gt;
            &lt;/tr&gt;
        &lt;/thead&gt;

        &lt;tbody&gt;
            {% for item in object_list %}
            &lt;tr&gt;
                &lt;td&gt;{{ item.title }}&lt;/td&gt;
                &lt;td&gt;{{ item.url }}&lt;/td&gt;
                &lt;td&gt;{{ item.owner }}&lt;/td&gt;
                &lt;td&gt;&lt;a href=&quot;{% url &#39;bookmark:update&#39; item.id %}&quot;&gt;Update&lt;/a&gt;&lt;/td&gt;
                &lt;td&gt;&lt;a href=&quot;{% url &#39;bookmark:delete&#39; item.id %}&quot;&gt;Delete&lt;/a&gt;{&lt;/td&gt;
            &lt;/tr&gt;
            {% endfor %}
        &lt;/tbody&gt;
    &lt;/table&gt;

{% endblock %}</code></pre>
<h4 id="bookmark_confirm_delete">bookmark_confirm_delete</h4>
<p>레코드를 삭제하기 전 확인하는 화면</p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}bookmark_confirm_delete.html{% endblock %}

{% block content %}
    &lt;h1&gt;
        Bookmark Delete
    &lt;/h1&gt;
    &lt;br&gt;

    &lt;form action=&quot;.&quot; method=&quot;post&quot;&gt;
        {% csrf_token %}
        &lt;p&gt;Are you sure you want to delete &quot;{{ object }}&quot; ?&lt;/p&gt;
        &lt;input type=&quot;submit&quot; value=&quot;Confirm&quot; class=&quot;btn btn-danger btn-sm&quot; /&gt;
    &lt;/form&gt;

&lt;/div&gt;
{% endblock %}</code></pre>
<h4 id="blogpost_formhtml">blog/post_form.html</h4>
<p>Post 레코드를 생성 or 수정하기 위한 폼을 보여주는 화면</p>
<ul>
<li>slug 필드에 readonly 속성을 지정해서 사용자가 입력할 수 없도록 지정 -&gt; blog 앱이 title 필드에 입력된 단어를 사용해서 자동으로 만들어주기 때문</li>
<li>slug, descripton, tags 필드의 help_text 옵션 문구(도움말)를 출력</li>
</ul>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}
{% load widget_tweaks %}

{% block title %}post_form.html{% endblock %}

{% block content %}
    &lt;h1&gt;
        Post Create/Update - {{ user }}
    &lt;/h1&gt;
    &lt;p class=&quot;font-italic&quot;&gt;This is a creation or update form for your post.&lt;/p&gt;

    {% if form.errors %}
    &lt;div class=&quot;alert alert-danger&quot;&gt;
        &lt;div class=&quot;font-weight-bold&quot;&gt;Wrong! Please correct the error(s) below.&lt;/div&gt;
        {{ form.errors }}
    &lt;/div&gt;
    {% endif %}

    &lt;form action=&quot;.&quot; method=&quot;post&quot; class=&quot;card pt-3&quot;&gt;
        {% csrf_token %}

        &lt;div class=&quot;col-sm-5&quot;&gt;
            {{ form.title|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}

            &lt;div class=&quot;col-sm-5&quot;&gt;
                {{ form.title|add_class:&quot;form-control&quot;|attr:&quot;autofocus&quot; }}
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;div class=&quot;form-group row&quot;&gt;
            {{ form.slug|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
            &lt;div class=&quot;col-sm-5&quot;&gt;
                {{ form.slug|add_class:&quot;form-control&quot;|attr:&quot;readonly&quot; }}
            &lt;/div&gt;
            &lt;small class=&quot;font-text text-muted&quot;&gt;{{ form.slug.help_text }}&lt;/small&gt;
        &lt;/div&gt;

        &lt;div class=&quot;form-group row&quot;&gt;
            {{ form.description|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
            &lt;div class=&quot;col-sm-5&quot;&gt;
                {{ form.description|add_class:&quot;form-control&quot; }}
            &lt;/div&gt;
            &lt;small class=&quot;font-text text-muted&quot;&gt;{{ form.description.help_text }}&lt;/small&gt;
        &lt;/div&gt;

        &lt;div class=&quot;form-group row&quot;&gt;
            {{ form.content|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
            &lt;div class=&quot;col-sm-&quot;&gt;
                {{ form.content|add_class:&quot;form-control&quot; }}
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;div class=&quot;form-group row&quot;&gt;
            {{ form.tags|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
            &lt;div class=&quot;col-sm-5&quot;&gt;
                {{ form.tags|add_class:&quot;form-control&quot; }}
            &lt;/div&gt;
            &lt;small class=&quot;font-text text-muted&quot;&gt;{{ form.tags.help_text }}&lt;/small&gt;
        &lt;/div&gt;

        &lt;div class=&quot;form-group&quot;&gt;
            &lt;div class=&quot;offset-sm-2 col-sm-5&quot;&gt;
                &lt;input type=&quot;submit&quot; value=&quot;Submit&quot; class=&quot;btn btn-info&quot;/&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/form&gt;
{% endblock %}</code></pre>
<h4 id="blogpost_change_listhtml">blog/post_change_list.html</h4>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}post_change_list.html{% endblock %}

{% block content %}
    &lt;h1&gt;
        Post Change - {{ user }}
    &lt;/h1&gt;
    &lt;br&gt;

    &lt;table class=&quot;table table-bordered table-sm table-striped&quot;&gt;

        &lt;thead&gt;
            &lt;tr class=&quot;table-primary&quot;&gt;
                &lt;th&gt;Title&lt;/th&gt;
                &lt;th&gt;Description&lt;/th&gt;
                &lt;th&gt;Owner&lt;/th&gt;
                &lt;th&gt;Update&lt;/th&gt;
                &lt;th&gt;Delete&lt;/th&gt;
            &lt;/tr&gt;
        &lt;/thead&gt;

        &lt;tbody&gt;
            {% for item in object_list %}
            &lt;tr&gt;
                &lt;td&gt;{{ item.title }}&lt;/td&gt;
                &lt;td&gt;{{ item.description }}&lt;/td&gt;
                &lt;td&gt;{{ item.owner }}&lt;/td&gt;
                &lt;td&gt;&lt;a href=&quot;{% url &#39;blog:update&#39; item.id %}&quot;&gt;Update&lt;/a&gt;&lt;/td&gt;
                &lt;td&gt;&lt;a href=&quot;{% url &#39;blog:delete&#39; item.id %}&quot;&gt;Delete&lt;/a&gt;{&lt;/td&gt;
            &lt;/tr&gt;
            {% endfor %}
        &lt;/tbody&gt;
    &lt;/table&gt;

{% endblock %}</code></pre>
<h4 id="blogpost_confirm_deletehtml">blog/post_confirm_delete.html</h4>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}post_confirm_delete.html{% endblock %}

{% block content %}
    &lt;h1&gt;
        Post Delete
    &lt;/h1&gt;
    &lt;br&gt;

    &lt;form action=&quot;.&quot; method=&quot;post&quot;&gt;
        {% csrf_token %}
        &lt;p&gt;Are you sure you want to delete &quot;{{ object }}&quot; ?&lt;/p&gt;
        &lt;input type=&quot;submit&quot; value=&quot;Confirm&quot; class=&quot;btn btn-danger btn-sm&quot; /&gt;
    &lt;/form&gt;

&lt;/div&gt;
{% endblock %}</code></pre>
<h4 id="templates403html">templates/403.html</h4>
<p>OwnerOnlyMixin 클래스에서 사용하는 템플릿 파일로 403 익셉션을 발생시키면 장고의 디폴트 핸들러 중 하나인 permission_denied() 함수에서 403.html을 렌더링해서 클라이언트에게 403 응답을 보낸다. </p>
<pre><code class="language-html">{% extends &quot;base.html&quot; %}

{% block title %}403.html{% endblock %}

{% block content %}

    &lt;h1&gt;Permission Denied&lt;/h1&gt;
    &lt;br&gt;

    &lt;div class=&quot;alert alert-danger&quot;&gt;
        &lt;div class=&quot;font-weight-bold&quot;&gt;
            {{ exception }}
        &lt;/div&gt;
    &lt;/div&gt;

{% endblock %}</code></pre>
<p>{{ exception }} 컨텍스트 변수는 장고의 permission_denied() 핸들러에서 넘겨주는 템플릿 변수로 우리가 지정한 permission_denied_message 문구에 해당한다.</p>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(실전편) - 김석훈님</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[인증 기능]]></title>
            <link>https://velog.io/@jusung-c/%EC%9D%B8%EC%A6%9D-%EA%B8%B0%EB%8A%A5</link>
            <guid>https://velog.io/@jusung-c/%EC%9D%B8%EC%A6%9D-%EA%B8%B0%EB%8A%A5</guid>
            <pubDate>Tue, 14 Dec 2021 06:39:58 GMT</pubDate>
            <description><![CDATA[<p>웹 개발 시 필수 기능인 인증 기능은 일반적으로 로그인 시 username/password를 인증하는 것 외에도 로그인한 사용자에 대한 권한 부여와 웹 요청에 따른 사용자 식별, 사용자별 세션 할당 및 관리 기능 등 세션 처리 기능까지 포함된다. </p>
<h3 id="테이블-설계---장고-기본-기능-사용">테이블 설계 - 장고 기본 기능 사용</h3>
<p>장고에서 제공하는 User 테이블을 기본으로 사용한다. </p>
<ul>
<li>User 테이블 구조
<img src="https://images.velog.io/images/jusung-c/post/b8280a51-ab07-4db9-ae5b-49c2bdd29cee/image.png" alt=""></li>
</ul>
<h3 id="url-설계---장고-기본-기능-사용">URL 설계 - 장고 기본 기능 사용</h3>
<p>장고에서 제공하는 인증 기능은 URL과 뷰는 이미 개발되어 있고, 템플릿은 템플릿 파일명만 정해져 있으므로 그 템플릿 내용은 개발자가 코딩해야 한다. </p>
<p>다만 회원가입 기능은 장고에서 제공하지 않으므로 직접 코딩해준다.</p>
<p><img src="https://images.velog.io/images/jusung-c/post/4806dd69-30f7-4729-829a-d336c521f1a6/image.png" alt=""></p>
<p>아래 2개는 직접 개발해야 한다.</p>
<h3 id="settings-설정">settings 설정</h3>
<p>setitng.py 파일에 지정해야 하는 항목 3가지</p>
<ol>
<li><p>LOGIN_URL: 로그인이 필요해서 로그인 페이지로 리다이렉트시키고자 할 때 사용하는 URL로 login_required() 데코레이터를 사용해야 한다. 
default: /accounts/login/ </p>
</li>
<li><p>LOGIN_REDIRECT_URL: 장고의 LoginView 뷰는 로그인 처리가 성공한 후 next 파라미터로 지정한 URL로 리다이렉트 시킨다. next 파라미터가 지정되지 않으면 이 설정 항목에서 지정한 URL로 리다이렉트 시킨다. 
default: /accounts/profile/ </p>
</li>
<li><p>LOGOUT_REDIRECT_URL: 장고의 LogoutView 뷰는 로그아웃 처리가 성공한 후 next_page 속성으로 지정한 URL로 리다이렉트 시킨다. 없다면 이 설정 항목에서 지정한 URL로 리다이렉트 시킨다. 요청에 next 파라미터가 있으면 next에 지정한 URL이 next_page 속성으로 사용된다. </p>
</li>
</ol>
<p>위 3가지 중 LOGIN_URL은 디폴트 값을 사용할 것이고, LOGOUT_REDIRECT_URL은 사용하지 않아도 되니까 LOGIN_REDIRECT_URL만 지정해준다.</p>
<pre><code>LOGIN_REDIRECT_URL = &#39;/&#39;</code></pre><p>또 폼을 장식하는 데 유용한 django-widget-tweaks 앱을 설치하고 등록한다.</p>
<ol>
<li><p>설치
pip install django-widget-tweaks</p>
</li>
<li><p>settings.py 등록</p>
<pre><code class="language-py"> &#39;widget_tweaks&#39;,
</code></pre>
</li>
</ol>
<pre><code>
### 모델 코딩
인증 기능에 필요한 테이블은 장고에서 기본 제공하므로 코딩할 필요가 없다.

### URLconf 코딩
인증에 필요한 URL은 장고에서 기본으로 제공하기 때문에 django.contrib.auth.urls 모듈을 include() 함수로 가져와서 사용하면 된다.

BookMarkApp/urls.py
```py
    # 장고의 인증 URLconf를 가져와서 사용한다. 
    path(&#39;accounts/&#39;, include(&#39;django.contrib.auth.urls&#39;)),
    path(&#39;accounts/register/&#39;, UserCreateView.as_view(), name=&#39;register&#39;),
    path(&#39;accounts/register/done/&#39;, UserCreateDoneTV.as_view(), name=&#39;register_done&#39;),
</code></pre><h3 id="뷰-코딩">뷰 코딩</h3>
<p>LoginView 등 장고 auth 모듈에서 제공하는 뷰는 따로 코딩할 필요가 없고 가입 처리용 뷰 UserCreateView와 UserCreateDoneTV만 코딩해주면 된다.</p>
<p>BookMarkApp/views.py
<img src="https://images.velog.io/images/jusung-c/post/a8278488-31b2-4873-9fa2-8a4baaae5b0f/image.png" alt=""></p>
<h3 id="템플릿-코딩">템플릿 코딩</h3>
<h4 id="basehtml">base.html</h4>
<p>base.html에서 우상단 Username 영역을 수정해준다.</p>
<p><img src="https://images.velog.io/images/jusung-c/post/5153f0ff-4189-4809-b28b-32863df71dbd/image.png" alt=""></p>
<h4 id="loginhtml">login.html</h4>
<p><img src="https://images.velog.io/images/jusung-c/post/8202930c-a27f-43b9-b31e-af18ed038fa1/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/c7fefd2c-7337-4f74-a030-926ea413e21a/image.png" alt=""></p>
<p>{{ next }} 변수는 /accounts/login/?next=/post/3/ 처럼 로그인 URL의 쿼리 문자열로 지정된다. 만일 URL에 next 쿼리 문자열이 없으면, settigns.LOGIN_REDIRECT_URL 항목에 지정된 URL이 사용되고, 이 항목도 지정되어 있지 않으면 디폴트로 /accounts/profile/ URL로 리다이렉트된다.</p>
<h4 id="registerhtml">register.html</h4>
<p>가입 화면, 즉 사용자 계정을 생성하는 화면을 보여준다. </p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}
{% load widget_tweaks %}

{% block title %}register.html{% endblock %}

{% block content %}

    &lt;h1&gt;New User Registration&lt;/h1&gt;
    &lt;p class=&quot;font-italic&quot;&gt;Please enter your username and password twice.&lt;/p&gt;

    {% if form.errors %}
    &lt;div class=&quot;alert alert-danger&quot;&gt;
        &lt;div class=&quot;font-weight-bold&quot;&gt;Wrong! Please correct the error(s) below.&lt;/div&gt;
        {{ form.errors }}
    &lt;/div&gt;
    {% endif %}

    &lt;form action=&quot;.&quot; method=&quot;post&quot; class=&quot;card pt-3&quot;&gt;
        {% csrf_token %}
        &lt;div class=&quot;form-group row&quot;&gt;
            {{ form.username|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
            &lt;div class=&quot;col-sm-5&quot;&gt;
                {{ form.username|add_class:&quot;form-control&quot;|attr:&quot;autofocus&quot; }}
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;form-group row&quot;&gt;
            {{ form.password1|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
            &lt;div class=&quot;col-sm-5&quot;&gt;
                {{ form.password1|add_class:&quot;form-control&quot; }}
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;form-group row&quot;&gt;
            {{ form.password2|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
            &lt;div class=&quot;col-sm-5&quot;&gt;
                {{ form.password2|add_class:&quot;form-control&quot; }}
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;div class=&quot;form-group&quot;&gt;
            &lt;div class=&quot;offset-sm-2 col-sm-5&quot;&gt;
                &lt;input type=&quot;submit&quot; value=&quot;Register&quot; class=&quot;btn btn-info&quot;/&gt;
            &lt;/div&gt;
        &lt;/div&gt;

    &lt;/form&gt;

{% endblock %}

</code></pre>
<p>사용자 계정을 생성하기 위해 비밀번호를 두 번 입력할 수 있도록 password1 및 password2 입력 요소가 있다는 점 정도만 빼면 login.html과 거의 같다. </p>
<p>한가지 중요한 차이점은 login.html 파일에서는 AuthenticationForm을 사용했지만 이번 register.html 파일의 form 변수는 UserCreationForm 객체라는 점이다. UserCreationForm 폼도 장고에서 기본으로 제공한다. </p>
<blockquote>
<p>템플릿 파일에서 장고의 폼을 장식하려 할 때 django-widget-tweaks 앱을 사용하면 HTML 태그의 요소들을 직접 다룰 수 있어서 편리하다.</p>
</blockquote>
<h4 id="register_donehtml">register_done.html</h4>
<p>가입 처리가 성공한 후에 보여주는 화면</p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}register_done.html{% endblock %}

{% block content %}

    &lt;h1&gt;Registration Completed Successfully&lt;/h1&gt;
    &lt;br&gt;

    &lt;p&gt;Thank you for registering.&lt;/p&gt;

    &lt;p class=&quot;font-italic&quot;&gt;&lt;a href=&quot;{% url &#39;login&#39; %}&quot;&gt;Log in again&lt;/a&gt;&lt;/p&gt;

{% endblock %}

</code></pre>
<h4 id="password_change_formhtml">password_change_form.html</h4>
<p>비밀번호를 변경하기 위한 화면</p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}
{% load widget_tweaks%}

{% block title %}password_change_form.html{% endblock %}

{% block content %}

    &lt;h1&gt;{{ title }}&lt;/h1&gt;
    &lt;p class=&quot;font-italic&quot;&gt;Please enter your old password for security&#39;s sake,
    and then enter your new password twice.&lt;/p&gt;

    {% if form.errors %}
    &lt;div class=&quot;alert alert-danger&quot;&gt;
        &lt;div class=&quot;font-weight-bold&quot;&gt;Wrong! Please correct the error(s) below.&lt;/div&gt;
        {{ form.errors }}
    &lt;/div&gt;
    {% endif %}

    &lt;form action=&quot;.&quot; method=&quot;post&quot; class=&quot;card pt-3&quot;&gt;
        {% csrf_token %}
        &lt;div class=&quot;form-group row&quot;&gt;
            {{ form.old_password|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
            &lt;div class=&quot;col-sm-5&quot;&gt;
                {{ form.old_password|add_class:&quot;form-control&quot;|attr:&quot;autofocus&quot; }}
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;form-group row&quot;&gt;
            {{ form.new_password1|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
            &lt;div class=&quot;col-sm-5&quot;&gt;
                {{ form.new_password1|add_class:&quot;form-control&quot; }}
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;form-group row&quot;&gt;
            {{ form.new_password2|add_label_class:&quot;col-form-label col-sm-2 ml-3 font-weight-bold&quot; }}
            &lt;div class=&quot;col-sm-5&quot;&gt;
                {{ form.new_password2|add_class:&quot;form-control&quot; }}
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;div class=&quot;form-group&quot;&gt;
            &lt;div class=&quot;offset-sm-2 col-sm-5&quot;&gt;
                &lt;input type=&quot;submit&quot; value=&quot;Password change&quot; class=&quot;btn btn-info&quot;/&gt;
            &lt;/div&gt;
        &lt;/div&gt;

    &lt;/form&gt;


{% endblock %}
</code></pre>
<ul>
<li>PasswordChangeView 뷰에서 title=&#39;Password change&#39;라는 컨텍스 변수를 넘겨줌</li>
<li>form 변수는 장고에서 기본 제공하는 PasswordChangeForm 객체이다.</li>
</ul>
<h4 id="password_change_donehtml">password_change_done.html</h4>
<p>비밀번호 변경 처리가 성공한 후에 보여주는 화면</p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}password_change_done.html{% endblock %}

{% block content %}

    &lt;h1&gt;{{ title }}&lt;/h1&gt;
    &lt;br&gt;

    &lt;p&gt;Your password was changed&lt;/p&gt;

{% endblock %}</code></pre>
<h4 id="logged_outhtml">logged_out.html</h4>
<p>로그아웃 처리가 성공한 후 나타나는 화면</p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}logged_out.html{% endblock %}

{% block content %}

    &lt;h1&gt;Logged out&lt;/h1&gt;
    &lt;br&gt;

    &lt;div&gt;
        &lt;i class=&quot;fas fa-quote-left&quot;&gt;&lt;/i&gt;
        &lt;span class=&quot;h6&quot;&gt;&amp;ensp;Thanks for spending 
            your quality time with this web site today.&amp;ensp;&lt;/span&gt;
        &lt;i class=&quot;fas fa-quote-right&quot;&gt;&lt;/i&gt;
    &lt;/div&gt;

    &lt;p class=&quot;font-italic&quot;&gt;&lt;a href=&quot;{% url &#39;login&#39; %}&quot;&gt;Log in again&lt;/a&gt;&lt;/p&gt;

{% endblock %}

</code></pre>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(실전편) - 김석훈님</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Photo 앱]]></title>
            <link>https://velog.io/@jusung-c/Photo-%EC%95%B1</link>
            <guid>https://velog.io/@jusung-c/Photo-%EC%95%B1</guid>
            <pubDate>Thu, 09 Dec 2021 12:25:56 GMT</pubDate>
            <description><![CDATA[<p>사진들을 앨범으로 그룹화해 관리하고 각 사진에 대한 정보를 등록하고 열람할 수 있는 Photo 앱 개발</p>
<p>사진의 썸네일을 처리하기 위해 새로운 커스텀 필드가 필요하므로, Pillow 라이브러리를 활용해서 커스텀 필드를 작성해야 한다. </p>
<h3 id="설계">설계</h3>
<h4 id="테이블-설계">테이블 설계</h4>
<ol>
<li><p>Album 테이블
<img src="https://images.velog.io/images/jusung-c/post/52930a97-a4cf-4ae8-900b-2c7a7495ba74/image.png" alt=""></p>
</li>
<li><p>Photo 테이블
<img src="https://images.velog.io/images/jusung-c/post/1e0d446b-1871-4bb2-b7b0-efe4df5e0d26/image.png" alt=""></p>
</li>
</ol>
<p>하나의 앨범은 여러 개의 사진을 가질 수 있고, 하나의 사진은 하나의 앨범에만 속할 수 있으므로 다음 관계가 성립한다.</p>
<p><strong>Album 테이블- 1 : N -Photo 테이블</strong></p>
<h4 id="url-설계">URL 설계</h4>
<p><img src="https://images.velog.io/images/jusung-c/post/82556d1f-ddac-419b-abf7-80277e9e199a/image.png" alt=""></p>
<h3 id="포토앱-틀잡기">포토앱 틀잡기</h3>
<h4 id="포토앱-생성">포토앱 생성</h4>
<pre><code>python manage.py startapp photo</code></pre><h4 id="installed_apps-등록">INSTALLED_APPS 등록</h4>
<pre><code class="language-py">    &#39;photo.apps.PhotoConfig&#39;,
</code></pre>
<h3 id="model-코딩">Model 코딩</h3>
<p>models.py
<img src="https://images.velog.io/images/jusung-c/post/d32e6749-c7ce-46b1-b7fb-201ca2ae2b6f/image.png" alt=""></p>
<h3 id="adminpy">admin.py</h3>
<ol>
<li>정의한 테이블이 Admin 사이트에 보이도록 admin.py 파일 </li>
<li>Admin 사이트의 모습을 정의하는 AlbumAdmin, PhotoAdmin 클래스 정의</li>
</ol>
<p>외래 키로 연결된 Album, Photo 테이블 간에는 1:N 관계이므로 앨범 객체를 보여줄 때 객체에 연결된 사진 객체들을 같이 보여준다. 같이 보여주는 형식은 세로로 나열되는 형식인 Stackedlnline과 테이블 모양처럼 행으로 나열되는 형식인 Tabularlnline이 있다.</p>
<p><img src="https://images.velog.io/images/jusung-c/post/b841d747-6f48-45d5-aa09-e8db91698c8c/image.png" alt=""></p>
<h3 id="fieldspy">fields.py</h3>
<p>사진을 저장하기 위한 필드인 ThumbnailImageField 커스텀 필드 작성</p>
<p>커스텀 필드를 작성할 때는 기존의 비슷한 필드를 상속받아 작성하는 것이 보통으로 이미지 관련 커스텀 필드는 ImageField 클래스를 상속받아 작성한다. ImageField 필드는 이미지 파일을 파일 시스템에 쓰고 삭제하는 작업이 필요하므로, ImageFieldFile 클래스가 필요하고 두 클래스를 연계시켜주는 코드도 필요하다.</p>
<p><img src="https://images.velog.io/images/jusung-c/post/ea00b5ce-11de-4cc5-abdf-e5d5b8c64330/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/14ed17d6-3bd7-49c3-a324-8a587d19dd74/image.png" alt=""></p>
<h4 id="db에-반영">DB에 반영</h4>
<p>두 개의 테이블을 신규로 정의했으니까 DB에 반영해준다.</p>
<p><img src="https://images.velog.io/images/jusung-c/post/f5947957-cb09-410c-9dee-e0dd7aea01c7/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/e5286215-aadf-422e-80c3-feb735c89279/image.png" alt=""></p>
<p>admin.py 파일에 정의된 StackedInline 설정에 따라, 입력 양식이 세로로 추가되는 방식으로 extra 설정에 따라 앨범 하나에 사진 2개를 입력할 수 있는 형식이다.</p>
<h3 id="urlconf-코딩">URLconf 코딩</h3>
<h4 id="bookmarkappurlspy">BookMarkApp/urls.py</h4>
<ol>
<li>/photo/ URL 요청이 오면, 포토 앱의 APP_URLCONF에 처리를 위임하도록</li>
<li>기존 URl 패턴에 static(정적 파일을 처리하기 위해 그에 맞는 URL 패턴을 반환하는 함수) 함수가 반환하는 URL 패턴 추가</li>
</ol>
<pre><code class="language-py">urlpatterns = [
    path(&#39;&#39;, HomeView.as_view(), name=&#39;home&#39;),
    path(&#39;admin/&#39;, admin.site.urls),
    path(&#39;bookmark/&#39;, include(&#39;bookmark.urls&#39;)),
    path(&#39;blog/&#39;, include(&#39;blog.urls&#39;)),
    path(&#39;photo/&#39;, include(&#39;photo.urls&#39;)),
] + static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT) 
</code></pre>
<p>setting.MEDIA_URL로 정의된 /media/ URL 요청이 오면 django.views.static.serve() 뷰 함수가 처리하고, 이 뷰 함수에 document_root = settings.MEDIA_ROOT 키워드 인자가 전달된다. </p>
<p>static.serve() 함수는 개발용이고 상용에는 httpd, nginx 등의 웹 서버 프로그램을 사용한다.</p>
<h4 id="photourlspy">photo/urls.py</h4>
<pre><code class="language-py">app_name = &#39;photo&#39;

urlpatterns = [
    # /photo/
    path(&#39;&#39;, views.AlbumLV.as_view(), name=&#39;index&#39;),

    # /photo/album/
    path(&#39;album/&#39;, views.AlbumLV.as_view(), name=&#39;album_list&#39;),

    # /photo/album/99
    path(&#39;album/&lt;int:pk&gt;/&#39;, views.AlbumDV.as_view(), name=&#39;album_detail&#39;),

    # /photo/photo/99/
    path(&#39;photo/&lt;int:pk&gt;/&#39;, views.PhotoDV.as_view(), name=&#39;photo_detail&#39;),
]</code></pre>
<h3 id="view-코딩">View 코딩</h3>
<pre><code class="language-py">class AlbumLV(ListView):
    model = Album

class AlbumDV(DetailView):
    model = Album

class PhotoDV(DetailView):
    model = Photo</code></pre>
<h3 id="템플릿-코딩">템플릿 코딩</h3>
<p>뷰에서 템플릿 파일명을 지정하지 않았으므로 디폴트 템플릿 명인 album_list.html, album_detail.html, photo_detail.html을 작성한다.</p>
<h4 id="album_listhtml">album_list.html</h4>
<p>각 앨범에 대한 앨범 정보와 앨범에 속한 사진 5개를 보여주는 화면</p>
<p><img src="https://images.velog.io/images/jusung-c/post/64e186b6-42b6-403c-a6c4-605ee1b017e5/image.png" alt=""></p>
<h4 id="album_detailhtml">album_detail.html</h4>
<p>특정 앨범에 들어 있는 사진을 모두 출력하는 템플릿</p>
<p><img src="https://images.velog.io/images/jusung-c/post/0ade7703-a91d-46e7-8e18-378d401f36d3/image.png" alt=""></p>
<h4 id="photo_detailhtml">photo_detail.html</h4>
<p>특정 사진에 대한 정보를 출력하는 화면</p>
<pre><code class="language-html">{% extends &quot;base.html&quot; %}

{% block title %}photo_detail.html{% endblock %}

{% block content %}
    &lt;h2 class=&quot;mt-5&quot;&gt;{{ object.title }}&lt;/h2&gt;

    &lt;div class=&quot;row&quot;&gt;
          &lt;-- 원본 이미지 --&gt;
        &lt;div class=&quot;col-md-9&quot;&gt;
            &lt;a href=&quot;{{ object.image.url }}&quot;&gt;
                &lt;img src=&quot;{{ object.image.url }}&quot; style=&quot;width: 100%;&quot;&gt;
            &lt;/a&gt;
        &lt;/div&gt;

        &lt;ul class=&quot;col-md-3 mt-3&quot;&gt;
            &lt;li class=&quot;h5&quot;&gt;Photo Description&lt;/li&gt;
                {% if object.description %}
                    &lt;p&gt;{{ object.description|linebreaks }}&lt;/p&gt;
                {% else %}
                    &lt;p&gt;(blank)&lt;/p&gt;
                {% endif %}

            &lt;li class=&quot;h5&quot;&gt;Date Uploaded&lt;/li&gt;
                &lt;p class=&quot;font-italic&quot;&gt;{{ object.upload_dt }}&lt;/p&gt;
            &lt;li class=&quot;h5&quot;&gt;Album Name&lt;/li&gt;
                &lt;p class=&quot;font-italic&quot;&gt;
                    &lt;a href=&quot;{% url &#39;photo:album_detail&#39; object.album.id %}&quot;&gt;
                        {{ object.album.name }}
                    &lt;/a&gt;
                &lt;/p&gt;
        &lt;/ul&gt;
    &lt;/div&gt;

{% endblock %}</code></pre>
<h4 id="basehtml-수정">base.html 수정</h4>
<p>photo:index로 향하는 url 추가</p>
<pre><code>&lt;li class=&quot;nav-item mx-1 btn btn-primary&quot;&gt;
                        &lt;a class=&quot;nav-link&quot; text-white href=&quot;{% url &#39;photo:index&#39; %}&quot;&gt;Photo&lt;/a&gt;
                    &lt;/li&gt;</code></pre><h2 id="썸네일-만들기-수정">썸네일 만들기 수정</h2>
<p>기존에 PIL을 이용해 썸네일을 만들었는데 django-imagekit을 이용하면 더 간단하게 만들 수 있다.</p>
<ol>
<li><p>pip install django-imagekit</p>
</li>
<li><p>settings.py에 imagekit 추가</p>
</li>
<li><p>models.py 수정</p>
<pre><code class="language-py">class Photo(models.Model):
 album = models.ForeignKey(Album, on_delete=models.CASCADE)
 title = models.CharField(&#39;TITLE&#39;, max_length=30)
 description = models.TextField(&#39;Photo Description&#39;, blank=True)
 image = models.ImageField(upload_to=&#39;photo/%Y/%m&#39;)
 image_thumbnail = ImageSpecField(source=&#39;image&#39;,
                                  processors=[ResizeToFill(1024,1024)],
                                  format=&#39;JPEG&#39;)
 upload_dt = models.DateTimeField(&#39;Upload Date&#39;, auto_now_add=True)

 class Meta:
     ordering = (&#39;title&#39;,)

 def __str__(self):
     return self.title

 def get_absolute_url(self):
     return reverse(&#39;photo:photo_detail&#39;, args=(self.id,))</code></pre>
</li>
</ol>
<p>ResizeToFill의 값을 높여주니까 썸네일의 화질이 좋아지는 것을 발견!
대신 로딩 속도가 좀 느려진 것 같기도..?</p>
<ol start="4">
<li>template에서 photo.image_thumbnail.url로 사용</li>
</ol>
<p><img src="https://images.velog.io/images/jusung-c/post/483116dd-c769-4b9a-a656-ba08214cee1d/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/1efb9985-cb6e-4b4e-b00e-d8c394f4fef1/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/ab8c6f91-0494-4fb3-82c9-26486181affa/image.png" alt=""></p>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(실전편) - 김석훈님
        <a href="https://sss20-02.tistory.com/40">https://sss20-02.tistory.com/40</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Blog 앱 - 검색 기능]]></title>
            <link>https://velog.io/@jusung-c/Blog-%EC%95%B1-%EA%B2%80%EC%83%89-%EA%B8%B0%EB%8A%A5</link>
            <guid>https://velog.io/@jusung-c/Blog-%EC%95%B1-%EA%B2%80%EC%83%89-%EA%B8%B0%EB%8A%A5</guid>
            <pubDate>Tue, 30 Nov 2021 10:18:46 GMT</pubDate>
            <description><![CDATA[<p>검색 기능도 블로그 앱에서 자주 쓰는 기능이므로 오픈 소스로 제공하는 패키지가 많다. 구글 검색 기능을 제공해주는 패키지도 있고, AJAX 기능으로 검색해주는 패키지도 있다.</p>
<p>블로그 앱 내에서의 검색 기능 정도는 장고 자체의 Q-객체(테이블에 대한 복잡한 쿼리를 처리하기 위한 객체)를 이용하면 간단히 구현할 수 있다. </p>
<ol>
<li>검색 기능을 위해 검색 단어를 입력받는 폼 기능</li>
<li>Q-객체를 사용해 검색 단어가 들어 있는 블로그를 찾고 그 결과를 보여주는 기능</li>
</ol>
<h3 id="url-설계">URL 설계</h3>
<p>검색 폼 처리를 위한 URL 하나를 추가한다.</p>
<p>URL 패턴: /blog/search/
뷰 이름: SearchFormView(FormView)
템플릿 파일명: post_search.html</p>
<pre><code class="language-py">    # /blog/search/
    path(&#39;search/&#39;, views.SearchFormView.as_view(), name=&#39;search&#39;),
</code></pre>
<h3 id="view-코딩">View 코딩</h3>
<p>검색 기능을 제공하기 위해서는 검색 폼을 보여줘야 하고, 검색 폼의 데이터가 제출되어야 뷰가 처리하는 순서이믜로 폼을 먼저 만들어야 한다.</p>
<p>forms.py</p>
<pre><code class="language-py">from django import forms

class PostSearchForm(forms.Form):
    search_word = forms.CharField(label=&#39;Search Word&#39;)</code></pre>
<p>폼을 정의하는 방법은 테이블의 모델 클래스를 정의하는 방법과 유사하다. CharField 필드는 TextInput 위젯으로 표현되고, label 인자인 Search Word는 폼 위젯 앞에 출력되는 레이블이 되고, 변수 search_word는 input 태그에 대한 name 속성이 되어 사용자가 입력한 값을 저장하는 데 사용된다.</p>
<p>views.py
<img src="https://images.velog.io/images/jusung-c/post/f78363d0-27ea-40c4-9a1a-f5e32532f225/image.png" alt=""></p>
<h3 id="template-코딩">Template 코딩</h3>
<p>UI 상단 [Util &gt; Search] 메뉴에 링크를 달아주고 검색 폼과 검색 결과를 보여줄 수 있도록 한다.</p>
<p><img src="https://images.velog.io/images/jusung-c/post/5b7ebe0d-5606-4739-987a-48c80c0ba673/image.png" alt=""></p>
<p>base.html 수정</p>
<pre><code class="language-html"> &lt;a class=&quot;dropdown-item&quot; href=&quot;{% url &#39;blog:search&#39; %}&quot;&gt;Search&lt;/a&gt;</code></pre>
<p>post_search.html
<img src="https://images.velog.io/images/jusung-c/post/ff36cb0e-fa61-490a-96de-36073e77c765/image.png" alt=""></p>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(실전편) - 김석훈님</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Blog 앱 - 댓글]]></title>
            <link>https://velog.io/@jusung-c/Blog-%EC%95%B1-%EB%8C%93%EA%B8%80</link>
            <guid>https://velog.io/@jusung-c/Blog-%EC%95%B1-%EB%8C%93%EA%B8%80</guid>
            <pubDate>Sun, 28 Nov 2021 14:40:44 GMT</pubDate>
            <description><![CDATA[<p>유연하고 확장성 높은 오픈소스 플랫폼 Disqus 앱(DISQUS에서 제공하는 자바스크립트 코드)를 사용한다.</p>
<p>DISQUS 플랫폼을 사용하기 위해서는 DISQUS 홈페이지에서 댓글을 사용할 우리 프로젝트에 대해 필요한 사항을 설정해줘야 한다.</p>
<p>disqus 앱은 DISQUS 플랫폼과의 연동을 통해 즉, DISQUS 사이트에서 제공하는 웹 서비스를 통해 댓글 기능을 제공한다. 따라서 일반적인 앱 개발 외에도 DISQUS 사이트 접속에 필요한 계정과 웹 서비스 연동을 위한 설정이 필요하다. </p>
<h4 id="url-설계">URL 설계</h4>
<p>댓글 처리는 Disqus 앱에서 js로 처리하기 때문에 댓글관련 URL 변경 사항은 없다.</p>
<h3 id="disqus-홈페이지-설정"><a href="https://disqus.com/">DISQUS 홈페이지</a> 설정</h3>
<p>[GET STARTED] - [I wnat to install Disqus on my site] </p>
<h3 id="settingpy에-disqus-설정-등록">setting.py에 DISQUS 설정 등록</h3>
<pre><code class="language-py">DISQUS_SHORTNAME = &#39;pydjango-web-programming-535687cqtc&#39;
DISQUS_MY_DOMAIN = &#39;http://127.0.0.1:8000/&#39;</code></pre>
<h3 id="뷰-코딩">뷰 코딩</h3>
<p>Disqus 앱은 템플릿 파일에서 js 코드로 실행된다. 그래서 js 코드에 필요한 항목들을 뷰에서 컨텍스트 변수로 만들어서 템플릿 파일로 넘겨준다.</p>
<p>blog/views.py의 PostDV 클래스를 수정한다.</p>
<p><img src="https://images.velog.io/images/jusung-c/post/d1a31d66-197b-4165-b044-1a8afb7a7c74/image.png" alt=""></p>
<h3 id="템플릿-코딩">템플릿 코딩</h3>
<p>Disqus js 앱을 템플릿 파일에 넣어주는 작업으로 DISQUS 애플리케이션 로컬 서버에 댓글을 저장하는 것이 아니라, DISQUS 플랫폼에 저장된 댓글을 가져와서 브라우저 화면에 보여주는 기능이 주된 역할이기 때문이다. js Disqus 앱이 이런 역할을 수행하므로 우리는 Disqus 앱을 템플릿 파일에 정확하게 사용하면 그만이다.</p>
<p>post_detail.html 수정</p>
<p>content block에 다음 코드 추가</p>
<pre><code class="language-py">        &lt;div id=&quot;disqus_thread&quot;&gt;&lt;/div&gt;</code></pre>
<p>Disqus 앱의 js 부분은 extra-script 블록 내에 정의한다. 이 부분의 js 코드가 DISQUS 플랫폼의 pydjango-web-programming.disqus.com 사이트에 저장된 댓글을 가져와서 보여주는 역할을 한다.</p>
<p>extra-script block 코딩 -&gt; base.html에 블록지정 안해뒀더니 댓글창 계속 안나와서 고생했다ㅠㅠ 바보같다ㅠ</p>
<p><img src="https://images.velog.io/images/jusung-c/post/5fb8288b-d279-47b1-95d7-59d372037da1/image.png" alt=""></p>
<p>Disqus 앱에서 보여주는 댓글은 글자 크기를 조정할 수 없으므로 본문과 댓글의 글자 크기가 어울리지 않으면 본문의 글자 크기를 변경해야 한다. Disqus 댓글과 어울리는 글자 크기는 16pt이므로 extra-style 블록에 post-body 스타일을 추가해서 본문을 수정해준다.</p>
<pre><code class="language-css">{% block extra-style %}
    &lt;style&gt;
        .post-body {
            width: 80%;
            margin: auto;
            font-family: &quot;Lucida Grande&quot;, Verdana, Arial, sans-serif;
            font-size: 16px;
        }
    &lt;/style&gt;
{% endblock extra-style %}</code></pre>
<p><img src="https://images.velog.io/images/jusung-c/post/bc04c30c-26ce-489c-9a97-5f9da78de6dd/image.png" alt=""></p>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(실전편) - 김석훈님</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Blog 앱 - Tag 달기]]></title>
            <link>https://velog.io/@jusung-c/Blog-%EC%95%B1-Tag-%EB%8B%AC%EA%B8%B0</link>
            <guid>https://velog.io/@jusung-c/Blog-%EC%95%B1-Tag-%EB%8B%AC%EA%B8%B0</guid>
            <pubDate>Sat, 27 Nov 2021 14:22:06 GMT</pubDate>
            <description><![CDATA[<p>각 포스트마다 태그를 달 수 있는 기능으로 태그를 달고 태그별로 포스트의 리스트를 보여주며 태그 클라우드를 만든다. 태그 기능을 제공하는 많은 오픈소스 중에 django-taggit 패키지를 이용할 것이고, 탬플릿 태그 및 태그 클라우드 기능이 추가된 django-taggit-templatetag2 패키지도 같이 사용할 것이다.</p>
<h2 id="설계">설계</h2>
<h3 id="ui">UI</h3>
<p>기존 포스트 상세 화면은 수정을, 태그와 관련된 2개의 화면은 신규로 추가할 것이다.
<img src="https://images.velog.io/images/jusung-c/post/6708bf82-8d64-4669-9f02-c3e6eb9d66cc/image.png" alt=""></p>
<h3 id="테이블-설계">테이블 설계</h3>
<p>이미 개발된 Post 테이브에 태그 기능을 위한 필드 하나를 추가한다.</p>
<ul>
<li>필드명: tags</li>
<li>타입: TaggableManager</li>
<li>제약 조건: Blank, Null</li>
</ul>
<h3 id="url-설계">URL 설계</h3>
<ol>
<li><p>태그 클라우드를 보기 위한 URL</p>
<pre><code> - URL: /blog/tag/
 - 뷰 이름: TagCloudTV(TemplateView)
 - 템플릿 파일명: taggit_cloud.html</code></pre></li>
<li><p>특정 태그가 달려 있는 포스트들의 리스트를 보여주는 URL</p>
<pre><code> - URL: /blog/tag/tagname/
 - 뷰 이름: TaggedObjectLV(ListView)
 - 템플릿 파일명: taggit_post_list.html</code></pre></li>
</ol>
<h2 id="개발">개발</h2>
<h3 id="taggit-설치">taggit 설치</h3>
<pre><code>pip install django-taggit
pip install django-taggit-templatetags</code></pre><h4 id="설정-파일-등록">설정 파일 등록</h4>
<pre><code class="language-py">    &#39;taggit.apps.TaggitAppConfig&#39;,
    &#39;taggit_templatetags2&#39;,</code></pre>
<p>추가 설정</p>
<pre><code class="language-py"># 태그 이름에 대소문자 구분하지 않는다는 뜻
TAGGIT_CASE_INSENSITIVE = True

# 태그 클라우드에 나타나는 태그의 최대 개수
TAGGIT_LIMIT = 50</code></pre>
<h3 id="모델-코딩">모델 코딩</h3>
<p>모델에 tag 필드 추가</p>
<pre><code>    tags = TaggableManager(blank=True)
</code></pre><p>TaggableManager 매니저는 ManyToManyField 및 models.Manager 역할을 동시에 한다. (Tags 별칭, null=True 옵션 디폴트)</p>
<h4 id="admin에-노출">admin에 노출</h4>
<p>포스트별로 태그 이름이 admin 화면에 노출되도록 한다.</p>
<p>admin.py
<img src="https://images.velog.io/images/jusung-c/post/73700829-deb2-48eb-aef2-6158fb50594c/image.png" alt=""></p>
<h4 id="post-테이블-정의가-변경됐으므로-db에-반영">Post 테이블 정의가 변경됐으므로 DB에 반영</h4>
<h3 id="urlconf-코딩">URLconf 코딩</h3>
<ol>
<li>태그 클라우드를 보기 위한 URL</li>
<li>특정 태그가 달려 있는 포스트들의 리스트를 보여주는 URL</li>
</ol>
<pre><code class="language-py">    # /blog/tag/
    path(&#39;tag/&#39;, views.TagCloudTV.as_view(), name=&#39;tag_cloud&#39;),

    # /blog/tag/tagname/
    path(&#39;tag/&lt;str:tag&gt;/&#39;, views.TaggedObjectLV.as_view(), name=&#39;tagged_object_list&#39;),
</code></pre>
<h3 id="view-코딩">View 코딩</h3>
<p><img src="https://images.velog.io/images/jusung-c/post/f6583fe9-d654-45f1-b419-add119f131c4/image.png" alt=""></p>
<h3 id="템플릿-코딩">템플릿 코딩</h3>
<h4 id="1-post_detailhtml-수정">1. post_detail.html 수정</h4>
<p>태그를 클릭해 taggit_post_list.html 화면을 보여줄 수 있도록.</p>
<p><img src="https://images.velog.io/images/jusung-c/post/7aa0ed39-83f5-4a14-b9be-00f6e79711de/image.png" alt=""></p>
<h4 id="2-taggit_cloudhtml">2. taggit_cloud.html</h4>
<p>태그 클라우드: 태그들에게 가중치를 부여해 위치나 글자 크기 등을 강조함으로써 태그들의 리스트를 효과적으로 시각화한 것</p>
<p>위치: BookMarkApp\blog\templates\taggit\taggit_cloud.html</p>
<p><img src="https://images.velog.io/images/jusung-c/post/bb291cf1-46c2-4201-b4b5-2ddf2c01edf1/image.png" alt=""></p>
<h4 id="3-taggit_post_listhtml">3. taggit_post_list.html</h4>
<p>태그 클라우드에서 특정 태그를 클릭했을 때, 그 태그가 걸려 있는 포스트들의 리스트를 보여주도록 코딩</p>
<p>위치: BookMarkApp\blog\templates\taggit\taggit_post_list.html</p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}taggit_post_list.html{% endblock %}

{% block content %}
    &lt;h1&gt;Post for tag - {{ tagname }}&lt;/h1&gt;
    &lt;br&gt;

    {% for post in object_list %}
        &lt;h2&gt;&lt;a href=&quot;{{ post.get_absolute_url }}&quot;&gt;
            {{ post.title }}
        &lt;/a&gt;&lt;/h2&gt;
        {{ post.modify_dt|date:&quot;N d, Y&quot; }}
        &lt;p&gt;{{ post.description }}&lt;/p&gt;
    {% endfor %}
{% endblock %}</code></pre>
<p>object_list는 TaggedObjectLV 뷰에서 넘겨주는 컨텍스트 변수로 특정 tag가 달려있는 Post 리스트가 담겨있다.</p>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(실전편) - 김석훈님</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 첫 페이지 설계]]></title>
            <link>https://velog.io/@jusung-c/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B2%AB-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@jusung-c/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B2%AB-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Mon, 22 Nov 2021 10:32:05 GMT</pubDate>
            <description><![CDATA[<h3 id="gnbglobal-navigation-bar">GNB(Global Navigation Bar)</h3>
<p><img src="https://images.velog.io/images/jusung-c/post/97032e79-f0b1-49fa-9468-58f15979a994/image.png" alt=""></p>
<p>메인 메뉴와 본문, 바닥글로 이루어져 있다. </p>
<h3 id="urlconf">URLconf</h3>
<p>첫 페이지 설계이므로 URLconf에서 루트 URL 처리 로직만 설계하면 된다.</p>
<pre><code class="language-py">path(&#39;/&#39;, HomeView.as_view(), name=&#39;home&#39;),</code></pre>
<h3 id="view">View</h3>
<p>HomeView 코딩
애플리케이션이 아닌 프로젝트와 관련된 뷰이므로 BookMarkApp/views.py을 새로 만들어 코딩한다.</p>
<pre><code class="language-py">from django.views.generic import TemplateView


class HomeView(TemplateView):
    template_name = &#39;home.html&#39;</code></pre>
<h3 id="templates">Templates</h3>
<ul>
<li>프로젝트 화면의 전체 윤곽</li>
<li>애플리케이션별 바로가기 링크 및 메뉴</li>
<li>템플릿 상속 기능 구현</li>
<li>부트스트랩 사용</li>
</ul>
<h4 id="basehtml">base.html</h4>
<p><img src="https://images.velog.io/images/jusung-c/post/3cf14f74-deb3-47c2-9cf0-cc7e5eb7011f/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/d3c8d0ce-74b2-4eda-8479-643878448144/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/ac510db2-788d-4b06-a628-6e8664c37bde/image.png" alt=""></p>
<p>[ 코드 ]</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&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&quot;&gt;

    &lt;title&gt;{% block title %}Django Web Programming{% endblock %}&lt;/title&gt;

    &lt;!--    부트스트랩 CDN
    intergrity, crossorigin 속성은 보안을 위한 속성인데 편의상 제거    --&gt;
    &lt;link href=&quot;https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css&quot;
          rel=&quot;stylesheet&quot;&gt;

    {% block extra-style %}{% endblock %}

&lt;/head&gt;

&lt;body&gt;
    &lt;nav class=&quot;navbar navbar-expand-lg navbar-dark bg-dark fixed top&quot;&gt;
        &lt;div class=&quot;container-fluid&quot;&gt;
            &lt;span class=&quot;navbar-brand mx-5 mb-0 font-weight-bold font-italic&quot;&gt;Django - Python Web Programming&lt;/span&gt;
            &lt;button class=&quot;navbar-toggler&quot; type=&quot;button&quot; data-bs-toggle=&quot;collapse&quot; data-bs-target=&quot;#navbarSupportedContent&quot;&gt;
                &lt;span class=&quot;navbar-toggler-icon&quot;&gt;&lt;/span&gt;
            &lt;/button&gt;

            &lt;div class=&quot;collapse navbar-collapse&quot; id=&quot;navbarSupportedContent&quot;&gt;
                &lt;ul class=&quot;navbar-nav me-auto&quot;&gt;
                    &lt;li class=&quot;nav-item mx-1 btn btn-primary&quot;&gt;
                        &lt;a class=&quot;nav-link&quot; href=&quot;{% url &#39;home&#39;%}&quot;&gt;Home&lt;/a&gt;
                    &lt;/li&gt;
                    &lt;li class=&quot;nav-item mx-1 btn btn-primary&quot;&gt;
                        &lt;a class=&quot;nav-link&quot; href=&quot;{% url &#39;bookmark:index&#39;%}&quot;&gt;Bookmark&lt;/a&gt;
                    &lt;/li&gt;
                    &lt;li class=&quot;nav-item mx-1 btn btn-primary&quot;&gt;
                        &lt;a class=&quot;nav-link&quot; href=&quot;{% url &#39;blog:index&#39;%}&quot;&gt;Blog&lt;/a&gt;
                    &lt;/li&gt;
                    &lt;li class=&quot;nav-item mx-1 btn btn-primary&quot;&gt;
                        &lt;a class=&quot;nav-link&quot; href=&quot;#&quot;&gt;Photo&lt;/a&gt;
                    &lt;/li&gt;

                    &lt;li class=&quot;nav-item dropdown mx-1 btn btn-primary&quot;&gt;
                        &lt;a class=&quot;nav-link dropdown-toggle text-white&quot; href=&quot;#&quot; data-bs-toggle=&quot;dropdown&quot;&gt;Util&lt;/a&gt;
                        &lt;div class=&quot;dropdown-menu&quot;&gt;
                            &lt;a class=&quot;dropdown-item&quot; href=&quot;{% url &#39;admin:index&#39; %}&quot;&gt;Admin&lt;/a&gt;
                            &lt;div class=&quot;dropdown-divider&quot;&gt;&lt;/div&gt;
                            &lt;a class=&quot;dropdown-item&quot; href=&quot;{% url &#39;blog:post_archive&#39; %}&quot;&gt;Archive&lt;/a&gt;
                            &lt;a class=&quot;dropdown-item&quot; href=&quot;&quot;&gt;Search&lt;/a&gt;
                        &lt;/div&gt;
                    &lt;/li&gt;
                &lt;/ul&gt;
                &lt;form class=&quot;form-inline my-2&quot; action=&quot;&quot; method=&quot;post&quot;&gt;
                    {% csrf_token %}
                    &lt;input class=&quot;form-control mr-sm-2&quot; type=&quot;search&quot; placeholder=&quot;global search&quot; name=&quot;search_word&quot;&gt;
                &lt;/form&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/nav&gt;

    &lt;div class=&quot;container&quot;&gt;
        {% block content %}{% endblock %}
    &lt;/div&gt;

    {% block footer %}{% endblock %}

    &lt;!--    부트스트랩 CDN    --&gt;
    &lt;script src=&quot;https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js&quot; integrity=&quot;sha384-7+zCNj/IqJ95wo16oMtfsKbZ9ccEh31eOz1HGyDuCQ6wgnyJNSYdrPa03rtR1zdB&quot; crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js&quot; integrity=&quot;sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13&quot; crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;

    &lt;!--    FontAwesome CDN     --&gt;
    &lt;script src=&quot;https://kit.fontawesome.com/9445e30193.js&quot; crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;</code></pre>
<h4 id="headhtml">head.html</h4>
<p><img src="https://images.velog.io/images/jusung-c/post/be165b34-b1d8-47e0-a17c-6b118f3288a6/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/37b38e55-c188-43e6-aa66-177fdb0d0dbc/image.png" alt=""></p>
<p>[ 코드 ]</p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% load static %}

{% block title %}home.html{% endblock %}

{% block extra-style %}
&lt;style type=&quot;text/css&quot;&gt;
    .home-image {
        background-image: url({% static &#39;img/cat.jpeg&#39; %});
        background-repeat: no-repeat;
        background-position: center;
        background-size: 100%;
        height: 500px;
        border-top: 10px solid #ccc;
        border-bottom: 10px solid #ccc;
        padding: 20px 0 0 20px;
    }
    .title {
        color: #c80;
        font-weight: bold;
    }
    .powered {
        position: relative;
        top: 77%;
        color: #cc0;
        font-style: italic;
    }
&lt;/style&gt;
{% endblock %}

{% block content %}
    &lt;div class=&quot;home-image&quot;&gt;
        &lt;h2 class=&quot;title&quot;&gt;Django - Python Web Programming&lt;/h2&gt;
        &lt;h4 class=&quot;powered&quot;&gt;&lt;i class=&quot;fas fa-arrow-circle-right&quot;&gt;&lt;/i&gt; powered by django and bootstrap.&lt;/h4&gt;
    &lt;/div&gt;

    &lt;hr style=&quot;margin: 10px 0&quot;&gt;

    &lt;div class=&quot;row text-center&quot;&gt;
        &lt;div class=&quot;col-sm-6&quot;&gt;
            &lt;h3&gt;Bookmark App&lt;/h3&gt;
            &lt;p&gt;
                Bookmark is a Uniform Resource Identifier (URI)
                That is stored for later retrieval in any of various storage formats.
                You can store your own bookmarks by Bookmark application.
                It&#39;s also possible to update or delete your bookmarks.
            &lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;col-sm-6&quot;&gt;
            &lt;h3&gt;Blog App&lt;/h3&gt;
            &lt;p&gt;
                This application makes it possible to log daily events or write
                your own interests such as hobbies, techniques, etc.
                A typical blog combines text, digital images, and links to other blogs, web pages,
                and other media related to its topic
            &lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
{% endblock content %}

{% block footer %}
    &lt;footer class=&quot;fixed-bottom bg-info&quot;&gt;
        &lt;div class=&quot;text-white font-italic text-right mr-5&quot;&gt;
            Copyright &amp;copy; 2019 DjangoBook by jusung
        &lt;/div&gt;
    &lt;/footer&gt;
{% endblock %}</code></pre>
<br>

<p>가로 너비 작은 경우) 
<img src="https://images.velog.io/images/jusung-c/post/7691c71a-7570-41b6-9802-ac734c5023c8/image.png" alt=""></p>
<hr>

<p>가로 너무 큰 경우)
<img src="https://images.velog.io/images/jusung-c/post/734d994b-3db6-482b-b79e-54d81f33d1c7/image.png" alt=""></p>
<h3 id="bookmark-blog-app-개선">bookmark, blog app 개선</h3>
<h3 id="ui">UI</h3>
<p>북마크, 블로그 앱의 각 페이지에도 네이게이션 메뉴 바가 나타나도록 수정한다.</p>
<h4 id="bookmark_listhtml-수정">bookmark_list.html 수정</h4>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}bookmark_list.html{% endblock %}

{% block content %}
    &lt;h1&gt;Bookmark List&lt;/h1&gt;
    &lt;br&gt;

    &lt;ul&gt;
        {% for bookmark in object_list %}
            &lt;li&gt;
                &lt;a href=&quot;{% url &#39;bookmark:detail&#39; bookmark.id %}&quot;&gt;
                    {{ bookmark }}
                &lt;/a&gt;
            &lt;/li&gt;
        {% endfor %}
    &lt;/ul&gt;
{% endblock %}</code></pre>
<h4 id="bookmark_detailhtml-수정">bookmark_detail.html 수정</h4>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block title %}bookmark_detail.html{% endblock %}

{% block content %}
    &lt;h1&gt;
        {{ object.title }}
    &lt;/h1&gt;

    &lt;ul&gt;
        &lt;li&gt;
            &lt;a href=&quot;{{ object.url }}&quot;&gt;
                {{ object.url }}
            &lt;/a&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
{% endblock %}</code></pre>
<h4 id="이런-식으로-나머지-blog-app-templates-basehtml-상속해주기">이런 식으로 나머지 blog app templates &#39;base.html&#39; 상속해주기</h4>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(실전편) - 김석훈님</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Blog 앱]]></title>
            <link>https://velog.io/@jusung-c/Blog-%EC%95%B1</link>
            <guid>https://velog.io/@jusung-c/Blog-%EC%95%B1</guid>
            <pubDate>Sat, 20 Nov 2021 07:19:40 GMT</pubDate>
            <description><![CDATA[<ol>
<li>글 등록/열람</li>
<li>태그 기능</li>
<li>댓글 기능</li>
<li>검색 기능</li>
<li>콘텐츠 생성 및 편집</li>
</ol>
<h3 id="ui-설계">UI 설계</h3>
<p><img src="https://images.velog.io/images/jusung-c/post/95244d6b-d171-4222-afd1-f0f15d3fbf07/image.png" alt=""></p>
<h3 id="테이블-설계">테이블 설계</h3>
<p><img src="https://images.velog.io/images/jusung-c/post/0a81d91b-9df2-44f5-9819-7ef55711a1c4/image.png" alt=""></p>
<h3 id="url-설계">URL 설계</h3>
<p><img src="https://images.velog.io/images/jusung-c/post/8f5669fd-4865-4bc4-88d8-4bda1eae231d/image.png" alt=""></p>
<h3 id="코딩-순서">코딩 순서</h3>
<ol>
<li><p>뼈대 만들기 - 이미 bookmarkapp에서 했으므로 패쓰</p>
<ul>
<li>startproejct</li>
<li>setting.py 설정</li>
<li>migrate</li>
<li>createsuperuser 슈퍼 유저 생성</li>
<li>startapp blog 앱 생성</li>
<li>setting.py에 blog 앱 등록</li>
</ul>
</li>
<li><p>모델 코딩</p>
<ul>
<li>models.py 모델 정의</li>
<li>admin.py Admin 사이트에 모델 등록</li>
<li>makemigrations, migrate 작업</li>
</ul>
</li>
<li><p>URLconf 코딩</p>
<ul>
<li>urls.py URL 정의</li>
</ul>
</li>
<li><p>View 코딩</p>
<ul>
<li>views.py 뷰 로직 작성</li>
</ul>
</li>
</ol>
<ol start="5">
<li>템플릿 코딩<ul>
<li>templates 디렉토리</li>
</ul>
</li>
</ol>
<h3 id="model-코딩">Model 코딩</h3>
<h4 id="modelspy">models.py</h4>
<p><img src="https://images.velog.io/images/jusung-c/post/1218fd03-9256-4e2a-843d-1a88240e42dd/%ED%8C%8C%EC%9D%BC_000.png" alt=""></p>
<blockquote>
<p>Slug?
Slug는 페이지나 포스트를 설명하는 핵심 단어의 집합으로 이 슬러그를 URL로 사용함으로써 검색 엔진에서 더 빨리 페이지를 찾아주고 정확도를 높여준다.</p>
</blockquote>
<blockquote>
<p>SlugField 필드 타입
슬러그는 보통 제목의 단어들을 하이픈으로 연결해 생성하며, URL에서 pk 대신 사용된다. pk는 숫자로만 되어 있어 내용 유추가 힘들지만 슬러그는 명확하기 때문이다. </p>
</blockquote>
<h4 id="adminpy">admin.py</h4>
<p><img src="https://images.velog.io/images/jusung-c/post/9b039d8e-e5c8-4171-a1cf-ae90435d8982/%ED%8C%8C%EC%9D%BC_000%20(1).png" alt=""></p>
<h4 id="makemigrations-migrate-작업">makemigrations, migrate 작업</h4>
<p><img src="https://images.velog.io/images/jusung-c/post/43ae1710-5826-4145-bbd6-7eb3ab4daace/image.png" alt="">
<img src="https://images.velog.io/images/jusung-c/post/84c50128-6e84-4f1e-866d-5854dd7ec90e/image.png" alt=""></p>
<h3 id="urlconf-코딩">URLconf 코딩</h3>
<pre><code class="language-py">from django.urls import path, re_path

from bookmark.views import BookmarkLV, BookmarkDV

app_name = &#39;blog&#39;
urlpatterns = [
    path(&#39;&#39;, PostLV.as_view(), name=&#39;index&#39;),

    # /blog/post/
    path(&#39;post/&#39;, views.PostLV.as_view(), name=&#39;post_list&#39;),

    # /blog/post/django-example
    # 슬러그 컨버터 한글 인식을 위해 re_path로 정규식 구현
    re_path(r&#39;^post(?P&lt;slug&gt;[-\w]+)/$&#39;, views.PostDV.as_view(), name=&#39;post_detail&#39;),

    # /blog/archive/2019/
    path(&#39;archive/&lt;int:year&gt;/&#39;, views.PostYAV.as_view(), name=&#39;post_year_archive&#39;),

    # blog/archive/2019/nov/
    path(&#39;archive/&lt;int:year&gt;/&lt;str:month&gt;/&#39;, views.PostMAV.as_view(), name=&#39;post_month_archive&#39;),

    # blog/archive/2019/nov/10
    path(&#39;archive/&lt;int:year&gt;/&lt;str:month&gt;/&lt;int:day&gt;/&#39;, views.PostDAV.as_view(), name=&#39;post_day_archive&#39;),

    # blog/archive/today/
    path(&#39;archive/today/&#39;, views.PostTAV.as_view(), name=&#39;post_today_archive&#39;),

]</code></pre>
<h3 id="뷰-코딩하기">뷰 코딩하기</h3>
<pre><code class="language-py">from django.shortcuts import render

# Create your views here.
from django.views.generic import ListView, DetailView
from django.views.generic.dates import ArchiveIndexView, YearArchiveView, MonthArchiveView, \
    DayArchiveView, TodayArchiveView

from blog.models import Post


class PostLV(ListView):
    model = Post
    template_name = &#39;blog/post_all.html&#39;
    context_object_name = &#39;posts&#39;
    paginate_by = 2

# 특정 객체를 조회하기 위한 키는 기본 키 대신 slug 속성 사용
# URLconf에서 slug 추출해서 뷰로 넘겨준다.
class PostDV(DetailView):
    model = Post

# ArchiveIndexView : 테이블로부터 객체 리스트를 가져와
# 날짜 필드를 기준으로 최신 객체 먼저 출력
class PostAV(ArchiveIndexView):
    model = Post
    date_field = &#39;modify_dt&#39;

# YearArchiveView : 날짜 필드의 연도를 기준으로 객체 리스트를 가져와
# 그 객체들이 속한 월을 리스트로 출력
class PostYAV(YearArchiveView):
    model = Post
    date_field = &#39;modify_dt&#39;
    # 해당 연도에 해당하는 객체의 리스트를 만들어서 템플릿에 넘겨준다.
    # 템플릿 파일에서 object_list 컨텍스트 변수 사용 가능
    make_object_list = True

# MonthArchiveView : 날짜 필드의 연월을
# 기준으로 객체 리스트를 가져와 출력
class PostMAV(MonthArchiveView):
    model = Post
    date_field = &#39;modify_dt&#39;


# DayArchiveView : 날짜 필드의 연월일을 기준으로 객체
# 리스트를 가져와 출력
class PostDAV(DayArchiveView):
    model = Post
    date_field = &#39;modify_dt&#39;


class PostTAV(TodayArchiveView):
    model = Post
    date_field = &#39;modify_dt&#39;
</code></pre>
<h3 id="템플릿-코딩">템플릿 코딩</h3>
<p>post_all.html</p>
<pre><code class="language-html">
&lt;h1&gt;Blog List&lt;/h1&gt;

&lt;br&gt;

{% for post in posts %}
    &lt;h3&gt;
        &lt;a href=&quot;{{ post.get_absolute_url }}&quot;&gt;
            {{ post.title }}
        &lt;/a&gt;
    &lt;/h3&gt;

    {{ post.modify_dt|date:&quot;N d, Y&quot; }}

    &lt;p&gt;
        {{ post.description }}
    &lt;/p&gt;
{% endfor %}

&lt;br&gt;

&lt;div&gt;
    &lt;span&gt;
        {% if page_obj.has_previous %}
            &lt;a href=&quot;?page={{ page_obj.previous_page_number }}&quot;&gt;
                PreviousPage
            &lt;/a&gt;
        {% endif %}

        Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}

        {% if page_obj.has_next %}
            &lt;a href=&quot;?page={{ page_obj.next_page_number }}&quot;&gt;
                NextPage
            &lt;/a&gt;
        {% endif %}


    &lt;/span&gt;
&lt;/div&gt;</code></pre>
<p>post_detail.html
<img src="https://images.velog.io/images/jusung-c/post/cf1e9108-bbeb-4fc4-90e6-51f934b23b23/%ED%8C%8C%EC%9D%BC_000%20(2).png" alt=""></p>
<p>post_archive.html
<img src="https://images.velog.io/images/jusung-c/post/11ef6aa6-3d5c-47c1-8625-32bd7cf8bd07/image.png" alt=""></p>
<p>post_archive_year.html</p>
<pre><code class="language-html">
&lt;h1&gt;Post Archive for {{ year|date:&quot;Y&quot; }}&lt;/h1&gt;

&lt;ul&gt;
    {% for date in date_list %}
    &lt;li style=&quot;display: inline;&quot;&gt;
        &lt;a href=&quot;{% url &#39;blog:post_month_archive&#39; year|date:&#39;Y&#39; date|date:&#39;b&#39; %}&quot;&gt;
            {{ date|date:&quot;F&quot; }}
        &lt;/a&gt;
    &lt;/li&gt;
    {% endfor %}
&lt;/ul&gt;

&lt;br/&gt;

&lt;div&gt;
    &lt;ul&gt;
        {% for post in object_list %}
        &lt;li&gt;
            {{ post.modify_dt|date:&#39;Y-m-d&#39; }}&amp;nbsp;&amp;nbsp;&amp;nbsp;
            &lt;a href=&quot;{{ post.get_absolute_url }}&quot;&gt;
                &lt;strong&gt;{{ post.title }}&lt;/strong&gt;
            &lt;/a&gt;
        &lt;/li&gt;
        {% endfor %}
    &lt;/ul&gt;
&lt;/div&gt;</code></pre>
<p>post_archive_month.html</p>
<pre><code class="language-html">
&lt;h1&gt;Post Archive for {{ year|date:&quot;N d, Y&quot; }}&lt;/h1&gt;

&lt;div&gt;
    &lt;ul&gt;
        {% for post in object_list %}
        &lt;li&gt;
            {{ post.modify_dt|date:&#39;Y-m-d&#39; }}&amp;nbsp;&amp;nbsp;&amp;nbsp;
            &lt;a href=&quot;{{ post.get_absolute_url }}&quot;&gt;
                &lt;strong&gt;{{ post.title }}&lt;/strong&gt;
            &lt;/a&gt;
        &lt;/li&gt;
        {% endfor %}
    &lt;/ul&gt;
&lt;/div&gt;</code></pre>
<p>post_archive_day.html</p>
<pre><code class="language-html">
&lt;h1&gt;Post Archive for {{ year|date:&quot;N, Y&quot; }}&lt;/h1&gt;

&lt;div&gt;
    &lt;ul&gt;
        {% for post in object_list %}
        &lt;li&gt;
            {{ post.modify_dt|date:&#39;Y-m-d&#39; }}&amp;nbsp;&amp;nbsp;&amp;nbsp;
            &lt;a href=&quot;{{ post.get_absolute_url }}&quot;&gt;
                &lt;strong&gt;{{ post.title }}&lt;/strong&gt;
            &lt;/a&gt;
        &lt;/li&gt;
        {% endfor %}
    &lt;/ul&gt;
&lt;/div&gt;</code></pre>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(실전편) - 김석훈님</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Bookmark앱]]></title>
            <link>https://velog.io/@jusung-c/Bookmark%EC%95%B1</link>
            <guid>https://velog.io/@jusung-c/Bookmark%EC%95%B1</guid>
            <pubDate>Tue, 16 Nov 2021 15:12:53 GMT</pubDate>
            <description><![CDATA[<p>Bookmark 앱은 자주 방문하는 사이트를 등록해 두었다가 나중에 그 사이트에 재방문할 때 쉽게 찾아갈 수 있게 해주는 앱이다. </p>
<h3 id="ui-설계">UI 설계</h3>
<p><img src="https://images.velog.io/images/jusung-c/post/734cf196-9bd2-4e60-bd68-6310bd3b49d4/image.png" alt=""></p>
<h3 id="테이블-설계">테이블 설계</h3>
<p><img src="https://images.velog.io/images/jusung-c/post/3e0b5264-5f68-4726-b7c9-259518ac8724/image.png" alt=""></p>
<h2 id="model">Model</h2>
<pre><code class="language-py">from django.db import models

# Create your models here.

class Bookmark(models.Model):
    title = models.CharField(&#39;TITLE&#39;, max_length=100, blank=True)
    url = models.URLField(&#39;URL&#39;, unique=True)

    def __str__(self):
        return self.title</code></pre>
<ul>
<li>&#39;TITLE&#39;, &#39;URL&#39;은 별칭으로 Admin 사이트에서 이 문구가 사용된다.</li>
<li>_<em>str_</em>() 함수는 객체(테이블에 들어 있는 레코드 하나)를 문자열로 표현할 때 사용하는 함수로 꼭 정의해야 나중에 Admin 사이트 등에서 레코드명이 제대로 표현된다. </li>
</ul>
<h3 id="admin-사이트에-테이블-반영">Admin 사이트에 테이블 반영</h3>
<pre><code class="language-py">from django.contrib import admin
from bookmark.models import Bookmark

@admin.register(Bookmark)
class BookmarkAdmin(admin.ModelAdmin):
    list_display = (&#39;id&#39;, &#39;title&#39;, &#39;url&#39;)</code></pre>
<p><strong>이처럼 테이블을 새로 만들 때에는 models.py와 admin.py를 함께 수정해야 함!</strong></p>
<p><img src="https://images.velog.io/images/jusung-c/post/468b01e7-8af4-4600-bdb0-2ee4b279fe6a/image.png" alt=""></p>
<blockquote>
<p>테이블의 신규 생성, 테이블 변경 등 DB에 변경사항 있으면 makemigrations, migrate로 바로 반영</p>
</blockquote>
<h2 id="urlconf">URLconf</h2>
<p>3개의 URL과 뷰 필요</p>
<ol>
<li>admin</li>
<li>bookmark/ - BookmarkLV</li>
<li>bookmark/<a href="int:pk">int:pk</a>/ - BookmarkDV</li>
</ol>
<pre><code class="language-py">from django.contrib import admin
from django.urls import path

from bookmark.views import BookmarkLV, BookmarkDV

urlpatterns = [
    path(&#39;admin/&#39;, admin.site.urls),
    path(&#39;bookmark/&#39;, BookmarkLV.as_view(), name=&#39;index&#39;),
    path(&#39;bookmark/&lt;int:pk&gt;/&#39;, BookmarkDV.as_view(), name=&#39;detail&#39;),
]
</code></pre>
<h2 id="view">View</h2>
<ol>
<li>BookmarkLV - ListView</li>
<li>BookmarkDV - DetailView<pre><code>from django.views.generic import ListView, DetailView
</code></pre></li>
</ol>
<p>from bookmark.models import Bookmark</p>
<p>class BookmarkLV(ListView):
    model = Bookmark</p>
<p>class BookmarkDV(DetailView):
    model = Bookmark</p>
<pre><code>
지정해주지 않아도 디폴트로 지정되는 것
- PK로 특정 객체를 조회해서 가져오는 경우이므로 모델 클래스만 지정해주면 됨
- context_object_name이 ListView에서는 object_list, DetailView에서는 object로 설정
- 템플릿 파일명을 모델명 소문자_detail.html형식의 이름으로 저장


## Template
### 1. bookmark_list.html

```html
&lt;!DOCTYPE html&gt;
&lt;head&gt;
    &lt;title&gt;Django Bookmark List&lt;/title&gt;
&lt;/head&gt;

&lt;body&gt;
&lt;!--    div에 id 지정  --&gt;
    &lt;div id=&quot;content&quot;&gt;
        &lt;h1&gt;Bookmark List&lt;/h1&gt;

        &lt;ul&gt;
            {% for bookmark in object_list %}
                &lt;li&gt;
                    &lt;a href=&quot;{% url &#39;detail&#39; bookmark.id %}&quot;&gt;
                        {{ bookmark }}                    
                    &lt;/a&gt;
                &lt;/li&gt;
            {% endfor %}
        &lt;/ul&gt;
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><p>{{ bookmark }} 템플릿 변수는 Bookmark 테이블의 특정 레코드 하나(객체)를 의미한다. 해당 객체를 프린트하면 정의해두었던 _<em>str_</em>() 메소드를 호출해서 그 결과를 출력한다.</p>
<h3 id="2-bookmark_detailhtml">2. bookmark_detail.html</h3>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;head&gt;
    &lt;title&gt;Django Bookmark Detail&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div id=&quot;content&quot;&gt;
        &lt;h1&gt;
            {{ object.title }}
        &lt;/h1&gt;

        &lt;ul&gt;
            &lt;li&gt;
                &lt;a href=&quot;{{ object.url }}&quot;&gt;
                    {{ object.url }}
                &lt;/a&gt;
            &lt;/li&gt;
        &lt;/ul&gt;
    &lt;/div&gt;

&lt;/body&gt;
&lt;/html&gt;</code></pre>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(실전편) - 김석훈님</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 다듬기]]></title>
            <link>https://velog.io/@jusung-c/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%8B%A4%EB%93%AC%EA%B8%B0</link>
            <guid>https://velog.io/@jusung-c/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%8B%A4%EB%93%AC%EA%B8%B0</guid>
            <pubDate>Wed, 10 Nov 2021 05:53:58 GMT</pubDate>
            <description><![CDATA[<h3 id="게시글-작성-시-프로젝트를-고를-때-프로젝트-명이-아닌-project-object-1-형식으로-나오는-문제">게시글 작성 시 프로젝트를 고를 때 프로젝트 명이 아닌 Project Object (1) 형식으로 나오는 문제</h3>
<p>projectapp/models.py에서 _<em>str_</em> 함수로 프로젝트의 pk : 프로젝트 title을 출력해준다.</p>
<pre><code class="language-py">class Project(models.Model):
    image = models.ImageField(upload_to=&#39;project/&#39;, null=False)
    title = models.CharField(max_length=200, null=True)
    description = models.TextField(null=True)

    created_at = models.DateField(auto_now_add=True, null=True)

    # 번호 : 제목
    def __str__(self):
        return f&#39;{self.pk} : {self.title}&#39;
</code></pre>
<h3 id="projectapp의-detailview에서-로그인을-안했을-경우-subscription이-정의되지-않았는데-return에서-subscription-값을-요구하는-경우-생기는-문제">projectapp의 DetailView에서 로그인을 안했을 경우 subscription이 정의되지 않았는데 return에서 subscription 값을 요구하는 경우 생기는 문제</h3>
<p>else 구문을 통해 subscription을 None값으로 지정해준다.</p>
<pre><code class="language-py">        # user의 로그인 여부
        if user.is_authenticated:
            subscription = Subscription.objects.filter(user=user, project=project)
        else:
            subscription = None
        object_list = Article.objects.filter(project=self.get_object())
        return super(ProjectDetailView, self).get_context_data(object_list=object_list,
</code></pre>
<h3 id="처음-articleapp에서-학습용으로-제작한-helloworld-등-과거-자료들-정리">처음 articleapp에서 학습용으로 제작한 helloworld 등 과거 자료들 정리</h3>
<p>LeeBook 우클릭 -&gt; Find in Files -&gt; hello 검색 -&gt; In Project 탭
helloworld 관련 코드 삭제</p>
<p><a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a> 주소를 입력하면 찾을 수 없는 페이지라고 나온다. 이 페이지를 설정해준다.
LeeBook/urls.py</p>
<pre><code>    path(&#39;&#39;, ArticleListView.as_view(), name=&#39;home&#39;),</code></pre><p>LOGIN_REDIRECT_URL &#39;home&#39;으로 변경</p>
<pre><code class="language-py">LOGIN_REDIRECT_URL = reverse_lazy(&#39;home&#39;)</code></pre>
<h3 id="mypage-edit-change-info-quit-버튼-느낌있게-수정">Mypage edit, Change Info, Quit 버튼 느낌있게 수정</h3>
<p>구글에서 제공해주는 무료 아이콘 팩 활용</p>
<p>Google Material icons : <a href="https://material.io/resources/icons/?style=baseline">https://material.io/resources/icons/?style=baseline</a> </p>
<p>Google Material icons Github : <a href="https://github.com/google/material-design-icons">https://github.com/google/material-design-icons</a></p>
<p>templates/head.html에 추가</p>
<pre><code class="language-html">    &lt;!--    GOOGLE MATERIAL ICONS--&gt;
    &lt;link href=&quot;https://fonts.googleapis.com/css2?family=Material+Icons&quot;
      rel=&quot;stylesheet&quot;&gt;</code></pre>
<p>accountapp/detail.html</p>
<pre><code class="language-html">                    &lt;a class=&quot;material-icons&quot;
                       style=&quot;box-shadow: 0 0 4px #ccc; border-radius: 10rem; padding: 0.4rem&quot;
                       href=&quot;{% url &#39;profileapp:update&#39; pk=target_user.profile.pk %}&quot;&gt;
                            edit
                    &lt;/a&gt;
                &lt;a class=&quot;material-icons&quot;
                   style=&quot;box-shadow: 0 0 4px #ccc; border-radius: 10rem; padding: 0.4rem&quot;
                   href=&quot;{% url &#39;accountapp:update&#39; pk=user.pk %}&quot;&gt;
                        settings
                &lt;/a&gt;
                &lt;a class=&quot;material-icons&quot;
                   style=&quot;box-shadow: 0 0 4px #fcc; border-radius: 10rem; padding: 0.4rem&quot;
                   href=&quot;{% url &#39;accountapp:delete&#39; pk=user.pk %}&quot;&gt;
                        cancel
                &lt;/a&gt;</code></pre>
<p><img src="https://images.velog.io/images/jusung-c/post/285782cd-ccb8-40d9-bfeb-841d342cb9f8/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WYSIWYG]]></title>
            <link>https://velog.io/@jusung-c/WYSIWYG</link>
            <guid>https://velog.io/@jusung-c/WYSIWYG</guid>
            <pubDate>Mon, 08 Nov 2021 14:09:37 GMT</pubDate>
            <description><![CDATA[<h3 id="wysiwyg">WYSIWYG?</h3>
<p>What You See Is What You Get의 약자인 WYSIWYG는 보는대로 글이 써진다는 뜻으로 좀 더 글을 쓸 때 풍부함을 더해주는 기능이다.</p>
<p><img src="https://images.velog.io/images/jusung-c/post/f91693fd-f525-4aa2-8621-73e1d49c3bf5/image.png" alt=""></p>
<p>Medium Editor github:  <a href="https://github.com/yabwe/medium-editor">https://github.com/yabwe/medium-editor</a></p>
<p>사용법</p>
<ol>
<li>forms에서 사용할 editable 클래스를 위해 create.html에 다음 코드를 작성한다.<pre><code class="language-html">&lt;script src=&quot;//cdn.jsdelivr.net/npm/medium-editor@latest/dist/js/medium-editor.min.js&quot;&gt;&lt;/script&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;//cdn.jsdelivr.net/npm/medium-editor@latest/dist/css/medium-editor.min.css&quot; type=&quot;text/css&quot; media=&quot;screen&quot; charset=&quot;utf-8&quot;&gt;
</code></pre>
</li>
</ol>
<script>var editor = new MediumEditor('.editable');</script>
<pre><code>
2. forms의 content 필드에 editable 클래스를 적용시킨다.

```py
    content = forms.CharField(widget=forms.Textarea(attrs={&#39;class&#39;: &#39;editable text-start&#39;,
                                                           &#39;style&#39;: &#39;heigth: auto;&#39;}))</code></pre><p>게시글을 작성할 때 적용시킬 것이므로 articleapp/forms의 content 필드에 추가한다.</p>
<p>적용 후 게시글을 작성하면 detail 페이지에서 마크다운이 그대로 노출된다. articleapp/detail.html에서 content를 노출시키는 코드에 safe 필터를 걸어준다. </p>
<blockquote>
<p>safe 필터는 태그와 같은 마크다운을 없애주고 내용만 보여준다.</p>
</blockquote>
<pre><code class="language-html">            &lt;div class=&quot;text-start&quot;&gt;
                {{ target_article.content | safe  }}
            &lt;/div&gt;</code></pre>
<p>마찬가지로 update 시에도 똑같이 적용될 수 있도록 수정한다.</p>
<p>articleapp/update.html에 다음 코드 추가</p>
<pre><code class="language-html">&lt;script src=&quot;//cdn.jsdelivr.net/npm/medium-editor@latest/dist/js/medium-editor.min.js&quot;&gt;&lt;/script&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;//cdn.jsdelivr.net/npm/medium-editor@latest/dist/css/medium-editor.min.css&quot; type=&quot;text/css&quot; media=&quot;screen&quot; charset=&quot;utf-8&quot;&gt;

&lt;script&gt;var editor = new MediumEditor(&#39;.editable&#39;);&lt;/script&gt;</code></pre>
<h3 id="게시글-작성-시-프로젝트를-설정하지-않아도-되도록-수정">게시글 작성 시 프로젝트를 설정하지 않아도 되도록 수정</h3>
<p>ForeginKey를 설정하는 필드는 ModelChoiceField이다. 
<strong>ModelChoiceField</strong>는 모델간의 관계를 나타내는 필드로 queryset을 필수로 작성해야 한다. </p>
<p>required를 이용해 없어도 되도록 설정한다.</p>
<pre><code class="language-py">    project = forms.ModelChoiceField(queryset=Project.objects.all(), required=False)
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Subscribeapp 구현]]></title>
            <link>https://velog.io/@jusung-c/Subscribeapp-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@jusung-c/Subscribeapp-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Mon, 08 Nov 2021 11:53:04 GMT</pubDate>
            <description><![CDATA[<h3 id="redirectview">RedirectView</h3>
<p>구독 버튼을 누르면 그 페이지에서 다른 페이지로 넘어가는 게 아닌 구독 버튼만 바뀐다. 별 다른 정보를 넘겨주지도 않는다. 요청을 받자마자 처리할 것들을 하고 바로 Redirect하도록 한다.</p>
<h3 id="startapp-subscribeapp">startapp subscribeapp</h3>
<h3 id="leebook의-settingspy-urlspy-추가">LeeBook의 settings.py, urls.py 추가</h3>
<h3 id="subscribeapp-urlspy-생성-후-app_name-설정">subscribeapp urls.py 생성 후 app_name 설정</h3>
<h3 id="model-코딩">Model 코딩</h3>
<p>같은 게시판을 여러번 구독할 수 없으므로 user과 project를 연결하는 쌍이 가지는 구독 정보가 하나여야 한다.</p>
<p>Meta 정보로 unique_together를 이용하여 둘을 한 쌍으로 묶어준다.</p>
<pre><code class="language-py">from django.contrib.auth.models import User
from django.db import models

# Create your models here.
from projectapp.models import Project


class Subscription(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name=&#39;subscription&#39;)
    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name=&#39;subscription&#39;)

    class Meta:
        unique_together = (&#39;user&#39;, &#39;project&#39;)</code></pre>
<p>model 작업을 했으므로 makemigration, migrate를 해준다.</p>
<h3 id="view-코딩">View 코딩</h3>
<p>get_redirect_url 메소드를 이용해 projectapp:detail로 향하게 한다.
GET방식으로 project_pk를 받아서 이 pk를 가지고 있는 detail 페이지로 돌아가는 것이다.</p>
<p>project: get_object_or_404 단축함수를 이용해 project_pk를 가지고 있는 Project를 찾는데 없다면 404 반응을 낸다.
user: self.request.user</p>
<p>project, user 두 정보를 찾아낸 후 subscription을 찾는다. 
만약 있다면 삭제해주고 없다면 만들고 저장해준다.</p>
<pre><code class="language-py">@method_decorator(login_required, &#39;get&#39;)
class SubscriptionView(RedirectView):

    def get_redirect_url(self, *args, **kwargs):
        return reverse(&#39;projectapp:detail&#39;, kwargs={&#39;pk&#39;: self.request.GET.get(&#39;project_pk&#39;)})


    def get(self, request, *args, **kwargs):
        project = get_object_or_404(Project, pk=self.request.GET.get(&#39;project_pk&#39;))
        user= self.request.user

        subscription = Subscription.objects.filter(user=user,
                                                   project=project)

        # 구독했으면 없애고 안했으면 해주고
        if subscription.exists():
            subscription.delete()
        else:
            Subscription(user=user, project=project).save()

        return super(SubscriptionView, self).get(request, *args, **kwargs)
</code></pre>
<h3 id="subscribe-url-추가">subscribe/ url 추가</h3>
<pre><code class="language-py">from django.urls import path

from subscribeapp.views import SubscriptionView

app_name = &#39;subscribeapp&#39;

urlpatterns = [
    path(&#39;subscribe/&#39;, SubscriptionView.as_view(), name=&#39;subscribe&#39;)
]</code></pre>
<h4 id="구독-버튼">구독 버튼</h4>
<p>projectapp/detail.html</p>
<p>SubscriptionView에서 GET방식으로 project_pk라는 데이터를 넘겨주기로 했으니까 detail에서 target_project.pk를 project_pk로 넘겨준다.</p>
<pre><code>        &lt;a href=&quot;{% url &#39;subscribeapp:subscribe&#39;%}?project_pk={{ target_project.pk }}&quot;
           class=&quot;btn btn-primary rounded-pill px-4&quot;&gt;
        &lt;/a&gt;</code></pre><p>이렇게 하면 작동은 되지만 구독을 했는지 안했는지 알 방법이 없다. </p>
<p>projectapp/views.py의 DetailView에서 지금 접속한 유저이 이 게시판에 대해 구독 정보가 있는 지 없는지 확인을 해주는 작업이 필요하다. </p>
<p>get_context_data 안에서 현재 project와 user 정보를 얻어온다. 그런데 여기서 그 user가 로그인을 했는지 안했는 지 확인하는 작업부터 해줘야 한다.</p>
<p>유저가 로그인이 되어 있으면 subscription 변수에 얻어온 user와 project를 넣어줘서 찾아준다. 그 후 찾은 subscription을 return 해준다.</p>
<pre><code class="language-py">    def get_context_data(self, **kwargs):
        project = self.object
        user= self.request.user

        # user의 로그인 여부
        if user.is_authenticated:
            subscription = Subscription.objects.filter(user=user, project=project)

        object_list = Article.objects.filter(project=self.get_object())
        return super(ProjectDetailView, self).get_context_data(object_list=object_list,
</code></pre>
<p>detail에서 토글이 되는 것을 눈으로 확인할 수 있도록 수정해준다.</p>
<pre><code class="language-html">&lt;!--구독버튼--&gt;
    &lt;div class=&quot;text-center mb-5&quot;&gt;
        {% if user.is_authenticated %}
            {% if not subscription %}
            &lt;a href=&quot;{% url &#39;subscribeapp:subscribe&#39;%}?project_pk={{ target_project.pk }}&quot;
               class=&quot;btn btn-primary rounded-pill px-4&quot;&gt;
                Subscribe
            &lt;/a&gt;
            {% else %}
            &lt;a href=&quot;{% url &#39;subscribeapp:subscribe&#39;%}?project_pk={{ target_project.pk }}&quot;
               class=&quot;btn btn-dark rounded-pill px-4&quot;&gt;
                Unsubscribe
            &lt;/a&gt;
            {% endif%}
        {% endif %}
    &lt;/div&gt;</code></pre>
<h3 id="field-lookup을-사용한-구독-페이지-구현">Field Lookup을 사용한 구독 페이지 구현</h3>
<p>field를 사용할 때 다음과 같이 특정 조건을 넣는 형식으로 했었다.</p>
<pre><code class="language-py">Model.objects.filter(pk=xxx, user=xxx)</code></pre>
<p>(pk=xxx, user=xxx)는 pk와 user 값의 AND function이다. 그렇다면 OR function, WHERE 구문은 어떻게 사용할까?</p>
<p>다음 작업을 진행하면서 알아보자</p>
<ol>
<li><p>Find user Subscripted projects. 
유저가 구독하고 있는 프로젝트들을 확인하기 </p>
</li>
<li><p>Find articles in projects.
그 프로젝트 안 모든 게시글 가져오기</p>
</li>
</ol>
<p>기존에 사용하던 방식에서 다음과 같이 바꾼다.</p>
<pre><code class="language-py">Model.objects.filter(project__in=projects)</code></pre>
<p>이 project__in=projects같이 double underscore로 이루어진 것들이 장고에서 제공하는 field lookup이다.</p>
<h4 id="sql안에서-field-lookup의-작동-방식">SQL안에서 field lookup의 작동 방식</h4>
<pre><code class="language-sql">SELECT ... WHERE project IN (&#39;&#39;&#39;);</code></pre>
<p><img src="https://images.velog.io/images/jusung-c/post/a55646e8-c950-4a17-8fdf-ddeca03532b6/image.png" alt=""></p>
<p>목적 : 조금 더 복잡한 DB 쿼리를 사용자가 구현할 수 있도록 사용한다. For advanced DB query</p>
<p>종류</p>
<ul>
<li>Model__exact</li>
<li>Model__iexact</li>
<li>Model__contains</li>
<li>Model__in</li>
<li>Model__gte</li>
<li>등등</li>
</ul>
<h4 id="subscriptionlistview">SubscriptionListView</h4>
<p>특정 조건을 만족하는 게시글을 가져와야 하기 때문에 SubscriptionListView안에서 queryset 관련 함수를 새로 작성한다.</p>
<p>value_list(&#39;project&#39;)는 값들을 list화 시키는 함수인데 우리는  Subscription 모델에서 user와 project를 지정해줬다. 이 project에 대해 list화 시킨다는 것이다. </p>
<p>이제 여기서 field lookup을 사용해 article_list를 만든다.</p>
<pre><code class="language-py">@method_decorator(login_required, &#39;get&#39;)
class SubscriptionListView(ListView):
    model = Article
    context_object_name = &#39;article_list&#39;
    template_name = &#39;subscribeapp/list.html&#39;
    paginate_by = 5

    # 특정 조건을 만족하는 게시글을 가져와야 하기 때문에 queryset 관련 함수를 새로 작성한다.
    def get_queryset(self):
        # user가 구독하고 있는 프로젝트 찾기
        projects = Subscription.objects.filter(user=self.request.user).values_list(&#39;project&#39;)

        # 프로젝트 안 모든 게시글 가져오기
        article_list = Article.objects.filter(project__in=projects)
        return article_list</code></pre>
<h4 id="subscribelisthtml">subscribe/list.html</h4>
<p>이전에 list_fragment.html로 조각화해놓은 것을 그대로 가져온다.</p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block content %}

    &lt;div&gt;
        {% include &#39;snippets/list_fragment.html&#39; with article_list=article_list %}
    &lt;/div&gt;

{% endblock%}</code></pre>
<h4 id="url-추가">url 추가</h4>
<pre><code class="language-py">    path(&#39;list/&#39;, SubscriptionListView.as_view(), name=&#39;list&#39;),
</code></pre>
<h4 id="subscribe-버튼">Subscribe 버튼</h4>
<p>header.html에서 버튼을 생성해준다.</p>
<pre><code class="language-html">            &lt;a href=&quot;{% url &#39;subscribeapp:list&#39; %}&quot; class=&quot;leebook_header_nav&quot;&gt;
                &lt;span&gt;Subscription&lt;/span&gt;
            &lt;/a&gt;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Projectapp 구현]]></title>
            <link>https://velog.io/@jusung-c/Projectapp-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@jusung-c/Projectapp-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Sat, 06 Nov 2021 09:34:39 GMT</pubDate>
            <description><![CDATA[<p>Projectapp은 Article을 카테고리별로 묶어주는 기능이다. </p>
<ul>
<li>Create / Detail/ List View</li>
<li>Success_url to related Project</li>
<li>Login_required to CreateView</li>
<li>Model : title/ description/ image/ created_at</li>
</ul>
<h3 id="1-python-managepy-startapp-projectapp">1. python manage.py startapp projectapp</h3>
<h3 id="2-leebook의-settingspy-에서-projectapp-추가--urlspy에서-경로-추가">2. LeeBook의 settings.py 에서 projectapp 추가 + urls.py에서 경로 추가</h3>
<h3 id="3-projectappurlspy에서-app_name-설정">3. projectapp/urls.py에서 app_name 설정</h3>
<h3 id="4-projectappmodelspy">4. projectapp/models.py</h3>
<pre><code class="language-py">class Project(models.Model):
    image = models.ImageField(upload_to=&#39;project/&#39;, null=False)
    title = models.CharField(max_length=200, null=True)
    description = models.TextField(null=True)
    created_at = models.DateField(auto_now_add=True, null=True)</code></pre>
<h3 id="5-projectappformspy">5. projectapp/forms.py</h3>
<pre><code class="language-py">class ProjectCreationForm(ModelForm):
    class Meta:
        model = Project
        fields = [&#39;image&#39;, &#39;title&#39;, &#39;description&#39;]</code></pre>
<h3 id="6-makemigrate와-migrate로-db-연동">6. makemigrate와 migrate로 DB 연동</h3>
<h3 id="7-projectappviewspy">7. projectapp/views.py</h3>
<ul>
<li><p>CreateView</p>
<pre><code class="language-py">@method_decorator(login_required, &#39;get&#39;)
@method_decorator(login_required, &#39;post&#39;)
class ProjectCreateView(CreateView):
  model = Project
  form_class = ProjectCreationForm
  template_name = &#39;projectapp/create.html&#39;

  def get_success_url(self):
      return reverse(&#39;projectapp:detail&#39;, kwargs={&#39;pk&#39;: self.object.pk})
</code></pre>
</li>
</ul>
<pre><code>
- DetailView
```py
class ProjectDetailView(DetailView):
    model = Project
    context_object_name = &#39;target_project&#39;
    template_name = &#39;projectapp/detail.html&#39;</code></pre><ul>
<li>ListView<pre><code class="language-py">class ProjectListView(ListView):
  model = Project
  context_object_name = &#39;project_list&#39;
  template_name = &#39;projectapp/list.html&#39;
  paginate_by = 25</code></pre>
</li>
</ul>
<h3 id="8-projectapptemplatespy">8. projectapp/templates.py</h3>
<ul>
<li>create.html
이때까지와 같은 방식으로</li>
</ul>
<ul>
<li>detail.html
DeatilView에서 context_object_name으로 지정한 target_project를 이용해 detail 페이지 완성</li>
</ul>
<p>profileapp에서 사용했던 레이아웃 그대로 사용 (둥근 image, title, description)</p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block content %}

    &lt;div style=&quot;text-align: center; max-width: 700px; margin: 4rem auto;&quot;&gt;
        &lt;div&gt;
            &lt;img src=&quot;{{ target_project.image.url }}&quot; alt=&quot;&quot;
                style=&quot;heigth: 12rem; width: 12rem; border-radius: 20rem; margin-bottom: 2rem; object-fit: cover;&quot;&gt;
            &lt;h2 style=&quot;font-family: &#39;NanumGimYuICe&#39;&quot;&gt;
                {{ target_project.title }}
            &lt;/h2&gt;
            &lt;h5 style=&quot;margin-bottom: 3rem&quot;&gt;
                {{ target_project.description }}
            &lt;/h5&gt;


        &lt;/div&gt;
    &lt;/div&gt;

{% endblock %}</code></pre>
<ul>
<li>list.html<ul>
<li>card_project.html<ul>
<li>pagination articleapp뿐만 아니라 다른 app도 모두 적용할 수 있도록 수정</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>먼저 list.html</p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}
{% load static %}

{% block content %}

&lt;style&gt;

    .container {
        padding: 0;
        margin: 0, auto;
    }

    .container div {
      display: flex;
      justify-content: center;
      align-items: center;
      border-radius: 1rem;
    }

    .container img {
        width: 7rem;
        height: 7rem;
        object-fit: cover;
        border-radius: 1rem;

    }
&lt;/style&gt;

    {% if project_list %}
    &lt;div class=&quot;container&quot;&gt;
        {% for project in project_list %}
        &lt;a href=&quot;{% url &#39;projectapp:detail&#39; pk=project.pk%}&quot;&gt;
            &lt;!--            card의 레이아웃을 바꿀 때 card.html만 수정하면 가능하도록 따로 지정해준다.--&gt;
            &lt;!--            card.html에서 필요한 article 객체가 for문에서 정해준 article과 같다는 의미의 코드 작성--&gt;
            {% include &#39;snippets/card_project.html&#39; with project=project %}
        &lt;/a&gt;
        {% endfor %}
    &lt;/div&gt;
    &lt;script src=&quot;{% static &#39;js/magicgrid.js&#39; %}&quot;&gt;&lt;/script&gt;
    {% else %}
    &lt;div style=&quot;text-align: center&quot;&gt;
        &lt;h1&gt;No Projects YET!&lt;/h1&gt;
    &lt;/div&gt;
    {% endif %}

    {% include &#39;snippets/pagination.html&#39; with page_obj=page_obj %}

    &lt;div style=&quot;text-align: center&quot;&gt;
        &lt;a href=&quot;{% url &#39;projectapp:create&#39; %}&quot; class=&quot;btn btn-dark rounded-pill mt-3 mb-3 px-3&quot;&gt;
            Create Project
        &lt;/a&gt;
    &lt;/div&gt;

{% endblock %}</code></pre>
<p>card_project.html</p>
<pre><code class="language-html">&lt;div style=&quot;display: block; text-align: center;&quot;&gt;
    &lt;img src=&quot;{{ project.image.url }}&quot; alt=&quot;&quot;&gt;
    &lt;h5 class=&quot;mt-2&quot;&gt;
&lt;!--        truncatechars로 긴 제목 짜르기--&gt;
        {{ project.title | truncatechars:8 }}
    &lt;/h5&gt;
&lt;/div&gt;</code></pre>
<p>pagination.html</p>
<p>?page={{ page_obj.previous_page_number }} 앞에 있던 article에 한정시키는 코드 지우면 모든 app에 적용가능</p>
<pre><code class="language-html">&lt;a href=&quot;?page={{ page_obj.previous_page_number }}&quot;
    class=&quot;btn btn-secondary rounded-pill&quot;&gt;
    {{ page_obj.previous_page_number }}
&lt;/a&gt;</code></pre>
<h3 id="9-basecss로-외관-수정">9. base.css로 외관 수정</h3>
<pre><code class="language-css">a {
    color: black;
    text-decoration: none;
}


a:hover {
    color: black;
    text-decoration: none;
}

.leebook_header_nav {
    margin: 0 0.5rem;
}

.leebook_header_navbar {
    margin: 1rem 0;
}</code></pre>
<h3 id="project와-article-사이의-연결고리-만들기">Project와 Article 사이의 연결고리 만들기</h3>
<p>articleapp/models.py에서 projcet를 Foreinkey로 만들어준 후 forms.py에도 적용시켜 create할 때 만들어져 있는 project중 골라서 선택할 수 있도록 한다. </p>
<p>articleapp/models.py</p>
<pre><code class="language-py">    project = models.ForeignKey(Project, on_delete=models.SET_NULL, related_name=&#39;project&#39;, null=True)
</code></pre>
<p>articleapp/forms.py</p>
<pre><code class="language-py">        fields = [&#39;title&#39;, &#39;image&#39;, &#39;project&#39;, &#39;content&#39;]</code></pre>
<p>모델을 수정했으니 다시 DB에 연동시키기 위해 makemigration, migrate 작업을 해준다.</p>
<h3 id="project에-articles들-보여주기">Project에 articles들 보여주기</h3>
<p>이제 Project를 들어갔을 때 그 project인 article들을 보여줘야한다. </p>
<p>articleapp에 commentapp을 Mixin으로 박아 넣었던 것처럼 비슷하게 해야하는데 이 방법은 무조건 다 가져올 때의 방식이고 조건을 달아서 좀 더 여러가지 작업을 하고 싶을 때에는 이런 방식은 한계가 있을 수 있다.</p>
<p>그래서 View안에서 MultipleObjectMixin 이용해 비슷한 방식으로 구현한다.
MultipleObjectMixin은 이름 그대로 여러가지 object를 다룰 수 있게 해준다. </p>
<p>get_context_data 메소드를 이용해서 실질적으로 어떤 게시글을 가져올 것인지에 대한 필터링 구문을 완성한다. object_list 안에 filter를 걸어서 project가 현재 프로젝트의 object와 같은 것을 필터링한다. 그 후 return으로 필터링한 값을 반환해준다.</p>
<p>projectapp/views.py</p>
<pre><code class="language-py">class ProjectDetailView(DetailView, MultipleObjectMixin):
    model = Project
    context_object_name = &#39;target_project&#39;
    template_name = &#39;projectapp/detail.html&#39;

    paginate_by = 25

    def get_context_data(self, **kwargs):
        object_list = Article.objects.filter(project=self.get_object())
        return super(ProjectDetailView, self).get_context_data(object_list=object_list, **kwargs)
</code></pre>
<p>articleapp/list.html을 복사해서 아무곳에도 종속되지 않은 snippets/list_fragment.html에 list_fragment.html라는 이름으로 붙여넣고 이를 projectapp/detail.html에서 include 구문으로 사용할 것이다.</p>
<p>list_fragment.html는 base.html이랑 관련 없으므로 extends 구문과 block 구문을 지워준다. </p>
<p>article_list를 object_list로 detail.html에서 지정해주면 include 구문으로 그대로 적용하여 사용할 수 있다.</p>
<p>projectapp/detail.html</p>
<pre><code class="language-html">    &lt;div&gt;

        {% include &#39;snippets/list_fragment.html&#39; with article_list=object_list %}

    &lt;/div&gt;</code></pre>
<h3 id="accountapp에서-mypage로-접속했을-때-내가-쓴-글-필터링하여-표시하기">accountapp에서 Mypage로 접속했을 때 내가 쓴 글 필터링하여 표시하기</h3>
<p>accountapp/views.py</p>
<pre><code class="language-py">class AccountDetailView(DetailView, MultipleObjectMixin):
    model = User
    # 다른 pk로 접속해도 target_user의 정보를 볼 수 있도록 지정해준다.
    context_object_name = &#39;target_user&#39;
    template_name = &#39;accountapp/detail.html&#39;

    paginate_by = 25

    def get_context_data(self, **kwargs):
        object_list = Article.objects.filter(writer=self.get_object())
        return super(AccountDetailView, self).get_context_data(object_list=object_list, **kwargs)
</code></pre>
<p>accountapp/detail.html</p>
<pre><code class="language-html">    &lt;div&gt;

        {% include &#39;snippets/list_fragment.html&#39; with article_list=object_list %}

    &lt;/div&gt;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[500 에러]]></title>
            <link>https://velog.io/@jusung-c/500-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@jusung-c/500-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Fri, 20 Aug 2021 00:25:31 GMT</pubDate>
            <description><![CDATA[<p>모든 작업을 완료한 후 사이트를 구동시켜보니 특정 사이트들에서 500 에러가 발생했다.</p>
<p><img src="https://images.velog.io/images/jusung-c/post/bea033fe-1a58-4ee0-862a-6adc31b75823/image.png" alt=""></p>
<p>어디서 에러가 발생했는 지 알기 위해 log 기록들을 확인했다.</p>
<p>django.db.utils.OperationalError: unable to open database file 에러가 발생하고 있었다.</p>
<p>db 파트에서 에러가 나고 있던 것인데 sqlite의 파일 경로 설정을 settings.py 파일에서 바궈주니 에러가 사라졌다.</p>
<pre><code class="language-py">DATABASES = {
    &#39;default&#39;: {
        &#39;ENGINE&#39;: &#39;django.db.backends.sqlite3&#39;,
        # 기존 코드 &#39;NAME&#39;: BASE_DIR / &#39;db.sqlite3&#39;, 
        &#39;NAME&#39;: os.path.join(BASE_DIR, &#39;db.sqlite3&#39;),
    }
}</code></pre>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(기초) - 김석훈님</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Cloud 서버에 Django 배포]]></title>
            <link>https://velog.io/@jusung-c/Cloud-%EC%84%9C%EB%B2%84%EC%97%90-Django-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@jusung-c/Cloud-%EC%84%9C%EB%B2%84%EC%97%90-Django-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Thu, 19 Aug 2021 23:50:52 GMT</pubDate>
            <description><![CDATA[<h3 id="클라우드-서비스란">클라우드 서비스란?</h3>
<p>인터넷에 있는 서버를 빌려서 사용하는 것으로 필요한 만큼 서버 자원을 빌려 쓰고 쓴 만큼 비용을 지불하는 방식이다.</p>
<blockquote>
<p>클라우드 서비스를 제공해주는 사이트는 AWS, Digital Ocean, Heroku, PythonAnywhere 등 상당히 많은데 여기선 PythonAnywhere을 사용해본다. 그러나 상용 서비스 운영 예정이라면 유료이면서 안정적인 클라우드 서비스를 선택해야 한다.</p>
</blockquote>
<h3 id="장고-소스-가져오기">장고 소스 가져오기</h3>
<ol>
<li>파일 압축</li>
<li>Files - New Directoriy - 압축 파일 업로드</li>
</ol>
<h3 id="리눅스-병령으로-압축-풀기">리눅스 병령으로 압축 풀기</h3>
<ol>
<li>Consoles - Bash</li>
<li><img src="https://images.velog.io/images/jusung-c/post/22bc6b1c-af66-4d7d-8f44-1e8771b93bfb/image.png" alt=""></li>
<li>압축 파일을 풀고 ch7 프로젝트의 디렉토리 및 파일들 구성
<img src="https://images.velog.io/images/jusung-c/post/21c63130-ca69-4b75-911d-269873b96833/image.png" alt="">
<img src="https://images.velog.io/images/jusung-c/post/55af2b7c-663a-4353-bcd1-e022834e7a34/image.png" alt=""></li>
</ol>
<h3 id="가상-환경-만들기">가상 환경 만들기</h3>
<ol>
<li><p>가상 환경을 모아둘 디렉토리 VENV 만들기
<img src="https://images.velog.io/images/jusung-c/post/ee74c2c7-af0b-43e0-8a13-2cf9542c5fa4/image.png" alt=""></p>
</li>
<li><p>파이썬 3.6을 사용하는 가상 환경 myproject 만들기
<img src="https://images.velog.io/images/jusung-c/post/cff3596e-dd57-42b5-8e70-1897e5375f77/image.png" alt=""></p>
</li>
<li><p>장고 설치 
<img src="https://images.velog.io/images/jusung-c/post/c4dc5821-7205-449a-938f-9057590787aa/image.png" alt=""></p>
</li>
</ol>
<h3 id="pythonanywhere-서버에서-장고-설정-변경">PythonAnywhere 서버에서 장고 설정 변경</h3>
<p>PythonAnywhere 서버도 운영 서버로 웹 서버가 실행되고 있는데 이 서버에서 장고 프로그램을 실행하기 위해서는 장고 설정을 변경해줘야 한다. </p>
<p>vi settings.py를 통해 아래 코드를 추가한다.
<img src="https://images.velog.io/images/jusung-c/post/ea5bc3be-15b9-462e-9555-b0b5891a22fd/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jusung-c/post/82c62daa-8b89-4ba2-a131-3a2e782c61a3/image.png" alt=""></p>
<p>설정 파일을 변경한 후엔 정적 파일을 모으기 위해 collectstatic 명령을 실행한다.</p>
<p><img src="https://images.velog.io/images/jusung-c/post/271a39dd-5508-4ba0-9608-56db0489c5be/image.png" alt=""></p>
<h3 id="pythonanywhere-웹-서버-설정">PythonAnywhere 웹 서버 설정</h3>
<p>PythonAnywhere 서버 H/W에도 웹 서버가 실행되고 있는데 이 웹 서버가 장고 프로그램을 인식할 수 있도록 웹 서버의 설정을 변경해야 한다. </p>
<h4 id="웹-서버-가동">웹 서버 가동</h4>
<p><img src="https://images.velog.io/images/jusung-c/post/6f258e1f-596e-4320-a6b7-77f896946038/image.png" alt=""></p>
<h4 id="설정-마법사-수정">설정 마법사 수정</h4>
<ol>
<li>Code 섹션
<img src="https://images.velog.io/images/jusung-c/post/9d35ee30-ad31-48b2-bd7e-05ec16e230ea/image.png" alt=""></li>
</ol>
<p>클릭 후 기존 내용 삭제 후 아래처럼 작성한다.
<img src="https://images.velog.io/images/jusung-c/post/7e1267ed-1ed9-423f-a097-bfb9bb93a441/image.png" alt=""></p>
<ol start="2">
<li><p>Virtualenv 섹션
<img src="https://images.velog.io/images/jusung-c/post/8488fdfc-e96f-4fc2-abfe-4844cbd9e9a3/image.png" alt=""></p>
</li>
<li><p>Static files 섹션
<img src="https://images.velog.io/images/jusung-c/post/8e423730-9770-472d-9bbd-77e9aa7e0568/image.png" alt=""></p>
</li>
<li><p>모든 설정 마친 후 Reload</p>
</li>
</ol>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(기초) - 김석훈님</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django 웹 서버 연동 원리]]></title>
            <link>https://velog.io/@jusung-c/Django-%EC%9B%B9-%EC%84%9C%EB%B2%84-%EC%97%B0%EB%8F%99-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@jusung-c/Django-%EC%9B%B9-%EC%84%9C%EB%B2%84-%EC%97%B0%EB%8F%99-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Wed, 18 Aug 2021 15:46:09 GMT</pubDate>
            <description><![CDATA[<p>개발한 것을 실제로 서비스하기 위해선 개발한 프로그램을 <strong>운영 환경에 배포</strong>하고 실행해야 한다. 개발 환경에서 운영 환경으로 옮겨가기 위해선 <strong>개발 시 지정했던 설정 사항을 변경</strong>해줘야 하고 <strong>운영 환경의 웹 서버에서도 만든 애플리케이션을 인식할 수 있도록 설정 사항 변경</strong>이 필요하다.</p>
<h3 id="장고의-wsgipy-파일">장고의 wsgi.py 파일</h3>
<p>startproject 명령어를 통해 프로젝트의 뼈대를 만들 때 wsgi.py 파일이 만들어진다. 이 모듈이 <strong>장고와 웹 서버를 연결하는 데 필요한 파일</strong>로 <strong>WSGI 규격에 따라 호출 가능한 애플리케이션 객체를 정의</strong>하고 있다. </p>
<p><strong>객체명은 반드시 application</strong>이어야 한다.
<em>장고의 wsgi.py 파일</em></p>
<pre><code class="language-py">application = get_wsgi_application()</code></pre>
<p>application 객체는 <strong>아파치와 같은 운영 웹 서버 뿐만 아니라 runserver에서도 같이 사용하는 객체</strong>다. <strong>다른 점은 application 객체의 위치를 지정하는 방식</strong>으로 아파치나 NGINX/uWSGI 설정 파일에서 지정하고, 즉 httpd.conf 설정 파일의 WSGIScriptAlias 지시자 또는 uwsgi.ini 설정 파일의 module 항목으로 지정하고, 개발용 runserver에서는 settings 모듈의 WSGI_APPLICATION 변수로 지정한다. </p>
<p>웹 서버는 이 application 객체를 호출하여 장고의 애플리케이션을 실행하는데 그 전에 <strong>현재의 장고 프로젝트 및 프로젝트에 포함된 모든 애플리케이션에 대한 설정 정보를 로딩하는 작업이 필요</strong>하다.</p>
<p>설정 정보는 담고 있는 settings 모듈의 위치를 아래와 같이 <strong>wsgi.py 파일</strong>에서 지정해준다.</p>
<pre><code class="language-py">import os

from django.core.wsgi import get_wsgi_application

# settings 모듈의 위치를 testsite/wsgi.py 파일에서 지정함
os.environ.setdefault(&#39;DJANGO_SETTINGS_MODULE&#39;, &#39;testsite.settings&#39;)

application = get_wsgi_application()</code></pre>
<h3 id="장고의-wsgi-인터페이스">장고의 WSGI 인터페이스</h3>
<p>운영 환경에서는 <strong>NGINX와 같은 웹 서버 프로그램이 클라이언트 요청을 수신</strong>하므로, <strong>요청을 수신한 웹 서버가 uWSGI와 같은 WAS 서버를 통해 장고 웹 애플리케이션을 호출</strong>할 수 있어야 한다.</p>
<ul>
<li>웹 서버: Apache httpd 또는 nginx와 같은 웹 서버 프로그램</li>
<li>웹 애플리케이션 서버: uwsgi, gunicorn과 같은 WAS 서버 프로그램</li>
</ul>
<p>장고는 <strong>파이썬 웹 애플리케이션을 만들어주는 프레임워크</strong>라고 할 수 잇다. 그래서 장고로 만든 프로그램은 <strong>WAS 서버에서 호출될 수 있도록 WSGI 규격을 준수</strong>해야 하는데 startproject 명령을 실행하면 자동으로 생성되는 <strong>wsgi.py 파일이 이 역할</strong>을 한다.</p>
<p><em>testsite/wsgi.py</em></p>
<pre><code class="language-py">from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()</code></pre>
<p><em>~/site-packages/django/core/wsgi.py</em></p>
<pre><code class="language-py">def get_wsgi_application():
    return WSGIHandler()</code></pre>
<p><strong>WAS 서버가 장고 애플리케이션의 wsgi.py 파일을 호출하여 WSGIHandler 객체를 얻을 후 다시 호출해서 최종 응답을 생성하고 이를 웹 서버에 돌려주는 것이다.</strong></p>
<blockquote>
<p><em><strong>정리!</strong></em>
장고 : 웹 애플리케이션을 만들어주는 프레임 워크로 WSGI 규격의 애플리케이션 스펙을 구현하기 위해 wsgi.py 파일 제공
WSGI 서버 : application(wsgi.py 파일에서 정의) 호출자(장고에서는 이 호출자를 WSGIHandler 클래스로 정의)를 호출.</p>
</blockquote>
<p>WAS 서버가 장고 애플리케이션을 실행하기 위해서는 <strong>application 호출자가 정의된 wsgi.py 파일의 위치를 알아야 한다.</strong> 그래서 아파치, NGINX, uWSGI 등의 <strong>웹/WAS 서버의 설정 파일에는 application 호출자의 경로가 정의</strong>되어야 한다. </p>
<blockquote>
<p>개발용 웹 서버인 runserver도 WSGI 규격에 따라 application 호출자를 호출하는데 settings.py 파일에 정의된 WSGI_APPLICATION 항목으로 호출자의 경로를 파악한다.</p>
</blockquote>
<h3 id="운영-서버-적용-전-장고의-설정-변경-사항">운영 서버 적용 전 장고의 설정 변경 사항</h3>
<p>운영 서버에서 필요한 항목들이 제대로 설정되었는지 체크하는 명령이 있다. 운영 서버에 배포한 이후에는 아래 명령으로 설정 파일을 확인할 수 있다.</p>
<pre><code>python manage.py check --deploy</code></pre><p>SECRET_KEY는 개발 모드에서는 settings.py 파일에 하드코딩되어 있는데 이 항목은 프로젝트 내에서 암호화가 필요할 때 사용되는 항목으로 <strong>외부에 노출되면 안된다.</strong> 따라서 운영 모드에서는 환경 변수에 저장하거나 파일에 저장한 후 settings.py에서 아래처럼 부르는 게 좋다.</p>
<pre><code class="language-py"># SECURITY WARNING: keep the secret key used in production secret!
# SECRET_KEY = &#39;ixf!t98=p4ymrbua(!2nqar$qdysf-gs)7b_$v2spz(uoa27$3&#39;
# SECRET_KEY = os.environ[&#39;SECRET_KEY&#39;]
# 또는 
with open(os.path.join(BASE_DIR, &#39;www_dir&#39;, &#39;secret_key.txt&#39;)) as f:
    SECRET_KEY = f.read().strip()
# SECURITY WARNING: don&#39;t run with debug turned on in production!
</code></pre>
<p>개발 모드에서는 에러 발생 시 디버그를 위해 브라우저에서 여러 가지 정보를 출력해서 보여주는데 이 정보는 <strong>프로젝트에 관련된 중요 정보들이므로 운영 모드에서는 settings 모듈의 DEBUG 값을 False로 셋팅하여 노출되지 않도록 해야한다.</strong></p>
<pre><code class="language-py">DEBUG = False</code></pre>
<p><strong>DEBUG = False로 설정되어 있으면 반드시 settings 모듈의 ALLOWED_HOSTS 항목을 설정해야 한다.</strong> 악의적인 공격자가 HTTP Host 헤더를 변조하여 CSRF 공격하는 것을 방지하기 위해서이다. 장고가 실행되는 서버의 IP 주소나 도메인 명을 등록한다. </p>
<pre><code class="language-py">ALLOWED_HOSTS = [ &#39;192.168.56.101&#39; ]</code></pre>
<p>개발 서버에서는 이미지, jsp, CSS 등의 정적 파일들을 알아서 찾아줬지만 운영 모드에서는 <strong>아파치와 같은 웹 서버에게 정적 파일들이 어디에 있는지 알려줘야 한다.</strong> settings 모듈의 STATIC_ROOT 항목은 장고의 collectstatic 명령 실행 시 정적 파일들을 한곳에 모아주는 디렉토리이다.</p>
<pre><code>STATIC_ROOT = os.path.join(BASE_DIR, &#39;www_dir&#39;, &#39;static&#39;)
</code></pre><p>collectstatic 명령은 settings 모듈의 STATICFILES_DIRS 항목에, STATIC_ROOT 항목에서 정의된 디렉토리가 포함되면 안된다는 것을 주의해야 한다. STATICFILES_DIRS 항목에 정의된 디렉토리에서 정적 파일을 찾아 STATIC_ROOT 디렉토리에서 복사해주기 때문이다.</p>
<pre><code>$ python manage.py collectstatic</code></pre><p>개발 모드에서는 runserver를 실행시킨 사용자의 권한으로 DB 파일이나 로그 파일을 엑세스한다. <strong>그러나 운영 모드에서는 프로세스의 소유자 권한으로 해당 파일을 엑세스할 수 있어야 한다</strong>. settings 모듈의 DATABASES 항목에서 NAME속성값의 경로를 db/db.sqlite3로 변경해주고 해당 디렉토리 및 파일의 엑세스 권한을 아래처럼 변경해준다.</p>
<pre><code>DATABASES = {
    ...
    &#39;NAME&#39;: os.path.join(BASE_DIR, &#39;db&#39;, &#39;db.sqlite3&#39;),
}</code></pre><pre><code>mkdir db
mv db.sqlite3 db/
chmod 777 db/
dhmod 666 db/db.sqlite3</code></pre><p>웹 환경에서는 캐시 서버와 DB 서버가 사용되는 경우가 많은데 이들 서버는 <strong>보안 기능이 취약</strong>하므로 <strong>외부에서 직접 엑세스하지 않고 웹 서버 또는 WAS 서버에서만 연결하도록 제한</strong>하는 게 좋다. </p>
<p>또한 <strong>DB 접속 패스워드</strong>도 개발 모드에서는 settings.py 파일에 하드코딩되어 있는데 운영 모드에서는 <strong>다른 곳에서 저장</strong>해야 한다. </p>
<blockquote>
<p>프로젝트에 메일을 발송하는 기능이 있다면 장고는 발신자 주소를 디폴트로 root@localhost 및 webmaster@localhost로 지정한다. SERVER_EMAIL 및 DEFAULT_FROM_EMAIL 설정 항목을 사용해 발신자 주소를 변경하는 게 좋다.</p>
</blockquote>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(기초) - 김석훈님</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[voting 애플리케이션 - 클래스 뷰로 변경]]></title>
            <link>https://velog.io/@jusung-c/voting-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%ED%81%B4%EB%9E%98%EC%8A%A4-%EB%B7%B0%EB%A1%9C-%EB%B3%80%EA%B2%BD</link>
            <guid>https://velog.io/@jusung-c/voting-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%ED%81%B4%EB%9E%98%EC%8A%A4-%EB%B7%B0%EB%A1%9C-%EB%B3%80%EA%B2%BD</guid>
            <pubDate>Sat, 14 Aug 2021 16:58:13 GMT</pubDate>
            <description><![CDATA[<h3 id="urlconf-코딩">URLconf 코딩</h3>
<p><img src="https://images.velog.io/images/jusung-c/post/a886c9a4-ca0e-486a-8e3e-fb82b396bce7/image.png" alt=""></p>
<p><em>voting/urls.py 파일 수정</em></p>
<pre><code class="language-py">...
urlpatterns = [
    # /voting/
    path(&#39;&#39;, views.IndexView.as_view(), name=&#39;index&#39;),

    # /voting/99
    path(&#39;&lt;int:pk&gt;/&#39;, views.DetailView.as_view(), name=&#39;detail&#39;),

    # /voting/99/results/
    path(&#39;&lt;int:pk&gt;/results/&#39;, views.ResultsView.as_view(), name=&#39;results&#39;),

    # /voting/99/vote/
    path(&#39;&lt;int:question_id&gt;/vote/&#39;, views.vote, name=&#39;vote&#39;),

]</code></pre>
<h3 id="view-코딩">View 코딩</h3>
<p>함수형 뷰 <strong>-&gt;</strong> 클래스형 뷰</p>
<p><img src="https://images.velog.io/images/jusung-c/post/f36022bb-c77c-4a65-b88d-05f031ffa5d2/image.png" alt=""></p>
<p><em>voting/views.py 파일 수정</em></p>
<pre><code class="language-py">from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic

from voting.models import Question, Choice

# ListView를 상속받는 경우 객체가 들어있는 리스트를 구성해서
# 이를 컨텍스트 변수로 템플릿 시스템으로 넘겨주면 되는데
# 이 리스트가 모든 테이블의 레코드 구성이라면 모델 클래스만 지정
# 아니면 get_queryset() 메소드 오버라이딩하여 원하는 리스트 구성
class IndexView(generic.ListView):
    template_name = &#39;voting/index.html&#39;
    # 컨텍스트 변수명 지정
    context_object_name = &#39;latest_question_list&#39;

    def get_queryset(self):
        # 최근 생성된 질문 5개 반환
        return Question.objects.order_by(&#39;-pub_date&#39;)[:5]

class DetailView(generic.DetailView):
    # Question 테이블로부터 특정 레코드를 가져와 컨텍스트 변수 구성
    # 컨텍스트 변수명은 디폴트 값을 사용하고
    # object와 모델명 소문자인 quesiton 둘 다 가능
    model = Question
    template_name = &#39;voting/detail.html&#39;

class ResultsView(generic.DetailView):
    # Choice가 아닌 Question 객체를 넘겨준다.
    # Question 객체를 구해서 해당 객체와 FK로 연결된 Choice를 구한다.
    # 이 로직은 results.html 템플릿 파일에서
    # question.choice_set.all() 구문으로 구현되어 있다.
    model = Question
    template_name = &#39;voting/results.html&#39;

def vote(request, question_id):
    # Choice 테이블을 검색한다. request.POST는 제출된 폼의 데이터를 담고 있는
    # 객체로서 key로 그 값을 구할 수 있다.
    # request.POST[&#39;choice&#39;]는 폼 데이터에서 키가 &#39;choice&#39;에 해당하는 값인
    # choice.id를 스트링으로 리턴한다.
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST[&#39;choice&#39;])

    # &#39;choice&#39;라는 키가 없으면 KeyError 익셉션 발생
    # 검색 조건에 맞는 객체가 없으면 Choice.DoesNotExist 익셉션 발생
    except (KeyError, Choice.DoseNotExist):
        # 익셉션이 발생하면 render() 함수에 의해 question과 error_message
        # 컨텍스트 변수를 detail.html 템플릿으로 전달
        # 에러 메세지와 함께 질문 항목 폼을 다시 보여줘서 재입력할 수 있도록 함
        return render(request, &#39;voting/detail.html&#39;, {
            &#39;question&#39;: question,
            &#39;error_message&#39;: &quot;You didn&#39;t select a choice&quot;,
        })
    else:
        selected_choice.votes += 1

        # 변경 사항 Choice 테이블 저장
        selected_choice.save()

        # POST 데이터를 정상적으로 처리했으면
        # 항상 HttpResponseRedirect를 반환하여 리다이렉션 처리
        # 최종적으로 vote() 뷰 함수는 리다이렉트할 타겟 URL을 담은
        # HttpResponseRedirect 객체 반환
        return HttpResponseRedirect(reverse(&#39;voting:results&#39;, args=(question.id,)))
</code></pre>
<h3 id="template-코딩">Template 코딩</h3>
<p>상속 기능 추가하기
base.html을 이미 코딩했기 때문에 이를 상속 받는 base_voting.html 템플릿 파일을 만들고 기존 각 템플릿 파일에서 base_voting.html 템플릿을 상속받으면 된다.</p>
<p><em>base_voting.html</em></p>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

&lt;title&gt;{% block title %}Voting Application Site{% endblock %}&lt;/title&gt;

{% block sidebar %}
{{ block.super }}
&lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;/voting/&quot;&gt;Voting_Home&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
{% endblock%}</code></pre>
<p><em>voting/index.html 수정</em></p>
<pre><code class="language-html">{% extends &quot;base_books.html&quot; %}

{% block content %}
    &lt;h2&gt;Voting Question List&lt;/h2&gt;
...

{% endblock content %}</code></pre>
<p><em>voting/detail.html 수정</em></p>
<pre><code class="language-html">{% extends &quot;base_voting.html&quot; %}

{% block content %}

&lt;h1&gt;{{ question.question_text }}&lt;/h1&gt;

...
{% endblock content%}</code></pre>
<p><em>voting/results.html 수정</em></p>
<pre><code>{% extends &quot;base_voting.html&quot; %}

{% block content %}

...
{% endblock content%}</code></pre><h3 id="로그-추가">로그 추가</h3>
<p>settings.py 파일에 로깅 설정을 해주고 로거를 취득해서 로그 기록을 원하는 곳에서 로거의 메소드를 호출하면 된다.</p>
<p>로깅 설정에서 배웠던 코드 그대로 적용하면 된다. </p>
<p><em>settings.py 파일 수정</em></p>
<pre><code class="language-py">...
LOGGING = {
    &#39;version&#39;: 1,
    &#39;disable_existing_loggers&#39;: False,
    &#39;formatters&#39;: {

        # [로그 메시지를 기록한 시간], 로그 레벨 이름
        # [로거이름:라인번호], 로그 메시지 순서로 출력
        &#39;verbose&#39;: {
            &#39;format&#39;: &quot;[%(asctime)s %(levelname)s [%(name)s:%(lineno)s] %(message)s&quot;,
            &#39;datefmt&#39;: &quot;%d%b%Y %H:%M:%S&quot;
        }
    },
    &#39;handlers&#39;: {
        # DEBUG 이상의 메시지를 파일로 출력해주는 FileHandler 사용
        # 로그가 기록되는 파일 이름은 D:web_programing\web_programing\logs\testsite.log
        &#39;file&#39;: {
            &#39;level&#39;: &#39;DEBUG&#39;,
            &#39;class&#39;: &#39;logging.FileHandler&#39;,
            &#39;filters&#39;: os.path.join(BASE_DIR, &#39;logs&#39;, &#39;testsite.log&#39;),
            # 위에서 정의한 verbose 포맷터 사용
            &#39;formatter&#39;: &#39;verbose&#39;
        },
    },
    &#39;loggers&#39;: {
        # testsite 로거 이름 대신에 voting 로거 이름으로 바꿨다.
        # views.py 파일에서 __name__변수로 로거를 취득하기 위함이다.
        &#39;voting&#39;: {
            &#39;handlers&#39;: [&#39;file&#39;],
            &#39;level&#39;: &#39;DEBUG&#39;,
        },
    }
}</code></pre>
<p><em>voting/views.py 수정</em></p>
<pre><code class="language-py"># logging 추가
import logging
# getLogger(__name__) 메소드를 호출해서 voting.views 로거 객체 취득
logger = logging.getLogger(__name__)

...

def vote(request, question_id):
    # 로거 객체의 debug 메소드를 호출해서 로거에게
    # DEBUG 수준으로 로그 레코드를 생성하도록 요청
    # 로거는 앞에서 수정한 settings.py 파일의 로깅 설정에 따라
    # file 핸들러를 사용하여  로그 메시지를 기록한다.
    logger.debug(&quot;vote().question_id: %s&quot; % question_id)

    ...
</code></pre>
<blockquote>
<p>logs 디렉토리가 없다면 만들어줘야 한다.
<img src="https://images.velog.io/images/jusung-c/post/54474c11-922b-43eb-840a-750dfc3b9b3d/image.png" alt=""></p>
</blockquote>
<p><img src="https://images.velog.io/images/jusung-c/post/7f69e37f-dba7-45aa-a16a-c2b667653100/image.png" alt=""></p>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(기초) - 김석훈님</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[실습확장 - 첫 페이지 설계]]></title>
            <link>https://velog.io/@jusung-c/%EC%8B%A4%EC%8A%B5%ED%99%95%EC%9E%A5-%EC%B2%AB-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@jusung-c/%EC%8B%A4%EC%8A%B5%ED%99%95%EC%9E%A5-%EC%B2%AB-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Sat, 14 Aug 2021 15:17:57 GMT</pubDate>
            <description><![CDATA[<p>각 애플리케이션의 첫 페이지들은 만들었지만 프로젝트의 첫 페이지인 루트(/) URL에 대한 처리 로직은 아직 개발하지 못한 상태</p>
<h3 id="프로젝트-첫-페이지">프로젝트 첫 페이지</h3>
<p>네비게이션 항목 중 [Project_Home] 항목을 클릭했을 때 에러가 발생한 이유가 프로젝트 첫 페이지인 루트(/) URL에 대한 처리 로직이 없기 때문이다.</p>
<br>

<p><em><strong>로직 설계</strong></em>
<img src="https://images.velog.io/images/jusung-c/post/ceabf829-0600-4e38-87ef-3912451f5130/image.png" alt=""></p>
<p><em><strong>UI 설계</strong></em>
<img src="https://images.velog.io/images/jusung-c/post/de7cc510-da6a-4467-8ae5-639cb2587863/image.png" alt=""></p>
<h3 id="urlconf-코딩">URLconf 코딩</h3>
<p>테이블은 변경사항이 없으므로 모델 코딩은 필요 없다.</p>
<p>애플리케이션에 대한 URL이 아닌 프로젝트에 대한 URL이므로 testsite/urls.py 파일에 루트(/) URL 및 임포트 문장, 두 줄만 추가하면 된다.</p>
<pre><code class="language-py">    path(&#39;&#39;, views.HomeView.as_view(), name=&#39;home&#39;),</code></pre>
<h3 id="view-코딩">View 코딩</h3>
<p>앞에서 정의한 HomeView 코딩. 프로젝트와 관련된 뷰이므로 testsite/views.py 파일에 코딩한다.</p>
<pre><code class="language-py">from django.views.generic import TemplateView

# TemplateView
class HomeView(TemplateView):

    # TemplateView 제네릭 뷰를 상속받을 경우 template_name 필수
    # 템플릿 파일이 위치하는 디렉토리는 settings.py 파일의 
    # TEMPLATES 항목에 리스트 요소로 추가되어 있다.
    template_name = &#39;home.html&#39;

    # 템플릿 시스템으로 넘겨줄 컨텍스트 변수는 
    # get_context_data() 메소드를 오버라이딩해서 정의
    def get_context_data(self, **kwargs):
        # get_context_data() 메소드 정의시 super() 메소드 호출 필수
        context = super().get_context_data(**kwargs)
        # testsite 프로젝트 하위에 있는 애플리케이션들의 리스트들을
        # 보여주기 위해 컨텍스트 변수 app_list에 담아서 
        # 템플릿 시스템에 넘겨준다.
        context[&#39;app_list&#39;] = [&#39;polls&#39;, &#39;books&#39;]
        return context</code></pre>
<h3 id="template-코딩">Template 코딩</h3>
<p>home.html 템플릿은 프로젝트 템플릿이므로 상속에 사용하는 부모 템플릿의 위치와 동일한 디렉토리에 생성</p>
<pre><code class="language-html">{% extends &#39;base_books.html %}

{% block content %}
    &lt;h2&gt;shkim Django Application&lt;/h2&gt;
    &lt;ul&gt;
        {% for appname in app_list %}
            {% with appname|add:&quot;:&quot;|add:&quot;index&quot; as urlvar %}
                &lt;li&gt;&lt;a href=&quot;{% url urlvar %}&quot;&gt;{{ appname }}&lt;/a&gt;&lt;/li&gt;
            {% endwith %}
        {% endfor%}
    &lt;/ul&gt;
{% endblock content %}</code></pre>
<p>뷰로부터 app_list 컨텍스트 변수를 전달받아 app_list에 있는 appname들을 하나씩 순회하면서 화면에 보여준다.
애플리케이션명을 클릭 시 접속할 URL을 추출하기 위해 {% url urlvar %} 태그를 사용했고, urlvar 인자는 {% with %} 태그를 사용해 정의한다. </p>
<blockquote>
<p>예를 들어, 애플리케이션명이 books라면 urlvar는 books:index가 된다.</p>
</blockquote>
<h3 id="appspy-활용">apps.py 활용</h3>
<p>프로젝트의 전반적인 항목들을 설정하는 곳 : settings.py
각 앱마다 필요한 항목을 설정하는 곳 : apps.py</p>
<p>앱의 별칭을 부여하는 예제를 통해 apps.py 파일 기능 활용하기</p>
<p><em>books/apps.py 파일 수정</em></p>
<pre><code class="language-py">...

class BooksConfig(AppConfig):
    ...
    verbose_name = &#39;Book-Author-Publisher App&#39;
</code></pre>
<p>books 앱의 설정 클래스인 BooksConfig의 속성 중 하나인 verbose_name을 정의했다.</p>
<p><em>testsite/views.py 파일 수정</em></p>
<pre><code class="language-py">...
    def get_context_data(self, **kwargs):
        ...

        # 이 라인 대신 아래 5라인 추가
        # context[&#39;app_list&#39;] = [&#39;voting&#39;, &#39;books&#39;]
        dictVerbose = {}
        # apps 객체의 get_app_configs() 메소드를 호출하면
        # settings.py 파일의 INSTALLED_APPS에 등록된
        # 각 앱의 설정 클래스들을 담은 리스트를 반환
        for app in apps.get_app_configs():
            # app.path는 각 설정 클래스의 path 속성으로 
            # 애플리케이션의 물리적 경로를 뜻한다.
            # site-packages 문자열이 있으면 외부 앱이므로 제거
            if &#39;site-packages&#39; not in app.path:
                # 설정 클래스의 label 속성값을 key로 
                # verbose_name 속성값을 value로 해서 dicVerbose 사전에 등록
                dictVerbose[app.label] = app.verbose_name
        # verbose_dict 컨텍스트 변수에 dictVerbose 사전 대입        
        context[&#39;verbose_dict&#39;] = dictVerbose
        return context</code></pre>
<p><em>tempaltes/home.html 수정</em></p>
<pre><code class="language-html">...
&lt;!--    ul 부분을 수정   --&gt;
    &lt;ul&gt;
        {% for key, value in verbose_dict.items %}
                &lt;li&gt;&lt;a href=&quot;{% url key|add:&#39;:index&#39; %}&quot;&gt;{{ value }}&lt;/a&gt;&lt;/li&gt;
        {% endfor%}
    &lt;/ul&gt;
{% endblock content %}</code></pre>
<p>뷰로부터 전달받은 컨텍스트 변수 verbose_dict 사전을 순회하기 위해 items() 메소드를 호출한다.
key : 설정 클래스의 label 속성값
value : 설정 클래스의 verbose_name 속성값</p>
<p><img src="https://images.velog.io/images/jusung-c/post/e39dce2d-6cb6-4a4a-aa85-033acc1cfa25/image.png" alt=""></p>
<br>
<br>
<br>
<br>

<blockquote>
<p>출처: Django로 배우는 파이썬 웹 프로그래밍(기초) - 김석훈님</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>