<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Nonamed Develog</title>
    <link>https://nonamed1.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 2 Aug 2026 01:40:28 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>노네임드개발자</managingEditor>
    <image>
      <title>Nonamed Develog</title>
      <url>https://tistory1.daumcdn.net/tistory/6907655/attach/1a0e589884a1471db6f76a8acdaa07f0</url>
      <link>https://nonamed1.tistory.com</link>
    </image>
    <item>
      <title>[TIL][240920] 트러블 슈팅</title>
      <link>https://nonamed1.tistory.com/95</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 상황&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원 등급을 3단계로 나누는 과정에서, is_authenticated 값을 false로 설정하여 특정 등급을 처리하려 했다. 그러나, 이 설정 때문에 모든 사용자가 로그인을 할 수 없게 되어 문제가 발생했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;원인 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;is_authenticated는 Django에서 인증된 사용자인지를 판단하는 매우 중요한 필드다. is_authenticated 값이 False로 설정되면 Django는 사용자가 인증되지 않은 것으로 간주하고, 로그인을 할 수 없게 된다. 따라서, 해당 필드를 임의로 수정하는 것은 적절하지 않았다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해결 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;is_authenticated 대신, 회원 등급을 처리할 수 있는 별도의 필드 is_master를 새로 추가했다. 이를 통해 사용자의 로그인 여부와 상관없이 회원 등급을 판단할 수 있도록 했다. 이제 회원 등급 로직은 로그인 상태와는 별개로 동작하며, is_authenticated는 Django의 기본 인증 기능으로 유지하게 했다.&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;Django의 기본 필드 (is_authenticated)는 매우 중요한 역할을 하므로, 임의로 수정하는 것보다는 새로운 필드를 추가하는 방식으로 로직을 처리하는 것이 더 안전하다는 점을 배웠다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>WHAT I LEARN/TIL</category>
      <author>노네임드개발자</author>
      <guid isPermaLink="true">https://nonamed1.tistory.com/95</guid>
      <comments>https://nonamed1.tistory.com/95#entry95comment</comments>
      <pubDate>Mon, 23 Sep 2024 10:33:51 +0900</pubDate>
    </item>
    <item>
      <title>[TIL][240919] Readme에 이미지 올리는 2가지 방법</title>
      <link>https://nonamed1.tistory.com/94</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. &lt;b&gt;GitHub 리포지토리에 이미지를 업로드하여 사용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 GitHub 리포지토리 내에 이미지를 포함하고, 이를 README 파일에 표시하는 방식이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;절차:&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;이미지 파일 저장&lt;/b&gt;: 먼저 로컬에서 리포지토리의 특정 폴더(예: images/)를 생성한 후, 여기에 이미지 파일을 저장한다. GitHub에 푸시하면 이 이미지 파일이 리포지토리에 포함된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Markdown 문법 사용&lt;/b&gt;: README 파일에서 해당 이미지 파일의 상대 경로를 이용해 이미지를 불러온다. 이때 Markdown 문법을 사용하여 이미지를 표시하는데, ![이미지 설명](이미지 경로) 형식을 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상대 경로의 중요성&lt;/b&gt;: 이미지 경로를 절대 경로로 지정하지 않고 상대 경로로 지정하면, 리포지토리 내 파일 구조와 일치하는 경로로 이미지를 불러온다. 예를 들어, README 파일과 이미지가 같은 디렉토리 안에 있으면 ./이미지파일이름.png와 같이 경로를 지정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 이미지가 프로젝트 내에서 유지되며, GitHub가 해당 이미지를 렌더링할 수 있게 해준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. &lt;b&gt;온라인 호스팅 서비스를 이용한 이미지 삽입&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지를 리포지토리에 포함하지 않고, 온라인 이미지 호스팅 서비스를 이용해 이미지를 업로드하고, 해당 이미지의 URL을 README 파일에 삽입하는 방법이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;절차:&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;이미지 업로드&lt;/b&gt;: Imgur 같은 이미지 호스팅 서비스나 GitHub Issues 기능을 사용해 이미지를 먼저 업로드합니다. 이미지가 업로드되면 고유한 URL이 생성된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Markdown 문법 사용&lt;/b&gt;: 이 URL을 README 파일에서 Markdown 문법을 사용하여 불러온다. 형식은 GitHub 리포지토리에 이미지를 저장하는 방식과 동일하지만, URL 대신 온라인에서 제공된 이미지의 링크를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 리포지토리 용량을 줄일 수 있고, 이미지를 외부에서 쉽게 관리할 수 있지만, 호스팅 서비스에 의존하게 되므로 이미지가 삭제되거나 URL이 바뀔 경우 깨진 링크가 될 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;두 방식의 차이&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;GitHub에 직접 이미지를 저장하는 방법&lt;/b&gt;은 이미지가 리포지토리의 일부로 관리되며, 해당 리포지토리를 클론하거나 포크할 때 함께 제공된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;온라인 호스팅 서비스를 이용하는 방법&lt;/b&gt;은 이미지 파일을 리포지토리에 추가하지 않으므로 저장소 용량을 절약할 수 있지만, 외부 서비스에 의존하게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 가지 방법을 고려하여 프로젝트의 요구 사항에 맞는 방식으로 이미지를 삽입할 수 있다.&lt;/p&gt;</description>
      <category>WHAT I LEARN/TIL</category>
      <author>노네임드개발자</author>
      <guid isPermaLink="true">https://nonamed1.tistory.com/94</guid>
      <comments>https://nonamed1.tistory.com/94#entry94comment</comments>
      <pubDate>Thu, 19 Sep 2024 20:36:13 +0900</pubDate>
    </item>
    <item>
      <title>[TIL][240914] DRF Permission Class</title>
      <link>https://nonamed1.tistory.com/93</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Django REST Framework(DRF)에서 사용되는 Permission Class에 대해 학습했다. DRF에서 API 요청에 대한 권한을 설정할 때 request.user를 통해 간단히 접근할 수 있지만, 그럼에도 불구하고 Permission Class를 사용하는 이유가 무엇인지에 대해 고민했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. request.user로 권한 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Django는 기본적으로 request.user를 통해 현재 요청을 보낸 사용자의 정보를 제공한다. 이 정보를 활용하면, 간단한 권한 체크가 가능하다. 예를 들어, 로그인된 사용자만 접근 가능하게 하려면 뷰에서 다음과 같은 방식으로 처리할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1726245503177&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def get(self, request):
    if not request.user.is_authenticated:
        return Response({&quot;detail&quot;: &quot;Authentication required&quot;}, status=401)&lt;/code&gt;&lt;/pre&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;이처럼 request.user를 직접 활용하여 권한을 제어하는 것은 쉽고 직관적이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Permission Class를 사용하는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, DRF에서 이미 request.user로 권한을 제어할 수 있음에도 왜 Permission Class를 추가로 사용하는 걸까?&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;a. 코드 재사용성과 가독성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권한 검사를 각 뷰에서 request.user를 통해 직접 처리하게 되면, 같은 검사가 여러 뷰에 반복될 수 있다. Permission Class를 사용하면 권한 검사를 하나의 클래스에서 정의하고 여러 뷰에서 재사용할 수 있어 코드의 중복을 줄이고, 관리가 쉬워진다.&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1726245531384&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from rest_framework.permissions import BasePermission

class IsAdminUser(BasePermission):
    def has_permission(self, request, view):
        return request.user and request.user.is_staff&lt;/code&gt;&lt;/pre&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;이렇게 정의한 Permission Class는 다양한 뷰에서 적용 가능하며, 가독성 측면에서도 권한과 관련된 로직을 분리하여 뷰 코드를 간결하게 유지할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;b. 역할과 목적의 명확화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;request.user는 기본적으로 사용자의 인증 정보를 제공하는 역할을 한다. 하지만 권한 관리는 이와 별개로 더 세밀한 통제가 필요한 경우가 많다. 예를 들어, 사용자가 관리자인지 여부, 특정 리소스에 대한 접근 권한이 있는지 등을 확인하려면 더 복잡한 로직이 필요할 수 있다. Permission Class는 이러한 &quot;권한&quot;에 대한 논리를 명확하게 분리해 관리할 수 있게 해준다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;c. DRF의 공식적인 구조 활용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DRF는 권한 설정을 위한 공식적인 구조로 Permission Class를 제공하며, 이를 사용하면 DRF의 다른 기능과 통합하여 쉽게 관리할 수 있다. 예를 들어, permission_classes 속성을 통해 뷰별로 간단히 권한을 설정할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1726245549256&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from rest_framework.permissions import IsAuthenticated

class MyView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        # 인증된 사용자만 이 뷰에 접근 가능
        pass&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;d. 다양한 권한 조합 가능&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Permission Class는 여러 권한 클래스를 조합하여 사용할 수 있다. 예를 들어, 사용자가 로그인했는지 여부와 관리자 권한을 동시에 체크할 때 다음과 같이 설정할 수 있다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1726245563342&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from rest_framework.permissions import IsAuthenticated, IsAdminUser

class MyAdminView(APIView):
    permission_classes = [IsAuthenticated, IsAdminUser]

    def get(self, request):
        # 인증된 관리자만 이 뷰에 접근 가능
        pass&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 자주 쓰이는 Permission Class&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DRF에서 자주 사용되는 Permission Class는 다음과 같다:&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. IsAuthenticated&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그인한 사용자만 접근을 허용한다.&lt;/li&gt;
&lt;li&gt;익명 사용자(비로그인 상태)는 접근할 수 없으며, 인증이 필요하다.&lt;/li&gt;
&lt;li&gt;보통 인증이 필요한 API에 자주 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1726245584224&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from rest_framework.permissions import IsAuthenticated

class MyView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        # 인증된 사용자만 이 뷰에 접근 가능
        pass&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. IsAdminUser&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관리자인 사용자(즉, is_staff=True인 사용자)만 접근 가능하다.&lt;/li&gt;
&lt;li&gt;관리자 페이지나 특정 권한을 가진 사용자만 접근할 수 있도록 할 때 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1726245597342&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from rest_framework.permissions import IsAdminUser

class AdminView(APIView):
    permission_classes = [IsAdminUser]

    def get(self, request):
        # 관리자만 이 뷰에 접근 가능
        pass&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. AllowAny&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 사용자에게 접근을 허용한다.&lt;/li&gt;
&lt;li&gt;일반적으로 로그인 여부와 상관없이 접근 가능한 API에 사용되며, 기본적으로 사용자가 인증되지 않아도 접근 가능한 페이지에서 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1726245613730&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from rest_framework.permissions import AllowAny

class PublicView(APIView):
    permission_classes = [AllowAny]

    def get(self, request):
        # 누구나 접근 가능
        pass&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. IsAuthenticatedOrReadOnly&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증된 사용자에게는 모든 작업을 허용하지만, 인증되지 않은 사용자에게는 읽기 작업(GET 요청)만 허용한다.&lt;/li&gt;
&lt;li&gt;일반적인 게시글 목록 조회는 누구나 가능하지만, 게시글 작성은 로그인된 사용자만 할 수 있도록 할 때 유용하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1726245628177&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from rest_framework.permissions import IsAuthenticatedOrReadOnly

class PostViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticatedOrReadOnly]

    def perform_create(self, serializer):
        # 인증된 사용자만 글을 작성할 수 있다
        serializer.save(author=self.request.user)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. DjangoModelPermissions&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Django 모델에서 정의된 권한을 기반으로 접근을 허용한다.&lt;/li&gt;
&lt;li&gt;기본적으로 조회, 추가, 변경, 삭제 권한이 부여된 사용자만 해당 작업을 수행할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1726245641125&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from rest_framework.permissions import DjangoModelPermissions

class RestrictedModelViewSet(viewsets.ModelViewSet):
    permission_classes = [DjangoModelPermissions]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비록 request.user를 통해 간단한 권한 체크를 할 수 있지만, DRF의 Permission Class는 코드의 재사용성, 관리의 용이성, 그리고 역할의 명확성을 고려했을 때 매우 유용하다. Permission Class를 사용함으로써 권한 관련 로직을 중앙화하고, DRF의 기능을 더 효과적으로 활용할 수 있다. 또한, DRF에서 제공하는 다양한 Permission Class를 적절히 활용하면 훨씬 더 유연하고 강력한 권한 관리를 할 수 있다.&lt;/p&gt;</description>
      <category>WHAT I LEARN/TIL</category>
      <author>노네임드개발자</author>
      <guid isPermaLink="true">https://nonamed1.tistory.com/93</guid>
      <comments>https://nonamed1.tistory.com/93#entry93comment</comments>
      <pubDate>Sat, 14 Sep 2024 01:41:49 +0900</pubDate>
    </item>
    <item>
      <title>[TIL][240912] Pull Request와 Local Merge</title>
      <link>https://nonamed1.tistory.com/92</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Git에서 Pull Request와 Local Merge에 대해 배웠다. 기존에는 항상 remote에서만 pull request를 통해 merge를 했었지만, 이번에 새로운 방법을 익혔다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Pull Request 방식:&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;기능 개발&lt;/b&gt;: 내 로컬에서 새로운 기능 브랜치를 생성하고 개발을 완료한 후, remote로 브랜치를 push한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pull Request 생성&lt;/b&gt;: GitHub (또는 다른 플랫폼)에서 pull request를 생성하고, 팀원들에게 코드 리뷰를 요청한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Remote에서 Merge&lt;/b&gt;: 팀원들이 코드를 검토한 후, merge를 승인하면 remote에서 기능이 dev 브랜치에 합쳐진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Local Merge 방식:&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Remote 브랜치 Pull&lt;/b&gt;: dev 브랜치를 최신 상태로 로컬로 가져오기 위해 git pull origin dev 명령어를 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기능 브랜치로 이동&lt;/b&gt;: git switch feature-branch 명령어를 사용해 내가 작업한 기능 브랜치로 이동한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;dev 브랜치와 Merge&lt;/b&gt;: git merge dev 명령어를 사용하여 기능 브랜치에서 dev 브랜치를 병합한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 및 Push&lt;/b&gt;: 로컬에서 merge된 코드를 테스트한 후, 정상 동작하는지 확인하고 remote로 다시 push한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 통해 코드 충돌을 미리 로컬에서 해결할 수 있고, remote에서 일어날 수 있는 문제를 줄일 수 있다는 장점을 느꼈다. 앞으로도 상황에 따라 local merge를 적절히 활용할 계획이다.&lt;/p&gt;</description>
      <category>WHAT I LEARN/TIL</category>
      <author>노네임드개발자</author>
      <guid isPermaLink="true">https://nonamed1.tistory.com/92</guid>
      <comments>https://nonamed1.tistory.com/92#entry92comment</comments>
      <pubDate>Fri, 13 Sep 2024 08:24:05 +0900</pubDate>
    </item>
    <item>
      <title>[TIL][240911] 프로젝트 시작, 끝 없는 문서 작성...(SA)</title>
      <link>https://nonamed1.tistory.com/91</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘부터 프로젝트를 시작해서 SA 문서를 작성하고, 튜터님의 피드백을 받은 내용을 정리하고자 한다.&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;SA 문서: &lt;a href=&quot;https://www.notion.so/S-A-18b0371e1833439883a0be87d77840ca?pvs=4&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.notion.so/S-A-18b0371e1833439883a0be87d77840ca?pvs=4&lt;/a&gt;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 데이터 아키텍처 (ERD)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주요 테이블&lt;/b&gt;:&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1175&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZJMSh/btsJyTiRJs7/xPS18sbjXceilRhLAw9R00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZJMSh/btsJyTiRJs7/xPS18sbjXceilRhLAw9R00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZJMSh/btsJyTiRJs7/xPS18sbjXceilRhLAw9R00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZJMSh%2FbtsJyTiRJs7%2FxPS18sbjXceilRhLAw9R00%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;1175&quot; height=&quot;604&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1175&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;튜터님 피드백&lt;/b&gt;&lt;br /&gt;user의 권한을 구분하여 Articles를 사용할 수 있는 user가 있고, NBCNs 을 사용할 수 있는 user을 구분하기 위해&lt;br /&gt;Articles와 NBCNs 테이블을 구분하였지만,&amp;nbsp; 비슷한 필드를 가지고 있는 테이블일 경우, 나누지 않고 같은 테이블을 사용하는 것이 좋다고 하셨다. 같은 테이블을 사용할 경우, API 작업이나 메소드 작업하기 용이하기 때문에 같은 필드의 테이블은 하나로 묶는 것이 좋다.&lt;br /&gt;그러나 작업 상황에 따라 구분이 필요하다면 그렇게 진행해보고, 이렇게 작업했을 때의 장단점을 기억하고 추후 프로젝트 작업 시, 기호에 따라 필요한 작업을 진행해보면 된다.&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;와이어프레임&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다음은 Sparta News 프로젝트의 백엔드 중심 와이어프레임입니다. API의 구조 및 주요 기능을 보여줍니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; 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-filename=&quot;SpartaNews.drawio (1).png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;919&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PRE3k/btsJzGpGkNk/6dYtb5dzNufWP2UcQqQfGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PRE3k/btsJzGpGkNk/6dYtb5dzNufWP2UcQqQfGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PRE3k/btsJzGpGkNk/6dYtb5dzNufWP2UcQqQfGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPRE3k%2FbtsJzGpGkNk%2F6dYtb5dzNufWP2UcQqQfGK%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;1280&quot; height=&quot;919&quot; data-filename=&quot;SpartaNews.drawio (1).png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;919&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Tutor's Feedback&lt;/b&gt;&lt;br /&gt;페이지별로 여러 와이어프레임을 만드는 것이 좋다. 각 페이지의 기능을 쉽게 알아볼 수 있고, 여러 와이어프레임으로 구분해두어야 작업할 때 서로 의견 충돌이 없다.&lt;/blockquote&gt;</description>
      <category>WHAT I LEARN/TIL</category>
      <author>노네임드개발자</author>
      <guid isPermaLink="true">https://nonamed1.tistory.com/91</guid>
      <comments>https://nonamed1.tistory.com/91#entry91comment</comments>
      <pubDate>Wed, 11 Sep 2024 20:58:43 +0900</pubDate>
    </item>
    <item>
      <title>[TIL][240910] Django와 웹 크롤링</title>
      <link>https://nonamed1.tistory.com/90</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;웹 크롤링이란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 크롤링은 프로그램을 통해 웹사이트를 자동으로 탐색하고, 그 안에서 데이터를 추출하는 과정을 의미한다. 이 작업은 &quot;크롤러&quot; 또는 &quot;스파이더&quot;라 불리는 스크립트에 의해 수행되며, 크롤러는 웹 페이지의 링크를 따라가며 데이터를 수집한다. 주로 분석, 모니터링, 데이터베이스에 정보를 저장하는 등의 목적으로 사용된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;웹 크롤링의 주요 개념&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;요청(Requests):&lt;/b&gt; 크롤러는 HTTP 요청을 통해 웹 페이지의 내용을 가져온다. Python에서는 주로 requests 라이브러리를 사용해 웹 페이지에 요청을 보낸다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파싱(Parsing):&lt;/b&gt; HTML을 가져온 후, 이를 분석하고 필요한 데이터를 추출한다. BeautifulSoup(bs4) 또는 lxml과 같은 라이브러리를 사용해 HTML 구조를 파싱한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;탐색:&lt;/b&gt; 크롤러는 HTML에서 찾은 링크를 따라가며 다른 페이지를 탐색하여 데이터 수집을 진행한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;속도 조절(Rate Limiting):&lt;/b&gt; 웹 크롤링 시 서버에 부담을 주지 않기 위해 너무 많은 요청을 짧은 시간 안에 보내지 않도록 속도를 조절해야 한다. robots.txt 파일의 규칙을 준수하여 서버에 부담을 주지 않도록 해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 저장:&lt;/b&gt; 수집한 데이터를 파일(CSV, JSON)이나 데이터베이스(SQL, NoSQL)에 저장할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Django에서 웹 크롤링을 활용하는 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Django는 주로 웹 개발에 사용되지만, 크롤링한 데이터를 자동으로 Django 애플리케이션에 삽입하는 데에도 사용할 수 있다. Django의 데이터베이스 모델, 관리 명령, 스케줄링 기능을 활용하여 크롤링한 데이터를 처리할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Django에서 웹 크롤링을 구현하는 이론적인 단계&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;크롤링 목적 정의:&lt;/b&gt; 먼저 어떤 데이터를 수집하고 그 데이터를 Django 프로젝트에서 어떻게 사용할지를 명확히 정해야 한다. 예를 들어, 전자 상거래 웹사이트의 상품 데이터를 수집하거나, 다양한 뉴스 웹사이트에서 블로그 게시물을 추출할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Django 모델 설정:&lt;/b&gt; 크롤링한 데이터를 저장할 모델을 models.py에 정의한다. 예를 들어, 상품 데이터를 수집하는 경우, 아래와 같이 Product 모델을 정의할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1725970854213&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Product(models.Model):
    title = models.CharField(max_length=255)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    description = models.TextField()
    image_url = models.URLField()
    source_url = models.URLField()

    def __str__(self):
        return self.title&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;&lt;b&gt;크롤러 로직 작성 (독립 스크립트 또는 Django 관리 명령): &lt;/b&gt;Python의 requests와 BeautifulSoup 같은 라이브러리를 사용해 웹 페이지를 가져오고 파싱한다. 이를 독립된 Python 스크립트로 작성할 수 있지만, Django와 통합하려면 &lt;b&gt;관리 명령(management command)&lt;/b&gt; 형태로 작성하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1725970933050&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import requests
from bs4 import BeautifulSoup
from django.core.management.base import BaseCommand
from products.models import Product

class Command(BaseCommand):
    help = '웹사이트에서 상품 데이터를 크롤링합니다.'

    def handle(self, *args, **kwargs):
        url = 'https://example.com/products'
        response = requests.get(url)
        soup = BeautifulSoup(response.text, 'html.parser')

        for product in soup.find_all('div', class_='product'):
            title = product.find('h2').text
            price = product.find('span', class_='price').text
            description = product.find('p', class_='description').text
            image_url = product.find('img')['src']
            source_url = product.find('a')['href']

            # Django 모델에 데이터 저장
            Product.objects.create(
                title=title,
                price=price,
                description=description,
                image_url=image_url,
                source_url=source_url
            )
            self.stdout.write(self.style.SUCCESS(f'{title} 크롤링 성공'))&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;이 스크립트는 특정 URL에서 제품 목록을 가져와 HTML을 파싱한 후, Django의 Product 모델에 데이터를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;크롤링 프로세스 스케줄링:&lt;/b&gt; Django는 기본적으로 작업 스케줄링 기능이 포함되어 있지 않지만, &lt;b&gt;Celery&lt;/b&gt;(비동기 작업 처리용) 또는 &lt;b&gt;크론(cron) 작업&lt;/b&gt;(유닉스 기반 시스템)을 사용해 크롤링 작업을 정기적으로 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1725971047550&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;0 3 * * * /path/to/virtualenv/bin/python /path/to/django/manage.py crawl_products&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;b&gt;Django Q&lt;/b&gt;나 &lt;b&gt;Celery&lt;/b&gt;를 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;크롤링한 데이터 뷰에서 처리하기:&lt;/b&gt; 크롤링한 데이터를 Django 모델에 저장한 후, 이를 웹 페이지에서 보여주려면 뷰에서 해당 데이터를 쿼리해야 한다. Django의 클래스 기반 뷰(CBVs)나 함수 기반 뷰(FBVs)를 사용해 저장된 데이터를 웹 페이지에 표시할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1725971080086&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.shortcuts import render
from products.models import Product

def product_list(request):
    products = Product.objects.all()
    return render(request, 'products/product_list.html', {'products': products})&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;b&gt;대규모 크롤링 처리:&lt;/b&gt; 대규모 웹 크롤링 작업이 필요하다면, &lt;b&gt;Scrapy&lt;/b&gt;와 같은 크롤링 프레임워크를 사용하는 것이 더 효율적일 수 있다. Scrapy는 다중 스레드를 사용해 크롤링을 병렬로 처리할 수 있어 성능이 뛰어나며, Django와 통합하여 모델에 데이터를 저장하거나 API로 데이터를 전송할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;윤리적인 웹 크롤링 고려 사항&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;robots.txt 파일 준수:&lt;/b&gt; 크롤링을 하기 전, 해당 웹사이트의 robots.txt 파일을 확인하여 크롤링 규칙을 준수해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 과부하 방지:&lt;/b&gt; 시간 간격을 두고 요청을 보내 서버에 과도한 부담을 주지 않도록 해야 한다. Python의 time.sleep()을 사용하거나 Scrapy의 속도 조절 기능을 활용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;페이징 처리:&lt;/b&gt; 많은 웹사이트가 콘텐츠 목록을 페이징 처리한다. 크롤러가 페이지 내비게이션을 처리할 수 있도록 다음 링크를 따라가거나 특정 페이징 요소를 파싱하는 기능을 구현해야 한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>WHAT I LEARN/TIL</category>
      <author>노네임드개발자</author>
      <guid isPermaLink="true">https://nonamed1.tistory.com/90</guid>
      <comments>https://nonamed1.tistory.com/90#entry90comment</comments>
      <pubDate>Tue, 10 Sep 2024 21:25:40 +0900</pubDate>
    </item>
    <item>
      <title>[TIL][240906] Troubleshooting 3: url routing의 순서</title>
      <link>https://nonamed1.tistory.com/89</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 상황&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;products 앱에서 CategoryViewSet으로 요청을 보낼 때, Postman에서 &lt;b&gt;{&quot;detail&quot;: &quot;Not found.&quot;}&lt;/b&gt; 응답을 받는 문제가 발생했다. ProductViewSet은 정상적으로 작동했지만, categories/ 경로에 요청이 처리되지 않았다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 원인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우팅 설정에서, 빈 경로로 등록한 ProductViewSet이 /categories/ 경로를 포함한 모든 요청을 가로채면서, CategoryViewSet으로의 요청이 제대로 전달되지 않았다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;초기 코드&lt;/h4&gt;
&lt;pre id=&quot;code_1725883948940&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProductViewSet, CategoryViewSet

router = DefaultRouter()  # 라우터 생성
router.register(r'', ProductViewSet)  # 'products' 엔드포인트에 ProductViewSet 등록
router.register(r'categories', CategoryViewSet)  # 'categories' 엔드포인트에 CategoryViewSet 등록

urlpatterns = [
    path('', include(router.urls)),  # 라우터에서 생성된 URL 패턴 포함
]&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;이 코드에서 빈 경로 r''로 ProductViewSet을 먼저 등록했기 때문에, 모든 경로에 대한 요청을 가로채고 있었다. 따라서 /categories/ 경로로의 요청도 ProductViewSet에서 처리되어 &lt;b&gt;{&quot;detail&quot;: &quot;Not found.&quot;}&lt;/b&gt; 응답을 받았다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;디버깅 과정&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Postman 테스트&lt;/b&gt;&lt;br /&gt;categories/ 경로에 GET 요청을 보냈으나, &lt;b&gt;{&quot;detail&quot;: &quot;Not found.&quot;}&lt;/b&gt; 응답을 받았다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;라우팅 패턴 확인&lt;/b&gt;&lt;br /&gt;show_urls 명령어로 등록된 URL 패턴을 확인해보니, /categories/ 경로가 제대로 등록되지 않은 것을 발견했다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문제 분석&lt;/b&gt;&lt;br /&gt;빈 경로(r'')로 먼저 등록된 ProductViewSet이 /categories/를 포함한 모든 경로를 가로채고 있었다. 따라서 CategoryViewSet이 요청을 처리하지 못한 것이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해결 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우팅 순서를 수정하여, 더 구체적인 categories/ 경로를 먼저 등록하고, 그 후에 ProductViewSet을 등록했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;수정 코드&lt;/h4&gt;
&lt;pre id=&quot;code_1725884051688&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProductViewSet, CategoryViewSet

router = DefaultRouter()  # 라우터 생성
router.register(r'categories', CategoryViewSet)  # 'categories' 엔드포인트에 CategoryViewSet 등록
router.register(r'', ProductViewSet)  # 'products' 엔드포인트에 ProductViewSet 등록

urlpatterns = [
    path('', include(router.urls)),  # 라우터에서 생성된 URL 패턴 포함
]&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;결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우팅 순서를 변경한 후, Postman에서 categories/ 경로로 요청을 다시 테스트했을 때, 정상적으로 응답을 받을 수 있었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배운 점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;라우팅 순서의 중요성&lt;/b&gt;: Django에서 라우트를 설정할 때, 구체적인 경로를 먼저 등록하는 것이 중요하다. 빈 경로를 먼저 등록하면 모든 경로를 가로챌 수 있으므로 주의해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DefaultRouter 사용 시 주의점&lt;/b&gt;: 빈 경로를 사용하면 해당 ViewSet이 최상위 경로부터 모든 하위 경로까지 처리할 수 있으므로, 경로 설정 시 우선순위를 고려해야 한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>WHAT I LEARN/TIL</category>
      <author>노네임드개발자</author>
      <guid isPermaLink="true">https://nonamed1.tistory.com/89</guid>
      <comments>https://nonamed1.tistory.com/89#entry89comment</comments>
      <pubDate>Mon, 9 Sep 2024 21:21:08 +0900</pubDate>
    </item>
    <item>
      <title>[TIL][240906] Troubleshooting 2: 중복된 코드의 냄새</title>
      <link>https://nonamed1.tistory.com/88</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 문제 인식: 중복된 코드의 냄새&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 각각의 기능(회원가입, 로그인, 프로필 조회, 프로필 수정)에 맞춰 별도의 시리얼라이저를 만들었다. 아래는 그 첫 번째 코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1725621588210&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SignUpSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True)
    
    class Meta:
        model = User
        fields = ['username', 'password', 'email', 'first_name', 'last_name', 'nickname', 'date_of_birth', 'gender', 'bio']

class SignInSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['username', 'email', 'first_name', 'last_name']

class ProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['username', 'email', 'first_name', 'last_name', 'nickname', 'date_of_birth', 'gender', 'bio']

class ProfileUpdateSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['email', 'first_name', 'last_name', 'nickname', 'date_of_birth', 'gender', 'bio']&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;처음 코드를 작성할 때는 필드 목록을 각각 정의하는 것이 문제가 없어 보였지만, 시간이 지남에 따라 공통된 필드가 반복적으로 정의되고 있다는 사실이 눈에 들어왔다. 특히 username, email, first_name, last_name과 같은 필드들이 여러 시리얼라이저에서 반복되면서 코드가 중복되고 있었다.&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;ldquo;코드 냄새&amp;rdquo;의 전형적인 예였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 리팩토링의 필요성: 효율성을 위한 상속 도입&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 개선하기 위한 다음 단계는, 공통된 부분을 하나의 부모 클래스에 모으고 필요한 시리얼라이저에서 이를 상속받는 구조로 바꾸는 것이었다. 여기서 등장한 것이 MyBaseSerializer이다. 이 시리얼라이저는 모든 시리얼라이저가 상속받는 기본 시리얼라이저로, 공통 필드를 한 곳에 정의해 중복을 줄였다.&lt;/p&gt;
&lt;pre id=&quot;code_1725621634352&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyBaseSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['username', 'email', 'first_name', 'last_name']  # 공통 필드

class SignUpSerializer(MyBaseSerializer):
    password = serializers.CharField(write_only=True)

    class Meta(MyBaseSerializer.Meta):
        fields = MyBaseSerializer.Meta.fields + ['password', 'nickname', 'date_of_birth', 'gender', 'bio']

class SignInSerializer(MyBaseSerializer):
    class Meta(MyBaseSerializer.Meta):
        fields = MyBaseSerializer.Meta.fields

class ProfileSerializer(MyBaseSerializer):
    class Meta(MyBaseSerializer.Meta):
        fields = MyBaseSerializer.Meta.fields + ['nickname', 'date_of_birth', 'gender', 'bio']

class ProfileUpdateSerializer(MyBaseSerializer):
    class Meta(MyBaseSerializer.Meta):
        fields = ['email', 'first_name', 'last_name', 'nickname', 'date_of_birth', 'gender', 'bio']&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;MyBaseSerializer 도입: username, email, first_name, last_name과 같은 공통 필드를 MyBaseSerializer에 정의하고, 나머지 시리얼라이저는 필요한 필드를 추가하는 식으로 설계가 간소화되었다. 이로써 코드 중복이 눈에 띄게 줄어들었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. to_representation 메서드 고민&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링 과정에서, 시리얼라이저에서 반환할 때 특정 필드를 동적으로 수정할 필요가 있을지 고민이 있었다. 예를 들어, 프로필 조회 시 닉네임을 대문자로 변환하거나 데이터를 좀 더 동적으로 처리하는 방법으로 to_representation을 활용할 수 있다고 생각했다.&lt;/p&gt;
&lt;pre id=&quot;code_1725621761515&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def to_representation(self, instance):
    representation = super().to_representation(instance)
    if 'nickname' in representation:
        representation['nickname'] = representation['nickname'].upper()
    return representation&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;아이디어: to_representation 메서드를 사용하면 반환되는 데이터를 조정할 수 있다는 점에서 유용해 보였다. 하지만, 이 메서드를 실제로 적용하려고 보니 필요 이상의 복잡성이 추가될 것 같았다. 단순한 필드 변환을 위해 메서드를 오버라이드하는 것보다는, 간단한 필드 추가와 수정으로도 충분히 문제를 해결할 수 있었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 결론: 상속으로 문제 해결, to_representation은 결국 사용하지 않음&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국, 상속을 통한 코드 중복 제거가 주요 개선점이 되었으며, to_representation을 사용하지 않아도 요구 사항을 충분히 충족할 수 있었다. MyBaseSerializer를 통해 공통 필드를 효율적으로 관리할 수 있었고, 추가적인 커스터마이징 없이도 필요한 데이터를 충분히 처리할 수 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이전: 각 시리얼라이저에 중복된 필드들이 정의되어 유지보수와 관리가 어려웠다.&lt;/li&gt;
&lt;li&gt;이후: 공통 필드를 MyBaseSerializer로 묶어 중복을 제거하고, 복잡성을 추가하지 않으면서도 효율적인 설계를 할 수 있었다. to_representation은 결국 사용하지 않았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 리팩토링 과정은 코드를 간결하고 효율적으로 유지하면서도, 불필요한 복잡성을 피하는 것이 얼마나 중요한지 깨닫는 과정이었다.&lt;/p&gt;</description>
      <category>WHAT I LEARN/TIL</category>
      <author>노네임드개발자</author>
      <guid isPermaLink="true">https://nonamed1.tistory.com/88</guid>
      <comments>https://nonamed1.tistory.com/88#entry88comment</comments>
      <pubDate>Fri, 6 Sep 2024 20:25:05 +0900</pubDate>
    </item>
    <item>
      <title>[TIL][240905] RefreshToken 속 check_blacklist()</title>
      <link>https://nonamed1.tistory.com/87</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 상황&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Django REST Framework로 로그아웃 기능을 구현하던 중, 사용자가 POST 요청으로 refresh_token을 보냈을 때 **{&quot;detail&quot;: &quot;This token is already blacklisted.&quot;}**라는 에러가 발생했다. 이는 이미 블랙리스트에 등록된 토큰을 처리하려 할 때 발생한 문제였다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 원인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SignoutView 클래스에서 RefreshToken 객체의 check_blacklist() 메서드를 호출할 때, 이미 블랙리스트에 등록된 토큰이 있을 경우 TokenError가 발생하면서 해당 에러 메시지가 반환되었다. 이 문제는 사용자가 이미 로그아웃한 상태에서 발생할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;초기 코드&lt;/h4&gt;
&lt;pre id=&quot;code_1725535313073&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SignoutView(APIView):
    permission_classes = [IsAuthenticated]
    def post(self, request):
        refresh_token_str = request.data.get(&quot;refresh_token&quot;)
        refresh_token = RefreshToken(refresh_token_str)
        try:
            refresh_token.check_blacklist()
        except TokenError as e:
            return Response({&quot;msg&quot;: &quot;This token is already blacklisted.&quot;,}, status=400)
        refresh_token.blacklist()
        return Response(status=200)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 refresh_token이 이미 블랙리스트에 등록되어 있는지 확인한 후, 해당 토큰이 블랙리스트에 있을 경우 에러를 발생시킨다. 이로 인해 토큰이 이미 블랙리스트에 등록된 상태에서 요청이 들어오면 문제가 발생하며, 사용자는 요청이 실패한 것처럼 인식하게 된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해결 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰이 이미 블랙리스트에 있더라도 에러를 발생시키지 않고 정상적으로 로그아웃 처리가 되도록 코드를 수정할 필요가 있다. TokenError가 발생했을 때도 성공적으로 처리된 것으로 간주하고, 로그아웃을 완료한 것처럼 응답을 보내는 방식으로 변경했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;수정 코드 (예시)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1725535329987&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class SignoutView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        refresh_token_str = request.data.get(&quot;refresh_token&quot;)
        refresh_token = RefreshToken(refresh_token_str)
        try:
            # 블랙리스트에 등록 시도
            refresh_token.blacklist()
        except TokenError:
            # 이미 블랙리스트에 있을 경우에도 성공 응답 반환
            return Response({&quot;msg&quot;: &quot;Token already blacklisted.&quot;}, status=200)
        return Response(status=200)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;결과&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정된 코드에서는 토큰이 이미 블랙리스트에 있어도 에러가 발생하지 않으며, 요청이 성공적으로 처리된다. Postman을 통해 테스트한 결과, 이미 블랙리스트에 있는 토큰에 대해서도 정상적인 응답이 반환되었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;배운 점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;TokenError 처리의 중요성&lt;/b&gt;: 이미 블랙리스트에 등록된 토큰에 대해 불필요한 에러를 발생시키지 않고, 처리 과정을 간소화하는 것이 사용자 경험을 개선할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;블랙리스트 로직 최적화&lt;/b&gt;: 에러를 줄이고, 토큰 상태에 관계없이 요청을 성공적으로 처리하는 흐름을 만드는 것이 중요하다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>WHAT I LEARN/TIL</category>
      <author>노네임드개발자</author>
      <guid isPermaLink="true">https://nonamed1.tistory.com/87</guid>
      <comments>https://nonamed1.tistory.com/87#entry87comment</comments>
      <pubDate>Thu, 5 Sep 2024 20:22:57 +0900</pubDate>
    </item>
    <item>
      <title>[TIL][240904] Troubleshooting 1: validaors.py의 활용</title>
      <link>https://nonamed1.tistory.com/86</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 상황&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Django REST Framework를 이용해 사용자 회원가입, 로그인, 로그아웃, 프로필 관리 등을 구현하는 과정에서, 검증 로직이 각 뷰에 산재해 있어 중복된 코드가 많았고 유지보수가 어렵다는 문제를 겪었다. 또한, 비밀번호 확인과 같은 검증 로직이 일부 누락되어 있어 보완할 필요가 있었다. 특히, &lt;b&gt;회원가입 시 비밀번호 확인&lt;/b&gt;과 &lt;b&gt;이메일 중복 검사&lt;/b&gt;가 간단한 수준에서 처리되었고, 수정 시 이를 반복적으로 작성해야 했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 원인&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;유효성 검증이 분산되어 관리&lt;/b&gt;: 회원가입, 로그인, 프로필 수정 등에서 유효성 검증 로직이 뷰에 직접 구현되어 있어 중복된 코드가 많았고, 검증 기준이 다를 수 있는 문제점이 발생했다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비밀번호 및 이메일 검증 누락&lt;/b&gt;: 회원가입 시 비밀번호 일치 여부를 확인하는 부분이 없었고, 이메일 형식과 중복 검사가 제한적이었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;초기 코드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 모든 검증 로직이 뷰나 시리얼라이저에 흩어져 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1725886544367&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class NewAccountSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True)

    class Meta:
        model = User
        fields = ['username', 'password', 'email', 'name', 'nickname', 'date_of_birth', 'gender', 'bio']
        
    def create(self, validated_data):
        user = User.objects.create_user(
            username=validated_data['username'],
            password=validated_data['password'],
            email=validated_data['email'],
            name=validated_data.get('name', ''),
            nickname=validated_data.get('nickname', ''),
            date_of_birth=validated_data.get('date_of_birth'),
            gender=validated_data.get('gender', ''),
            bio=validated_data.get('bio', '')
        )
        return user&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;문제 분석&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;중복된 검증 로직&lt;/b&gt;: 회원가입뿐만 아니라 로그인, 계정 삭제 등 여러 뷰에서 비슷한 검증 로직이 사용되고 있었고, 이를 효율적으로 재사용할 필요가 있었다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비밀번호 검증 부재&lt;/b&gt;: 기존 회원가입에서는 비밀번호와 비밀번호 확인을 비교하는 과정이 빠져 있어, 비밀번호 불일치로 인한 오류 처리가 미비했다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이메일 중복 검증의 미흡함&lt;/b&gt;: 이메일 형식 검증과 중복 검사가 제대로 이루어지지 않아, 잘못된 이메일이나 중복된 이메일로 회원가입이 이루어질 가능성이 있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해결 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;validators.py 파일을 만들어 유효성 검증 로직을 중앙 집중화하고, 회원가입, 프로필 수정, 비밀번호 변경 등에서 이를 재사용하도록 코드를 리팩토링하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1725886625110&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.core.exceptions import ValidationError
from django.core.validators import validate_email as django_validate_email
from django.contrib.auth.hashers import check_password
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.exceptions import TokenError

# 회원가입 검증
def validate_user_data(signup_data):
    err_msg_dict = {}
    username = signup_data.get('username')
    email = signup_data.get('email')
    password = signup_data.get('password')
    password_check = signup_data.get('password_check')

    # 사용자명 중복 확인
    if User.objects.filter(username=username).exists():
        err_msg_dict['username'] = &quot;이미 존재하는 사용자명입니다.&quot;
    
    # 비밀번호 일치 여부 확인
    if password != password_check:
        err_msg_dict['password'] = &quot;비밀번호가 일치하지 않습니다.&quot;

    # 이메일 형식 및 중복 확인
    try:
        django_validate_email(email)
    except ValidationError:
        err_msg_dict['email'] = &quot;유효하지 않은 이메일 형식입니다.&quot;

    if User.objects.filter(email=email).exists():
        err_msg_dict['email'] = &quot;이미 사용 중인 이메일입니다.&quot;

    if not err_msg_dict:
        return True, None
    return False, err_msg_dict&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;이 코드를 통해 &lt;b&gt;회원가입 시&lt;/b&gt;에 필요한 다양한 유효성 검증이 하나의 함수로 처리되며, 뷰와 직렬화에서 코드가 간결해졌다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;변경된 뷰 코드&lt;/h4&gt;
&lt;pre id=&quot;code_1725886675706&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class NewAccountAPIView(APIView):
    def post(self, request):
        # validators.py를 통한 유효성 검증 호출
        is_valid, error_message = validate_user_data(request.data)
        if not is_valid:
            return Response({&quot;error&quot;: error_message}, status=status.HTTP_400_BAD_REQUEST)

        serializer = NewAccountSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)&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;추가된 검증 기능&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;비밀번호 검증&lt;/b&gt;: 비밀번호와 비밀번호 확인을 일치하도록 검증하는 로직이 추가되었다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이메일 중복 및 형식 검증&lt;/b&gt;: 이메일의 형식을 확인하고, 중복되는 이메일이 있을 경우 에러 메시지를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;결과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;중앙 집중화된 유효성 검사&lt;/b&gt;: 검증 로직이 validators.py에 모여 있어 코드 재사용성이 높아졌고, 관리가 훨씬 수월해졌다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;코드 간소화&lt;/b&gt;: 뷰와 직렬화에서의 검증 로직이 사라지면서 코드가 깔끔해졌고, 유지보수가 쉬워졌다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;에러 처리 개선&lt;/b&gt;: 회원가입 시 비밀번호 불일치나 이메일 중복 등의 에러를 세밀하게 처리할 수 있게 되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;배운 점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;유효성 검증 로직의 분리&lt;/b&gt;: 뷰와 직렬화에서 검증 로직을 분리해 validators.py로 집중화함으로써, 코드 중복을 줄이고, 효율적인 에러 처리가 가능해졌다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;더 나은 유지보수&lt;/b&gt;: 검증 로직을 한곳에 모아두면 코드 변경 시에도 유지보수가 쉬워지고, 필요한 부분만 수정하면 전체 로직에 영향을 줄 수 있어 더욱 효율적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 통해 유효성 검증 로직을 개선하고, 더 깔끔한 코드 구조를 갖출 수 있었습니다.&lt;/p&gt;</description>
      <category>WHAT I LEARN/TIL</category>
      <author>노네임드개발자</author>
      <guid isPermaLink="true">https://nonamed1.tistory.com/86</guid>
      <comments>https://nonamed1.tistory.com/86#entry86comment</comments>
      <pubDate>Wed, 4 Sep 2024 22:03:41 +0900</pubDate>
    </item>
  </channel>
</rss>