<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>나의 서브 하드디스크</title>
    <link>https://mixedprograming.tistory.com/</link>
    <description>백엔드와 인프라를 공부하는 초보 개발자 입니다.
공부 및 주로 사용될 거 같은 코드들을 저장하는 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Mon, 13 Apr 2026 21:55:40 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>친구들안녕</managingEditor>
    <item>
      <title>django-jsonform을 사용한 django admin 활용</title>
      <link>https://mixedprograming.tistory.com/37</link>
      <description>&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본문은 django-jsonform을 사용하여, 어드민 페이지에 쉽고(그나마), 간단히 사용한 예시를 작성하였습니다.&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;본론&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;라이브러리 설치 및 settings에 추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pip install django-jsonform == 2.22.0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721551831393&quot; class=&quot;makefile&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;INSTALLED_APPS = [
    ...
    &quot;django_jsonform&quot;,
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JSONField 사용하여 model 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;json 타입을 지정하여, 원하는 dict 형태로 생성이 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721553706345&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
from django_jsonform.models.fields import JSONField


class AbstractModel(models.Model):
    QUERIES_SCHEMA = {
        &quot;type&quot;: &quot;list&quot;,
        &quot;items&quot;: {
            &quot;type&quot;: &quot;dict&quot;,
            &quot;keys&quot;: {
                &quot;type&quot;: {&quot;type&quot;: &quot;string&quot;, &quot;choices&quot;: [&quot;choice1&quot;, &quot;choice2&quot;]},
                &quot;name&quot;: {&quot;type&quot;: &quot;string&quot;},
            },
        },
    }
    queries = JSONField(schema=QUERIES_SCHEMA)
    createdAt = models.DateTimeField(null=True, auto_now_add=True)

    class Meta:
        abstract = True&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;Admin 페이지 사용 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;choice 타입으로, choice 내용을 정할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String 타입으로 일반 텍스트를 입력할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nFL8D/btsIG2Vm1uP/8dhIOJsjgFG0ZqJwvzNkJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nFL8D/btsIG2Vm1uP/8dhIOJsjgFG0ZqJwvzNkJ0/img.png&quot; data-alt=&quot;데이터 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nFL8D/btsIG2Vm1uP/8dhIOJsjgFG0ZqJwvzNkJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnFL8D%2FbtsIG2Vm1uP%2F8dhIOJsjgFG0ZqJwvzNkJ0%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;817&quot; height=&quot;476&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;데이터 추가&lt;/figcaption&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xety5/btsIGuyirrp/YddsqE71kIJdOHkVPgZFtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xety5/btsIGuyirrp/YddsqE71kIJdOHkVPgZFtk/img.png&quot; data-alt=&quot;Json 형태로 저장된 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xety5/btsIGuyirrp/YddsqE71kIJdOHkVPgZFtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxety5%2FbtsIGuyirrp%2FYddsqE71kIJdOHkVPgZFtk%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;489&quot; height=&quot;70&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Json 형태로 저장된 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Admin 페이지 &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000; text-align: start;&quot;&gt;visualizing&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000; text-align: start;&quot;&gt;간단하게 Admin을 수정하여, 조금 더 보기 쉽게 비주얼라이징도 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721554159447&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.contrib import admin
from django.utils.safestring import mark_safe

class AbstractModelAdmin(admin.ModelAdmin):
    list_display = [&quot;queries_visualizing&quot;, &quot;createdAt&quot;]
    # list_display = [&quot;queries&quot;, &quot;createdAt&quot;]

    def queries_visualizing(self, obj):
        return mark_safe(&quot;&amp;lt;br/&amp;gt;&quot;.join([f&quot;{query[&quot;type&quot;]} / {query[&quot;name&quot;]}&quot; for query in obj.queries]))&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;187&quot; data-origin-height=&quot;80&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ7pkL/btsIG2Vm6DB/dhD91hRi4Jn5SFsyaIhNJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ7pkL/btsIG2Vm6DB/dhD91hRi4Jn5SFsyaIhNJ0/img.png&quot; data-alt=&quot;visualizing 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ7pkL/btsIG2Vm6DB/dhD91hRi4Jn5SFsyaIhNJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ7pkL%2FbtsIG2Vm6DB%2FdhD91hRi4Jn5SFsyaIhNJ0%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;187&quot; height=&quot;80&quot; data-origin-width=&quot;187&quot; data-origin-height=&quot;80&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;visualizing 예시&lt;/figcaption&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; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://django-jsonform.readthedocs.io/en/latest/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://django-jsonform.readthedocs.io/en/latest/&lt;/a&gt;&lt;/p&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;예시코드&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/NGG-kang/tistory/tree/main/djang-jsonform&quot;&gt;https://github.com/NGG-kang/tistory/tree/main/djang-jsonform&lt;/a&gt;&lt;/p&gt;</description>
      <category>Django</category>
      <author>친구들안녕</author>
      <guid isPermaLink="true">https://mixedprograming.tistory.com/37</guid>
      <comments>https://mixedprograming.tistory.com/37#entry37comment</comments>
      <pubDate>Sun, 21 Jul 2024 18:54:09 +0900</pubDate>
    </item>
    <item>
      <title>drf_spectacular를 사용한 api docs 만들기</title>
      <link>https://mixedprograming.tistory.com/36</link>
      <description>&lt;div class=&quot;book-toc&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt; 
 &lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt; 
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;필자가 drf_spectacularf를 사용하여 만들었던 docs 페이지를 기억하기 위해 작성하는 페이지입니다.&lt;br&gt;찾으시는 내용이 없을 수 있습니다.&lt;br&gt;또한 정확히 알지 않고 필자의 경험으로만 이루어져 있기 때문에 없는 내용 및 틀린 내용이 존재할 수 있습니다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;본론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;1. drf-spectacular settings&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;settings.py에 API docs에 보여질 title, description, version을 지정할 수 있습니다.&lt;br&gt;이외의 기타 설정값들도 지정할수 있으니 &lt;a href=&quot;https://drf-spectacular.readthedocs.io/en/latest/settings.html#django-rest-framework-settings&quot; target=&quot;_self&quot;&gt;&lt;span&gt;링크&lt;/span&gt;&lt;/a&gt; 를 참조하시길 바랍니다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;SPECTACULAR_SETTINGS = {
    'TITLE': 'TITLE',
    'DESCRIPTION': 'DESCRIPTION',
    'VERSION': '1.0.0',
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;431&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCn3BT/btrPNbL5H9n/p0NmuTc4Jbc36gbECL6d30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCn3BT/btrPNbL5H9n/p0NmuTc4Jbc36gbECL6d30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCn3BT/btrPNbL5H9n/p0NmuTc4Jbc36gbECL6d30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCn3BT%2FbtrPNbL5H9n%2Fp0NmuTc4Jbc36gbECL6d30%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;431&quot; height=&quot;177&quot; data-origin-width=&quot;431&quot; data-origin-height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;2. extend_schema&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot; style=&quot;text-align: left;&quot;&gt;- 기본 사용법&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: left;&quot;&gt;사용할 api의 method 함수 부분에 @extend_schema 데코레이터를 추가하여 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class TistoryView(APIView):

    @extend_schema(
    ...
    )
    def get(self, request):
        ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt; &lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;3. tags, summary, description&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;tags: swagger에 어떤 부분에 tag 되어있을건지 정한다.&lt;br&gt;기본적으로 django의 app name에따라 default로 정해진다.&lt;br&gt; &lt;br&gt;summary: url 옆에 요약줄 부분에 내용을 쓸 수 있다.&lt;br&gt; &lt;br&gt;description: 접은글을 펴면 상단에 위치한 description을 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class TistoryView(APIView):

    @extend_schema(
        tags=['files'],
        summary=&quot;요약줄&quot;,
        description=&quot;&quot;&quot;설명란&amp;lt;br/&amp;gt;
            html 태그를 사용할 수도 있습니다. &quot;&quot;&quot;,
    )
    def get(self, request):
        ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;215&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YP8eP/btrRltqhO5B/u6vUfnHJKLuXAYLp7hWsRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YP8eP/btrRltqhO5B/u6vUfnHJKLuXAYLp7hWsRk/img.png&quot; data-alt=&quot; tas, summary, description 적용 이미지 &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YP8eP/btrRltqhO5B/u6vUfnHJKLuXAYLp7hWsRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYP8eP%2FbtrRltqhO5B%2Fu6vUfnHJKLuXAYLp7hWsRk%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;464&quot; height=&quot;215&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;215&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; tas, summary, description 적용 이미지 &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;4. parameters&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;paramters를 사용하여 url의 parameter를 추가할 수 있습니다.&lt;br&gt;또한 아래 이미지와 같이 parameter를 추가하여, 테스트 할 수 있습니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class TistoryView(APIView):

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@extend_schema(
&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;parameters=[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OpenApiParameter(
&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;name=&quot;name&quot;,
&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;type=str,
&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;description=&quot;description&quot;,
&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;required=False,
&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;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def get(self, request):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pass&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y84BC/btrRiltf9ne/u0A2wkavKrcKJlRgWIV11k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y84BC/btrRiltf9ne/u0A2wkavKrcKJlRgWIV11k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y84BC/btrRiltf9ne/u0A2wkavKrcKJlRgWIV11k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy84BC%2FbtrRiltf9ne%2Fu0A2wkavKrcKJlRgWIV11k%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;448&quot; height=&quot;356&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt; &lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;5. request&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;아래와 같이 request에 Serailizer를 사용하여 이 api에서 받을 request 데이터를 자동으로 정할 수 있습니다.&lt;br&gt;그러나 이런 Serializer를 사용한 docs 자동화는 serialier_class를 사용 할 수 있는 GenericAPIView 이상부터 가능합니다.&lt;br&gt;즉 일반 APIView는 사용이 불가능 합니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class TistoryView(GenericAPIView):
    serializer_class = TistorySerializer

    @extend_schema(
        request=TistorySerializer,
    )
    def post(self, request, *args, **kwargs):
        pass&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lAaq6/btrRGmpZtFA/zmVnstnpYlV4TjcXlAyob0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lAaq6/btrRGmpZtFA/zmVnstnpYlV4TjcXlAyob0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lAaq6/btrRGmpZtFA/zmVnstnpYlV4TjcXlAyob0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlAaq6%2FbtrRGmpZtFA%2FzmVnstnpYlV4TjcXlAyob0%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;454&quot; height=&quot;389&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;389&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt; &lt;br&gt;물론 다른 방법으로 inline_serializer를 사용하여 정의할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;class TistoryView(APIView):

    @extend_schema(
        request=inline_serializer(
            name='inline serializer',
            fields={
                &quot;name&quot;: serializers.CharField(default=&quot;name&quot;),
                &quot;integer&quot;: serializers.IntegerField(default=100)
            }
        )
    )
    def post(self, request, *args, **kwargs):
        pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCc9aW/btrRHCzlAIa/swU7g130ZOd8cXrGWcikmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCc9aW/btrRHCzlAIa/swU7g130ZOd8cXrGWcikmk/img.png&quot; data-alt=&quot; 비교를 위해 default 값을 넣었습니다 &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCc9aW/btrRHCzlAIa/swU7g130ZOd8cXrGWcikmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCc9aW%2FbtrRHCzlAIa%2FswU7g130ZOd8cXrGWcikmk%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;450&quot; height=&quot;394&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; 비교를 위해 default 값을 넣었습니다 &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: left;&quot;&gt;inline_serializer를 사용하면, 일반적인 APIView를 사용해도 request 값을 정할 수 있습니다.&lt;br&gt; &lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;6. responses&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;responses를 사용하여 api의 response를 정할 수 있습니다.&lt;br&gt;dictionary형태로 status_code: response 형태로 적용합니다.&lt;br&gt;그러나 중복된 status_code의 여러 가지 형태의 response는 보여줄 수 없습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class TistoryView(GenericAPIView):
    serializer_class = TistorySerializer

    @extend_schema(
        request=None,
        responses={
            200: TistorySerializer,
            400: None
        }
    )
    def post(self, request, *args, **kwargs):
        pass&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zbDML/btrRFgcqTML/91qjnnQAoyIozbcvM7BpwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zbDML/btrRFgcqTML/91qjnnQAoyIozbcvM7BpwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zbDML/btrRFgcqTML/91qjnnQAoyIozbcvM7BpwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzbDML%2FbtrRFgcqTML%2F91qjnnQAoyIozbcvM7BpwK%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;448&quot; height=&quot;600&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;7. examples&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;examples를 사용하여 API의 예시를 추가할 수 있습니다.&lt;br&gt;OpenAPiExaple을 사용하여 넣을 수 있습니다.&lt;br&gt;parameters, requests에도 사용이 가능하나 필자는 response를 사용한 예시를 보여드리겠습니다.&lt;br&gt; &lt;br&gt;name: 키값(중복된 값이 있을시, 마지막으로 정의된 값으로 고정됩니다)&lt;br&gt;summary: 선택 목록에 보일 내용&lt;br&gt;value: response value&lt;br&gt;status_code: response에서 고정될 status_code&lt;br&gt;response_only: response에만 들어갈 내용인지&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class TistoryView(GenericAPIView):
    serializer_class = TistorySerializer

    @extend_schema(
        request=None,
        responses={
            200: TistorySerializer,
            400: OpenApiTypes.OBJECT
        },
        examples=[
            OpenApiExample(
                name=&quot;400(1)&quot;,
                summary=&quot;400(1)&quot;,
                value={
                    &quot;message&quot;: &quot;400(1)&quot;,
                },
                status_codes=[&quot;400&quot;],
                response_only=True
            ),
            OpenApiExample(
                name=&quot;400(2)&quot;,
                summary=&quot;400(2)&quot;,
                value={
                    &quot;message&quot;: &quot;400(2)&quot;,
                },
                status_codes=[&quot;400&quot;],
                response_only=True
            ),
        ]
    )
    def post(self, request, *args, **kwargs):
        pass&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwViWZ/btrRANo6gD5/GuyLV15RbDgwOdkRsitin0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwViWZ/btrRANo6gD5/GuyLV15RbDgwOdkRsitin0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwViWZ/btrRANo6gD5/GuyLV15RbDgwOdkRsitin0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwViWZ%2FbtrRANo6gD5%2FGuyLV15RbDgwOdkRsitin0%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;746&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: left;&quot;&gt;주의할 사항으로는 responses에서 400의 값이 None이라면 examples가 적용되지 않으므로 OpenApiTypes.OBJECT를 넣었습니다.&lt;br&gt;어떤 값이든 들어가면 됩니다.&lt;br&gt; &lt;br&gt; &lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;추가 팁 (파일 업로드)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot; style=&quot;text-align: left;&quot;&gt;&lt;b&gt;실패 사례&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;먼저 일반적으로 사용되는 FileField drf-spectacular에서 적용되지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class TistoryView(APIView):
    @extend_schema(
        request=inline_serializer(
            name=&quot;upload example&quot;,
            fields={
                &quot;file&quot;: serializers.FileField(),
            },
        )
    )
    def post(self, request, *args, **kwargs):
        pass&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;589&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vmfTL/btrRBd18g84/E8Co94AHl9q1LnnfyELL71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vmfTL/btrRBd18g84/E8Co94AHl9q1LnnfyELL71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vmfTL/btrRBd18g84/E8Co94AHl9q1LnnfyELL71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvmfTL%2FbtrRBd18g84%2FE8Co94AHl9q1LnnfyELL71%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;539&quot; height=&quot;589&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;589&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot; style=&quot;text-align: left;&quot;&gt;&lt;b&gt;성공사례&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;spectacular settings에 COMPONENT_SPLIT_REQUEST: True와&lt;br&gt;APIView에 parser_classes를 추가하면 파일 업로드가 가능해집니다.&lt;br&gt; &lt;br&gt;settings.py&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;SPECTACULAR_SETTINGS = {
    ...
    'COMPONENT_SPLIT_REQUEST': True
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;views.py&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class TistoryView(APIView):
    parser_classes = (MultiPartParser,)
    @extend_schema(
        request=inline_serializer(
            name=&quot;upload example&quot;,
            fields={
                &quot;file&quot;: serializers.FileField(),
            },
        )
    )
    def post(self, request, *args, **kwargs):
        pass&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SZBg6/btrRAfszdGU/SHpnp3SgBs48hla5tJSUf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SZBg6/btrRAfszdGU/SHpnp3SgBs48hla5tJSUf0/img.png&quot; data-alt=&quot; 파일 선택하는 버튼이 생성되었습니다 &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SZBg6/btrRAfszdGU/SHpnp3SgBs48hla5tJSUf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSZBg6%2FbtrRAfszdGU%2FSHpnp3SgBs48hla5tJSUf0%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;547&quot; height=&quot;420&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; 파일 선택하는 버튼이 생성되었습니다 &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;</description>
      <category>Django</category>
      <category>Django</category>
      <category>drf-spectacular</category>
      <category>openapi3</category>
      <category>schema</category>
      <category>swagger</category>
      <author>친구들안녕</author>
      <guid isPermaLink="true">https://mixedprograming.tistory.com/36</guid>
      <comments>https://mixedprograming.tistory.com/36#entry36comment</comments>
      <pubDate>Sun, 20 Nov 2022 17:11:09 +0900</pubDate>
    </item>
    <item>
      <title>poetry를 사용한 파이썬 dependency관리</title>
      <link>https://mixedprograming.tistory.com/35</link>
      <description>&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;poetry가 1.2 버전으로 업데이트하면서 기존의 main, dev 이외의 group을 추가하여&lt;br /&gt;dependency가 관리가 가능해졌습니다.&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;&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;본론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;poetry==1.2.1 기준입니다.&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;p data-ke-size=&quot;size18&quot;&gt;1. init&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;poetry init 첫 시작은 init을 사용하여, name, version등 프로젝트 정보를 적고 dependencies를 추가합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. add&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;poetry add library 일반적으로 library를 설치하기 위해서는 이 명령어를 사용합니다. &lt;br /&gt;자동으로 main dependencies[tool.poetry.dependencies]에 속합니다&lt;/li&gt;
&lt;li&gt;poetry add library -G group&lt;/li&gt;
&lt;li&gt;library를 설치하되 group dependencies에 속합니다. poetry add django -G test를 입력하면 [tool.poetry.group.test.dependencies] 에 속하게 됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. remove&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;poetry remove library 모든 dependencies에 있는 library를 지웁니다.&lt;/li&gt;
&lt;li&gt;poetry remove library -G group group에 속한 library를 지웁니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. install&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;poetry config virtualenvs.create false virtualenv 미 생성 명령어로 Dockerfile에 필수로 추가해야하는 명령어입니다. 미 입력 시, &lt;br /&gt;poetry run manage.py 와 같은 형태로 파이썬 스크립트를 실행해야 합니다.&lt;/li&gt;
&lt;li&gt;poetry install poetry에 설정된 libarary를 모두 설치합니다.&lt;/li&gt;
&lt;li&gt;poetry install &amp;mdash;with group 명시한 group dependencies를 포함하여 설치합니다.&lt;/li&gt;
&lt;li&gt;poetry install &amp;mdash;without group 명시한 group dependencies를 제외하여 설치합니다.&lt;/li&gt;
&lt;li&gt;poetry install --only main,group 명시한 group dependencies를 포함하여 설치하나 &amp;mdash;only는 main도 제외하기 때문에 주의해야 합니다.&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;size18&quot;&gt;pyproject.toml&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로 설치할 pyproject.toml 파일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main은 django와 pyscopg2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test는 coverage, pytest&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dev는 django-debug-toolbar&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;worker 는 celery를 설치할 예정입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1664243462139&quot; class=&quot;ini&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[tool.poetry]
name = &quot;tistory&quot;
version = &quot;0.1.0&quot;
description = &quot;&quot;
authors = [&quot;Your Name &amp;lt;you@example.com&amp;gt;&quot;]
readme = &quot;README.md&quot;

[tool.poetry.dependencies]
python = &quot;^3.10&quot;
Django = &quot;^4.1.1&quot;
psycopg2 = &quot;^2.9.3&quot;


[tool.poetry.group.test.dependencies]
coverage = &quot;^6.4.4&quot;
pytest = &quot;^7.1.3&quot;


[tool.poetry.group.dev.dependencies]
django-debug-toolbar = &quot;^3.7.0&quot;


[tool.poetry.group.worker.dependencies]
celery = &quot;^5.2.7&quot;

[build-system]
requires = [&quot;poetry-core&quot;]
build-backend = &quot;poetry.core.masonry.api&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;web&lt;/p&gt;
&lt;pre id=&quot;code_1664243298136&quot; class=&quot;cmake&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;poetry install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 기본적인 install입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;group 관계 없이 모두 설치가 되었습니다.&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;web show.PNG&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;513&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UyIds/btrM6b9emHg/HR0xF2w0g7SAtuFVMbsXfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UyIds/btrM6b9emHg/HR0xF2w0g7SAtuFVMbsXfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UyIds/btrM6b9emHg/HR0xF2w0g7SAtuFVMbsXfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUyIds%2FbtrM6b9emHg%2FHR0xF2w0g7SAtuFVMbsXfK%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;476&quot; height=&quot;513&quot; data-filename=&quot;web show.PNG&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;513&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;test&lt;/p&gt;
&lt;pre id=&quot;code_1664243295913&quot; class=&quot;cmake&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;poetry install --with test&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 with을 사용한 test 설치입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 install은 전체 설치이기 때문에 with을 추가해도 위와 동일한 걸 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;test show.PNG&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biNtjx/btrM6cNQWOH/qReyHkpN1MYCU9FHiSJFbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biNtjx/btrM6cNQWOH/qReyHkpN1MYCU9FHiSJFbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biNtjx/btrM6cNQWOH/qReyHkpN1MYCU9FHiSJFbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiNtjx%2FbtrM6cNQWOH%2FqReyHkpN1MYCU9FHiSJFbK%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;350&quot; height=&quot;516&quot; data-filename=&quot;test show.PNG&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;dev&lt;/p&gt;
&lt;pre id=&quot;code_1664243301992&quot; class=&quot;cmake&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;poetry install --without test,worker&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;without으로 test, worker을 뺀 dev입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;celery, coverage와 그 dependency들이 빠진걸 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dev show.PNG&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zx2yz/btrM983XA6X/0EA2W6w9HVugsHtorgi1N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zx2yz/btrM983XA6X/0EA2W6w9HVugsHtorgi1N1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zx2yz/btrM983XA6X/0EA2W6w9HVugsHtorgi1N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzx2yz%2FbtrM983XA6X%2F0EA2W6w9HVugsHtorgi1N1%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;345&quot; height=&quot;514&quot; data-filename=&quot;dev show.PNG&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;worker&lt;/p&gt;
&lt;pre id=&quot;code_1664242324707&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;poetry install --only worker&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;only를 사용하여 worker만 설치한 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;web, test, dev 제외한 celery와 dependency만 설치된걸 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;only는 기본 dependency인 main까지 무시하므로 only를 사용할땐 주의해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;worker show.PNG&quot; data-origin-width=&quot;371&quot; data-origin-height=&quot;513&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZD9uc/btrM8gam6lE/ip4SvJu0eY3qZG4GgDaKu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZD9uc/btrM8gam6lE/ip4SvJu0eY3qZG4GgDaKu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZD9uc/btrM8gam6lE/ip4SvJu0eY3qZG4GgDaKu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZD9uc%2FbtrM8gam6lE%2Fip4SvJu0eY3qZG4GgDaKu1%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;371&quot; height=&quot;513&quot; data-filename=&quot;worker show.PNG&quot; data-origin-width=&quot;371&quot; data-origin-height=&quot;513&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <category>poetry</category>
      <category>python dependency</category>
      <author>친구들안녕</author>
      <guid isPermaLink="true">https://mixedprograming.tistory.com/35</guid>
      <comments>https://mixedprograming.tistory.com/35#entry35comment</comments>
      <pubDate>Tue, 27 Sep 2022 10:30:12 +0900</pubDate>
    </item>
    <item>
      <title>Django서버에 간단한 HTTPS Let's Encrypt 적용하기</title>
      <link>https://mixedprograming.tistory.com/32</link>
      <description>&lt;h1 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서비스에 기본적으로 들어가는 https는 nginx 설정, ssl 발급, 키파일 적용까지 &lt;br /&gt;필자에게는 너무 복잡하고 어려운 일이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 아래 레퍼런스의 튜토리얼을 보고 간단하게 ssl 적용하는 내용을 기억할 겸 다시 적어보는 내용이다.&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;글로만 설명 해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. docker-compose로 nginx-proxy, acme-companion(let's encrypt) 연결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 환경변수를 통한 도메인 ssl 적용&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;그리고 인증서의 만료는 3개월이나&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;&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;docker container를 사용하여 무료 ssl인 let's encrypt를 적용하는 내용입니다.&lt;/li&gt;
&lt;li&gt;레퍼런스 그대로 따라가면서 최신 docker image를 사용한 내용이라 레퍼런스와 조금 다를 수 있습니다&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;h1&gt;본론&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해야 할 내용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. docker-compose로 nginx-proxy와 acme-companion(let's encrypt) 연결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx-proxy와 acme-companion(let's encrypt)를 볼륨으로 연결&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;2. 도메인 준비&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하지만 ssl을 적용하려면 도메인이 필요하다.&lt;br /&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;3. 환경변수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;let's encrypt에 적용될 도메인 host와&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx에 적용될 port 정보가 필요하므로 env 파일을 만들어 적용한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;docker-compose.prod.yml&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;certs, vhost, html, docker.sock을 volume으로 nginx와 let's encrypt 연결&lt;/p&gt;
&lt;pre id=&quot;code_1659182579948&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3.8'
services:
  ###########################
  web:
    container_name: web
    build:
      context: ./company_review
      dockerfile: Dockerfile/Dockerfile.prod
    command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
    ports:
      - 8000
    env_file:
      - ./.env/.env.prod
    volumes:
      - static:/usr/src/company_review/static
      - media:/usr/src/company_review/media
  ###########################
  redis:
    image: redis:6.2.6-alpine
    ports:
      - 6379:6379
    volumes:
      - redis_data:/etc/redis_data/data
    depends_on:
      - web
  ###########################
  nginx-proxy:
    container_name: nginx-proxy
    restart: always
    build:
      context: nginx
    ports:
      - 80:80
      - 443:443
    volumes:
      - certs:/etc/nginx/certs
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - static:/usr/src/company_review/static
      - media:/usr/src/company_review/media
    depends_on:
      - web
  ###########################
  nginx-proxy-acme:
    container_name: nginx-proxy-acme
    image: nginxproxy/acme-companion
    volumes:
      - certs:/etc/nginx/certs
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - acme:/etc/acme.sh
    env_file:
      - ./.env/.env.prod.nginx_acme
    depends_on:
      - nginx-proxy
  ###########################
volumes:
  redis_data:
  certs:
  vhost:
  html:
  acme:
  static:
  media:&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;.env.prod&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VIRTUAL_HOST와 VIRTUAL_PORT로 nginx에 적용할 호스트를 적고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LETSENCRYPT_HOST로 let's encrypt에 적용할 도메인을 정의해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 env는 django에 사용되는 env이므로 설정하지 않아도 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1659179765213&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DEBUG=False
STAGE=PROD
DJANGO_SETTINGS_MODULE=config.settings.prod
VIRTUAL_HOST=mixedprogramming.net
VIRTUAL_PORT=8000
LETSENCRYPT_HOST=mixedprogramming.net&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;.env.prod.nginx_acme&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx-proxy와 acme-companion(let's encrypt) 연결을 위한 컨테이너 명 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;email은 ssl 적용 후 만료 알람 등 이메일 보내는 용도&lt;/p&gt;
&lt;pre id=&quot;code_1659179765214&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DEFAULT_EMAIL=skarndrkd1@naver.com
NGINX_PROXY_CONTAINER=nginx-proxy&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;let's encrypt 인증에는 시간당 횟수 제한이 있으므로 테스트 환경에서는 아래의 staging letsencrypt를 추가하여 사용하도록 하자&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;ACME_CA_URI=https://acme-staging-v02.api.letsencrypt.org/directory&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;이후 적용된 nginx / default.conf&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 보면 단순 env 설정만으로 도메인, 포트 연결이 된 걸 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하겠지만 ssl도 적용이 완료되었다.&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;#default.conf&lt;/p&gt;
&lt;pre id=&quot;code_1659184419329&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
# 생략
# mixedprogramming.net
upstream mixedprogramming.net {
	## Can be connected with &quot;job-search-and-company-review_default&quot; network
	# web
	server 172.18.0.3:8000;
}
server {
	server_name mixedprogramming.net;
	listen 80 ;
	access_log /var/log/nginx/access.log vhost;
	# Do not HTTPS redirect Let'sEncrypt ACME challenge
	location ^~ /.well-known/acme-challenge/ {
		auth_basic off;
		auth_request off;
		allow all;
		root /usr/share/nginx/html;
		try_files $uri =404;
		break;
	}
	location / {
		return 301 https://$host$request_uri;
	}
}
server {
	server_name mixedprogramming.net;
	listen 443 ssl http2 ;
	access_log /var/log/nginx/access.log vhost;
	ssl_session_timeout 5m;
	ssl_session_cache shared:SSL:50m;
	ssl_session_tickets off;
	ssl_certificate /etc/nginx/certs/mixedprogramming.net.crt;
	ssl_certificate_key /etc/nginx/certs/mixedprogramming.net.key;
	ssl_dhparam /etc/nginx/certs/mixedprogramming.net.dhparam.pem;
	ssl_stapling on;
	ssl_stapling_verify on;
	ssl_trusted_certificate /etc/nginx/certs/mixedprogramming.net.chain.pem;
	add_header Strict-Transport-Security &quot;max-age=31536000&quot; always;
	include /etc/nginx/vhost.d/default;
	location / {
		proxy_pass http://mixedprogramming.net;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Reference&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Django on Docker Series:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/&quot;&gt;Dockerizing Django with Postgres, Gunicorn, and Nginx&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1659182366101&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;Dockerizing Django with Postgres, Gunicorn, and Nginx&quot; data-og-description=&quot;This tutorial details how to configure Django to run on Docker along with Postgres, Nginx, and Gunicorn.&quot; data-og-host=&quot;testdriven.io&quot; data-og-source-url=&quot;https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/&quot; data-og-url=&quot;https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/98jvR/hyPf19pX0k/I65GGf2zvJEd5T3MwYuZbk/img.png?width=810&amp;amp;height=456&amp;amp;face=0_0_810_456,https://scrap.kakaocdn.net/dn/DVphj/hyPgdhH6tK/WzDfg4sUCQ5SVInLd6rgk0/img.png?width=810&amp;amp;height=456&amp;amp;face=0_0_810_456&quot;&gt;&lt;a href=&quot;https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/98jvR/hyPf19pX0k/I65GGf2zvJEd5T3MwYuZbk/img.png?width=810&amp;amp;height=456&amp;amp;face=0_0_810_456,https://scrap.kakaocdn.net/dn/DVphj/hyPgdhH6tK/WzDfg4sUCQ5SVInLd6rgk0/img.png?width=810&amp;amp;height=456&amp;amp;face=0_0_810_456');&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;Dockerizing Django with Postgres, Gunicorn, and Nginx&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This tutorial details how to configure Django to run on Docker along with Postgres, Nginx, and Gunicorn.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;testdriven.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://testdriven.io/blog/django-lets-encrypt/&quot;&gt;Securing a Containerized Django Application with Let's Encrypt&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1659182367650&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;Securing a Containerized Django Application with Let's Encrypt&quot; data-og-description=&quot;In this tutorial, we'll look at how to secure a containerized Django app running behind an HTTPS Nginx proxy with Let's Encrypt SSL certificates.&quot; data-og-host=&quot;testdriven.io&quot; data-og-source-url=&quot;https://testdriven.io/blog/django-lets-encrypt/&quot; data-og-url=&quot;https://testdriven.io/blog/django-lets-encrypt/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mKr8N/hyPf0CIrMT/bTd9S0i0jqYJNIuz5JPhZK/img.png?width=810&amp;amp;height=456&amp;amp;face=0_0_810_456,https://scrap.kakaocdn.net/dn/Ny4e9/hyPf19pZFP/UlpuVxIQNKwVKytzUyJuaK/img.png?width=810&amp;amp;height=456&amp;amp;face=0_0_810_456&quot;&gt;&lt;a href=&quot;https://testdriven.io/blog/django-lets-encrypt/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://testdriven.io/blog/django-lets-encrypt/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mKr8N/hyPf0CIrMT/bTd9S0i0jqYJNIuz5JPhZK/img.png?width=810&amp;amp;height=456&amp;amp;face=0_0_810_456,https://scrap.kakaocdn.net/dn/Ny4e9/hyPf19pZFP/UlpuVxIQNKwVKytzUyJuaK/img.png?width=810&amp;amp;height=456&amp;amp;face=0_0_810_456');&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;Securing a Containerized Django Application with Let's Encrypt&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In this tutorial, we'll look at how to secure a containerized Django app running behind an HTTPS Nginx proxy with Let's Encrypt SSL certificates.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;testdriven.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://testdriven.io/blog/django-docker-https-aws/&quot;&gt;Deploying Django to AWS with Docker and Let's Encrypt&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1659182368989&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;Deploying Django to AWS with Docker and Let's Encrypt&quot; data-og-description=&quot;In this tutorial, we'll deploy a Django app to AWS EC2 with Docker and Let's Encrypt.&quot; data-og-host=&quot;testdriven.io&quot; data-og-source-url=&quot;https://testdriven.io/blog/django-docker-https-aws/&quot; data-og-url=&quot;https://testdriven.io/blog/django-docker-https-aws/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Pt1t1/hyPf9TW8OD/gCqdOpzta8nUuZiK7G0881/img.png?width=810&amp;amp;height=456&amp;amp;face=0_0_810_456,https://scrap.kakaocdn.net/dn/dbc38o/hyPgdINxaV/LabYJGKFHk6SXorqmknCw0/img.png?width=810&amp;amp;height=456&amp;amp;face=0_0_810_456&quot;&gt;&lt;a href=&quot;https://testdriven.io/blog/django-docker-https-aws/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://testdriven.io/blog/django-docker-https-aws/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Pt1t1/hyPf9TW8OD/gCqdOpzta8nUuZiK7G0881/img.png?width=810&amp;amp;height=456&amp;amp;face=0_0_810_456,https://scrap.kakaocdn.net/dn/dbc38o/hyPgdINxaV/LabYJGKFHk6SXorqmknCw0/img.png?width=810&amp;amp;height=456&amp;amp;face=0_0_810_456');&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;Deploying Django to AWS with Docker and Let's Encrypt&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In this tutorial, we'll deploy a Django app to AWS EC2 with Docker and Let's Encrypt.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;testdriven.io&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;div id=&quot;gtx-anchor&quot; style=&quot;position: absolute; visibility: hidden; left: -3.35544e+07px; top: 78px; width: 373px; height: 185.625px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;jfk-bubble gtx-bubble&quot; style=&quot;visibility: visible; left: -6.71087e+07px; top: 294px; opacity: 0;&quot; role=&quot;alertdialog&quot; aria-describedby=&quot;bubble-3&quot;&gt;
&lt;div id=&quot;bubble-3&quot; class=&quot;jfk-bubble-content-id&quot;&gt;
&lt;div id=&quot;gtx-host&quot; style=&quot;min-width: 200px; max-width: 400px;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jfk-bubble-closebtn-id jfk-bubble-closebtn&quot; tabindex=&quot;0&quot; role=&quot;button&quot; aria-label=&quot;Close&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jfk-bubble-arrow-id jfk-bubble-arrow jfk-bubble-arrowup&quot; style=&quot;left: 15px;&quot;&gt;
&lt;div class=&quot;jfk-bubble-arrowimplbefore&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jfk-bubble-arrowimplafter&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Django</category>
      <category>Django</category>
      <category>https</category>
      <category>Let's encrypt</category>
      <category>nginx</category>
      <category>SSL</category>
      <author>친구들안녕</author>
      <guid isPermaLink="true">https://mixedprograming.tistory.com/32</guid>
      <comments>https://mixedprograming.tistory.com/32#entry32comment</comments>
      <pubDate>Sat, 30 Jul 2022 21:46:29 +0900</pubDate>
    </item>
    <item>
      <title>python apple, google  ID TOKEN 검증기</title>
      <link>https://mixedprograming.tistory.com/31</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 진행 중 구글, 애플에서 발급되는 ID token 검증을 할 일이 있어, 야매 적용해보고 정리하기 위한 글입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #c8c3bc;&quot; data-darkreader-inline-color=&quot;&quot;&gt;더 좋은 방법이 있을 수 있으므로 더 찾아보고 적용해보는 걸 추천드립니다.&lt;/span&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;span style=&quot;color: #c8c3bc;&quot; data-darkreader-inline-color=&quot;&quot;&gt;(네이버, 카카오, 구글, 애플)&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;회원가입, 로그인을 만드는 중에 소셜로 오는 토큰은 무조건 access token으로 소셜에 대하여 직접 검증 및 데이터를 받을 줄 알았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 프론트에서 넘어오는 토큰 값은 네이버, 카카오는 예상하는 대로 access token이 넘어왔으나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 애플은 id_token이라는 생소한 토큰이 넘어왔다.&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;이것저것 검색 해보니 jwt와 같이 토큰 안에 유저 데이터가 있는 형태였다.&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;h2 data-ke-size=&quot;size26&quot;&gt;본론&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Goole ID token 검증&lt;/h4&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;먼저 google-auth를 설치한다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;$ pip install google-auth&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;pre id=&quot;code_1646467176042&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from google.oauth2 import id_token
from google.auth.transport import requests

token_val = id_token.verify_oauth2_token(
    {token},
    requests.Request(),
    audience={audience}), # optional
)&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;또는 아래와 같이단순히 id token request 요청으로 가능도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 audience를 포함한 검증은 불가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1646467790559&quot; class=&quot;routeros&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;requests.get(&quot;https://oauth2.googleapis.com/tokeninfo?id_token={token}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Apple ID token 검증&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #c8c3bc;&quot; data-darkreader-inline-color=&quot;&quot;&gt;검증하는 내용을 보면 애플은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://appleid.apple.com/auth/keys&quot;&gt;https://appleid.apple.com/auth/keys&lt;/a&gt;&lt;span style=&quot;color: #c8c3bc;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;span&gt; 이 곳의 &lt;/span&gt;키 값들을 가지고 토큰의 kid 일치 및 알고리즘 검증으로 토큰 검증을 한다고 한다.&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;s&gt;&lt;span style=&quot;color: #c8c3bc;&quot; data-darkreader-inline-color=&quot;&quot;&gt;apple의 토큰 검증은 딱히 라이브러리는 찾지는 못했지만, 검증 코드를 만들어져 있는 게 있어서 그 코드를 &lt;/span&gt;사용했다.&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;소스코드는 아래 참조에 있으나 참조의 검증과정은 막히는 부분이 있어 약간의 커스텀을 하였다.&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;물론 필자의 코드는 로 검증 성공할때까지 시도하는 로직이라 비효율적인 로직이다.&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;더 나은 로직도 참조에 있으니 그 내용으로 사용하면 될듯하다.&lt;/s&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;미리 key dictionary로 매칭하는 조금은 더 효율적인 방법으로 변경하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1646469073415&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import jwt
import requests
import json
from jwt.algorithms import RSAAlgorithm
from rest_framework import exceptions

APPLE_PUBLIC_KEY_URL = &quot;https://appleid.apple.com/auth/keys&quot;


def _fetch_apple_public_key(unverified_header):
    key_payload = requests.get(APPLE_PUBLIC_KEY_URL).json()
    keys = key_payload[&quot;keys&quot;]

    for key in keys:
        if key[&quot;kid&quot;] == unverified_header[&quot;kid&quot;]:
            return RSAAlgorithm.from_jwk(json.dumps(key))


def _decode_apple_user_token(apple_user_token):
    unverified_header = jwt.get_unverified_header(apple_user_token)
    public_key = _fetch_apple_public_key(unverified_header)
    try:
        token = jwt.decode(
            apple_user_token,
            public_key,
            algorithms=&quot;RS256&quot;,
        )
        return token
    except Exception:
        raise exceptions.AuthenticationFailed&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;Reference&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;google&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/359472/how-can-i-verify-a-google-authentication-api-access-token&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/359472/how-can-i-verify-a-google-authentication-api-access-token&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://google-auth.readthedocs.io/en/stable/reference/google.oauth2.id_token.html#google.oauth2.id_token.verify_oauth2_token&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://google-auth.readthedocs.io/en/stable/reference/google.oauth2.id_token.html#google.oauth2.id_token.verify_oauth2_token&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;apple&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 사용중&lt;br /&gt;&lt;a href=&quot;https://gist.github.com/davidhariri/b053787aabc9a8a9cc0893244e1549fe&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gist.github.com/davidhariri/b053787aabc9a8a9cc0893244e1549fe&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 나은 로직&lt;br /&gt;​&lt;span style=&quot;color: #c8c3bc;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;a href=&quot;https://gist.github.com/davidhariri/b053787aabc9a8a9cc0893244e1549fe?permalink_comment_id=3374998#gistcomment-3374998&quot;&gt;https://gist.github.com/davidhariri/b053787aabc9a8a9cc0893244e1549fe?permalink_comment_id=3374998#gistcomment-3374998&lt;/a&gt;&lt;/span&gt;​&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <category>Apple id_token</category>
      <category>Google id_token</category>
      <category>id token</category>
      <category>id_token</category>
      <category>python</category>
      <author>친구들안녕</author>
      <guid isPermaLink="true">https://mixedprograming.tistory.com/31</guid>
      <comments>https://mixedprograming.tistory.com/31#entry31comment</comments>
      <pubDate>Sat, 5 Mar 2022 18:19:51 +0900</pubDate>
    </item>
    <item>
      <title>Docker-compose에서 localhost 사용하기</title>
      <link>https://mixedprograming.tistory.com/30</link>
      <description>&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;개인 프로젝트 간 애로사항 및 해결 내용을 정리한 페이지입니다.&lt;br&gt; &lt;br&gt; &lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;본론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;Docker를 사용하여 서비스를 연결할 때 localhost를 사용하지 못하는 상황이 벌어진다.&lt;br&gt;필자는 꽤나 여기저기 찾았으나 결론은 매우 간단했다.&lt;br&gt;각 서비스 부분에 &lt;b&gt;extra_hosts&lt;/b&gt;를 넣으면 된다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;memcached: image: memcached ports: - &quot;11211:11211&quot; extra_hosts: - host.docker.internal:host-gateway&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;이렇게 &lt;b&gt;extra_hosts&lt;/b&gt;에 &lt;b&gt;host.docker.internal:host-gateway&lt;/b&gt;를 넣고&lt;br&gt;localhost 사용할 부분에 &lt;b&gt;host.docker.internal을&lt;/b&gt; 넣으면 된다.&lt;br&gt; &lt;br&gt; &lt;br&gt; &lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;From inside of a Docker container, how do I connect to the localhost of the machine?&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;So I have a Nginx running inside a docker container, I have a mysql running on localhost, I want to connect to the MySql from within my Nginx. The MySql is running on localhost and not exposing a p...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/uLGNc/hyK6lpKIJA/5w6MOFvO4BCWZZL8b9i1Lk/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot; data-og-url=&quot;https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach&quot;&gt;
 &lt;a href=&quot;https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach&quot; target=&quot;_blank&quot; data-source-url=&quot;https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach&quot;&gt;
  &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/uLGNc/hyK6lpKIJA/5w6MOFvO4BCWZZL8b9i1Lk/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316')&quot;&gt; 
  &lt;/div&gt;
  &lt;div class=&quot;og-text&quot;&gt;
   &lt;p class=&quot;og-title&quot;&gt;From inside of a Docker container, how do I connect to the localhost of the machine?&lt;/p&gt;
   &lt;p class=&quot;og-desc&quot;&gt;So I have a Nginx running inside a docker container, I have a mysql running on localhost, I want to connect to the MySql from within my Nginx. The MySql is running on localhost and not exposing a p...&lt;/p&gt;
   &lt;p class=&quot;og-host&quot;&gt;stackoverflow.com&lt;/p&gt;
  &lt;/div&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;</description>
      <category>개발 일기</category>
      <category>docker-compose</category>
      <category>docker-compose localhost</category>
      <author>친구들안녕</author>
      <guid isPermaLink="true">https://mixedprograming.tistory.com/30</guid>
      <comments>https://mixedprograming.tistory.com/30#entry30comment</comments>
      <pubDate>Mon, 2 Aug 2021 14:50:29 +0900</pubDate>
    </item>
    <item>
      <title>Windows 포트 잠김 해결법</title>
      <link>https://mixedprograming.tistory.com/29</link>
      <description>&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;개인 프로젝트 도중 잘 되던 서비스 실행이 port가 막혀서 정리 겸 쓰는 페이지입니다.&lt;br&gt; &lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;본론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;아래의 명령어로 잠긴 포트들을 확인할 수 있다.&lt;br&gt;잘 쓰던 포트가 왜 잠기는지는 의문점이지만 &lt;br&gt;Window NAT(winnat) 드라이버로 인해 생기는 문제라고 한다.&lt;br&gt;&lt;span style=&quot;background-color: #E4E6E8;&quot;&gt;netsh int ip show excludedportrange protocol=tcp&lt;/span&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;249&quot; data-origin-height=&quot;311&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/bWSTnT/btraRjwmgXw/OnG0NXQcePQljs6xD8HPM0/img.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWSTnT/btraRjwmgXw/OnG0NXQcePQljs6xD8HPM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWSTnT/btraRjwmgXw/OnG0NXQcePQljs6xD8HPM0/img.png&quot; data-alt=&quot;표기되는 port는 사용자마다 다릅니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWSTnT/btraRjwmgXw/OnG0NXQcePQljs6xD8HPM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWSTnT%2FbtraRjwmgXw%2FOnG0NXQcePQljs6xD8HPM0%2Fimg.png&quot; data-origin-width=&quot;249&quot; data-origin-height=&quot;311&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/bWSTnT/btraRjwmgXw/OnG0NXQcePQljs6xD8HPM0/img.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;표기되는 port는 사용자마다 다릅니다&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;잠금 해제는 cmd창에서 &lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
  net stop winnat 
 &lt;br&gt;docker start ... 
 &lt;br&gt;net start winnat 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;winnat을 중지 후 다시 시작하면 포트 잠김 해제 성공!&lt;br&gt; &lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/54010365/how-to-see-what-is-reserving-ephemeral-port-ranges-on-windows&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://stackoverflow.com/questions/54010365/how-to-see-what-is-reserving-ephemeral-port-ranges-on-windows&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;How to see what is reserving ephemeral port ranges on Windows?&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;I have a Windows application that needs to use ports 50005 and 50006 but it is being blocked. I see the following when I run netsh int ip show excludedportrange protocol=tcp: Protocol tcp Port&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/54010365/how-to-see-what-is-reserving-ephemeral-port-ranges-on-windows&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bCmepY/hyK4LjhnZe/jHtNY88g3tCmKgUXl6qug0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot; data-og-url=&quot;https://stackoverflow.com/questions/54010365/how-to-see-what-is-reserving-ephemeral-port-ranges-on-windows&quot;&gt;
 &lt;a href=&quot;https://stackoverflow.com/questions/54010365/how-to-see-what-is-reserving-ephemeral-port-ranges-on-windows&quot; target=&quot;_blank&quot; data-source-url=&quot;https://stackoverflow.com/questions/54010365/how-to-see-what-is-reserving-ephemeral-port-ranges-on-windows&quot;&gt;
  &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bCmepY/hyK4LjhnZe/jHtNY88g3tCmKgUXl6qug0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316')&quot;&gt; 
  &lt;/div&gt;
  &lt;div class=&quot;og-text&quot;&gt;
   &lt;p class=&quot;og-title&quot;&gt;How to see what is reserving ephemeral port ranges on Windows?&lt;/p&gt;
   &lt;p class=&quot;og-desc&quot;&gt;I have a Windows application that needs to use ports 50005 and 50006 but it is being blocked. I see the following when I run netsh int ip show excludedportrange protocol=tcp: Protocol tcp Port&lt;/p&gt;
   &lt;p class=&quot;og-host&quot;&gt;stackoverflow.com&lt;/p&gt;
  &lt;/div&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;</description>
      <category>개발 일기</category>
      <category>windows port lock</category>
      <author>친구들안녕</author>
      <guid isPermaLink="true">https://mixedprograming.tistory.com/29</guid>
      <comments>https://mixedprograming.tistory.com/29#entry29comment</comments>
      <pubDate>Mon, 2 Aug 2021 14:07:50 +0900</pubDate>
    </item>
    <item>
      <title>django.core.exceptions.SuspiciousFileOperation: Detected path traversal attempt</title>
      <link>https://mixedprograming.tistory.com/28</link>
      <description>&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;이 글은 &lt;a href=&quot;https://mixedprograming.tistory.com/26&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;Django FileField upload_to Custom 야매 적용기&lt;/span&gt;&lt;/a&gt; 와 연관된 페이지입니다.&lt;br&gt;찾으시는 내용이 없을 수 있습니다.&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Django FileField upload_to Custom 야매 적용기&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;서론 필자의 개인 프로젝트 간 사용했던 내용을 저장 및 정리 용도로 쓰는 페이지입니다. 찾으시는 내용이 없을 수 있습니다. Crawler -&gt; Crwaler 오타가 있습니다. FileField upload_to Custom 먼저 filefield의&quot; data-og-host=&quot;mixedprograming.tistory.com&quot; data-og-source-url=&quot;https://mixedprograming.tistory.com/26&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/csuFCi/hyK4RXUoHe/3jTXF6uaMa3q4g6yWs6SzK/img.png?width=800&amp;amp;height=551&amp;amp;face=0_0_800_551,https://scrap.kakaocdn.net/dn/cLAYFT/hyK4OtlM98/NonsyfNGKiGY6kF9w3C6hk/img.png?width=800&amp;amp;height=551&amp;amp;face=0_0_800_551,https://scrap.kakaocdn.net/dn/eIp49/hyK6uNyDvf/fX93ZAdp780SktxYK8YMZ0/img.png?width=1077&amp;amp;height=743&amp;amp;face=0_0_1077_743&quot; data-og-url=&quot;https://mixedprograming.tistory.com/26&quot;&gt;
 &lt;a href=&quot;https://mixedprograming.tistory.com/26&quot; target=&quot;_blank&quot; data-source-url=&quot;https://mixedprograming.tistory.com/26&quot;&gt;
  &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/csuFCi/hyK4RXUoHe/3jTXF6uaMa3q4g6yWs6SzK/img.png?width=800&amp;amp;height=551&amp;amp;face=0_0_800_551,https://scrap.kakaocdn.net/dn/cLAYFT/hyK4OtlM98/NonsyfNGKiGY6kF9w3C6hk/img.png?width=800&amp;amp;height=551&amp;amp;face=0_0_800_551,https://scrap.kakaocdn.net/dn/eIp49/hyK6uNyDvf/fX93ZAdp780SktxYK8YMZ0/img.png?width=1077&amp;amp;height=743&amp;amp;face=0_0_1077_743')&quot;&gt; 
  &lt;/div&gt;
  &lt;div class=&quot;og-text&quot;&gt;
   &lt;p class=&quot;og-title&quot;&gt;Django FileField upload_to Custom 야매 적용기&lt;/p&gt;
   &lt;p class=&quot;og-desc&quot;&gt;서론 필자의 개인 프로젝트 간 사용했던 내용을 저장 및 정리 용도로 쓰는 페이지입니다. 찾으시는 내용이 없을 수 있습니다. Crawler -&amp;gt; Crwaler 오타가 있습니다. FileField upload_to Custom 먼저 filefield의&lt;/p&gt;
   &lt;p class=&quot;og-host&quot;&gt;mixedprograming.tistory.com&lt;/p&gt;
  &lt;/div&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt; &lt;br&gt; &lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;본론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;이전 글의 upload to Custom을 야매로 적용 후 Docker ec2환경에서 적용을 하려고 했더니 제목과 같은 에러가 떴다.&lt;br&gt; &lt;br&gt;upload_to를 적용하면 대충 /project3/media/~~~/~~~/~~~.png 이렇게 적용되는데&lt;br&gt;일단 에러 내용을 읽어보면 경로 부분에서 뭔가 에러가 발생했다고 한다.&lt;br&gt; &lt;br&gt;이에 대한 에러를 찾아보면 경로가 잘못됐다니 경로가 png 확장자까지 들어가야 한다니 이런 말이 있었는데&lt;br&gt;다 안됐고 비슷한 내용이 있는 사이트를 찾았다&lt;br&gt; &lt;br&gt;&lt;a href=&quot;https://code.djangoproject.com/ticket/32718&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://code.djangoproject.com/ticket/32718&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;#32718 (Saving a FileField raises SuspiciousFileOperation in some scenarios.)
     – Django&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;#32718 closed Bug (fixed) Saving a FileField raises SuspiciousFileOperation in some scenarios. Reported by: Jakub Kleň Owned by: Mariusz Felisiak Component: Database layer (models, ORM) Version: 2.2 Severity: Release blocker Keywords: 3.2.1 file model fil&quot; data-og-host=&quot;code.djangoproject.com&quot; data-og-source-url=&quot;https://code.djangoproject.com/ticket/32718&quot; data-og-image=&quot;&quot; data-og-url=&quot;https://code.djangoproject.com/ticket/32718&quot;&gt;
 &lt;a href=&quot;https://code.djangoproject.com/ticket/32718&quot; target=&quot;_blank&quot; data-source-url=&quot;https://code.djangoproject.com/ticket/32718&quot;&gt;
  &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('')&quot;&gt; 
  &lt;/div&gt;
  &lt;div class=&quot;og-text&quot;&gt;
   &lt;p class=&quot;og-title&quot;&gt;#32718 (Saving a FileField raises SuspiciousFileOperation in some scenarios.) &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; – Django&lt;/p&gt;
   &lt;p class=&quot;og-desc&quot;&gt;#32718 closed Bug (fixed) Saving a FileField raises SuspiciousFileOperation in some scenarios. Reported by: Jakub Kleň Owned by: Mariusz Felisiak Component: Database layer (models, ORM) Version: 2.2 Severity: Release blocker Keywords: 3.2.1 file model fil&lt;/p&gt;
   &lt;p class=&quot;og-host&quot;&gt;code.djangoproject.com&lt;/p&gt;
  &lt;/div&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;위의 사이트에서 일부 글을 가져와보면&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
  I believe an absolute path outside of Media Root should fail, that is correct (in my opinion) - as an absolute path might be able to write shell scripts around the file system... 
 &lt;br&gt;It should however, work for a relative file path. 
 &lt;br&gt; 
 &lt;br&gt;bad_file.name = &quot;/etc/init.d/nameofscript.sh&quot; 
 &lt;br&gt;# or 
 &lt;br&gt;bad_file.name = &quot;../../../../../usr/local/lib/bad.file&quot; 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;절대 경로를 작성하게 되면 외부에서 셸 스크립트라던지 리눅스 내부의 key 파일들을 탐색할 수 있다고 한다.&lt;br&gt;그러니까 맞는 경로로는 맨 앞의 ' / '를 지우는 것이 이 글의 해결 방법이었다.&lt;br&gt; &lt;br&gt;/project3/media/~~~/~~~/~~~.png 가 아닌&lt;br&gt;project3/media/~~~/~~~/~~~.png 이렇게 해야 정상적으로 저장이 가능하다.&lt;br&gt; &lt;br&gt; &lt;/p&gt;</description>
      <category>Django</category>
      <category>Detected path traversal attempt in</category>
      <author>친구들안녕</author>
      <guid isPermaLink="true">https://mixedprograming.tistory.com/28</guid>
      <comments>https://mixedprograming.tistory.com/28#entry28comment</comments>
      <pubDate>Mon, 2 Aug 2021 13:59:13 +0900</pubDate>
    </item>
    <item>
      <title>Django queryset distinct and sort</title>
      <link>https://mixedprograming.tistory.com/27</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Django의 정렬은 order_by로 시작하면 된다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.djangoproject.com/en/3.2/ref/models/querysets/#order-by&quot;&gt;참조&lt;/a&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;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.djangoproject.com/en/3.2/ref/models/querysets/#distinct&quot;&gt;django docs&lt;/a&gt;를 보면 distinct를 사용하려면 order_by가 같이 들어가는 상황이었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;gt;&amp;gt;&amp;gt; Entry.objects.order_by('author', 'pub_date').distinct('author')&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 사용하면 author, pub_date순서로 정렬되므로 날짜순서로 order_by가 불가능해진다.&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;h2 data-ke-size=&quot;size26&quot;&gt;본론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복제거를 한 후 python operator 라이브러리로 created_at을 얻어 정렬해주는 방법을 찾았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1627864331075&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;queryset = CrwalingModel.objects.order_by('enter_name').distinct('enter_name')
queryset = sorted(queryset, key=operator.attrgetter('created_at'), reverse=True)&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;Reference&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/2412770/good-ways-to-sort-a-queryset-django&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/2412770/good-ways-to-sort-a-queryset-django&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1627863591805&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;Good ways to sort a queryset? - Django&quot; data-og-description=&quot;what I'm trying to do is this: get the 30 Authors with highest score ( Author.objects.order_by('-score')[:30] ) order the authors by last_name Any suggestions?&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/2412770/good-ways-to-sort-a-queryset-django&quot; data-og-url=&quot;https://stackoverflow.com/questions/2412770/good-ways-to-sort-a-queryset-django&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/l8CMB/hyK6nt8GYn/IY4U2vCbDKh1ee7E8Q36lK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/2412770/good-ways-to-sort-a-queryset-django&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/2412770/good-ways-to-sort-a-queryset-django&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/l8CMB/hyK6nt8GYn/IY4U2vCbDKh1ee7E8Q36lK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&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;Good ways to sort a queryset? - Django&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;what I'm trying to do is this: get the 30 Authors with highest score ( Author.objects.order_by('-score')[:30] ) order the authors by last_name Any suggestions?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.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;</description>
      <category>Django</category>
      <category>django distinct</category>
      <category>Django distinct sort</category>
      <category>django sort</category>
      <author>친구들안녕</author>
      <guid isPermaLink="true">https://mixedprograming.tistory.com/27</guid>
      <comments>https://mixedprograming.tistory.com/27#entry27comment</comments>
      <pubDate>Mon, 2 Aug 2021 09:35:25 +0900</pubDate>
    </item>
    <item>
      <title>Django FileField upload_to Custom 야매 적용기</title>
      <link>https://mixedprograming.tistory.com/26</link>
      <description>&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;p data-ke-size=&quot;size16&quot;&gt;찾으시는 내용이 없을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Crawler -&amp;gt; Crwaler 오타가 있습니다.&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;FileField upload_to Custom&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 filefield의 &lt;a href=&quot;https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.FileField.upload_to&quot;&gt;upload_to&lt;/a&gt; 페이지를 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 upload_to는 아래와 같이 model 딴에서 간단하게 사용이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1626061768941&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyModel(models.Model):
    # file will be uploaded to MEDIA_ROOT/uploads
    upload = models.FileField(upload_to='uploads/')
    # or...
    # file will be saved to MEDIA_ROOT/uploads/2015/01/30
    upload = models.FileField(upload_to='uploads/%Y/%m/%d/')&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1077&quot; data-origin-height=&quot;743&quot; data-filename=&quot;기본모델.PNG&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b78L4W/btq9mnfaEk5/SSN12PRnzKdkDV42kRwlM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b78L4W/btq9mnfaEk5/SSN12PRnzKdkDV42kRwlM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b78L4W/btq9mnfaEk5/SSN12PRnzKdkDV42kRwlM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb78L4W%2Fbtq9mnfaEk5%2FSSN12PRnzKdkDV42kRwlM0%2Fimg.png&quot; data-origin-width=&quot;1077&quot; data-origin-height=&quot;743&quot; data-filename=&quot;기본모델.PNG&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 회사는 Crawler Model 생성 시 Crwaler Model의 왜래키를 가지고 나머지는 빈 값으로 자동 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 회사의 사진들을 하나의 모델에서 받기 위해 한 개의 Cralwer Photos에 각 회사의 외래키를 때려 박았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 회사의 사진을 저장하기 위해서는 다르게 적용되는 upload path가 필요하다.&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;이해를 돕기 위한 Model 코드 더보기(Custom이 완성된 코드입니다)&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1626062752780&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CrwalingModel(TimeModel):
    enter_name = models.CharField(max_length=50)

    def __str__(self):
        return self.enter_name

    class Meta:
        ordering = ['created_at']


class CrwalingBaseModel(models.Model):
    enter = models.OneToOneField(CrwalingModel, on_delete=models.CASCADE)
    company_code = models.CharField(max_length=1000, blank=True)
    location = models.CharField(max_length=50, blank=True)
    url = models.SlugField(max_length=1000, blank=True)
    upload_to_path = models.CharField(max_length=2000, blank=True)


    def __str__(self):
        return self.enter.enter_name

    class Meta:
        abstract = True


@receiver(post_save, sender=CrwalingModel)
def create_Post(sender, instance, created, **kwargs):
    if created:
        if instance not in ['SaraminInfo', 'JobKoreaInfo', 'JobPlanetInfo', 'KreditJobInfo']:
            SaraminInfo.objects.create(enter=instance)
            JobKoreaInfo.objects.create(enter=instance)
            JobPlanetInfo.objects.create(enter=instance)
            KreditJobInfo.objects.create(enter=instance)


class SaraminInfo(CrwalingBaseModel):
    # name = models.CharField(max_length=7, default='saramin')
    pass


class JobKoreaInfo(CrwalingBaseModel):
    # name = models.CharField(max_length=8, default='jobkorea')
    pass


class JobPlanetInfo(CrwalingBaseModel):
    # name = models.CharField(max_length=9, default='jobplanet')
    pass


class KreditJobInfo(CrwalingBaseModel):
    # name = models.CharField(max_length=9, default='kreditjob')
    pass

# 경로: 회사이름 -&amp;gt; 크롤링회사 -&amp;gt; 코드 -&amp;gt; 날짜
# 이미지이름: 회사이름_회사코드_기타내용.png
def crwaling_photo_path(instance, filename):
    if instance.jobkorea_info:
        return '{0}/{1}'.format(instance.jobkorea_info.upload_to_path, filename)
    elif instance.saramin_info:
        return '{0}/{1}'.format(instance.saramin_info.upload_to_path, filename)
    elif instance.jobplanet_info:
        return '{0}/{1}'.format(instance.jobplanet_info.upload_to_path, filename)
    elif instance.kreditjob_info:
        return '{0}/{1}'.format(instance.kreditjob_info.upload_to_path, filename)

class CrwalingPhotos(TimeModel):
    photo = models.ImageField(upload_to=crwaling_photo_path, max_length=1000)
    saramin_info = models.ForeignKey(SaraminInfo, on_delete=models.RESTRICT, blank=True, null=True)
    jobkorea_info = models.ForeignKey(JobKoreaInfo, on_delete=models.RESTRICT, blank=True, null=True)
    jobplanet_info = models.ForeignKey(JobPlanetInfo, on_delete=models.RESTRICT, blank=True, null=True)
    kreditjob_info = models.ForeignKey(KreditJobInfo, on_delete=models.RESTRICT, blank=True, null=True)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&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;그럼 과연 django에는 upload_to를 어떻게 커스텀해서 적용할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #dddddd;&quot;&gt;이하 django filefield 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1626063236372&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def user_directory_path(instance, filename):
    # file will be uploaded to MEDIA_ROOT/user_&amp;lt;id&amp;gt;/&amp;lt;filename&amp;gt;
    return 'user_{0}/{1}'.format(instance.user.id, filename)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 instance와 filename가 넘어오는데 instance는 model에서 생성된 instance 내용물들을 말한다&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 살짝 바꿔서 테스트해보자&lt;/p&gt;
&lt;pre id=&quot;code_1626070397494&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def user_directory_path(instance, filename):
    # file will be uploaded to MEDIA_ROOT/user_&amp;lt;id&amp;gt;/&amp;lt;filename&amp;gt;
    return 'user_{0}/%Y/%m/%d/{1}'.format(instance.user.id, filename)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드의 결과로는 날짜가 들어가지 않고 %Y/%m/%d가 문자열로 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 time 라이브러리로 직접 넣어줘야 날짜가 들어간다.&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;time을 따로 적용해야 하는 걸 알았으니 직접 적용해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 path를 따로 만들어 db에 저장 후, db path를 이미지에 적용하도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Crawler Model 생성 시 각 회사가 자동 생성되므로 그 회사에 해당하는 코드를 넣었다.&lt;/p&gt;
&lt;pre id=&quot;code_1626078338558&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;path = os.path.join(BASE_DIR) + '/media/search_job/' + company_name + '/jobkorea/' + time.strftime(
            &quot;/%Y/%m/%d/&quot;)
jobkorea = JobKoreaInfo.objects.get(enter=self.enter)
jobkorea.company_code = company_code
jobkorea.upload_to_path = path
jobkorea.save()
CrwalingPhotos.objects.create(jobkorea_info=jobkorea, photo=image)&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;회사에 대한 upload_path를 생성 후, CrawlerPhotos의 Custom Upload Path를 만들어보자&lt;/p&gt;
&lt;pre id=&quot;code_1626080485072&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def crwaling_photo_path(instance, filename):
    if instance.jobkorea_info:
        return '{0}/{1}'.format(instance.jobkorea_info.upload_to_path, filename)
    elif instance.saramin_info:
        return '{0}/{1}'.format(instance.saramin_info.upload_to_path, filename)
    elif instance.jobplanet_info:
        return '{0}/{1}'.format(instance.jobplanet_info.upload_to_path, filename)
    elif instance.kreditjob_info:
        return '{0}/{1}'.format(instance.kreditjob_info.upload_to_path, filename)

class CrwalingPhotos(TimeModel):
    photo = models.ImageField(upload_to=crwaling_photo_path, max_length=1000)
    saramin_info = models.ForeignKey(SaraminInfo, on_delete=models.RESTRICT, blank=True, null=True)
    jobkorea_info = models.ForeignKey(JobKoreaInfo, on_delete=models.RESTRICT, blank=True, null=True)
    jobplanet_info = models.ForeignKey(JobPlanetInfo, on_delete=models.RESTRICT, blank=True, null=True)
    kreditjob_info = models.ForeignKey(KreditJobInfo, on_delete=models.RESTRICT, blank=True, null=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번의 Crawlerphoto당 한 개의 회사, 한 개의 photo만 들어갈 것이므로(나머지 회사는 null)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;crawling_photo_path에 조건문으로 instance.~~~_info가 생성되었다면 그 데이터에서 저장된 path를 불러와&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;upload_path와 filename을 적용하도록 만들었다.(참고로 filename은 db 저장하는 곳에서 적용한다.)&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;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 코드를 바탕으로 설명하느라 좀 길어졌는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 줄이자면 upload_to에는 생성된 instance와 filename이 넘어오는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 경우는 path를 직접 db에 넣어서 그 경로를 불러와서 file 저장 path로 쓰는 방식을 사용했다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 일기</category>
      <category>django fielfield</category>
      <category>django filefield upload_to</category>
      <category>django upload_to</category>
      <category>upload_to</category>
      <category>upload_to custom</category>
      <author>친구들안녕</author>
      <guid isPermaLink="true">https://mixedprograming.tistory.com/26</guid>
      <comments>https://mixedprograming.tistory.com/26#entry26comment</comments>
      <pubDate>Mon, 12 Jul 2021 13:06:23 +0900</pubDate>
    </item>
  </channel>
</rss>