<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>콩스토리</title>
    <link>https://allkong.tistory.com/</link>
    <description>콩 심은 데 콩 난다</description>
    <language>ko</language>
    <pubDate>Mon, 27 Jul 2026 04:57:06 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>올콩</managingEditor>
    <image>
      <title>콩스토리</title>
      <url>https://tistory1.daumcdn.net/tistory/4812998/attach/98e0c5fd35d3427f998579df6d88ff8c</url>
      <link>https://allkong.tistory.com</link>
    </image>
    <item>
      <title>[Django] URL 단축 서비스 만들기</title>
      <link>https://allkong.tistory.com/236</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YcU18/dJMcaci11Gi/F80GtcLbpROITi5IgDnyL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YcU18/dJMcaci11Gi/F80GtcLbpROITi5IgDnyL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YcU18/dJMcaci11Gi/F80GtcLbpROITi5IgDnyL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYcU18%2FdJMcaci11Gi%2FF80GtcLbpROITi5IgDnyL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;577&quot; height=&quot;577&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Django 강의에서 URL 단축 서비스 실습을 진행했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;URL 단축 서비스는 긴 웹 주소를 짧은 주소로 변환해 주는 서비스이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 아래와 같은 긴 URL이 있다고 가정해보자.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;http://www.climate-skeptic.com/2008/10/global-warming-accelerating.html&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 주소를 다음과 같이 짧게 줄일 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;https://bit.ly/x&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사용자는 짧은 URL로 접속하지만, 실제로는 원래의 긴 URL로 이동하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 URL 단축 서비스를 만들기 위한 Django 프로젝트를 생성한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가상환경 생성
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;`python3.13 -m venv .short_url`&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가상환경 활성화
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Mac: `source .short_url/bin/activate`&lt;/li&gt;
&lt;li&gt;Windows: `source .short_url\Scripts\activate`&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Django 설치
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;`pip install django`&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프로젝트 디렉토리 생성
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;`mkdir short_url`&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Django 프로젝트 생성
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;`django-admin startproject config short_url`&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프로젝트로 이동
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;`cd short_url`&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서버 실행
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;`python manage.py runserver`&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;앱 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 기능과 URL 단축 기능을 분리하기 위해 두 개의 앱을 생성했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`python manage.py startapp user`&lt;/li&gt;
&lt;li&gt;`python manage.py startapp shortener`&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성한 앱은 `settings.py`의 `INSTALLED_APPS`에 등록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780848697458&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'user.apps.UserConfig',
    'shortener.apps.ShortenerConfig',
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CustomUser 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Django 프로젝트에서는 기본 User 모델을 그대로 사용할 수도 있지만, 이후 확장 가능성을 고려해서 CustomUser를 정의했다.&lt;/p&gt;
&lt;pre id=&quot;code_1780848745895&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    pass&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 `settings.py`에 사용할 User 모델을 지정한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`AUTH_USER_MODEL = &quot;user.CustomUser&quot;`&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;User 모델을 변경했으면 마이그레이션을 적용해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`python manage.py makemigrations`&lt;/li&gt;
&lt;li&gt;`python manage.py migrate`&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;API 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URL 단축 서비스에서 사용할 기능은 다음과 같이 설계했다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Method&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;URL&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;GET&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;/&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;홈 화면, 모든 Short URL 목록&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;POST&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;/short-urls/&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;새로운 Short URL 생성&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;GET&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;/&amp;lt;str:short_url&amp;gt;/&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;원래 URL로 리디렉트&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;DELETE&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;/&amp;lt;str:short_url&amp;gt;/&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Short URL 삭제&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 `http://127.0.0.1:8000/b7ea4iQ/` 주소로 접속하면 `b7ea4iQ`에 해당하는 원래 주소로 이동한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ShortURL 모델&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;URL 단축 정보를 저장하기 위해 `ShortURL`&lt;/span&gt;&lt;span&gt;&amp;nbsp;모델을 만들었다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780848927261&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import string
import random

from django.db import models

class ShortURL(models.Model):
    code = models.CharField(max_length=8, unique=True)
    original_url = models.URLField(max_length=200)
    access_count = models.PositiveIntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

    # {app_label}_{클래스 이름 소문자}
    # db table: shortener_ shorturl -&amp;gt; short_url
    class Meta:
        app_label = &quot;shortener&quot;
        db_table = &quot;short_url&quot;
    
    @staticmethod
    def generate_code():
        characters = string.ascii_letters + string.digits
        return &quot;&quot;.join(random.choices(characters, k=8))&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`code` 필드에는 `unique=True` 적용
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;데이터베이스에 Unique 제약 조건을 생성한다는 의미&lt;/li&gt;
&lt;li&gt;동일한 `code` 값을 가진 데이터가 중복 저장되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Form 사용의 한계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;HTML Form 기반 요청에서는 기본적으로 `GET`&lt;/span&gt;&lt;span&gt;과 `POST` 메&lt;/span&gt;&lt;span&gt;서드만 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, RESTful하게 설계한 아래 요청은 HTML Form만으로는 바로 보내기 어렵다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`DELETE /&amp;lt;str:code&amp;gt;/`&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그래서 브라우저에서 Form을 사용할 때는 삭제 요청을 어떻게 처리할지 고민이 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;해결 방법&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;1. RESTful한 API 디자인 포기하고 URL 변경&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;삭제 요청을 &lt;/span&gt;&lt;span&gt;DELETE&lt;/span&gt;&lt;span&gt;가 아니라 &lt;/span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt;로 처리한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;` POST /&amp;lt;str:code&amp;gt;/delete/`&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;HTML Form에서 `POST` 요&lt;/span&gt;&lt;span&gt;청을 보내고, Django View에서는 이를 삭제 요청으로 처리한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780849184680&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;form method=&quot;post&quot; action=&quot;/abc123/delete/&quot;&amp;gt;
  {% csrf_token %}
  &amp;lt;button type=&quot;submit&quot;&amp;gt;삭제&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 사용할 수 있지만, RESTful하지 않아 아쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;2. Form에서 POST로 요청하고, Django에서 DELETE로 처리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Django Middleware를 사용하는 방법이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Form은 &lt;/span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt;로 보내되, hidden input을 추가해서 실제 의도를 표시한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780849261857&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;form method=&quot;post&quot; action=&quot;/abc123/&quot;&amp;gt;
  {% csrf_token %}
  &amp;lt;input type=&quot;hidden&quot; name=&quot;_method&quot; value=&quot;DELETE&quot;&amp;gt;
  &amp;lt;button type=&quot;submit&quot;&amp;gt;삭제&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Django에서는 &lt;/span&gt;&lt;span&gt;_method&lt;/span&gt;&lt;span&gt; 값을 확인해서 삭제 로직을 실행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Middleware를 사용하면 요청이 View에 도달하기 전에 공통 로직을 처리할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 Form에서 `_method=DELETE` 값&lt;/span&gt;&lt;span&gt;을 보내면 Middleware가 이를 확인하고 요청 메서드를 DELETE로 바꿀 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Django MiddleWare&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Middleware는 요청과 응답 사이에서 실행되는 중간 처리 계층이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사용자의 요청은 View에 도달하기 전에 Middleware를 거친다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Request &amp;rarr; Middleware &amp;rarr; View &amp;rarr; Middleware &amp;rarr; Response&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서 로그인 확인, 보안 처리, 공통 예외 처리, 요청 데이터 가공 같은 작업을 Middleware에서 처리할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번 프로젝트에서는 Form에서 보낸 &lt;/span&gt;&lt;span&gt;_method&lt;/span&gt;&lt;span&gt; 값을 확인해 요청 메서드를 바꾸는 용도로 활용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1142&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqVj95/dJMcajvJzpn/h7izkSm7PfyVEkemnWvcG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqVj95/dJMcajvJzpn/h7izkSm7PfyVEkemnWvcG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqVj95/dJMcajvJzpn/h7izkSm7PfyVEkemnWvcG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqVj95%2FdJMcajvJzpn%2Fh7izkSm7PfyVEkemnWvcG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;646&quot; height=&quot;290&quot; data-origin-width=&quot;1142&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ShortURL 삭제 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML Form은 DELETE 요청을 직접 보낼 수 없기 때문에 hidden input을 사용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1780849402900&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;form action=&quot;{% url 'short_url_detail' short_url.code %}&quot; method=&quot;post&quot;&amp;gt;
    {% csrf_token %}
    &amp;lt;input type=&quot;hidden&quot; name=&quot;_method&quot; value=&quot;DELETE&quot;&amp;gt;
    &amp;lt;button type=&quot;submit&quot; class=&quot;btn btn-danger&quot;&amp;gt;삭제&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View에서는 다음과 같이 처리할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780849423065&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ShortURLDetailView(View):
    # localhost:8000/abc12345 -&amp;gt; original_url
    def get(self, request, code):
        short_url = get_object_or_404(ShortURL, code=code)    
        short_url.access_count = F(&quot;access_count&quot;) + 1
        short_url.save()  
        return redirect(short_url.original_url)

    def delete(self, request, code):
        short_url = get_object_or_404(ShortURL, code=code)
        short_url.delete()
        return redirect(&quot;home&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 실제 HTTP 요청은 POST이지만, 서버에서는 삭제 요청으로 해석할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #9d9d9d; text-align: start;&quot;&gt;[참고] 실전! Django 입문 강의&lt;/span&gt;&lt;/p&gt;</description>
      <category>Django</category>
      <category>django</category>
      <author>올콩</author>
      <guid isPermaLink="true">https://allkong.tistory.com/236</guid>
      <comments>https://allkong.tistory.com/236#entry236comment</comments>
      <pubDate>Wed, 10 Jun 2026 22:29:03 +0900</pubDate>
    </item>
    <item>
      <title>[Django] Kakao 소셜 로그인</title>
      <link>https://allkong.tistory.com/235</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAEds5/dJMcagFPtLb/KMmC7YSbCAzgSjhGBgklKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAEds5/dJMcagFPtLb/KMmC7YSbCAzgSjhGBgklKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAEds5/dJMcagFPtLb/KMmC7YSbCAzgSjhGBgklKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAEds5%2FdJMcagFPtLb%2FKMmC7YSbCAzgSjhGBgklKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;554&quot; height=&quot;554&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;일반 회원가입은 사용자가 아이디와 비밀번호를 직접 생성해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 최근 대부분의 서비스는 Google, Kakao, Naver 같은 외부 플랫폼 계정을 이용해 간편하게 로그인할 수 있는 기능을 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;소셜 로그인(Social Login)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 별도의 회원가입 없이 이미 가입된 서비스의 계정을 사용하여 웹사이트나 애플리케이션에 로그인할 수 있도록 지원하는 인증 방식&lt;/li&gt;
&lt;li&gt;Google, Facebook, Naver, Kakao 등의 계정을 통해 다른 서비스에 접속&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;소셜 로그인 동작 과정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 사용자 동의 과정&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1497&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EAo8j/dJMcaaFCcgG/A8VOtoHr1sY6hNjYSRjidK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EAo8j/dJMcaaFCcgG/A8VOtoHr1sY6hNjYSRjidK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EAo8j/dJMcaaFCcgG/A8VOtoHr1sY6hNjYSRjidK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEAo8j%2FdJMcaaFCcgG%2FA8VOtoHr1sY6hNjYSRjidK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;303&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1497&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 &quot;카카오 로그인&quot; 버튼을 클릭하면 카카오 로그인 페이지로 이동한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780844851328&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class KakaoSocialLoginView(View):
    def get(self, request):
        return redirect(
            f&quot;https://kauth.kakao.com/oauth/authorize&quot;
            f&quot;?client_id={settings.KAKAO_REST_API_KEY}&quot;
            f&quot;&amp;amp;redirect_uri={settings.KAKAO_CALLBACK_URL}&quot;
            f&quot;&amp;amp;response_type=code&quot;
        )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이때 사용자는 카카오에게 다음과 같은 동의를 요청받는다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;이메일 정보 제공&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;프로필 정보 제공&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;기타 서비스에서 필요한 정보&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사용자가 동의하면 카카오는 우리 서비스가 지정한 Callback URL로 이동시킨다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 사용자 정보 조회&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1513&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X7Hvr/dJMcaaFCcgK/mX9ZcEicJBpKBQcN1fnRl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X7Hvr/dJMcaaFCcgK/mX9ZcEicJBpKBQcN1fnRl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X7Hvr/dJMcaaFCcgK/mX9ZcEicJBpKBQcN1fnRl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX7Hvr%2FdJMcaaFCcgK%2FmX9ZcEicJBpKBQcN1fnRl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;654&quot; height=&quot;307&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1513&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사용자가 동의했다고 해서 바로 사용자 정보를 받을 수 있는 것은 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;OAuth 인증 과정에서는 다음 두 가지 개념이 등장한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인가 코드(authorization code)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;사용자의 정보 조회 권한 요청이 성공했다는 것을 확인하는 임시 중개 수단&lt;/li&gt;
&lt;li&gt;예시: `http://localhost:8000/users/social/kakao/callback/?code=abcd1234`
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;여기서 `code` 값이 인가 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;access token
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;소셜 인증 제공자(Kakao) 서버에 API 요청을 할 수 있는 권한 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;auth code와 access token을 분리하는 이유
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;auth code를 access token으로 교환하는 과정은 인증 서버와의 직접적인 통신으로 이루어지기 때문에 access token 탈취 위험 감소&lt;/li&gt;
&lt;li&gt;auth code는 일회성으로 사용되고 짧은 시간만 유효&lt;/li&gt;
&lt;li&gt;access token이 브라우저에 직접 노출되는 것을 방지하여 보안성 높아짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;사용자&lt;br /&gt;&amp;darr; &lt;br /&gt;Authorization Code&lt;br /&gt;&amp;darr; &lt;br /&gt;우리 서버&lt;br /&gt;&amp;darr; &lt;br /&gt;Access Token 요청&lt;br /&gt;&amp;darr; &lt;br /&gt;카카오 서버&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 회원가입 및 로그인 &lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1539&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH6zbD/dJMcajoSrJ2/qBkcYn1z0iUnHxiT7OkEKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH6zbD/dJMcajoSrJ2/qBkcYn1z0iUnHxiT7OkEKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH6zbD/dJMcajoSrJ2/qBkcYn1z0iUnHxiT7OkEKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH6zbD%2FdJMcajoSrJ2%2FqBkcYn1z0iUnHxiT7OkEKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;652&quot; height=&quot;305&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1539&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 받은 사용자 정보를 기반으로 회원가입 또는 로그인을 수행한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카카오 사용자 ID를 기반으로 사용자명을 생성&lt;/li&gt;
&lt;li&gt;이미 가입된 사용자라면 바로 로그인&lt;/li&gt;
&lt;li&gt;회원이 존재하지 않는 경우 새 계정 생성&lt;/li&gt;
&lt;li&gt;비밀번호는 사용자가 직접 입력하지 않기 때문에 임의의 값을 생성하여 저장&lt;/li&gt;
&lt;li&gt;Django Session이 생성되면서 로그인 상태가 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780845085064&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class KakaoSocialCallbackView(View):
    def get(self, request):
        # kakao -&amp;gt; django
        auth_token = request.GET.get(&quot;code&quot;)

        # access_token 교환
        response = requests.post(
            &quot;https://kauth.kakao.com/oauth/token&quot;,
            data={
                &quot;grant_type&quot;: &quot;authorization_code&quot;,
                &quot;client_id&quot;: settings.KAKAO_REST_API_KEY,
                &quot;redirect_uri&quot;: settings.KAKAO_CALLBACK_URL,
                &quot;code&quot;: auth_token,
            },
            headers={
                &quot;Content-Type&quot;: &quot;application/x-www-form-urlencoded;charset=UTF-8&quot;
            }
        )

        if response.ok:
            access_token = response.json().get(&quot;access_token&quot;)

            # 사용자의 카카오 프로필 조회
            profile_response = requests.get(
                &quot;https://kapi.kakao.com/v2/user/me&quot;,
                headers={
                    &quot;Authorization&quot;: f&quot;Bearer {access_token}&quot;
                }
            )

            if profile_response.ok:
                # 사용자 프로필 정보 =&amp;gt; 회원가입/로그인
                kakao_user_id = profile_response.json()[&quot;id&quot;]
                username = f&quot;K#{kakao_user_id}&quot;

                try:
                    # 기존 회원 여부
                    user = User.objects.get(username=username)
                except User.DoesNotExist:
                    # 새로 회원가입
                    email = profile_response.json()[&quot;kakao_account&quot;][&quot;email&quot;]
                    user = User.objects.create_user(
                        username=username,
                        email=email,
                        password=str(uuid.uuid4()),
                        social_provider=&quot;kakao&quot;,
                    )
                
                login(request, user)
                return redirect(&quot;home&quot;)

        return JsonResponse({&quot;error&quot;: &quot;Social login failed.&quot;})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;카카오 애플리케이션 등록&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오 로그인을 사용하려면 먼저 카카오 애플리케이션을 생성해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카카오 개발자 사이트:&amp;nbsp;&lt;a href=&quot;https://developers.kakao.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.kakao.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1780839665858&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Kakao Developers&quot; data-og-description=&quot;카카오 API를 활용하여 다양한 어플리케이션을 개발해 보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.&quot; data-og-host=&quot;developers.kakao.com&quot; data-og-source-url=&quot;https://developers.kakao.com/&quot; data-og-url=&quot;https://developers.kakao.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bzhX88/dJMb9g5iOD0/HGdX78I5fDMx4TtCyLSL20/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/wYKHN/dJMb8Yp3eNU/ZFKAecjTkmv4k6YPMWLfQ1/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/oynkP/dJMb8PGDZVp/HekYxxR87a8LcdKgMYmpKk/img.png?width=1200&amp;amp;height=800&amp;amp;face=0_0_1200_800&quot;&gt;&lt;a href=&quot;https://developers.kakao.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.kakao.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bzhX88/dJMb9g5iOD0/HGdX78I5fDMx4TtCyLSL20/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/wYKHN/dJMb8Yp3eNU/ZFKAecjTkmv4k6YPMWLfQ1/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/oynkP/dJMb8PGDZVp/HekYxxR87a8LcdKgMYmpKk/img.png?width=1200&amp;amp;height=800&amp;amp;face=0_0_1200_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Kakao Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;카카오 API를 활용하여 다양한 어플리케이션을 개발해 보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필수 설정&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션 생성&lt;/li&gt;
&lt;li&gt;카카오 로그인 활성화&lt;/li&gt;
&lt;li&gt;Redirect URI 등록&lt;/li&gt;
&lt;li&gt;이메일 동의 항목 설정
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;비즈앱 전환 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;REST API 키 복사&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #9d9d9d; text-align: start;&quot;&gt;[참고] 실전! Django 입문 강의&lt;/span&gt;&lt;/p&gt;</description>
      <category>Django</category>
      <category>django</category>
      <author>올콩</author>
      <guid isPermaLink="true">https://allkong.tistory.com/235</guid>
      <comments>https://allkong.tistory.com/235#entry235comment</comments>
      <pubDate>Tue, 9 Jun 2026 22:15:16 +0900</pubDate>
    </item>
    <item>
      <title>[Django] 장고 회원 시스템 (Session과 User Model)</title>
      <link>https://allkong.tistory.com/234</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PoTMJ/dJMcagyZHBR/K8kifdiwZEXpGwFQcAFqkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PoTMJ/dJMcagyZHBR/K8kifdiwZEXpGwFQcAFqkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PoTMJ/dJMcagyZHBR/K8kifdiwZEXpGwFQcAFqkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPoTMJ%2FdJMcagyZHBR%2FK8kifdiwZEXpGwFQcAFqkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;580&quot; height=&quot;580&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Django Session&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Django는 로그인 상태를 유지하기 위해 Session 기능을 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Session은 사용자의 상태 정보를 서버에 저장하여 HTTP의 무상태성(Stateless)을 해결하는 역할을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP의 무상태성(stateless)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 클라이언트의 이전 요청에 대한 상태 정보를 저장하지 않는 특성&lt;/li&gt;
&lt;li&gt;각 요청은 독립적이며, 서버는 요청마다 클라이언트를 식별할 수 있어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;HTTP는 기본적으로 상태를 저장하지 않는 프로토콜이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, 서버는 이전 요청을 기억하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 사용자가 로그인 요청을 보냈다고 가정해 보자.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 로그인 요청&lt;br /&gt;2. 메인 페이지 요청&lt;br /&gt;3. 마이페이지 요청&lt;span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;각 요청은 서로 독립적으로 처리된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서 별도의 방법이 없다면 서버는 현재 요청을 보낸 사용자가 누구인지 알 수 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이러한 특징을 HTTP의 무상태성(Stateless)이라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Session의 역할&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Session은 사용자의 상태 정보를 서버에 저장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;로그인에 성공하면 Django는 Session을 생성하고 사용자 정보를 저장한다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;login(request, user)&lt;span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위 코드를 실행하면 Django가 자동으로 Session을 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이후 사용자가 다른 페이지를 요청할 때 Session 정보를 확인하여 로그인 상태를 유지할 수 있다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;request.user&lt;span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;덕분에 사용자는 매 요청마다 아이디와 비밀번호를 입력하지 않아도 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;User Model 확장&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Django는 기본 User 모델을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 서비스를 개발하다 보면 기본 User 모델만으로는 부족한 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 다음과 같은 요구사항이 생길 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;프로필 이미지 추가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;전화번호 추가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;닉네임 추가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;생년월일 추가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이메일을 로그인 ID로 사용&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이처럼 사용자 정보를 확장해야 하는 경우가 많다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AbstractUser&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Django는 User Model 확장을 위해 &lt;/span&gt;&lt;span&gt;AbstractUser&lt;/span&gt;&lt;span&gt;를 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Custom User를 만들 때는 기본 User 모델을 직접 수정하는 것이 아니라 AbstractUser를 상속받아 새로운 모델을 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;InconsistentMigrationHistory 에러 발생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 프로젝트 전체에 기본 User를 사용하도록 적용되어 있기 때문에 프로젝트 중간에 User 모델을 변경하는 것은 매우 까다롭다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쉬운 해결법: 데이터베이스 삭제하고 처음부터 다시 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;db.sqlite3 파일 삭제 후, 다시 mgirate&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 새로운 Django 프로젝트 생성 시, User 모델 확장이 필요하지 않더라도 미래의 혹시 모를 변경을 위해 CustomUser를 정의하는 것을 권장한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780843720355&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    pass&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #9d9d9d; text-align: start;&quot;&gt;[참고] 실전! Django 입문 강의&lt;/span&gt;&lt;/p&gt;</description>
      <category>Django</category>
      <category>django</category>
      <author>올콩</author>
      <guid isPermaLink="true">https://allkong.tistory.com/234</guid>
      <comments>https://allkong.tistory.com/234#entry234comment</comments>
      <pubDate>Mon, 8 Jun 2026 00:02:14 +0900</pubDate>
    </item>
    <item>
      <title>[Django] 장고 회원 시스템 (User 모델과 Admin 페이지)</title>
      <link>https://allkong.tistory.com/233</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pzldp/dJMcagFOT5I/wCMLftDY8rXLJUjta3wSkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pzldp/dJMcagFOT5I/wCMLftDY8rXLJUjta3wSkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pzldp/dJMcagFOT5I/wCMLftDY8rXLJUjta3wSkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPzldp%2FdJMcagFOT5I%2FwCMLftDY8rXLJUjta3wSkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;517&quot; height=&quot;517&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장고는 기본적으로 회원 시스템이 기본적으로 제공된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장고는 로그인, 로그아웃, 회원가입, 비밀번호 관리 같은 기능을 위한 인증(Authentication) 시스템을 내장하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장고의 기본 User 모델과 관리자 페이지(Admin)에 대해 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Django User&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장고는 기본적으로 회원 시스템을 내장
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;로그인, 로그아웃, 회원가입, 비밀번호 변경 같은 기능 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User Model
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;`from django.contrib.auth.models import User`&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;User 모델&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;필드명&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;username&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;사용자 ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;email&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;이메일 주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;password&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;암호화된 비밀번호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;is_staff&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;관리자 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;is_superuser&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;슈퍼 관리자 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;is_active&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;계정 활성화 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;date_joined&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;계정 생성일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;last_login&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;마지막 로그인 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비밀번호&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;User 모델의 password 필드는 비밀번호를 그대로 저장하지 않고 해시(hash)된 값을 저장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;` pbkdf2_sha256$...`&lt;/li&gt;
&lt;li&gt;관리자도 실제 비밀번호 확인 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AUTH_PASSWORD_VALIDATORS
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비밀번호 검증 기능 (조건)&lt;/li&gt;
&lt;li&gt;비밀번호 길이, 흔한 비밀번호, 숫자로만 이루어진 비밀번호, 사용자 정보와 지나치게 유사한 비밀번호 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일반 사용자 / 관리자 / 슈퍼 관리자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자를 권한에 따라 구분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;일반 사용자&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스에 가입한 일반 회원&lt;/li&gt;
&lt;li&gt;로그인, 로그아웃, 게시글 작성, 댓글 작성 등 서비스에서 허용한 기능 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;관리자 (staff)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`is_staff=True`인 사용자&lt;/li&gt;
&lt;li&gt;Django Admin 페이지에 접근 가능&lt;/li&gt;
&lt;li&gt;모든 권한을 가지는 것은 아니고 모델별로 권한 별도 부여 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;슈퍼 관리자 (superuser)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`is_superuser=True`인 Django의 최고 권한 사용자&lt;/li&gt;
&lt;li&gt;모든 모델에 대한 조회, 생성, 수정, 삭제 권한 소유&lt;/li&gt;
&lt;li&gt;슈퍼 유저 생성 명령어: `python manage.py createsuperuser`&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Django Admin&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관리자 페이지 기본 제공&lt;/li&gt;
&lt;li&gt;서버 실행 후 다음 주소로 접속 가능
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;`http://localhost:8000/admin/`&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;939&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4QMjC/dJMcaayOCsM/2jIR7huWkR9M9jeQTC9Kq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4QMjC/dJMcaayOCsM/2jIR7huWkR9M9jeQTC9Kq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4QMjC/dJMcaayOCsM/2jIR7huWkR9M9jeQTC9Kq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4QMjC%2FdJMcaayOCsM%2F2jIR7huWkR9M9jeQTC9Kq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;664&quot; height=&quot;326&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;939&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스를 직접 확인 하지 않아도 조회, 생성, 수정, 삭제 작업 수행 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #9d9d9d; text-align: start;&quot;&gt;[참고] 실전! Django 입문 강의&lt;/span&gt;&lt;/p&gt;</description>
      <category>Django</category>
      <category>django</category>
      <author>올콩</author>
      <guid isPermaLink="true">https://allkong.tistory.com/233</guid>
      <comments>https://allkong.tistory.com/233#entry233comment</comments>
      <pubDate>Sun, 7 Jun 2026 15:45:08 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 프로그래머스 42576번: 완주하지 못한 선수 (Lv.1)</title>
      <link>https://allkong.tistory.com/232</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  Problem&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42576&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;문제 보러 가기&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수많은 마라톤 선수들이 마라톤에 참여하였습니다. 단 한 명의 선수를 제외하고는 모든 선수가 마라톤을 완주하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마라톤에 참여한 선수들의 이름이 담긴 배열 participant와 완주한 선수들의 이름이 담긴 배열 completion이 주어질 때, 완주하지 못한 선수의 이름을 return 하도록 solution 함수를 작성해주세요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제한사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마라톤 경기에 참여한 선수의 수는 1명 이상 100,000명 이하입니다.&lt;/li&gt;
&lt;li&gt;completion의 길이는 participant의 길이보다 1 작습니다.&lt;/li&gt;
&lt;li&gt;참가자의 이름은 1개 이상 20개 이하의 알파벳 소문자로 이루어져 있습니다.&lt;/li&gt;
&lt;li&gt;참가자 중에는 동명이인이 있을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;입출력 예&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt; participant &lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;completion&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; return &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[&quot;leo&quot;, &quot;kiki&quot;, &quot;eden&quot;]&lt;/td&gt;
&lt;td&gt;[&quot;eden&quot;, &quot;kiki&quot;]&lt;/td&gt;
&lt;td&gt;&quot;leo&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[&quot;marina&quot;, &quot;josipa&quot;, &quot;nikola&quot;, &quot;vinko&quot;, &quot;filipa&quot;]&lt;/td&gt;
&lt;td&gt;[&quot;josipa&quot;, &quot;filipa&quot;, &quot;marina&quot;, &quot;nikola&quot;]&lt;/td&gt;
&lt;td&gt;&quot;vinko&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[&quot;mislav&quot;, &quot;stanko&quot;, &quot;mislav&quot;, &quot;ana&quot;]&lt;/td&gt;
&lt;td&gt;[&quot;stanko&quot;, &quot;ana&quot;, &quot;mislav&quot;]&lt;/td&gt;
&lt;td&gt;&quot;mislav&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;입출력 예 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 #1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;leo&quot;는 참여자 명단에는 있지만, 완주자 명단에는 없기 때문에 완주하지 못했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 #2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;vinko&quot;는 참여자 명단에는 있지만, 완주자 명단에는 없기 때문에 완주하지 못했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 #3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;mislav&quot;는 참여자 명단에는 두 명이 있지만, 완주자 명단에는 한 명밖에 없기 때문에 한명은 완주하지 못했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;✏️ Solution&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내가 푼 방식 (정렬 후 비교)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1736573729478&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(participant, completion):
    participant.sort()
    completion.sort()
    
    for i in range(len(completion)):
        if participant[i] != completion[i]:
            return participant[i]
    return participant[-1]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬하면 같은 이름끼리 같은 위치에 오게 되기 때문에 앞에서부터 비교했을 때 끝까지 같으면 마지막 참가자가 미완주자, 중간에 다르면 그 위치의 참가자가 미완주자이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 방식 (hash)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1780719762539&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(participant, completion):
    temp = 0
    dic = {}
    
    for p in participant:
        dic[hash(p)] = p
        temp += hash(p)
        
    for c in completion:
        temp -= hash(c)

    return dic[temp]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참가자 이름들의 hash 값을 전부 더하고, 완주자 이름들의 hash 값을 전부 빼면, 남는 값이 미완주자 이름의 hash 값이 된다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 36.5116%; height: 58px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;hash(&quot;eden&quot;) + hash(&quot;kiki&quot;) + hash(&quot;leo&quot;)&lt;br /&gt;- hash(&quot;eden&quot;) - hash(&quot;kiki&quot;)&lt;br /&gt;=&amp;nbsp;hash(&quot;leo&quot;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 방식 (딕셔너리 카운팅)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1780720359190&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import Counter

def solution(participant, completion):
    return list((Counter(participant) - Counter(completion)).keys())[0]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Counter를 써서 참가자 명단에서 완주자 명단을 빼면, 남는 값이 미완주자이다.&lt;/p&gt;</description>
      <category>Algorithm/프로그래머스</category>
      <category>HASH</category>
      <category>알고리즘</category>
      <category>프로그래머스</category>
      <author>올콩</author>
      <guid isPermaLink="true">https://allkong.tistory.com/232</guid>
      <comments>https://allkong.tistory.com/232#entry232comment</comments>
      <pubDate>Sat, 6 Jun 2026 13:34:59 +0900</pubDate>
    </item>
    <item>
      <title>[Django] Function-Based View와 Class-Based View</title>
      <link>https://allkong.tistory.com/231</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMU9pt/dJMcaiQ1Qm6/e4qlo8CqE6NKXRQcJuAnZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMU9pt/dJMcaiQ1Qm6/e4qlo8CqE6NKXRQcJuAnZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMU9pt/dJMcaiQ1Qm6/e4qlo8CqE6NKXRQcJuAnZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMU9pt%2FdJMcaiQ1Qm6%2Fe4qlo8CqE6NKXRQcJuAnZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;573&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Django에서 View는 사용자의 요청을 받아 처리하고 응답을 반환하는 역할을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://allkong.tistory.com/230&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 글&lt;/a&gt;에서는 함수를 사용하여 View를 작성하는 Function-Based View(FBV)를 살펴보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번에는 Function-Based View의 한계와 이를 개선하기 위해 등장한 Class-Based View(CBV)에 대해 알아보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Function-Based View(FBV)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수를 사용하여 View를 작성하는 방식&lt;/li&gt;
&lt;li&gt;하나의 함수에서 두 가지 이상의 기능 처리(GET, POST 등)&lt;/li&gt;
&lt;li&gt;쉬우나 코드가 길어지고 복잡도 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1780181965732&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.shortcuts import render

def post_create_view(request):
    if request.method == &quot;GET&quot;:
        return render(request, &quot;post_create.html&quot;)

    elif request.method == &quot;POST&quot;:
        # 게시글 저장 로직
        ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Classed-based View&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체지향 프로그래밍(OOP)의 상속과 메서드 오버라이딩 활용&lt;/li&gt;
&lt;li&gt;코드 중복을 줄이고 재사용 가능성 높임&lt;/li&gt;
&lt;li&gt;HTTP 메서드 별로 기능을 명시적으로 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1780182046018&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginRequiredView(View):
    def dispatch(self, request, *args, **kwargs):
        # 로그인 검사 ...
        
class PostCreateView(LoginRequiredView):
    def get(self, request):
        return render(request, &quot;post_create.html&quot;)
        
    def post(self, request):
        # 저장 ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 메서드별(`get()`, `post()` 등)로 로직을 명확하게 분리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 로직을 부모 클래스에 작성하여 다른 View에서 상속받아 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 중복 코드 줄이고 유지보수성 up&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #9d9d9d; text-align: start;&quot;&gt;[참고] 실전! Django 입문 강의&lt;/span&gt;&lt;/p&gt;</description>
      <category>Django</category>
      <category>django</category>
      <author>올콩</author>
      <guid isPermaLink="true">https://allkong.tistory.com/231</guid>
      <comments>https://allkong.tistory.com/231#entry231comment</comments>
      <pubDate>Thu, 4 Jun 2026 22:06:52 +0900</pubDate>
    </item>
    <item>
      <title>[Django] 좋아요 기능 구현과 F Expression</title>
      <link>https://allkong.tistory.com/230</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wKM0t/dJMcadPGfEM/4QQGC9bevjRquPKkXu2apK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wKM0t/dJMcadPGfEM/4QQGC9bevjRquPKkXu2apK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wKM0t/dJMcadPGfEM/4QQGC9bevjRquPKkXu2apK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwKM0t%2FdJMcadPGfEM%2F4QQGC9bevjRquPKkXu2apK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;533&quot; height=&quot;533&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;좋아요 기능은 단순히 게시글의 좋아요 수를 1 증가시키는 기능이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 여러 사용자가 동시에 좋아요를 누를 수 있기 때문에 단순히 Python 코드에서 `+= 1`&lt;/span&gt;&lt;span&gt;을 사용하는 방식은 주의가 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;좋아요 View 구현&lt;/h2&gt;
&lt;pre id=&quot;code_1780174559494&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def post_like_view(request, post_id):
    post = get_object_or_404(Post, id=post_id)
    
    post.points = F(&quot;points&quot;) + 1 # post.points = F()
    post.save()
    
    post.refresh_from_db()
    
    return render(
        request, template_name=&quot;post_detail.html&quot;, context={&quot;post&quot;: post}
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;get_object_or_404()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;` post = get_object_or_404(Post, id=post_id)`&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터가 존재하지 않으면 DoesNotExist 예외를 직접 처리하지 않고, 자동으로 404 응답 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;F Expression&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`F()`: Django에서 제공하는 표현식&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python에서 값을 계산하는 것이 아니라 데이터베이스에서 직접 값을 계산하도록 SQL을 만들어줌&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1780181475804&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;post.points = F(&quot;points&quot;) + 1
post.save()

# 위 코드는 아래와 유사하게 동작

UPDATE post
SET points = points + 1
WHERE id = 1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;경쟁 상태(Race Condition)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 개 이상의 프로세스나 스레드가 공유 자원(데이터, 파일 등)에 접근하여 이를 읽거나 쓰는 과정에서 실행 순서에 따라 예상치 못한 결과가 발생하는 상황&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 사용자 A와 사용자 B가 동시에 좋아요를 누를 경우, 둘 다 동일한 값을 읽어 +1을 했기 때문에 계산이 누락될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`F()`를 사용하면 Python에서 값을 읽고 계산하는 것이 아니라, 데이터베이스가 직접 현재 값을 기준으로 증가시키므로 동시에 요청이 들어와도 안전하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;refresh_from_db()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;`F()`&lt;/span&gt;&lt;span&gt;를 사용해 저장한 직후에는 Python 객체의 &lt;/span&gt;&lt;span&gt;post.points&lt;/span&gt;&lt;span&gt; 값이 실제 숫자가 아니라 &lt;/span&gt;&lt;span&gt;F(&quot;points&quot;) + 1&lt;/span&gt;&lt;span&gt; 표현식 상태로 남아 있을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서 변경된 값을 다시 확인하거나 템플릿에 최신 값을 넘기려면 데이터베이스에서 값을 다시 가져와야 하는데 `r&lt;/span&gt;&lt;span&gt;efresh_from_db()`를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 코드를 실행하면 DB에 저장된 최신 값으로 &lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt; 객체가 갱신된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #9d9d9d; text-align: start;&quot;&gt;[참고] 실전! Django 입문 강의&lt;/span&gt;&lt;/p&gt;</description>
      <category>Django</category>
      <category>django</category>
      <author>올콩</author>
      <guid isPermaLink="true">https://allkong.tistory.com/230</guid>
      <comments>https://allkong.tistory.com/230#entry230comment</comments>
      <pubDate>Wed, 3 Jun 2026 22:55:09 +0900</pubDate>
    </item>
    <item>
      <title>[Django] ORM으로 데이터 생성, 조회, 수정, 삭제(CRUD) 해보기</title>
      <link>https://allkong.tistory.com/229</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZlDAn/dJMcaciWZkA/3fcJYKYnkouyuKFiAOXUFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZlDAn/dJMcaciWZkA/3fcJYKYnkouyuKFiAOXUFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZlDAn/dJMcaciWZkA/3fcJYKYnkouyuKFiAOXUFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZlDAn%2FdJMcaciWZkA%2F3fcJYKYnkouyuKFiAOXUFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;575&quot; height=&quot;575&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;ORM(Object-Relational Mapping)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스의 테이블을 Python 객체처럼 다룰 수 있도록 도와주는 기술&lt;/li&gt;
&lt;li&gt;편리한 데이터베이스 스키마(테이블, 컬럼 등) 생성/수정/삭제 지원&lt;/li&gt;
&lt;li&gt;Python을 통해 데이터를 쉽게 조회/변경할 수 있도록 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ORM의 장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 직접 작성 X&lt;/li&gt;
&lt;li&gt;데이터베이스 종류에 크게 의존 X&lt;/li&gt;
&lt;li&gt;Python 코드로 데이터를 쉽게 조회, 생성, 수정, 삭제 가능&lt;/li&gt;
&lt;li&gt;데이터베이스 스키마 관리 편리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Django Shell 실행&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`python manage.py shell`&lt;/li&gt;
&lt;li&gt;shell: Django 프로젝트 환경이 적용된 Python&amp;nbsp; 콘솔&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존 모델 참고&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780179785605&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Post(models.Model):
    title = models.CharField(max_length=128)
    body = models.CharField(max_length=1024)
    author_name = models.CharField(max_length=32)
    points = models.PositiveIntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f&quot;{self.id}, {self.title}&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;ORM QuerySet&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Django ORM에서 데이터를 조회하면 대부분 QuerySet 반환
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;` &amp;gt;&amp;gt;&amp;gt; Post.objects.all()`&lt;/li&gt;
&lt;li&gt;`&amp;lt;QuerySet [...]&amp;gt;`&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;iterable하므로 반복 가능한 형태&lt;/li&gt;
&lt;li&gt;Lazy Loading(지연 로딩) 사용
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;값이나 객체의 계산을 우리가 실제로 필요할 때까지 연기하는 프로그래밍 기법&lt;/li&gt;
&lt;li&gt;생성되는 즉시 SQL를 실행하지 않고 실제로 데이터가 필요할 때 쿼리 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;QueryDict&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 데이터를 담는 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 89.7674%; height: 102px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 17.0542%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.2171%; height: 17px;&quot;&gt;&lt;b&gt;QuerySet&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 39.496%; height: 17px;&quot;&gt;&lt;b&gt;QueryDict&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 17.0542%; height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;역할&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.2171%; height: 17px;&quot;&gt;DB 조회 결과&lt;/td&gt;
&lt;td style=&quot;width: 39.496%; height: 17px;&quot;&gt;HTTP 요청 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 17.0542%; height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;사용 위치&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.2171%; height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;ORM(Model)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 39.496%; height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;View(Request)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 17.0542%; height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;생성 주체&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.2171%; height: 17px;&quot;&gt;Django ORM&lt;/td&gt;
&lt;td style=&quot;width: 39.496%; height: 17px;&quot;&gt;Django Request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 17.0542%; height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;예시&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.2171%; height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Post.objects.filter()&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 39.496%; height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;request.GET, request.POST&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 17.0542%; height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;담고 있는 것&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.2171%; height: 17px;&quot;&gt;Model 객체들&lt;/td&gt;
&lt;td style=&quot;width: 39.496%; height: 17px;&quot;&gt;사용자가 보낸 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 생성(Create)/조회(Read)&lt;/h2&gt;
&lt;pre id=&quot;code_1780169347106&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ python manage.py shell
13 objects imported automatically (use -v 2 for details).

Python 3.13.12 (tags/v3.13.12:1cbe481, Feb  3 2026, 18:22:25) [MSC v.1944 64 bit (AMD64)] on win32
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
(InteractiveConsole)

# Model 가져오기
&amp;gt;&amp;gt;&amp;gt; from post.models import Post

# 전체 게시글 조회
&amp;gt;&amp;gt;&amp;gt; Post.objects.all()
&amp;lt;QuerySet []&amp;gt;

# 새로운 게시글 생성
&amp;gt;&amp;gt;&amp;gt; new_post = Post(title=&quot;제목&quot;, body=&quot;본문&quot;, author_name=&quot;작성자&quot;)

# 결과: 데이터 없음
&amp;gt;&amp;gt;&amp;gt; new_post
&amp;lt;Post: Post object (None)&amp;gt;
&amp;gt;&amp;gt;&amp;gt; Post.objects.all()
&amp;lt;QuerySet []&amp;gt;

# 객체 생성했다고 데이터베이스에 저장되지 않음
# save()로 데이터 저장 필요
&amp;gt;&amp;gt;&amp;gt; new_post.save()
&amp;gt;&amp;gt;&amp;gt; Post.objects.all()
&amp;lt;QuerySet [&amp;lt;Post: Post object (1)&amp;gt;]&amp;gt;

# Primary Key 자동 생성 (id와 pk는 동일)
&amp;gt;&amp;gt;&amp;gt; new_post.id
1
&amp;gt;&amp;gt;&amp;gt; new_post.pk
1

# 저장 전에는 id(pk) 존재하지 않음
# save()가 실행되는 순간 데이터베이스에서 발급
&amp;gt;&amp;gt;&amp;gt; new_post_2 = Post(title=&quot;제목2&quot;, body=&quot;본문2&quot;, author_name=&quot;작성자2&quot;)
&amp;gt;&amp;gt;&amp;gt; new_post_2.id
&amp;gt;&amp;gt;&amp;gt; new_post_2.id is None
True&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ORM 데이터 수정(Update)&lt;/h2&gt;
&lt;pre id=&quot;code_1780169706552&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ python manage.py shell
13 objects imported automatically (use -v 2 for details).

Python 3.13.12 (tags/v3.13.12:1cbe481, Feb  3 2026, 18:22:25) [MSC v.1944 64 bit (AMD64)] on win32
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
(InteractiveConsole)

&amp;gt;&amp;gt;&amp;gt; from post.models import Post
&amp;gt;&amp;gt;&amp;gt; Post.objects.all()
&amp;lt;QuerySet [&amp;lt;Post: 1, 제목&amp;gt;]&amp;gt;
&amp;gt;&amp;gt;&amp;gt; Post.objects.all()[0]
&amp;lt;Post: 1, 제목&amp;gt;

# 조건에 맞는 데이터 조회
&amp;gt;&amp;gt;&amp;gt; Post.objects.get(id=1)
&amp;lt;Post: 1, 제목&amp;gt;
&amp;gt;&amp;gt;&amp;gt; post = Post.objects.get(id=1)
&amp;gt;&amp;gt;&amp;gt; post
&amp;lt;Post: 1, 제목&amp;gt;

# 데이터 수정
&amp;gt;&amp;gt;&amp;gt; post.title
'제목'
&amp;gt;&amp;gt;&amp;gt; post.title=&quot;새로운 제목&quot;
&amp;gt;&amp;gt;&amp;gt; post.title
'새로운 제목'
&amp;gt;&amp;gt;&amp;gt; post.save()
&amp;gt;&amp;gt;&amp;gt; post = Post.objects.get(id=1)
&amp;gt;&amp;gt;&amp;gt; post.title
'새로운 제목'
&amp;gt;&amp;gt;&amp;gt; post
&amp;lt;Post: 1, 새로운 제목&amp;gt;

# 자동 생성된 값 확인
&amp;gt;&amp;gt;&amp;gt; post.points
0
&amp;gt;&amp;gt;&amp;gt; post.created_at
datetime.datetime(2026, 5, 30, 19, 25, 35, 223307, tzinfo=datetime.timezone.utc)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ORM 데이터 조회(filter)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조건에 맞는 데이터를 조회할 때 사용&lt;/li&gt;
&lt;li&gt;Post.objects.filter(title=&quot;새로운 제목&quot;)&lt;/li&gt;
&lt;li&gt;`startswith`: 특정 문자열로 시작하는 조회
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span&gt;Post&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;objects&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;filter(&lt;/span&gt;&lt;span&gt;title__startswith&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;새&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`lt`: Less Than, 미만
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span&gt;Post&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;objects&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;filter(&lt;/span&gt;&lt;span&gt;points__lt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`gte`: Greater Than or Equal, 이상
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Post.objects.filter(points__gte=10)&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;get()과 filter()의 차이&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`get()`: 조건에 맞는 데이터를 &lt;b&gt;하나만 조회&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`post = Post.objects.get(id=1)`&lt;/li&gt;
&lt;li&gt;객체(Object) 반환&lt;/li&gt;
&lt;li&gt;조건에 맞는 데이터가 반드시 1개여야 함(2개 이상이면 예외 발생)&lt;/li&gt;
&lt;li&gt;데이터가 없으면 예외 발생(DoesNotExist)&lt;/li&gt;
&lt;li&gt;즉시 쿼리 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`filter()`: 조건에 맞는 데이터를 조회
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;QuerySet 반환&lt;/li&gt;
&lt;li&gt;데이터가 여러 개여도 가능&lt;/li&gt;
&lt;li&gt;데이터가 없어도 예외 발생 X&lt;/li&gt;
&lt;li&gt;Lazy Loading&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;ORM 데이터 삭제(Delete)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`post.delete()`&lt;/li&gt;
&lt;li&gt;조건에 맞는 데이터 여러 개 삭제: `Post.objects.filter(title=&quot;제목&quot;).delete()`&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ORM과 SQL 비교&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 80.1163%; height: 193px;&quot; border=&quot;1&quot; data-end=&quot;4812&quot; data-start=&quot;4512&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 45.7185%;&quot;&gt;&lt;b&gt;ORM&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 54.1364%;&quot;&gt;&lt;b&gt;SQL&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4589&quot; data-start=&quot;4542&quot;&gt;
&lt;td style=&quot;width: 45.7185%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;4565&quot; data-start=&quot;4542&quot;&gt;Post.objects.all()&lt;/td&gt;
&lt;td style=&quot;width: 54.1364%;&quot; data-end=&quot;4589&quot; data-start=&quot;4565&quot; data-col-size=&quot;sm&quot;&gt;SELECT * FROM post&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4652&quot; data-start=&quot;4590&quot;&gt;
&lt;td style=&quot;width: 45.7185%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;4617&quot; data-start=&quot;4590&quot;&gt;Post.objects.get(id=1)&lt;/td&gt;
&lt;td style=&quot;width: 54.1364%;&quot; data-end=&quot;4652&quot; data-start=&quot;4617&quot; data-col-size=&quot;sm&quot;&gt;SELECT * FROM post WHERE id=1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4730&quot; data-start=&quot;4653&quot;&gt;
&lt;td style=&quot;width: 45.7185%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;4689&quot; data-start=&quot;4653&quot;&gt;Post.objects.filter(title=&quot;제목&quot;)&lt;/td&gt;
&lt;td style=&quot;width: 54.1364%;&quot; data-end=&quot;4730&quot; data-start=&quot;4689&quot; data-col-size=&quot;sm&quot;&gt;SELECT * FROM post WHERE title='제목'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4771&quot; data-start=&quot;4731&quot;&gt;
&lt;td style=&quot;width: 45.7185%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;4747&quot; data-start=&quot;4731&quot;&gt;post.save()&lt;/td&gt;
&lt;td style=&quot;width: 54.1364%;&quot; data-end=&quot;4771&quot; data-start=&quot;4747&quot; data-col-size=&quot;sm&quot;&gt;INSERT 또는 UPDATE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4812&quot; data-start=&quot;4772&quot;&gt;
&lt;td style=&quot;width: 45.7185%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;4790&quot; data-start=&quot;4772&quot;&gt;post.delete()&lt;/td&gt;
&lt;td style=&quot;width: 54.1364%;&quot; data-end=&quot;4812&quot; data-start=&quot;4790&quot; data-col-size=&quot;sm&quot;&gt;DELETE FROM post&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #9d9d9d; text-align: start;&quot;&gt;[참고] 실전! Django 입문 강의&lt;/span&gt;&lt;/p&gt;</description>
      <category>Django</category>
      <category>django</category>
      <author>올콩</author>
      <guid isPermaLink="true">https://allkong.tistory.com/229</guid>
      <comments>https://allkong.tistory.com/229#entry229comment</comments>
      <pubDate>Tue, 2 Jun 2026 22:45:01 +0900</pubDate>
    </item>
    <item>
      <title>[Django] 장고 Model과 Migration 이해하기</title>
      <link>https://allkong.tistory.com/228</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMfna1/dJMcagyUDtJ/cB4kVWpcpssK3u2tVKTfLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMfna1/dJMcagyUDtJ/cB4kVWpcpssK3u2tVKTfLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMfna1/dJMcagyUDtJ/cB4kVWpcpssK3u2tVKTfLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMfna1%2FdJMcagyUDtJ%2FcB4kVWpcpssK3u2tVKTfLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;542&quot; height=&quot;542&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Django Model&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스의 테이블을 Python 클래스로 표시한 것 (테이블 구조 정의)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 게시글 데이터를 저장할 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시글에는 &amp;lt;제목, 본문, 작성자 이름, 좋아요 수, 생성 시간&amp;gt; 등의 정보가 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 정보를 데이터베이스에 저장하기 위해 모델을 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Django 모델 생성 방법&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;models.Model을 상속받아 class 정의&lt;/li&gt;
&lt;li&gt;타입에 맞는 field를 통해 속성 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1780178715791&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.db import models

# 게시물
# - 제목, 본문, 작성자 이름, 좋아요 개수, 게시물 생성 시간

class Post(models.Model):
    title = models.CharField(max_length=128)
    body = models.CharField(max_length=1024)
    author_name = models.CharField(max_length=32)
    points = models.PositiveIntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)
    
    def __str__(self): # 모델 객체를 문자열로 출력
        return f&quot;{self.id}, {self.title}&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Migration 생성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스의 스키마(테이블 구조 등)를 관리하는 데 사용되는 파일&lt;/li&gt;
&lt;li&gt;모델의 변화를 파일을 통해 순차적으로 기록&lt;/li&gt;
&lt;li&gt;migration 생성 방법: `python manage.py makemigrations`&lt;/li&gt;
&lt;li&gt;migrate 명령어를 통해 데이터베이스를 특정 상태로 변경하거나 이전 상태로 롤백&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1495&quot; data-origin-height=&quot;677&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cl4saY/dJMcajbfZNS/bDlqlkpDluDUawYLUI27b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cl4saY/dJMcajbfZNS/bDlqlkpDluDUawYLUI27b0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cl4saY/dJMcajbfZNS/bDlqlkpDluDUawYLUI27b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcl4saY%2FdJMcajbfZNS%2FbDlqlkpDluDUawYLUI27b0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;677&quot; height=&quot;307&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1495&quot; data-origin-height=&quot;677&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`makemigrations`를 실행하면 장고가 모델의 변경 사항을 감지하여 Migration 파일 생성&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 33.3721%; height: 76px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;post/ &lt;br /&gt;└── migrations/&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; └── 0001_initial.py&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터베이스에 반영&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Migration 파일 생성 후 실제로 데이터베이스에 적용하려면 migrate 실행 필요
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`python manage.py migrate`&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Migration 동작 과정&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;Model 작성&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;darr; &lt;br /&gt;makemigrations&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;darr; &lt;br /&gt;Migration 파일 생성&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;darr; &lt;br /&gt;migrate&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;darr; &lt;br /&gt;DB 테이블 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #9d9d9d; text-align: start;&quot;&gt;[참고] 실전! Django 입문 강의&lt;/span&gt;&lt;/p&gt;</description>
      <category>Django</category>
      <category>django</category>
      <author>올콩</author>
      <guid isPermaLink="true">https://allkong.tistory.com/228</guid>
      <comments>https://allkong.tistory.com/228#entry228comment</comments>
      <pubDate>Mon, 1 Jun 2026 23:10:50 +0900</pubDate>
    </item>
    <item>
      <title>[Django] 장고 App 생성과 등록</title>
      <link>https://allkong.tistory.com/227</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KzuNA/dJMcacDkX1E/km9Fozpxus0n5M8MkWciJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KzuNA/dJMcacDkX1E/km9Fozpxus0n5M8MkWciJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KzuNA/dJMcacDkX1E/km9Fozpxus0n5M8MkWciJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKzuNA%2FdJMcacDkX1E%2Fkm9Fozpxus0n5M8MkWciJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;535&quot; height=&quot;535&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Django Project와 App의 차이&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Project: 하나의 웹 서비스&lt;/li&gt;
&lt;li&gt;App: 프로젝트 안에서 특정 기능을 담당하는 모듈&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 35.3488%; height: 101px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;Project&amp;nbsp;(게시판&amp;nbsp;서비스) &lt;br /&gt;├──&amp;nbsp;user&amp;nbsp;App&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(회원&amp;nbsp;관리) &lt;br /&gt;├──&amp;nbsp;post&amp;nbsp;App&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(게시글&amp;nbsp;관리) &lt;br /&gt;├──&amp;nbsp;comment&amp;nbsp;App&amp;nbsp;&amp;nbsp;&amp;nbsp;(댓글&amp;nbsp;관리) &lt;br /&gt;└──&amp;nbsp;chat&amp;nbsp;App&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(채팅&amp;nbsp;기능)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Django App 생성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`python manage.py startapp post`&lt;/li&gt;
&lt;li&gt;앱(app): Django 프로젝트 내에서 특정 기능을 담당하는 독립적인 모듈&lt;/li&gt;
&lt;li&gt;프로젝트를 어떻게 논리적으로 구성할 건지에 따라서 앱 생성
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;예: user(회원 관리) 앱, post(게시글 관리) 앱 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Django App 구조&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;post/&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; (앱 디렉토리)&lt;br /&gt;├──&amp;nbsp;__init__.py &lt;br /&gt;├── admin.py&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; (관리자 기능 설정 파일) &lt;br /&gt;├── apps.py&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; (앱의 메타데이터와 설정 값을 정의하는 파일) &lt;br /&gt;├── migrations/&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp; (&lt;span style=&quot;background-color: #f6e199; color: #333333;&quot;&gt;데이터베이스 스키마 변경 내역 디렉토리&lt;/span&gt;) &lt;br /&gt;│&amp;nbsp;&amp;nbsp;&amp;nbsp;└── __init__.py &lt;br /&gt;├──&amp;nbsp;models.py&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;(&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333;&quot;&gt;데이터베이스 테이블을 정의하는 파일&lt;/span&gt;) &lt;br /&gt;├──&amp;nbsp;tests.py &lt;br /&gt;└──&amp;nbsp;views.py &lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199; color: #333333;&quot;&gt;웹 요청에 대한 응답을 처리하는 파일&lt;/span&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Django App 생성 후 작업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App을 생성했다고 바로 사용할 수 있는 것은 아니고 Django가 해당 App을 인식할 수 있도록 등록 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ settings.py에 App 등록&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;settings.py 안의 INSTALLED_APPS에 AppConfig 추가
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Django가 앱을 인식하고 그 앱에 대한 여러 기능들을 활성화&lt;/li&gt;
&lt;li&gt;INSTALLED_APPS: Django 프로젝트에서 사용하는 애플리케이션을 관리하는 목록&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1780168293617&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# settings.py

INSTALLED_APPS = [
    'django.contrib.admin',          # 관리자 사이트
    'django.contrib.auth',           # 인증 시스템
    'django.contrib.contenttypes',   # 콘텐츠 타입 프레임워크
    'django.contrib.sessions',       # 세션 관리
    'django.contrib.messages',       # 메시징 프레임워크
    'django.contrib.staticfiles',    # 정적 파일 관리
    'post.apps.PostConfig',          # Post 앱
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Django는 `INSTALLED_APPS`에 등록된 App만 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등록해야 다음 기능들이 동작한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Model 인식&lt;/li&gt;
&lt;li&gt;Migration 생성&lt;/li&gt;
&lt;li&gt;Admin 등록&lt;/li&gt;
&lt;li&gt;Template 탐색&lt;/li&gt;
&lt;li&gt;Static 파일 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2️⃣ view 작성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Django View&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 애플리케이션의 비즈니스 로직을 처리하는 부분&lt;/li&gt;
&lt;li&gt;클라이언트의 요청을 받아서 적절한 응답을 반환하는 역할&lt;/li&gt;
&lt;li&gt;(Spring의 Controller 역할)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1181&quot; data-origin-height=&quot;536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEGJSj/dJMcafGOZkT/WDQu5rqx6voW5Cs4LPZWj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEGJSj/dJMcafGOZkT/WDQu5rqx6voW5Cs4LPZWj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEGJSj/dJMcafGOZkT/WDQu5rqx6voW5Cs4LPZWj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEGJSj%2FdJMcafGOZkT%2FWDQu5rqx6voW5Cs4LPZWj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;675&quot; height=&quot;306&quot; data-origin-width=&quot;1181&quot; data-origin-height=&quot;536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;lt;클라이언트-서버&amp;gt; 모델&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업을 요청하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;클라이언트&lt;/b&gt;와 요청을 처리하고 결과를 제공하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;서버&lt;/b&gt;의 상호작용
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;요청: 게시물 조회, 회원가입, 로그인과 같이 서버의 특정 데이터를 요청하는 과정&lt;/li&gt;
&lt;li&gt;HTTP: 웹상에서 인터넷을 통해 데이터를 주고받는 규칙&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;525&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mQex8/dJMcaf7OWXx/52zLZ7jX738MEhrXnNJUK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mQex8/dJMcaf7OWXx/52zLZ7jX738MEhrXnNJUK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mQex8/dJMcaf7OWXx/52zLZ7jX738MEhrXnNJUK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmQex8%2FdJMcaf7OWXx%2F52zLZ7jX738MEhrXnNJUK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;636&quot; height=&quot;525&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;525&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가&amp;nbsp;회원가입&amp;nbsp;버튼을&amp;nbsp;클릭하거나&amp;nbsp;상세&amp;nbsp;정보를&amp;nbsp;보기&amp;nbsp;위해&amp;nbsp;게시글을&amp;nbsp;클릭하는&amp;nbsp;등의&amp;nbsp;액션을&amp;nbsp;취했을&amp;nbsp;때&amp;nbsp;이런&amp;nbsp;사용자의&amp;nbsp;신호를&amp;nbsp;클라이언트가&amp;nbsp;감지해서&amp;nbsp;사용자를&amp;nbsp;대신해&amp;nbsp;의도에&amp;nbsp;맞는&amp;nbsp;&lt;b&gt;HTTP&amp;nbsp;요청을&amp;nbsp;서버로&amp;nbsp;전송&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;서버는 요청에 맞는 처리를 한 뒤&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;HTTP 응답을 통해 요청의 결과를 클라이언트에게 전송&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청에&amp;nbsp;맞는&amp;nbsp;처리:&amp;nbsp;회원가입의&amp;nbsp;경우&amp;nbsp;사용자가&amp;nbsp;보낸&amp;nbsp;아이디와&amp;nbsp;비밀번호를&amp;nbsp;데이터베이스의&amp;nbsp;저장하여&amp;nbsp;사용자를&amp;nbsp;기록하는&amp;nbsp;과정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #9d9d9d; text-align: start;&quot;&gt;[참고] 실전! Django 입문 강의&lt;/span&gt;&lt;/p&gt;</description>
      <category>Django</category>
      <category>django</category>
      <author>올콩</author>
      <guid isPermaLink="true">https://allkong.tistory.com/227</guid>
      <comments>https://allkong.tistory.com/227#entry227comment</comments>
      <pubDate>Sun, 31 May 2026 06:55:54 +0900</pubDate>
    </item>
  </channel>
</rss>