<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev_dolxegod.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 05 Sep 2021 04:23:34 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev_dolxegod.log</title>
            <url>https://velog.velcdn.com/images/dev_dolxegod/profile/7eee60fc-4e8e-4884-814b-699b03603721/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev_dolxegod.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_dolxegod" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Django] 알고 사용하자! Queryset 카운트 내부 동작]]></title>
            <link>https://velog.io/@dev_dolxegod/Django-%EC%95%8C%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90-Queryset-%EC%B9%B4%EC%9A%B4%ED%8A%B8-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91</link>
            <guid>https://velog.io/@dev_dolxegod/Django-%EC%95%8C%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90-Queryset-%EC%B9%B4%EC%9A%B4%ED%8A%B8-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91</guid>
            <pubDate>Sun, 05 Sep 2021 04:23:34 GMT</pubDate>
            <description><![CDATA[<h1 id="소개-📄">소개 📄</h1>
<p>어느 서비스에서든 절대 빠질 수 없는 로직 중 하나는 바로 집계 로직입니다.
총 포스트 개수, 총 유저 수, 활동 중인 유저 수 등... 
어떤 간단한 서비스더라도, 특정 조건에 부합하는 데이터가 총 몇 개인지 집계하는 일은 항상 필요하죠.</p>
<p>Django 에서는 강력한 내부 API를 이용하여 여러 방법으로 집계 로직들을 아주 간단하게 처리할 수 있습니다.</p>
<p>하지만 방법마다 내부 동작이 다르다는 사실, 혹시 알고 사용하고 계셨나요? 🧐</p>
<p>이번 포스트에서는 Django에서 Queryset을 집계할 때, 내부적으로 어떻게 동작하는지 소개해드리려고 합니다.
⠀</p>
<h1 id="들어가기-전에-🎫">들어가기 전에 🎫</h1>
<h2 id="세팅">세팅</h2>
<ul>
<li>MySQL 8.0 버전을 이용하였습니다.</li>
<li>Django 3.2 버전을 이용하였습니다.</li>
<li>Python 3.9.6 버전을 이용하였습니다.</li>
<li>테스트 테이블을 생성하였습니다. 
<em>id | int | auto_increment primary_key
name | varchar | null = True
type | varchar | null = True
(스토리지 엔진: InnoDB)</em></li>
<li>테스트 테이블 내 총 780만 개의 더미 데이터를 삽입하였습니다.
<em>(각 방법 별 내부 동작과 성능 차이를 보다 확실히 확인하기 위해 많은 양의 더미 데이터를 이용했습니다.)</em></li>
</ul>
<h2 id="가정">가정</h2>
<p>별도의 조건 없이 모델의 모든 데이터의 수를 집계한다고 가정합니다.</p>
<p>⠀</p>
<h1 id="queryset을-집계하는-대표적인-방법-🗄">Queryset을 집계하는 대표적인 방법 🗄</h1>
<p>국내외 블로그, GitHub 등에서 가장 많이 볼 수 있었던 세 가지 방법을 예제로 사용해보려 합니다.</p>
<h2 id="1-lenqueryset">1. len(Queryset)</h2>
<pre><code class="language-python">queryset1 = Model.objects.all()
count = len(queryset1)</code></pre>
<h2 id="2-querysetcount">2. Queryset.count</h2>
<pre><code class="language-python">queryset1 = Model.objects.all()
count = queryset1.count()</code></pre>
<h2 id="3-querysetaggregate-count">3. Queryset.aggregate Count</h2>
<pre><code class="language-python">from django.db.models import Count

queryset1 = Model.objects.all()
count = queryset1.aggregate(count=Count(&#39;id&#39;))</code></pre>
<p>⠀
독자 여러분들께서 장고를 사용해보셨다면, 아마 세 방법 중 하나 이상은 한 번쯤 보신 경험이 있으리라 생긱됩니다.</p>
<h1 id="내부에선-어떻게-동작할까요--⚙️">내부에선 어떻게 동작할까요 ? ⚙️</h1>
<h2 id="lenqueryset">len(Queryset)</h2>
<p>Django의 Queryset 은 Django가 정의한 사용자 정의 클래스입니다.
그리고 이 Queryset 클래스 안에는, 특별 메서드 <code>__len__</code> 이 별도의 사용자 정의 메서드로 구현되어 있습니다.</p>
<p>때문에, 반환된 Queryset에 len 함수를 사용했을 때는 Queryset 클래스 안에 구현된 사용자 정의 특별 메서드 len 을 호출하게 됩니다.
<img src="https://images.velog.io/images/dev_dolxegod/post/c67456d4-5963-4f94-b11a-4d29beb0ff0a/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-09-04%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%203.58.38.png" alt="">
<em>* ( Queryset 클래스 내의 사용자 정의 특별 메서드 <code>__len__</code> )</em></p>
<p>메서드의 첫 줄을 보니 <code>self._fetch_all</code> 이라는 심상치 않은 친구가 있네요.
보통 fetch의 의미는 실제로 반영하고, 가져온다는 의미로 사용되는데요...
계속해서 <code>_fetch_all</code> 을 확인 해보겠습니다. </p>
<p><img src="https://images.velog.io/images/dev_dolxegod/post/820cf492-467e-40f8-9737-a80eb7247f60/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-09-05%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%204.27.27.png" alt="">
<em>* ( django/db/models/query.py - Queryset._fetch_all )</em></p>
<p>쿼리가 evaluate 되지 않아 캐시(_result_cache) 가 비어있다면 쿼리를 evaluate 시킨 후, 모델 객체 list 형태로 모두 캐싱하는 로직이 구현되어 있습니다.</p>
<p>위 로직이 모두 끝나면, <code>__len__</code> 에서 캐시 변수의 길이를 integer 타입으로 반환합니다.</p>
<p><strong>이렇게 len(Queryset)을 할 시엔 쿼리를 evaluate 시키고 반환된 모델 객체 list를 전부 캐싱까지 한 다음 해당 캐시의 길이를 반환하게 됨으로, <code>SELECT * FROM table</code> 을 실행하는 것을 알 수 있습니다.</strong></p>
<blockquote>
<p><code>SELECT * FROM table</code> 을 한다고 했지만
사실 쿼리를 hit 할 뿐만 아니라, 쿼리 결과를 iter 하며 Model 객체로 컨버팅 후 cache에 담는 시간 또한 치명적입니다 
⠀
위 로직에 대해 더 자세히 보고싶으시다면 <a href="https://github.com/django/django/blob/68b8eda78864b78d1b8b2529ec119ec440f8458a/django/db/models/query.py#L42">Django ModelIterable Class</a> 를 확인해보세요 :)</p>
</blockquote>
<h2 id="querysetcount">Queryset.count</h2>
<p>사실 Queryset에 len 메서드를 사용하는 경우는 극히 드물긴 합니다.(ㅎㅎ;;)</p>
<p>대부분은 거의 지금 소개해드릴 Queryset.count 또는 aggregate.Count를 많이 사용하곤 하는데요, 
두 API의 동작 과정도 미묘하게 다릅니다.
<img src="https://images.velog.io/images/dev_dolxegod/post/c456d218-647e-4c50-bd40-4d12ce547497/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-09-04%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.04.39.png" alt="">
<em>* ( django/db/models/query.py - Queryset.count )</em>
Queryset 클래스 내부의 인스턴스 메서드 count가 구현되어 있는 곳으로 왔습니다.
친절하게 주석이 잘 달려 있네요. 대충 해석을 해볼까요?</p>
<blockquote>
<p>SELECT COUNT() 를 실행하고 레코드의 개수를 integer 형태로 반환한다.
⠀
만약 쿼리셋이 이미 캐시되어 있다면 SELECT COUNT(*)를 중복 호출하는 행위를 방지하기 위해 캐시의 길이를 그대로 반환한다.</p>
</blockquote>
<p>미리 정의한 가정에 따라 count만 진행할 예정이기 때문에 Queryset이 캐싱 되어 있을 리 만무합니다. 
또한 카운트 쿼리를 날리는 것 정도는 까보지 않아도 이미 알고 있었던 내용입니다. </p>
<p>우리가 궁금한 것은 좀 더 세부적인 동작이기 때문에 좀 더 내부로 들어가보도록 하겠습니다.</p>
<p>계속해서 <code>self.query.get_count()</code> 가 구현되어 있는 곳으로 가보겠습니다.
<img src="https://images.velog.io/images/dev_dolxegod/post/d438c1d9-d922-46fe-8392-60c40f8c0d31/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-09-04%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.07.17.png" alt="">
<em>* ( django/db/models/sql/query.py - Queryset.get_count )</em></p>
<p>Count라는 익숙한 친구가 보이네요...? 맞습니다. aggregate의 Count 클래스입니다.
Count 생성자의 첫 번째 인자로 <code>star(*)</code>를 전달하며, <code>COUNT(*)</code> 쿼리를 만들도록 유도하고 있습니다.</p>
<p>또한 쿼리 결과를 dict 로 반환하는 <code>get_aggregation</code> 의 결과값에 바로 접근하여,
aggregate Count 방식과 다르게 결과 값을 바로 integer 타입으로 반환합니다.</p>
<p><strong>이렇게 Queryset.count는 결국 최종적으로 aggregate Count를 호출하게 되며,
<code>SELECT COUNT(*) FROM table</code> 을 실행하는 것을 알 수 있습니다.</strong></p>
<h2 id="querysetaggregate-count">Queryset.aggregate Count</h2>
<p>장고에서 제공해주는 aggregate API 입니다.</p>
<p>직접 사용하기 위해서는 아래와 같이 별도 import가 필요합니다.
<code>from django.db.models import Count</code></p>
<p>aggregate Count를 이용하기 위해선 특정 필드를 반드시 설정해야 합니다.</p>
<p>설정 후에는 Queryset.aggregate에서 전달 받은 aggregate API와 <em>(현재는 Count)</em> 전달 받은 field를 분석 후,
최종적으로 <code>get_aggregation</code>에서 쿼리를 실행하고 결과값을 dict 타입으로 반환합니다.</p>
<p><strong>이렇게 Queryset.aggregate(Count())는 <code>SELECT COUNT(field) FROM table</code> 을 실행하는 것을 알 수 있습니다.</strong></p>
<h3 id="중요-qurysetaggregate-count는-쿼리의-캐싱-여부를-확인하지-않는다">(중요) Quryset.aggregate Count는 쿼리의 캐싱 여부를 확인하지 않는다.</h3>
<p>놓치면 안되는 정말 중요한 내용이 하나 더 있습니다.</p>
<p>이번에는 쿼리가 캐싱 되어있지 않다는 가정 하에 포스팅을 작성하여 확인할 기회가 없었지만,
실무에서 모르고 사용했다가는 치명적일 수 있는  내용입니다.</p>
<p>아까 <code>Queryset.count</code> 메서드에</p>
<blockquote>
<p>만약 쿼리셋이 이미 캐시되어 있다면 SELECT COUNT(*)를 중복 호출하는 행위를 방지하기 위해 캐시의 길이를 그대로 반환한다.</p>
</blockquote>
<p>라고 작성되어있던 주석이 기억나시나요?
맞습니다. count는 그랬는데요, aggregate에는 캐시를 확인하는 로직이 별도로 없습니다.</p>
<p>쿼리가 evaluate 되어 캐시에 모두 저장되어있다 해도, aggregate는 상관하지 않고 매 번 새로운 count 쿼리를 날립니다.
반면, count는 쿼리가 evaluate 되어있으면 새로 쿼리를 날리지 않고 캐시의 길이를 반환합니다.</p>
<p>쿼리가 캐싱된 이후 실시간으로 create 후 다시 count를 해야 하거나 하는 로직을 작성하실 때, 주의하세요 !</p>
<h3 id="여담-querysetaggregate-count">(여담) Queryset.aggregate count(*)</h3>
<p>✅ 알고 계시는 분들은 알고 계시겠지만 ! aggregate 에서도 <code>Queryset.aggregate(count=Count(&#39;*&#39;))</code> 를 이용해서 특정 필드를 지정하지 않고 스타 표현식을 사용할 수 있어요</p>
<p>🚫 단, <code>count=Count(&#39;id&#39;, filter=Q(id__gte=500))</code> 처럼 Count에 filter 인자를 넘기는 경우엔 스타를 사용할 수 없으니 주의하세요 !</p>
<h1 id="성능-테스트-📟">성능 테스트 📟</h1>
<p>대략적으로 설명은 끝났지만, 역시 직접 눈으로 확인해보는게 좋습니다.
테스트 조건은 세팅과 가정에 명시되어있는 그대로 780만 건의 데이터를 조건 없이 모두 count 하였습니다.</p>
<p>아래는 각 케이스 별로 총 5회씩 진행한 후, DB에 HIT된 쿼리와 평균 소요 시간입니다.</p>
<h3 id="lenqueryset-1">len(Queryset)</h3>
<p><code>SELECT id, name, type FROM table</code>
<strong><code>81.42s</code></strong></p>
<h3 id="querysetcount-1">Queryset.count</h3>
<p><code>SELECT COUNT(*) FROM table</code>
<strong><code>00.41s</code></strong></p>
<h3 id="querysetaggregate-count-1">Queryset.aggregate Count</h3>
<p><code>SELECT COUNT(id) FROM table</code>
<strong><code>00.49s</code></strong></p>
<h1 id="정리-📖">정리 📖</h1>
<p>오늘 함께 알아본 내용을 정리해보겠습니다.</p>
<ul>
<li>len(Queryset)은 쿼리셋의 길이만 반환하는 것처럼 보이지만, 실제 내부 동작은 그렇지 않습니다. 모든 Queryset을 evaluate 시키기 때문에 사용에 주의하셔야겠습니다.</li>
<li>Queryset.count의 쿼리는 스타 표현식 Count(*) 으로 실행됩니다</li>
<li>Queryset.count는 궁극적으로 aggregate Count를 호출하게 됩니다.</li>
<li>Queryset.count는 Queryset이 캐싱 되어 있으면 COUNT 쿼리를 다시 실행하지 않습니다.</li>
<li>Queryset.aggregate Count는 필드를 지정해주어야 하지만, 스타 표현식도 이용할 수 있습니다.</li>
<li>Queryset.aggregate Count는 Queryset의 캐싱 여부와 상관 없이, 매 번 COUNT 쿼리를 실행합니다. </li>
</ul>
<hr>
<h1 id="마무리-🙇🏻♂️">마무리 🙇🏻‍♂️</h1>
<p>이번 글에서는 Queryset 집계 방법에 따른 내부 동작의 차이에 대해 알아보았습니다. 🙆🏻‍♂️</p>
<p>이 글을 보시는 독자분들 모두 장고를 사용해보셨더라면, 세 방법 중 하나 이상은 사용해보셨으리라 생각됩니다.</p>
<p>아마 Queryset의 개수만 세고자 사용하셨겠지만, 생각보다 내부 로직이 서로 많이 다르니 상황에 따라 시의적절하게 사용하시는 것이 좋을 것 같습니다.</p>
<p>제가 이해를 잘못한 부분이 있다고 생각되시거나 글에 이해가 힘든 부분이 있으시다면, 언제든 지적과 질문 부탁드리겠습니다 !</p>
<p>그럼, 오늘도 즐거운 코딩 데이 보내세요 ! 💻</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django Authentication System의 모든 것] #1 - 기본 User 모델]]></title>
            <link>https://velog.io/@dev_dolxegod/Django-Authentication-System%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83-1-authuser-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@dev_dolxegod/Django-Authentication-System%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83-1-authuser-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Sat, 03 Jul 2021 16:48:22 GMT</pubDate>
            <description><![CDATA[<h2 id="소개">소개</h2>
<p><em>*해당 시리즈는 독자분들이 Django에 대한 기본적인 경험 (장고 어드민, 장고 기본 로그인 구축 등)을 가지고 계시다는 가정 하에 작성된 글입니다. Django 자체를 전혀 사용해보시지 않으신 분들에겐 이해가 어려울 수 있습니다.</em></p>
<p><em>*해당 시리즈는 Django 3.2 버전을 기준으로 작성되었습니다.</em></p>
<p>장고를 경험하신 분들이라면, 처음 장고 프로젝트를 만든 후 첫 migrate를 했을 때 아래 사진처럼 Model에서 생성하지도 않은 (!) 테이블들이 DB에 잔뜩 생성된 경험이 있으실 겁니다.
<img src="https://images.velog.io/images/dev_dolxegod/post/ef5ce57b-29ac-4de5-92cb-21d0de27f0d3/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-28%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.29.27.png" alt=""></p>
<p>당황하지 마세요 :) 전부 여러분들의 빠른 웹 서비스 개발을 위해 Django에서 미리 만들어둔 선물이니까요.</p>
<p>Django에서는 프레임워크 사용자들의 빠른 웹 개발을 위해 다양한 모듈들을 지원하고 있고, 
탄탄한 <strong>User 모델과 인증(Authentication), 인가(Authorization) 시스템</strong>도 그 중 하나 입니다.
<em>*(보통 해당 기능들을 총괄하여 Django Authentication System 이라고 칭합니다.</em>)</p>
<p>그리고 이 Authentication System은 <strong>위에서 생성된 기본 테이블들과 매우 밀접한 관계로 동작</strong>하고 있습니다.</p>
<p>이에, 이번 시리즈에서는 기본 생성 테이블들과 Django Authentication System의 연관성, 동작 방식, 실전 사용에 대해서 소개해드리려 합니다.</p>
<p>하지만 이 글 한 곳에 모두 설명했다간, 포스트가 infinity scroll(?)이 될 수 있으니 😨
이번 글에선 기본 User 모델인 <strong>auth_user</strong>부터 차근차근 알아보려 합니다.</p>
<p>⠀</p>
<h2 id="user-모델-기본-필드-분석">User 모델 기본 필드 분석</h2>
<p>우선 User 모델의 구조부터 함께 보겠습니다.
<em>*(장고 공식 문서 일부를 직역 및 의역 하였습니다. 원문은 <a href="https://docs.djangoproject.com/en/3.2/ref/contrib/auth/">https://docs.djangoproject.com/en/3.2/ref/contrib/auth/</a> 를 참조해 주세요)</em></p>
<hr>
<p><strong>username (유저 명)</strong>_
필수, 최대 길이 150자.
알파벳과 숫자, 언더스코어(_), 엣(@), 플러스(+), 닷(.), 대쉬(-)를 포함할 수 있음.</p>
<blockquote>
<p>150 자의 최대 길이는 대부분의 경우를 만족할 겁니다. 만약 더 길게 설정해야 한다면 <strong>커스텀 유저 모델</strong>을 사용하세요. 만약 utf8mb4 인코딩이 적용된 MySQL을 사용하고 계시다면 최대 191자의 길이 까지도 설정이 가능합니다. </p>
</blockquote>
<p><strong>first_name (이름)</strong>
필수 아님 (blank=True), 최대 길이 150자</p>
<p><strong>last_name (성)</strong>
필수 아님 (blank=True), 최대 길이 150자</p>
<p><strong>email (이메일)</strong>
필수 아님 (blank=True)</p>
<p><strong>password (비밀번호)</strong>
필수</p>
<blockquote>
<p>장고는 비밀번호 평문을 그대로 저장하지 않고, <a href="https://en.wikipedia.org/wiki/PBKDF2">PBKDF2</a> 라는 기본 암호화 시스템을 제공하고 있습니다.
PBKDF2는 미 NIST에서 공인한 해쉬 컨테이너 알고리즘이며, 자세한 내용은 Naver D2의 <a href="https://d2.naver.com/helloworld/318732">안전한 패스워드 저장</a> 을 참고하시면 더욱 빠르게 이해하실 수 있으실 것 같습니다.</p>
</blockquote>
<p><strong>groups (그룹)</strong> 
<strong>&quot;Group&quot;</strong> 과 다대다 관계 <em>(Many-to-Many relationship)</em></p>
<p><strong>user_permissions (권한)</strong>
<strong>&quot;Permission&quot;</strong> 과 다대다 관계 <em>(Many-to-Many relationship)</em></p>
<p><strong>is_staff (스태프 여부)</strong>
Boolean 값. <em>( 최초 가입 시 Default False )</em></p>
<p><strong>is_active (활성 여부)</strong>
Boolean 값. <em>( 최초 가입 시 Default True )</em></p>
<p><strong>is_superuser (슈퍼유저 여부)</strong>
Boolean 값. <em>( 최초 가입 시 Default False )</em></p>
<p><strong>last_login (마지막 로그인 일자)</strong>
DATETIME </p>
<p><strong>date_joined (계정 생성일자)</strong>
DATETIME</p>
<hr>
<p>장고 기본 유저 모델은 이렇게 위 구조로 이루어져 있습니다.</p>
<p><strong>groups</strong>와 <strong>user_permissions</strong> 필드를 보시고 &quot;어? 내 테이블엔 없는데?&quot; 라고 걱정하지 않으셔도 됩니다.
두 필드의 내용은 실제 테이블 내 필드가 아닌, <strong>브릿지 테이블에서 이루어지는 다대다 관계를 설명하기 위함</strong>입니다.</p>
<p>그럼 브릿지 테이블은 어딨냐! Group은 뭐고 Permission은 또 뭐냐! 하실 수 있는데, 이번 시리즈의 다음 글에서 더 자세히 설명드릴 예정이니 너무 걱정하지 마시고 이번 글에선 보류하도록 하겠습니다.</p>
<p>근데 위 두 필드를 제외하더라도 is_active, is_staff, is_superuser 이 세 필드의 용도가 혼란스럽습니다 ! </p>
<p>해당 세 필드는 &quot;유저 개인 권한&quot;을 명시하기 위한 필드이며, 장고 공식문서에서 해당 필드에 대해 추가로 설명하는 바는 아래와 같습니다.</p>
<blockquote>
</blockquote>
<p><strong>is_active:</strong> 유저의 활성 여부를 판단하기 위한 필드입니다.
⠀
 계정을 지워야 할 일이 있을 때 실제 삭제보다는 이 필드 값을 False로 돌리는 것을 추천합니다. 삭제 시점에 서비스에 이미 이 기본 User 모델을 참조하는 외래키가 있을 시 영향을 끼치게 하지 않기 위함 입니다.
 ⠀
is_active 필드로 유저의 로그인 허용 여부를 항상 판단할 필요는 없습니다.
기본 backend인 ModelBackend와 RemoteUserBackend는 is_active 필드를 통해 로그인 허용 여부를 판단하고 있지만, 활성 여부(is_active)와 상관 없이 모든 유저의 로그인을 허용하고 싶다면 AllowAllUsersModelBackend 혹은 AllowAllUsersRemoteUserBackend를 사용하여 비활성 유저의 로그인을 허용할 수 있습니다.
⠀
이 경우, LoginView에서 사용되는 AuthenticationForm 또한 추가 커스터마이징이 필요합니다.
⠀
또한 has_perm() 등의 권한 체크 함수나 Django Admin 관련 인증은 비활성 유저에게 항상 False만 리턴하도록 되어있으니 주의하세요.</p>
<p>💡 <strong>LoginView를 사용하지 않으신다면 Backend만 변경하여도 무관합니다</strong></p>
<p><strong><em>Q. 왜 LoginView를 사용하면 Backend를 변경하였음에도 불구하고 AuthenticationForm을 추가 커스터마이징 해야 하나요?</em></strong>
<img src="https://images.velog.io/images/dev_dolxegod/post/481fc161-afe7-4c41-b272-8356a042ff93/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-03%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%204.23.32.png" alt="">
<em>( django/contrib/auth/views)</em></p>
<p>우선 위 사진과 같이 장고 LoginView의 기본 Form은 AuthenticationForm 입니다.</p>
<p><img src="https://images.velog.io/images/dev_dolxegod/post/18c6c1f2-c3e1-421d-8b05-a3ebc8513211/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-03%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%204.21.03.png" alt="">
<em>( django/views/generic/edit)</em></p>
<p>다음으로 LoginView의 부모 클래스인 ProcessFormView를 보겠습니다.
POST 요청 시 form에 대해 is_valid를 진행하게 되는데, is_valid 진행 과정에서 form 객체 (현재 AuthenticationForm) 의 clean을 진행하게 됩니다.</p>
<p>*( 장고의 Form Validation에 대해 보다 자세한 내용이 궁금하시다면 <a href="https://docs.djangoproject.com/en/3.2/ref/forms/validation/">공식 문서</a>를 참고해 주세요. )</p>
<p><img src="https://images.velog.io/images/dev_dolxegod/post/60b7874a-89d8-44c8-a509-ba3c57acc684/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-03%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%204.21.27.png" alt="">
<em>( django/contrib/auth/forms)</em></p>
<p>AuthenticationForm의 clean 메서드 입니다.
사진에서 보실 수 있으시듯이, <strong>authenticate에서 통과되더라도 아래 confirm_login_allowed 에서 유저가 is_active가 아니면 ValidationError가 발생</strong>하도록 되어있습니다.</p>
<p>이 때문에 Backend를 변경해서 authenticate를 통과하더라도, LoginView를 사용하게 되면 AuthenticationForm에 대한 추가 커스터마이징이 필요한 것입니다.</p>
<blockquote>
<p> <strong>is_staff:</strong> 유저의 admin 사이트 접근 허용 여부를 판단하기 위한 필드입니다.
<img src="https://images.velog.io/images/dev_dolxegod/post/a5793d29-7da1-4b7b-8380-26e87b6b2371/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-01%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.11.26.png" alt="">
admin 사이트 내에서의 추가적인 행동(특정 모델에 대한 CRUD)은 별도 Permission을 할당 받아야만 가능합니다.</p>
</blockquote>
<blockquote>
<p><strong>is_superuser:</strong> 유저의 슈퍼유저 여부를 판단하기 위한 필드입니다.
슈퍼유저는 별도의 Permission 할당 없이도 admin 사이트 내에서의 모든 행동 권한을 부여 받습니다</p>
</blockquote>
<p>⠀
⠀
위와 같이 auth user 의 해당 세 필드 또한 로그인, 어드민과 깊은 연관성이 있습니다.
<em>*( 혹 장고 어드민을 사용하시지 않으시거나 추후 Group &amp; Permission을 이용하실 생각이 없으시더라도
단순 별도 권한 체크 등 다른 용도로 충분히 사용하실 수 있으실 거예요. )</em></p>
<p>이처럼 superuser는 별도의 권한 없이도 admin 사이트에서 모든 모델을 대상으로 자유롭게 CRUD를 할 수 있습니다.</p>
<p>많은 장고 튜토리얼에서 createsuperuser를 하는 이유도, admin 사이트에서 자유자재로 모델에 대한 변경이 가능하기 때문에 여러분들의 원활한 장고 학습을 위함이었다는 것이죠 !</p>
<p>현업에서 여러 명의 개발자들과 장고 기반 웹을 개발하게 되신다면, 실제 프로덕션 환경의 admin에서 모든 개발자에게 superuser를 할당하는 것은 상당히 위험할 수도 있다는 점을 인지하고 계시면 좋을 것 같습니다.</p>
<p><em>+(보통은 프로덕션 환경에서 보안 상 admin 사이트 자체를 비활성화 해놓긴 합니다만, 모든 환경이 동일하진 않으니  혹시나 하는 마음에 언급했습니다. 프로덕션 레벨에서의 보안에 대해서는 역량이 된다면 추후 별도의 글로 작성할 예정입니다.)</em></p>
<h3 id="여담--last_login은-누가-언제-업데이트-하는가-">여담 : last_login은 누가 언제 업데이트 하는가 ?!</h3>
<p>User 모델 내의 last login 필드는 정확히 어느 시점에 갱신되는지 가볍게 확인해보겠습니다.
<img src="https://images.velog.io/images/dev_dolxegod/post/07457eef-28d3-4f24-ad36-fd1b724d1f4a/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-03%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%208.33.33.png" alt="">
<em>( django/contrib/auth/__init_\</em> -&gt; login())_</p>
<p><img src="https://images.velog.io/images/dev_dolxegod/post/5b7f0049-6e98-490a-8e7d-295b35ff08be/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-03%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%209.21.32.png" alt="">
<em>( django/contrib/auth/models)</em></p>
<p>로그인 시 사용되는 login() 함수의 최하단에 user_logged_in Signal을 이용하여 models의 update_last_login을 호출하고 있습니다.</p>
<p>이렇게 login 함수를 사용할 때 마다 last_login 필드가 자동으로 업데이트 된다는 것을 알 수 있습니다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>이번 글에서는 장고의 기본 User 모델에 대해 알아보았습니다. </p>
<blockquote>
<p>&quot; 앗 잠시만요 ! 저희 서비스의 유저 정보는 기본 User 모델로는 턱없이 부족해요 ! 이대로 끝인가요? &quot;</p>
</blockquote>
<p>맞습니다. 위에서 보셨듯 장고의 기본 User 모델 필드들은 보통의 서비스들에서 수집하는 많은 유저 정보들에 비하면 턱없이 부족합니다.</p>
<p>하지만 걱정 마세요, 그런 이유로 장고에서는 이미 기본 User 모델에 대한 여러가지 확장 방법을 제공하고 있습니다.</p>
<p>이에 시리즈 다음 글은 <strong>기본 User 모델 확장하기</strong> 를 주제로 포스팅 할 예정입니다.</p>
<p>요새 개인적인 일들이 많아 글을 자주 올리기는 어렵지만 역량이 닿는 대로 연재를 해 볼 생각입니다. 😅</p>
<p>아마 글이 자주 올라오진 못하더라도 해당 시리즈는 2021년 하반기 내에는 마무리 할 생각이니, 가끔 생각 나실 때 들러주세요 ! 😄</p>
<p>많이 부족한 첫 시리즈이지만 장고의 인지도가 점점 증가하는 요즈음, 아직은 낯선 장고를 처음 접하는 분들께 조금이나마 도움이 되었으면 하는 바램입니다.</p>
<p>부족한 글 읽어주셔서 감사드리며, <strong>잘못되거나 부족한 내용을 발견하셨다면 언제든지 날카롭게 지적해주시면 즉시 수정토록 하겠습니다.</strong> 감사합니다 !</p>
]]></description>
        </item>
    </channel>
</rss>