<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Try To do</title>
    <link>https://try-to-do.tistory.com/</link>
    <description>try-to-do 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Sun, 10 May 2026 12:41:03 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>잼이써요</managingEditor>
    <image>
      <title>Try To do</title>
      <url>https://tistory1.daumcdn.net/tistory/8423310/attach/40c5790ee7bf4436a097322f33ccda1d</url>
      <link>https://try-to-do.tistory.com</link>
    </image>
    <item>
      <title>error : no matches found: psycopg[binary]</title>
      <link>https://try-to-do.tistory.com/39</link>
      <description>&lt;h1&gt;no matches found: psycopg[binary]&lt;/h1&gt;
&lt;h2&gt;증상&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv add psycopg[binary]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;command result : no matches found: psycopg[binary]&lt;/p&gt;
&lt;h2&gt;원인&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;psyconpg&lt;/code&gt; 로 시작하고 &lt;code&gt;b,i,n,a,r,y&lt;/code&gt; 중 한 글자가 들어가는 파일을 찾으려고 시도 결과 오류 발생(당연)&lt;/p&gt;
&lt;h2&gt;해결 방법&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv add &amp;quot;psycopg[binary]&amp;quot;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알아두면 언젠간 유용할 몇 가지의 지식</category>
      <category>nomatchesfound</category>
      <category>psycopg[binary]</category>
      <category>UV</category>
      <author>잼이써요</author>
      <guid isPermaLink="true">https://try-to-do.tistory.com/39</guid>
      <comments>https://try-to-do.tistory.com/39#entry39comment</comments>
      <pubDate>Sat, 17 Jan 2026 17:31:22 +0900</pubDate>
    </item>
    <item>
      <title>console 환경에서 git 병합 후 push</title>
      <link>https://try-to-do.tistory.com/38</link>
      <description>&lt;pre id=&quot;code_1768131453848&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 로컬의 main 브랜치로 체크아웃
git checkout main

# 2. (필요시) 서버의 최신 main 내용을 가져옴
git pull origin main

# 3. dev 브랜치의 변경사항을 main에 병합 (Merge)
git merge dev

# 4. 병합된 main 브랜치를 GitHub 서버로 푸시
git push origin main&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알아두면 언젠간 유용할 몇 가지의 지식</category>
      <category>Git</category>
      <category>gitcommand</category>
      <category>github</category>
      <category>gitmerge</category>
      <category>gitpush</category>
      <author>잼이써요</author>
      <guid isPermaLink="true">https://try-to-do.tistory.com/38</guid>
      <comments>https://try-to-do.tistory.com/38#entry38comment</comments>
      <pubDate>Sun, 11 Jan 2026 20:38:07 +0900</pubDate>
    </item>
    <item>
      <title>VS Code에서 ollama list 를 인식하지 못할 때</title>
      <link>https://try-to-do.tistory.com/37</link>
      <description>&lt;p style=&quot;background-color: #e9eef6; color: #1f1f1f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;(venv)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;PS C:\Project\sample&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;ollama list&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;/p&gt;
&lt;p style=&quot;background-color: #e9eef6; color: #1f1f1f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ollama: The term 'ollama' is not recognized as a name of a cmdlet, function, script file, or executable program.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #e9eef6; color: #1f1f1f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Check the spelling of the name, or if a path was included, verify that the path is correct and try again.&lt;/span&gt;&lt;/b&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;VS Code에서 ollama 설치 및 연결을 확인하기 위해 입력해본 ollama list 명령어가 정상적으로수행되지 않을 경우&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;윈도우 검색창에 시스템 환경 변수 편집 검색 시 나오는 시스템 환경 변수 편집(제어판) 을 선택&lt;/li&gt;
&lt;li&gt;시스템 속성 화면 하단에 환경 변수 선택&lt;/li&gt;
&lt;li&gt;사용자 계정에 대한 사용자 변수 의 Path 선택 ( 없을 경우 새로 만들기)
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;새로 만들기 :
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;변수 이름 : Path 입력&lt;/li&gt;
&lt;li&gt;디렉터리 찾아보기로 이동&lt;br /&gt;&amp;gt; 위치 :C:\Users\사용자계정\AppData\Local\Programs\Ollama&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;기존 수정 :&lt;br /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Ollama 가 없다면 새로 만들기
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;C:\Users\사용자계정\AppData\Local\Programs\Ollama 입력&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Ollama 가 있다면 위치 확인&lt;br /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Ollama 위치가 VS Code 보다 위에 있는지 확인&lt;br /&gt;-&amp;gt; 저는 VS Code path 보다 아래 있었음.&lt;/li&gt;
&lt;li&gt;Ollama위치를 위로 이동 (VS Code 위로)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;확인 후 VS Code 완전 종료 후 다시 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 ollama list 가 정상적으로 보일 것입니다.&lt;/p&gt;</description>
      <category>알아두면 언젠간 유용할 몇 가지의 지식</category>
      <category>ollama와vscode</category>
      <category>ollama환경변수</category>
      <category>ollama환경변수세팅</category>
      <author>잼이써요</author>
      <guid isPermaLink="true">https://try-to-do.tistory.com/37</guid>
      <comments>https://try-to-do.tistory.com/37#entry37comment</comments>
      <pubDate>Sat, 3 Jan 2026 14:55:02 +0900</pubDate>
    </item>
    <item>
      <title>Ollama Model 저장 경로 설정 방법</title>
      <link>https://try-to-do.tistory.com/36</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AI를 이용해 Ollama 를 설치 진행 중이었다.&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;AI : Model 을 잘 관리하기 위해 별도 폴더 관리로 진행하는게 좋습니다.D:OllamaModels 폴더를 만드세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 : 응 만들께 ( D:OllamaModels )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI : 환경변수를 등록해 Ollama가 Models 위치를 확인하도록 설정합니다. ~ 환경변수 등록방법 ~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 : 응 설정할께 (~ 환경변수 등록 방법 ~)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI: 자 이제 모델을 실행하며 설정된 D:OllamaModels에 새로운 폴더가 생성되는 것을 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;Windows 에 설치된 ollama 는 settings 에 Model 경로를 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경변수 이런거 하지말고 ollama 실행 후 settings 들어가서 Model 경로를 설정하자.&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>알아두면 언젠간 유용할 몇 가지의 지식</category>
      <category>AI</category>
      <category>Ollama</category>
      <category>ollama모델관리</category>
      <category>ollama세팅</category>
      <author>잼이써요</author>
      <guid isPermaLink="true">https://try-to-do.tistory.com/36</guid>
      <comments>https://try-to-do.tistory.com/36#entry36comment</comments>
      <pubDate>Sat, 3 Jan 2026 14:12:32 +0900</pubDate>
    </item>
    <item>
      <title>Django 마이그레이션 불일치 (Migration Mismatch) 에러 관련</title>
      <link>https://try-to-do.tistory.com/35</link>
      <description>&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;models.py를 작성 후 1차 마이그레이션을 진행한 뒤 테이블 명중 1글자를 소문자에서 대문자로 변경할 일이 생겼다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;변경 후 하던대로 진행했을 뿐인데.&lt;/p&gt;
&lt;pre id=&quot;code_1767182790289&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python manage.py makemigrations appname
python manage.py migrate
python manage.py runserver&lt;/code&gt;&lt;/pre&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;NO SUCH TABLE '내가 만든 테이블명' 에러가 발생.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;이것, 저것 해보다가 최종 해결 방법은 간단했지만, 중간 과정이 고생스러워서 기록한다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size23&quot;&gt;1. 발생 원인&lt;/h3&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;Django 마이그레이션 시스템에서 발생하는 OperationalError는 **애플리케이션의 마이그레이션 상태(State)**와 &lt;b data-index-in-node=&quot;73&quot; data-path-to-node=&quot;5&quot;&gt;실제 데이터베이스 스키마(Schema)&lt;/b&gt; 간의 **동기화 실패(Desynchronization)**로 인해 발생.&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;Django는 데이터베이스의 형상 관리를 위해 다음 세 가지 요소를 유기적으로 일치시키는데, 이 중 하나라도 어긋나면 에러가 발생.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;마이그레이션 파일 (Migration Files):&lt;/b&gt; 개발자가 작성한 모델 변경 사항을 코드로 정의한 명세서 (/migrations/*.py).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;마이그레이션 기록 (Migration History):&lt;/b&gt; 데이터베이스 내 django_migrations 테이블에 저장된 적용 내역. (Django가 인지하는 현재 상태)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,2,0&quot;&gt;데이터베이스 스키마 (Database Schema):&lt;/b&gt; 실제 DBMS(SQLite, PostgreSQL 등)에 생성된 물리적인 테이블과 컬럼 구조.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-path-to-node=&quot;8&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;2. 주요 에러 유형 및 발생 메커니즘&lt;/h3&gt;
&lt;h3 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size23&quot;&gt;Case A. OperationalError: no such table&lt;/h3&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;마이그레이션 기록상으로는 '적용 완료' 상태이나, 실제 물리 테이블이 존재하지 않는 경우.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;상태 분석:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,1,0,0&quot;&gt;Migration History:&lt;/b&gt; 해당 마이그레이션 파일이 적용(Applied)되었다고 기록됨.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,1,1,0&quot;&gt;Actual Schema:&lt;/b&gt; 해당 테이블이 삭제되었거나 생성되지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;발생 원인:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스에 직접 접속(Shell, DB Tool 등)하여 SQL DROP TABLE 명령어로 테이블을 임의 삭제한 경우.&lt;/li&gt;
&lt;li&gt;migrate --fake 옵션을 사용하여 실제 테이블 생성 과정을 건너뛰고 기록만 남겼으나, 실제로는 테이블이 없었던 경우.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;Case B. OperationalError: table &quot;...&quot; already exists&lt;/h3&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;마이그레이션 기록상으로는 '미적용' 상태라 새로 생성을 시도했으나, 실제 물리 테이블이 이미 존재하는 경우입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;상태 분석:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,1,0,0&quot;&gt;Migration History:&lt;/b&gt; 해당 마이그레이션 기록이 없거나 초기화됨 (Not Applied).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,1,1,0&quot;&gt;Actual Schema:&lt;/b&gt; 과거에 생성된 테이블이 그대로 남아있음 (Zombie Table).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;발생 원인:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,1,0,0&quot;&gt;가장 흔한 원인:&lt;/b&gt; 프로젝트 내 migrations 폴더의 파일들을 삭제(초기화)했으나, DB에 남아있는 실제 테이블을 삭제하지 않은 상태에서 다시 makemigrations 후 migrate를 시도할 때 발생.&lt;/li&gt;
&lt;li&gt;migrate --fake &amp;lt;app&amp;gt; zero 명령어로 기록(History)만 지우고, 실제 테이블(Schema)을 정리하지 않은 경우.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;16&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;3. 근본적인 해결 전략: 동기화(Synchronization) 복구&lt;/h3&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;18&quot; data-path-to-node=&quot;18&quot;&gt;세 가지 요소(파일, 기록, 스키마)의 상태를 다시 일치시키는 과정&lt;/b&gt;.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;19&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0,0&quot;&gt;초기화 (Reset):&lt;/b&gt; 꼬여버린 마이그레이션 파일과 django_migrations 기록을 제거하여 상태를 '0(Zero)'로 초기화(__init__만 남기고 migrations 폴더 내 다른 파일 삭제).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0&quot;&gt;잔재 제거 (Clean Up):&lt;/b&gt; 충돌을 유발하는 실제 DB 테이블을 DROP 하여 스키마 상태를 '0'으로 만듭니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,2,0&quot;&gt;재동기화 (Resync):&lt;/b&gt; 깨끗해진 상태에서 다시 설계도(Migration File)를 만들고, 이를 실행(Migrate)하여 기록과 스키마를 동시에 생성합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-path-to-node=&quot;20&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;나는 파이썬 기본 제공인 sqlite3 을 사용해 개발테스트 중이었기 때문에 철거 코드를 입력하여 처리했다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;1. shell 진입&lt;/p&gt;
&lt;pre id=&quot;code_1767183126706&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python manage.py shell&lt;/code&gt;&lt;/pre&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;2. 철거코드 입력&lt;/p&gt;
&lt;pre id=&quot;code_1767183144359&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 connection

# 커서를 통해 직접 SQL 명령을 날려서 테이블을 날려버립니다.
with connection.cursor() as cursor:
    # 혹시 있을지 모르는 3가지 테이블을 모두 삭제 시도합니다.
    cursor.execute(&quot;DROP TABLE IF EXISTS gateways_gatewayconfig&quot;)
    cursor.execute(&quot;DROP TABLE IF EXISTS gateways_provider&quot;)
    cursor.execute(&quot;DROP TABLE IF EXISTS gateways_servicetype&quot;)

print(&quot;✅ 꼬인 테이블 삭제 완료! exit()를 입력해 나가세요.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;3. 나가기&lt;/p&gt;
&lt;pre id=&quot;code_1767183156553&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;exit()&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알아두면 언젠간 유용할 몇 가지의 지식</category>
      <category>MigrationMismatch</category>
      <category>Migrations</category>
      <category>NoSuchTable</category>
      <category>operationalError</category>
      <category>python</category>
      <author>잼이써요</author>
      <guid isPermaLink="true">https://try-to-do.tistory.com/35</guid>
      <comments>https://try-to-do.tistory.com/35#entry35comment</comments>
      <pubDate>Wed, 31 Dec 2025 21:15:42 +0900</pubDate>
    </item>
    <item>
      <title>pip install 을 사용해 설치했는데 인식하지 못할 때.</title>
      <link>https://try-to-do.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Django 프로젝트를 진행하는 중에 pip install Pillow 로 프로그램 설치를 진행 후 마이그레이션을 진행하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[bash]&lt;/p&gt;
&lt;pre id=&quot;code_1766499631769&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install Pillow
python manage.py makemigration appname&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 style=&quot;background-color: #e9eef6; color: #1f1f1f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SystemCheckError: System check identified some issues:&lt;/p&gt;
&lt;p style=&quot;background-color: #e9eef6; color: #1f1f1f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #e9eef6; color: #1f1f1f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ERRORS:&lt;/p&gt;
&lt;p style=&quot;background-color: #e9eef6; color: #1f1f1f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;posts.Post.thumbnail: (fields.E210) Cannot use ImageField because Pillow is not installed.&lt;/p&gt;
&lt;p style=&quot;background-color: #e9eef6; color: #1f1f1f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; HINT: Get Pillow at &lt;a href=&quot;https://pypi.org/project/Pillow/&quot;&gt;https://pypi.org/project/Pillow/&lt;/a&gt; or run command &quot;python -m pip install Pillow&quot;.&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;pip 업그레이드 및 재 설치하여 해결했다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjB9tbG69ORAxUAAAAAHQAAAAAQtgI&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;# 1. pip 자체를 최신버전으로 업데이트
python -m pip install --upgrade pip

# 2. 혹시 모르니 Pillow 삭제
pip uninstall Pillow -y

# 3. 캐시 없이 다시 깨끗하게 설치
pip install Pillow --no-cache-dir
&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;p data-ke-size=&quot;size16&quot;&gt;나는 Pillow 를 설치하면서 문제가 생겼기 때문에 Pillow 설치 확인 명령어로 확인했다.&lt;/p&gt;
&lt;/div&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjB9tbG69ORAxUAAAAAHQAAAAAQtwI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;python -c &quot;import PIL; print('Pillow 설치 완료:', PIL.__version__)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(정상 설치 확인)&lt;/p&gt;</description>
      <category>알아두면 언젠간 유용할 몇 가지의 지식</category>
      <category>Django</category>
      <category>pillow설치인식문제</category>
      <category>PIP</category>
      <category>pipinstall</category>
      <category>pip설치인식문제</category>
      <category>Pyhon</category>
      <author>잼이써요</author>
      <guid isPermaLink="true">https://try-to-do.tistory.com/34</guid>
      <comments>https://try-to-do.tistory.com/34#entry34comment</comments>
      <pubDate>Tue, 23 Dec 2025 23:25:24 +0900</pubDate>
    </item>
    <item>
      <title>Bonus Book 2. Django Admin 커스터마이징</title>
      <link>https://try-to-do.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지금 관리자 페이지(/admin)에 들어가 보면 게시글 목록이 단순히 Post object (1), Post object (2) 처럼 나오거나, 검색 기능이 없어서 관리가 불편할 겁니다. 이곳을 &lt;b&gt;엑셀처럼 보기 편하고, 검색도 되는 강력한 관리 도구&lt;/b&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;[Bonus Book 2] Django Admin 커스터마이징&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;boards/admin.py&lt;/b&gt; 파일을 열고 아래 코드로 내용을 채워주세요.&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;(기존에 있던 admin.site.register(Board) 등의 코드는 지우고 이 코드로 덮어쓰시면 됩니다.)&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 코드 작성 (boards/admin.py)&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwjA1Mfts7CRAxUAAAAAHQAAAAAQUQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from django.contrib import admin
from .models import Board, Post, Comment

# 1. 게시판(Board) 관리
@admin.register(Board)
class BoardAdmin(admin.ModelAdmin):
    list_display = ('code', 'title', 'description') # 목록에 보여줄 컬럼
    list_display_links = ('code', 'title')          # 클릭해서 수정할 수 있는 컬럼

# 2. 게시글(Post) 관리 - 여기가 제일 중요!
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    # 목록에 보일 항목들 (제목, 작성자, 게시판, 조회수, 작성일)
    list_display = ('title', 'author', 'board', 'views', 'created_at')
    
    # 우측 필터 사이드바 (게시판별, 작성일별 필터링)
    list_filter = ('board', 'created_at')
    
    # 상단 검색창 (제목, 내용, 작성자 닉네임으로 검색)
    # author__nickname: author(User) 모델의 nickname 필드를 검색하겠다는 뜻
    search_fields = ('title', 'content', 'author__nickname', 'author__username')
    
    # 날짜 계층 구조 (상단에 연도-월-일 네비게이션 생김)
    date_hierarchy = 'created_at'
    
    # 페이지당 보여줄 개수
    list_per_page = 20

# 3. 댓글(Comment) 관리
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
    list_display = ('content_summary', 'post', 'author', 'created_at')
    search_fields = ('content', 'author__username')
    
    # 댓글 내용이 길 수 있으니 앞부분만 잘라서 보여주는 함수
    def content_summary(self, obj):
        return obj.content[:20] + &quot;...&quot; if len(obj.content) &amp;gt; 20 else obj.content
    content_summary.short_description = &quot;댓글 내용&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;10&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 확인해 보기&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서버가 켜져 있는지 확인하고, 브라우저에서 /admin 페이지를 &lt;b&gt;새로고침&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Boards&lt;/b&gt; 앱 밑에 Posts를 클릭해 보세요.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이제 제목, 작성자, 조회수가 표처럼 깔끔하게 나오나요?&lt;/li&gt;
&lt;li&gt;오른쪽에 &lt;b&gt;Filter&lt;/b&gt; 사이드바가 생겼나요?&lt;/li&gt;
&lt;li&gt;맨 위에 **검색창(Search)**이 생겼나요?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;검색창에 테스트로 작성했던 글의 제목이나 작성자를 검색해 보세요.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Step by Step/[phase 2] django community upgrade</category>
      <category>관리자화면커스텀</category>
      <category>스텝바이스텝</category>
      <category>파이썬무작정부딪히기</category>
      <category>파이썬웹개발튜토리얼</category>
      <category>파이썬웹프로젝트</category>
      <author>잼이써요</author>
      <guid isPermaLink="true">https://try-to-do.tistory.com/33</guid>
      <comments>https://try-to-do.tistory.com/33#entry33comment</comments>
      <pubDate>Tue, 9 Dec 2025 20:40:06 +0900</pubDate>
    </item>
    <item>
      <title>Bonus Book 1. 게시판 관리자(Staff) 권한 부여</title>
      <link>https://try-to-do.tistory.com/32</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;[Bonus Book 1] 게시판 관리자(Staff) 권한 부여&lt;/h3&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;슈퍼유저(Superuser)는 너무 강력한 권한(모든 것 가능)을 가지므로, 평소에는 &lt;b&gt;&quot;게시판 관리자(Board Manager)&quot;&lt;/b&gt; 권한만 가진 계정으로 활동하는 것이 보안상 안전합니다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 모델 확인 (accounts/models.py)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;우리가 Phase 1에서 User 모델을 만들 때, 미리 &lt;b&gt;is_board_manager&lt;/b&gt; 라는 필드를 만들어 둔 것을 기억하시나요? 드디어 이것을 써먹을 때입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;혹시 기억이 안 나실까 봐 코드를 다시 확인해 봅니다. (수정할 필요는 없습니다. 눈으로만 확인하세요.)&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwjA1Mfts7CRAxUAAAAAHQAAAAAQPw&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# accounts/models.py
class User(AbstractUser):
    # 이 필드를 활용합니다!
    is_board_manager = models.BooleanField(default=False, verbose_name=&quot;게시판 관리자 여부&quot;)
    # ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;11&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;12&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.관리자 화면에 커스텀 필드 추가하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;accounts/admin.py&lt;/b&gt; 파일을 열고 아래 코드로 &lt;b&gt;전체 내용을 교체&lt;/b&gt;해 주세요.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwjA1Mfts7CRAxUAAAAAHQAAAAAQSA&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User

class CustomUserAdmin(UserAdmin):
    # [1] 목록 화면에서 보이는 컬럼 설정
    # (기본) 아이디, 이메일, 이름, 스태프여부 + (추가) 닉네임, 게시판관리자여부
    list_display = ('username', 'nickname', 'email', 'is_board_manager', 'is_staff', 'is_superuser')
    
    # [2] 수정 화면(상세)에서 보이는 필드 설정 (fieldsets)
    # 기존 UserAdmin의 설정 뒤에 '추가 정보' 섹션을 붙입니다.
    fieldsets = UserAdmin.fieldsets + (
        ('추가 정보', {'fields': ('nickname', 'avatar', 'is_board_manager')}),
    )

# User 모델을 우리가 만든 CustomUserAdmin 설정으로 등록
admin.site.register(User, CustomUserAdmin)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 권한 체크 로직 수정 (boards/views.py)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;기존의 삭제 로직은 &lt;b&gt;&quot;작성자가 아니면 튕겨내라!&quot;&lt;/b&gt; 였습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;이것을 &lt;b&gt;&quot;작성자가 아니고, 관리자도 아니면 튕겨내라!&quot;&lt;/b&gt; 로 바꿔야 합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;boards/views.py&lt;/b&gt;에서 &lt;b&gt;board_delete(게시글 삭제)와 comment_delete(댓글 삭제) 함수를 찾아 수정&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 게시글 삭제 (board_delete) 수정&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwjA1Mfts7CRAxUAAAAAHQAAAAAQQA&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;@login_required
def board_delete(request, board_code, pk):
    board = get_object_or_404(Board, code=board_code)
    post = get_object_or_404(Post, pk=pk, board=board)
    
    # [수정] 작성자 본인이거나, '게시판 관리자' 권한이 있는 경우 삭제 허용
    if request.user == post.author or request.user.is_board_manager or request.user.is_superuser:
        post.delete()
        return redirect('boards:board_list', board_code=board_code)
    else:
        # 권한 없는 사람이 시도하면 에러 페이지(403)를 띄우거나 뒤로 보냄
        # 여기서는 간단하게 상세 페이지로 돌려보냄
        return redirect('boards:board_detail', board_code=board_code, pk=pk)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 댓글 삭제 (comment_delete) 수정&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwjA1Mfts7CRAxUAAAAAHQAAAAAQQQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;@login_required
def comment_delete(request, board_code, pk, comment_pk):
    post = get_object_or_404(Post, board__code=board_code, pk=pk)
    comment = get_object_or_404(Comment, pk=comment_pk, post=post)

    # [수정] 작성자 본인 or 관리자 or 슈퍼유저 허용
    if request.user == comment.author or request.user.is_board_manager or request.user.is_superuser:
        comment.delete()
    
    return redirect('boards:board_detail', board_code=board_code, pk=pk)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;19&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 화면 수정 (boards/templates/boards/board_detail.html)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;삭제 버튼도 마찬가지입니다. 지금은 &quot;내 글&quot;일 때만 삭제 버튼이 보입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;내가 쓴 글이 아니더라도, 내가 관리자라면 삭제 버튼이 보여야&quot;&lt;/b&gt; 합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;board_detail.html&lt;/b&gt;에서 &lt;b&gt;게시글 삭제 버튼과 댓글 삭제 버튼의 if 조건을 수정&lt;/b&gt;하세요.&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 게시글 삭제 버튼 부분&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwjA1Mfts7CRAxUAAAAAHQAAAAAQQg&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;{% if post.author == user or user.is_board_manager or user.is_superuser %}
    &amp;lt;a href=&quot;{% url 'boards:board_edit' board.code post.pk %}&quot; class=&quot;btn btn-secondary&quot;&amp;gt;수정&amp;lt;/a&amp;gt;
    &amp;lt;a href=&quot;{% url 'boards:board_delete' board.code post.pk %}&quot; class=&quot;btn btn-danger&quot; onclick=&quot;return confirm('정말 삭제하시겠습니까?');&quot;&amp;gt;삭제&amp;lt;/a&amp;gt;
{% endif %}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 댓글 삭제 버튼 부분 (반복문 안쪽)&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwjA1Mfts7CRAxUAAAAAHQAAAAAQQw&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;{% if comment.author == user or user.is_board_manager or user.is_superuser %}
    &amp;lt;a href=&quot;{% url 'boards:comment_delete' board.code post.pk comment.pk %}&quot; 
       class=&quot;text-danger small text-decoration-none ms-2&quot;
       onclick=&quot;return confirm('댓글을 삭제하시겠습니까?')&quot;&amp;gt;삭제&amp;lt;/a&amp;gt;
{% endif %}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;27&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;28&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 테스트 (관리자 권한 부여)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;이제 일반 유저 계정 하나를 &lt;b&gt;&quot;게시판 매니저&quot;&lt;/b&gt;로 승진시켜서 테스트해 봅시다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;30&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;슈퍼유저 로그인:&lt;/b&gt; /admin 페이지로 접속해서 로그인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유저 수정:&lt;/b&gt; Users 메뉴에서 테스트용 아이디(예: testuser)를 클릭합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;권한 부여:&lt;/b&gt; 스크롤을 내려서 &lt;b&gt;게시판 관리자 여부&lt;/b&gt; (is_board_manager) 체크박스에 체크하고 &lt;b&gt;저장&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;삭제 테스트:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;30,3,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이제 그 testuser로 사이트에 로그인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;다른 사람&quot;&lt;/b&gt;이 쓴 글이나 댓글에 가봅니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[삭제]&lt;/b&gt; 버튼이 보이나요?&lt;/li&gt;
&lt;li&gt;버튼을 눌렀을 때 실제로 삭제가 되나요?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Step by Step/[phase 2] django community upgrade</category>
      <category>권한부여기능</category>
      <category>스텝바이스텝</category>
      <category>파이썬무작정부딪히기</category>
      <category>파이썬웹개발튜토리얼</category>
      <category>파이썬웹프로젝트</category>
      <author>잼이써요</author>
      <guid isPermaLink="true">https://try-to-do.tistory.com/32</guid>
      <comments>https://try-to-do.tistory.com/32#entry32comment</comments>
      <pubDate>Tue, 9 Dec 2025 20:37:35 +0900</pubDate>
    </item>
    <item>
      <title>8. 디테일 업그레이드 ( 조회수 쿠키 &amp;amp; Humanize)</title>
      <link>https://try-to-do.tistory.com/31</link>
      <description>&lt;h4 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size20&quot;&gt;[STEP 8] 디테일 업그레이드 (조회수 쿠키 &amp;amp; Humanize)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;본 카테고리의 마지막 단계입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;조회수 중복 증가(F5 연타)를 방지&lt;/b&gt;하고, &lt;b&gt;&quot;방금 전&quot;, &quot;5분 전&quot;&lt;/b&gt; 같은 날짜 표시를 적용합니다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 설정 추가 (config/settings.py)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;config/settings.py&lt;/b&gt; 를 열어 날짜를 사람이 보기 편하게 바꿔주는 &lt;b&gt;humanize&lt;/b&gt; 앱을 등록합니다.&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQiQk&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;INSTALLED_APPS = [
    # ... 기존 앱들 ...
    'django.contrib.staticfiles',
    'django.contrib.humanize', # [추가]
    # ...
]

# (참고) 언어 설정이 한국어여야 &quot;방금 전&quot;으로 나옵니다.
LANGUAGE_CODE = 'ko-kr'
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-path-to-node=&quot;20&quot; data-ke-style=&quot;style2&quot;&gt;⚠️ &lt;b&gt;중요:&lt;/b&gt; 서버가 실행 중이라면 설정 파일을 수정했으므로 서버를 재시작해 주세요.&lt;/blockquote&gt;
&lt;hr data-path-to-node=&quot;21&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 로직 수정 (boards/views.py)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;쿠키(Cookie)를 사용하여&lt;i&gt; &lt;b&gt;&quot;오늘 하루 동안은 같은 글의 조회수를 1번만 증가&quot;&lt;/b&gt;&lt;/i&gt;시키도록 로직을 변경합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;boards/views.py&lt;/b&gt;의 &lt;b&gt;board_detail 함수&lt;/b&gt;를 수정하세요.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQigk&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# [import 추가]
from django.utils import timezone
from datetime import datetime, timedelta, time

def board_detail(request, board_code, pk):
    board = get_object_or_404(Board, code=board_code)
    post = get_object_or_404(Post, pk=pk, board=board)
    
    # 1. 쿠키 이름 정의 (게시글마다 달라야 함)
    cookie_name = f'hitboard_{board_code}_{pk}'
    
    # 2. 응답 객체 미리 생성 (쿠키를 심기 위해)
    # select_related('author'): 댓글 작성자 정보를 한 번에 가져와서 DB 성능 최적화
    response = render(request, 'boards/board_detail.html', {
        'board': board,
        'post': post,
        'comments': post.comments.select_related('author').all(), 
        'form': CommentForm()
    })

    # 3. 쿠키 확인: 쿠키가 없을 때만 조회수 증가
    if request.COOKIES.get(cookie_name) is None:
        post.views += 1
        post.save()
        
        # 4. 쿠키 유효기간 설정 (오늘 밤 자정까지)
        tomorrow = datetime.now() + timedelta(days=1)
        midnight = datetime.combine(tomorrow, time.min)
        expires = (midnight - datetime.now()).total_seconds()
        
        # 쿠키 심기
        response.set_cookie(cookie_name, 'true', max_age=expires)
        
    return response
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;26&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;27&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 화면 수정 1 - 메인 대시보드 (templates/home.html)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메인 화면(templates/home.html)&lt;/b&gt;의 게시글 목록에 &lt;b&gt;작성 시간(naturaltime)&lt;/b&gt;을 추가합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQiwk&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;{% extends 'base.html' %}
{% load humanize %} {% block content %}
&amp;lt;div class=&quot;row mt-2&quot;&amp;gt;
    &amp;lt;div class=&quot;col-md-6 mb-4&quot;&amp;gt;
        &amp;lt;div class=&quot;card h-100&quot;&amp;gt;
            &amp;lt;div class=&quot;card-header bg-light&quot;&amp;gt;
                &amp;lt;i class=&quot;bi bi-clock&quot;&amp;gt;&amp;lt;/i&amp;gt; &amp;lt;strong&amp;gt;자유게시판 최신글&amp;lt;/strong&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;ul class=&quot;list-group list-group-flush&quot;&amp;gt;
                {% for post in free_posts %}
                &amp;lt;li class=&quot;list-group-item d-flex justify-content-between align-items-center&quot;&amp;gt;
                    &amp;lt;a href=&quot;{% url 'boards:board_detail' post.board.code post.pk %}&quot; class=&quot;text-decoration-none text-dark text-truncate&quot; style=&quot;max-width: 75%;&quot;&amp;gt;
                        {{ post.title }}
                        
                        &amp;lt;span class=&quot;small text-muted ms-1&quot;&amp;gt;
                             {{ post.created_at|naturaltime }}
                        &amp;lt;/span&amp;gt;
                    &amp;lt;/a&amp;gt;
                    &amp;lt;div class=&quot;small text-muted&quot;&amp;gt;
                        {{ post.author.username }}
                    &amp;lt;/div&amp;gt;
                &amp;lt;/li&amp;gt;
                {% empty %}
                &amp;lt;li class=&quot;list-group-item text-center text-muted small py-3&quot;&amp;gt;등록된 게시글이 없습니다.&amp;lt;/li&amp;gt;
                {% endfor %}
            &amp;lt;/ul&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot; data-path-to-node=&quot;3&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot; data-path-to-node=&quot;3&quot;&gt;&lt;b&gt;  naturaltime이 자동으로 해주는 것&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;{{ post.created_at|naturaltime }} 한 줄을 적으면, 내부적으로 이런 계산을 수행합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot; data-path-to-node=&quot;5&quot;&gt;
&lt;li&gt;&lt;b&gt;현재 시간(Now)&lt;/b&gt;과 &lt;b&gt;입력된 시간(Created_at)&lt;/b&gt;의 차이를 계산합니다.&lt;/li&gt;
&lt;li&gt;그 차이에 따라 적절한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;문자열&lt;/b&gt;을 리턴합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-path-to-node=&quot;5,1,1&quot;&gt;
&lt;li&gt;&lt;b&gt;몇 초 차이:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;방금 전 (now)&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;몇 분 차이:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;3분 전 (3 minutes ago)&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;몇 시간 차이:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;2시간 전 (2 hours ago)&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하루 이상:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;어제 (yesterday)&quot;, &quot;3일 전&quot;, &quot;1달 전&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-path-to-node=&quot;30&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;31&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 화면 수정 2 - 게시글 상세 (boards/board_detail.html)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;게시글 상세 화면(boards/board_detail.html)&lt;/b&gt;에서&lt;b&gt; 게시글과 댓글 작성 시간에도 적용&lt;/b&gt;합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQjAk&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;{% extends 'base.html' %}
{% load humanize %} {% block content %}
&amp;lt;span class=&quot;text-muted small ms-2&quot;&amp;gt;{{ post.created_at|naturaltime }}&amp;lt;/span&amp;gt;
&amp;lt;span class=&quot;text-muted small ms-2&quot;&amp;gt;{{ comment.created_at|naturaltime }}&amp;lt;/span&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;34&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;35&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 화면 수정 3 - 쪽지함 (accounts/message_list.html)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;쪽지함 (accounts/message_list.html)&lt;/b&gt;의 &lt;b&gt;쪽지 수신 시간에도 적용&lt;/b&gt;합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQjQk&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;{% extends 'base.html' %}
{% load humanize %} {% block content %}
&amp;lt;small class=&quot;text-muted&quot;&amp;gt;{{ msg.created_at|naturaltime }}&amp;lt;/small&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;38&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;39&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6. 테스트&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;40&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;조회수 방지:&lt;/b&gt; 게시글 접속 후 F5를 눌러도 조회수가 그대로인지 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시간 표시:&lt;/b&gt; &quot;방금 전&quot;, &quot;5분 전&quot; 등으로 예쁘게 나오는지 확인합니다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Step by Step/[phase 2] django community upgrade</category>
      <category>날짜표기방식</category>
      <category>방금전5분전표시등</category>
      <category>스텝바이스텝</category>
      <category>조회수증가중복방지</category>
      <category>조회수쿠키사용</category>
      <category>파이썬무작정부딪히기</category>
      <category>파이썬웹개발튜토리얼</category>
      <category>파이썬웹프로젝트</category>
      <author>잼이써요</author>
      <guid isPermaLink="true">https://try-to-do.tistory.com/31</guid>
      <comments>https://try-to-do.tistory.com/31#entry31comment</comments>
      <pubDate>Sun, 7 Dec 2025 22:18:57 +0900</pubDate>
    </item>
    <item>
      <title>7. 사용자 간 1:1 쪽지 기능(DM) 구현</title>
      <link>https://try-to-do.tistory.com/30</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;[STEP 7] 사용자 간 1:1 쪽지 기능 (DM) 구현하기&lt;/h4&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;이번 단계에서는 &lt;b&gt;이메일 스타일의 쪽지(Direct Message) 시스템&lt;/b&gt;을 구현해 보겠습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;단순한 텍스트 전송을 넘어, &lt;b&gt;제목(Title)&lt;/b&gt;이 있고 상대방이 읽었는지 확인할 수 있는 &lt;b&gt;수신 확인(Read Check)&lt;/b&gt; 기능을 포함한 Direct Message 기능을 구현해보도록 하겠습니다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 모델 수정 (accounts/models.py)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;쪽지 데이터를 저장할 모델을 정의합니다. 누가(Sender), 누구에게(Receiver), 무엇을(Title, Content) 보냈는지 저장하며, &lt;b&gt;read_at&lt;/b&gt; 필드를 통해 수신 확인 기능을 구현합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;accounts/models.py&lt;/b&gt; 하단에 &lt;b&gt;Message 클래스&lt;/b&gt;를 추가합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQ9QY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from django.db import models
from django.conf import settings # User 모델 참조

class Message(models.Model):
    # 보낸 사람, 받는 사람 (User 모델과 1:N 관계)
    sender = models.ForeignKey(settings.AUTH_USER_MODEL, related_name=&quot;sent_messages&quot;, on_delete=models.CASCADE, verbose_name=&quot;보낸 사람&quot;)
    receiver = models.ForeignKey(settings.AUTH_USER_MODEL, related_name=&quot;received_messages&quot;, on_delete=models.CASCADE, verbose_name=&quot;받는 사람&quot;)
    
    # 제목 및 내용
    title = models.CharField(max_length=200, verbose_name=&quot;제목&quot;)
    content = models.TextField(verbose_name=&quot;내용&quot;)
    
    # 시간 기록
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=&quot;보낸 시간&quot;)
    read_at = models.DateTimeField(null=True, blank=True, verbose_name=&quot;읽은 시간&quot;) # 읽었을 때만 시간이 기록됨

    def __str__(self):
        return f&quot;To {self.receiver}: {self.title}&quot;
    
    class Meta:
        ordering = ['-created_at'] # 최신 쪽지가 맨 위에 오도록 정렬&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;11&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. DB 반영 (마이그레이션)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;새로운 모델을 DB에 반영합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;[Bash]&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQ9gY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;python manage.py makemigrations&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-path-to-node=&quot;15&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-path-to-node=&quot;15,0&quot; data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;주의: 기존 데이터 처리 알림&lt;/b&gt; 만약 &quot;It is impossible to add a non-nullable field 'title'...&quot; 메시지가 뜬다면, 기존 데이터에 제목을 뭘로 채울지 묻는 것입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;15,1&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Select an option: 1 입력 (일회성 기본값 제공)&lt;/li&gt;
&lt;li&gt;&amp;gt;&amp;gt;&amp;gt; '제목 없음' 입력 (작은따옴표 필수!)&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQ9wY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;[Bash]&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;python manage.py migrate&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;17&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 폼 작성 (accounts/forms.py)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;쪽지 작성 시 사용할 폼을 만듭니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;accounts/forms.py&lt;/b&gt;를 수정합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQ-AY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# [import 추가] Message 모델 추가
from .models import User, Message 

# ... 기존 UserChangeForm ...

# [추가] 쪽지 작성 폼
class MessageForm(forms.ModelForm):
    class Meta:
        model = Message
        fields = ['title', 'content'] # 제목과 내용만 입력받음
        widgets = {
            'title': forms.TextInput(attrs={
                'class': 'form-control',
                'placeholder': '제목을 입력하세요.'
            }),
            'content': forms.Textarea(attrs={
                'class': 'form-control',
                'rows': 5,
                'placeholder': '내용을 입력하세요.'
            })
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;22&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;23&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. URL 설정 (accounts/urls.py)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;쪽지함(목록), 보내기, 상세 보기 3가지 URL을 등록합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;accounts/urls.py&lt;/b&gt;에 추가합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQ-QY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;urlpatterns = [
    # ... 기존 url들 ...
    
    # 1. 쪽지함 (받은/보낸 통합)
    path('messages/', views.message_list, name='message_list'),
    
    # 2. 쪽지 보내기 (receiver_pk: 받는 사람 ID)
    path('messages/send/&amp;lt;int:receiver_pk&amp;gt;/', views.message_send, name='message_send'),
    
    # 3. 쪽지 상세 보기 (message_pk: 쪽지 ID)
    path('messages/&amp;lt;int:message_pk&amp;gt;/', views.message_detail, name='message_detail'),
]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;27&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;28&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 로직 작성 (accounts/views.py)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;쪽지 기능의 핵심 로직입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;message_list에서 안 읽은 개수 계산&lt;/b&gt;과 &lt;b&gt;message_detail에서 읽음 처리&lt;/b&gt; 로직을 눈여겨보세요.&lt;/p&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;accounts/views.py&lt;/b&gt; 하단에 추가합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQ-gY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# [필수 import]
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.contrib.auth import get_user_model
from django.utils import timezone # 시간 기록용
from .models import Message
from .forms import MessageForm

# 1. 쪽지함 (목록)
@login_required
def message_list(request):
    # 받은 쪽지 &amp;amp; 보낸 쪽지 조회
    received_messages = Message.objects.filter(receiver=request.user)
    sent_messages = Message.objects.filter(sender=request.user)
    
    # [핵심] 안 읽은 쪽지 개수 계산 (read_at이 비어있는 것)
    unread_count = received_messages.filter(read_at__isnull=True).count()
    
    context = {
        'received_messages': received_messages,
        'sent_messages': sent_messages,
        'unread_count': unread_count, # 뱃지 표시용
    }
    return render(request, 'accounts/message_list.html', context)

# 2. 쪽지 보내기
@login_required
def message_send(request, receiver_pk):
    User = get_user_model()
    receiver = get_object_or_404(User, pk=receiver_pk)
    
    if request.method == 'POST':
        form = MessageForm(request.POST)
        if form.is_valid():
            message = form.save(commit=False)
            message.sender = request.user   # 보낸 사람: 나
            message.receiver = receiver     # 받는 사람: 지정된 유저
            message.save()
            return redirect('accounts:message_list')
    else:
        form = MessageForm()
        
    return render(request, 'accounts/message_send.html', {'form': form, 'receiver': receiver})

# 3. 쪽지 상세 보기 &amp;amp; 읽음 처리
@login_required
def message_detail(request, message_pk):
    message = get_object_or_404(Message, pk=message_pk)
    
    # [권한 체크] 당사자(보낸 사람, 받은 사람)가 아니면 접근 불가
    if request.user != message.sender and request.user != message.receiver:
        return redirect('accounts:message_list')

    # [읽음 처리] 받는 사람이 처음 열어본 경우에만 현재 시간 기록
    if request.user == message.receiver and not message.read_at:
        message.read_at = timezone.now()
        message.save()

    return render(request, 'accounts/message_detail.html', {'message': message})
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;32&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;33&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6. 화면 작성 1 - 쪽지함 (accounts/message_list.html)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size16&quot;&gt;Bootstrap Tabs를 이용해 받은 편지함과 보낸 편지함을 구분합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;받은 편지함 탭&lt;/b&gt;에는 안 읽은 메시지가 있을 때 &lt;b&gt;빨간 뱃지(N)&lt;/b&gt;를 띄워줍니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;accounts/templates/accounts/message_list.html 생성 &lt;/b&gt;후 아래 코드를 작성합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQ-wY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;{% extends 'base.html' %}

{% block content %}
&amp;lt;div class=&quot;container mt-4&quot;&amp;gt;
    &amp;lt;h3 class=&quot;mb-4&quot;&amp;gt;&amp;lt;i class=&quot;bi bi-envelope&quot;&amp;gt;&amp;lt;/i&amp;gt; 내 쪽지함&amp;lt;/h3&amp;gt;

    &amp;lt;ul class=&quot;nav nav-tabs&quot; id=&quot;messageTab&quot; role=&quot;tablist&quot;&amp;gt;
        &amp;lt;li class=&quot;nav-item&quot; role=&quot;presentation&quot;&amp;gt;
            &amp;lt;button class=&quot;nav-link active&quot; id=&quot;received-tab&quot; data-bs-toggle=&quot;tab&quot; data-bs-target=&quot;#received&quot; type=&quot;button&quot; role=&quot;tab&quot; aria-controls=&quot;received&quot; aria-selected=&quot;true&quot;&amp;gt;
                받은 쪽지 
                {% if unread_count &amp;gt; 0 %}
                    &amp;lt;span class=&quot;badge bg-danger rounded-pill&quot;&amp;gt;{{ unread_count }}&amp;lt;/span&amp;gt;
                {% endif %}
            &amp;lt;/button&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;li class=&quot;nav-item&quot; role=&quot;presentation&quot;&amp;gt;
            &amp;lt;button class=&quot;nav-link&quot; id=&quot;sent-tab&quot; data-bs-toggle=&quot;tab&quot; data-bs-target=&quot;#sent&quot; type=&quot;button&quot; role=&quot;tab&quot; aria-controls=&quot;sent&quot; aria-selected=&quot;false&quot;&amp;gt;
                보낸 쪽지 
                &amp;lt;/button&amp;gt;
        &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;

    &amp;lt;div class=&quot;tab-content p-3 border border-top-0 bg-white&quot; id=&quot;messageTabContent&quot;&amp;gt;
        
        &amp;lt;div class=&quot;tab-pane fade show active&quot; id=&quot;received&quot; role=&quot;tabpanel&quot; aria-labelledby=&quot;received-tab&quot;&amp;gt;
            &amp;lt;div class=&quot;list-group list-group-flush&quot;&amp;gt;
                {% for msg in received_messages %}
                &amp;lt;a href=&quot;{% url 'accounts:message_detail' msg.pk %}&quot; class=&quot;list-group-item list-group-item-action&quot;&amp;gt;
                    &amp;lt;div class=&quot;d-flex justify-content-between&quot;&amp;gt;
                        &amp;lt;strong&amp;gt;
                            {% if not msg.read_at %}
                            &amp;lt;span class=&quot;badge bg-danger me-1&quot;&amp;gt;N&amp;lt;/span&amp;gt; {% endif %}
                            {{ msg.title }}
                        &amp;lt;/strong&amp;gt;
                        &amp;lt;small class=&quot;text-muted&quot;&amp;gt;{{ msg.created_at|date:&quot;Y-m-d&quot; }}&amp;lt;/small&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div class=&quot;small text-muted mt-1&quot;&amp;gt;
                        From: 
                        {% if msg.sender.avatar %}
                        &amp;lt;img src=&quot;{{ msg.sender.avatar.url }}&quot; width=&quot;20&quot; height=&quot;20&quot; class=&quot;rounded-circle me-1&quot;&amp;gt;
                        {% endif %}
                        {{ msg.sender.nickname|default:msg.sender.username }}
                    &amp;lt;/div&amp;gt;
                &amp;lt;/a&amp;gt;
                {% empty %}
                &amp;lt;p class=&quot;text-center py-4 text-muted&quot;&amp;gt;받은 쪽지가 없습니다.&amp;lt;/p&amp;gt;
                {% endfor %}
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div class=&quot;tab-pane fade&quot; id=&quot;sent&quot; role=&quot;tabpanel&quot; aria-labelledby=&quot;sent-tab&quot;&amp;gt;
             &amp;lt;div class=&quot;list-group list-group-flush&quot;&amp;gt;
                {% for msg in sent_messages %}
                &amp;lt;a href=&quot;{% url 'accounts:message_detail' msg.pk %}&quot; class=&quot;list-group-item list-group-item-action&quot;&amp;gt;
                    &amp;lt;div class=&quot;d-flex justify-content-between&quot;&amp;gt;
                        &amp;lt;strong&amp;gt;{{ msg.title }}&amp;lt;/strong&amp;gt;
                        &amp;lt;small class=&quot;text-muted&quot;&amp;gt;
                            {% if msg.read_at %}
                            &amp;lt;span class=&quot;text-success&quot;&amp;gt;&amp;lt;i class=&quot;bi bi-check-all&quot;&amp;gt;&amp;lt;/i&amp;gt; 읽음&amp;lt;/span&amp;gt;
                            {% else %}
                            &amp;lt;span class=&quot;text-secondary&quot;&amp;gt;읽지 않음&amp;lt;/span&amp;gt;
                            {% endif %}
                        &amp;lt;/small&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div class=&quot;small text-muted mt-1&quot;&amp;gt;
                        To: 
                        {% if msg.receiver.avatar %}
                        &amp;lt;img src=&quot;{{ msg.receiver.avatar.url }}&quot; width=&quot;20&quot; height=&quot;20&quot; class=&quot;rounded-circle me-1&quot;&amp;gt;
                        {% endif %}
                        {{ msg.receiver.nickname|default:msg.receiver.username }}
                    &amp;lt;/div&amp;gt;
                &amp;lt;/a&amp;gt;
                {% empty %}
                &amp;lt;p class=&quot;text-center py-4 text-muted&quot;&amp;gt;보낸 쪽지가 없습니다.&amp;lt;/p&amp;gt;
                {% endfor %}
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;37&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;38&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;7. 화면 작성 2 - 쪽지 쓰기 (accounts/message_send.html)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size16&quot;&gt;제목과 내용을 입력받는 폼입니다. form.as_p 대신 수동으로 필드를 배치합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;accounts/templates/accounts/message_send.html&lt;/b&gt; 생성 후 아래 코드를 작성합니다. &lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQ_AY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;{% extends 'base.html' %}

{% block content %}
&amp;lt;div class=&quot;row justify-content-center&quot;&amp;gt;
    &amp;lt;div class=&quot;col-md-6&quot;&amp;gt;
        &amp;lt;div class=&quot;card mt-4&quot;&amp;gt;
            &amp;lt;div class=&quot;card-header bg-primary text-white&quot;&amp;gt;
                &amp;lt;i class=&quot;bi bi-send&quot;&amp;gt;&amp;lt;/i&amp;gt; 쪽지 보내기
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;card-body&quot;&amp;gt;
                &amp;lt;p&amp;gt;
                    받는 사람: 
                    &amp;lt;strong&amp;gt;
                        {% if receiver.avatar %}
                        &amp;lt;img src=&quot;{{ receiver.avatar.url }}&quot; width=&quot;24&quot; height=&quot;24&quot; class=&quot;rounded-circle me-1&quot;&amp;gt;
                        {% endif %}
                        {{ receiver.nickname|default:receiver.username }}
                    &amp;lt;/strong&amp;gt;
                &amp;lt;/p&amp;gt;
                
                &amp;lt;form method=&quot;post&quot;&amp;gt;
                    {% csrf_token %}
                    
                    &amp;lt;div class=&quot;mb-3&quot;&amp;gt;
                        &amp;lt;label for=&quot;{{ form.title.id_for_label }}&quot; class=&quot;form-label&quot;&amp;gt;제목&amp;lt;/label&amp;gt;
                        {{ form.title }}
                    &amp;lt;/div&amp;gt;

                    &amp;lt;div class=&quot;mb-3&quot;&amp;gt;
                        &amp;lt;label for=&quot;{{ form.content.id_for_label }}&quot; class=&quot;form-label&quot;&amp;gt;내용&amp;lt;/label&amp;gt;
                        {{ form.content }}
                    &amp;lt;/div&amp;gt;
                    
                    &amp;lt;div class=&quot;d-grid gap-2&quot;&amp;gt;
                        &amp;lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&amp;gt;전송하기&amp;lt;/button&amp;gt;
                        &amp;lt;a href=&quot;javascript:history.back()&quot; class=&quot;btn btn-secondary&quot;&amp;gt;취소&amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/form&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;42&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;43&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;8. 화면 작성 3 - 쪽지 상세 (accounts/message_detail.html)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size16&quot;&gt;쪽지의 내용을 확인하고, 받은 쪽지라면 바로 답장할 수 있는 버튼을 제공합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;accounts/templates/accounts/message_detail.html&lt;/b&gt; 생성&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;후 아래 코드를 작성합니다. &lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQ_QY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;{% extends 'base.html' %}

{% block content %}
&amp;lt;div class=&quot;row justify-content-center&quot;&amp;gt;
    &amp;lt;div class=&quot;col-md-8&quot;&amp;gt;
        &amp;lt;div class=&quot;card mt-4&quot;&amp;gt;
            &amp;lt;div class=&quot;card-header d-flex justify-content-between align-items-center&quot;&amp;gt;
                &amp;lt;h5 class=&quot;mb-0&quot;&amp;gt;{{ message.title }}&amp;lt;/h5&amp;gt;
                &amp;lt;small class=&quot;text-muted&quot;&amp;gt;{{ message.created_at|date:&quot;Y-m-d H:i&quot; }}&amp;lt;/small&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;card-body&quot;&amp;gt;
                &amp;lt;div class=&quot;mb-3 p-3 bg-light rounded d-flex justify-content-between align-items-center&quot;&amp;gt;
                    &amp;lt;div&amp;gt;
                        &amp;lt;span class=&quot;text-secondary me-2&quot;&amp;gt;보낸 사람:&amp;lt;/span&amp;gt;
                        {% if message.sender.avatar %}
                            &amp;lt;img src=&quot;{{ message.sender.avatar.url }}&quot; width=&quot;24&quot; height=&quot;24&quot; class=&quot;rounded-circle&quot;&amp;gt;
                        {% endif %}
                        &amp;lt;strong&amp;gt;{{ message.sender.nickname|default:message.sender.username }}&amp;lt;/strong&amp;gt;
                    &amp;lt;/div&amp;gt;
                    
                    {% if user == message.sender %}
                        &amp;lt;div class=&quot;small&quot;&amp;gt;
                            {% if message.read_at %}
                                &amp;lt;span class=&quot;text-success&quot;&amp;gt;&amp;lt;i class=&quot;bi bi-check-all&quot;&amp;gt;&amp;lt;/i&amp;gt; {{ message.read_at|date:&quot;m/d H:i&quot; }} 읽음&amp;lt;/span&amp;gt;
                            {% else %}
                                &amp;lt;span class=&quot;text-muted&quot;&amp;gt;읽지 않음&amp;lt;/span&amp;gt;
                            {% endif %}
                        &amp;lt;/div&amp;gt;
                    {% endif %}
                &amp;lt;/div&amp;gt;

                &amp;lt;div class=&quot;content mb-4&quot; style=&quot;min-height: 150px;&quot;&amp;gt;
                    {{ message.content|linebreaks }}
                &amp;lt;/div&amp;gt;

                &amp;lt;div class=&quot;d-flex justify-content-end gap-2&quot;&amp;gt;
                    &amp;lt;a href=&quot;{% url 'accounts:message_list' %}&quot; class=&quot;btn btn-secondary&quot;&amp;gt;목록&amp;lt;/a&amp;gt;
                    
                    {% if user == message.receiver %}
                        &amp;lt;a href=&quot;{% url 'accounts:message_send' message.sender.pk %}&quot; class=&quot;btn btn-primary&quot;&amp;gt;답장&amp;lt;/a&amp;gt;
                    {% endif %}
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;47&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-path-to-node=&quot;48&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;9. 네비게이션 및 게시판 연결&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;49&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 이 훌륭한 기능을 사용자가 쉽게 찾을 수 있도록 연결합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 상단바 수정 (templates/base.html)&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 프로필 클릭 시 마이페이지 | 쪽지함 드롭다운 메뉴 추가&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQ_gY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;li class=&quot;nav-item dropdown me-3&quot;&amp;gt;
    &amp;lt;a class=&quot;nav-link dropdown-toggle d-flex align-items-center gap-2&quot; href=&quot;#&quot; role=&quot;button&quot; data-bs-toggle=&quot;dropdown&quot; aria-expanded=&quot;false&quot;&amp;gt;
        &amp;lt;strong&amp;gt;{{ user.nickname|default:user.username }}&amp;lt;/strong&amp;gt;
    &amp;lt;/a&amp;gt;
    &amp;lt;ul class=&quot;dropdown-menu dropdown-menu-end&quot;&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a class=&quot;dropdown-item&quot; href=&quot;{% url 'accounts:profile' %}&quot;&amp;gt;&amp;lt;i class=&quot;bi bi-person-gear&quot;&amp;gt;&amp;lt;/i&amp;gt; 내 정보&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a class=&quot;dropdown-item&quot; href=&quot;{% url 'accounts:message_list' %}&quot;&amp;gt;&amp;lt;i class=&quot;bi bi-envelope&quot;&amp;gt;&amp;lt;/i&amp;gt; 쪽지함&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 게시판 상세 페이지 (board_detail.html)&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 작성자 클릭 시 '쪽지 보내기' 메뉴 추가&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiLwZ_PmauRAxUAAAAAHQAAAAAQ_wY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;span class=&quot;dropdown&quot;&amp;gt;
    &amp;lt;a href=&quot;#&quot; class=&quot;text-decoration-none text-dark fw-bold dropdown-toggle&quot; data-bs-toggle=&quot;dropdown&quot;&amp;gt;
        {{ post.author.nickname|default:post.author.username }}
    &amp;lt;/a&amp;gt;
    &amp;lt;ul class=&quot;dropdown-menu&quot;&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a class=&quot;dropdown-item&quot; href=&quot;{% url 'accounts:message_send' post.author.pk %}&quot;&amp;gt;쪽지 보내기&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
&amp;lt;/span&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;10. 테스트 (기능 점검)&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-path-to-node=&quot;4&quot; data-ke-style=&quot;style2&quot;&gt;테스트를 위해 Chrome 시크릿 모드나 다른 브라우저를 켜서, 두 개의 아이디(보내는 사람/받는 사람)로 &lt;br /&gt;동시에 로그인해 두면 편합니다.)&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;쪽지 보내기 (게시판 연동):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게시글 상세 페이지에서 작성자 닉네임을 클릭합니다.&lt;/li&gt;
&lt;li&gt;드롭다운 메뉴에서 **[쪽지 보내기]**를 누릅니다.&lt;/li&gt;
&lt;li&gt;제목과 내용을 입력하고 전송합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;수신 확인 (받는 사람 입장):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;받는 사람 아이디로 로그인합니다.&lt;/li&gt;
&lt;li&gt;상단 메뉴바의 프로필을 클릭하고 **[쪽지함]**으로 이동합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[받은 쪽지]&lt;/b&gt; 탭에 **빨간색 뱃지(N)**가 떠 있는지, 안 읽은 쪽지 개수가 맞는지 확인합니다.&lt;/li&gt;
&lt;li&gt;쪽지를 클릭해서 상세 내용을 읽습니다. (이 순간 '읽음' 처리가 됩니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;읽음 확인 (보낸 사람 입장):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다시 보낸 사람 아이디로 돌아옵니다.&lt;/li&gt;
&lt;li&gt;쪽지함의 &lt;b&gt;[보낸 쪽지]&lt;/b&gt; 탭으로 이동합니다.&lt;/li&gt;
&lt;li&gt;방금 보낸 쪽지의 상태가 **&quot;읽지 않음&quot;**에서 **&quot;읽음(체크 표시)&quot;**으로 바뀌었는지 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;답장 기능:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5,3,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;받은 쪽지 상세 페이지에서 &lt;b&gt;[답장]&lt;/b&gt; 버튼을 눌러 바로 답장을 보내봅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Step by Step/[phase 2] django community upgrade</category>
      <category>DM기능구현</category>
      <category>스텝바이스텝</category>
      <category>쪽지기능구현</category>
      <category>파이썬무작정부딪히기</category>
      <category>파이썬웹개발튜토리얼</category>
      <category>파이썬웹프로젝트</category>
      <author>잼이써요</author>
      <guid isPermaLink="true">https://try-to-do.tistory.com/30</guid>
      <comments>https://try-to-do.tistory.com/30#entry30comment</comments>
      <pubDate>Sun, 7 Dec 2025 21:40:11 +0900</pubDate>
    </item>
  </channel>
</rss>