<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>stella_k.log</title>
        <link>https://velog.io/</link>
        <description>초보개발자</description>
        <lastBuildDate>Mon, 30 Oct 2023 06:48:55 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>stella_k.log</title>
            <url>https://velog.velcdn.com/images/stella_k/profile/56b4230b-9baa-49e4-89da-ac859bdf4c0b/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. stella_k.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/stella_k" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Django DRF - 프로젝트 - User앱 관련 (1) :: models.py :: @classmethod 사용법.]]></title>
            <link>https://velog.io/@stella_k/Django-DRF-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-User%EC%95%B1-%EA%B4%80%EB%A0%A8-1-user.serializer%EA%B7%B8%EB%A6%AC%EA%B3%A0-view.py-classmethod-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@stella_k/Django-DRF-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-User%EC%95%B1-%EA%B4%80%EB%A0%A8-1-user.serializer%EA%B7%B8%EB%A6%AC%EA%B3%A0-view.py-classmethod-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Mon, 30 Oct 2023 06:48:55 GMT</pubDate>
            <description><![CDATA[<p>먼저 이번 프로젝츠에서는 깃관련 컨벤션에 대한 부분을 많이 배운거같다. 
Api문서 작성에 관련해서도 뭔가 더 자세히 어떻게 써야하는지 배운거 같은데 이부분에 관련해서는 추후에 작성을 진행하도록 하겠다.</p>
<p>먼저 처음에 serializer의 유효성 체크검사의 부분이 어느정도까지 커버해주는지 확실치 않아서 내가 직접 커버해주는 여러 메소드들을 구현하게되었는데 겹치는 부분들은 추후 다시 지워버렸다.</p>
<p>내가 맡은 부분은 회원수정 및 페이지네이션, 그리고 내가 좋아요한 게시물 불러오기였다. 그리고 AWS서버 배포를 담당하였는데 프론트엔드가 index.html안에 있는 script를 불러오지 못하여.. 완벽하게 서버배포에 성공하지는 못했다. </p>
<p>일단 먼저 어떤 부분들을 serializer가 해준는 지 몰라 구현한 부분들을 체크해보겠다.</p>
<blockquote>
<p>models.py</p>
</blockquote>
<pre><code>from datetime import datetime
from django.conf import settings
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.shortcuts import get_object_or_404</code></pre><p>먼저 커스터 마이징하는 유저모델을 사용할 경우 유저메니저를 따로 만들어줘야한다.
여기서 해주는 작업은 유저가 저장하려고 할때 이메일 형식 및 비밀번호 해싱화 부분에 대한 추가기능을 넣어주었다.</p>
<pre><code>class UserManager(BaseUserManager):

    &quot;&quot;&quot;사용자 모델을 생성하고 관리하는 클래스입니다.&quot;&quot;&quot;

    def create_user(self, email, nickname, password):
        &quot;&quot;&quot;일반 사용자를 생성하는 메서드입니다.&quot;&quot;&quot;
        if not email:
            raise ValueError(&quot;유효하지 않은 이메일 형식입니다.&quot;)

        user = self.model(
            email=self.normalize_email(email),
            password=password,
            nickname=nickname,
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, nickname, password=None):
        &quot;&quot;&quot;관리자를 생성하는 메서드입니다.&quot;&quot;&quot;
        if not email:
            raise ValueError(&quot;유효하지 않은 이메일 형식입니다.&quot;)

        user = self.create_user(
            email,
            password=password,
            nickname=nickname,
        )

        user.is_admin = True
        user.save(using=self._db)
        return user</code></pre><p>밑의 유저 모델링 부분은 당연히 데이터베이스에 저장될 필드 및 속성값을 정해주는 모델 클라스가 되겠다.</p>
<p>기본적으로 주어지는 속성 메소드들은 구현이 되어있는 상태이고, 내가 추가해준 부분은 </p>
<pre><code>&quot;&quot;&quot;클래스 변수에(user필드) 접급해야하여 클래스 메소드 사용&quot;&quot;&quot;

@classmethod
def is_email_duplicated(cls, email, user_id=None):
    print(f&quot;models: {email}&quot;)
    origin_email = cls.objects.filter(email=email)
    print(origin_email)
    if user_id:
        origin_email = origin_email.exclude(id=user_id)
    return origin_email.exists()

@classmethod
def is_nickname_duplicated(cls, nickname, user_id):
    origin_nickname = cls.objects.filter(nickname=nickname)
    if user_id:
        origin_nickname = origin_nickname.exclude(id=user_id)
    return origin_nickname.exists()

@classmethod
def is_password_same_as_previous(cls, password, user_id):
    user = get_object_or_404(User, id=user_id)
    if user.check_password(password):
        return True
    else:
        return False</code></pre><p> 이 부분인데, 여기서 먼저 classmethod가 뭔지 집고 넘어가면</p>
<blockquote>
<p> @classmethod</p>
</blockquote>
<p>@classmethod가 붙은 메서드는 첫 번째 인자로 클래스 자체를 받게 된다 : 이뜻이 뭐냐하면, 일반적인 인스턴스 메서드에서는 self를 통해 해당 인스턴스의 속성이나 상태를 참조하거나 변경할 수 있지만, 클래스 메서드에서는 cls를 통해 클래스 레벨의 속성이나 메서드만을 참조하거나 변경할 수 있다는 것이다.
클라스 변수처럼 인스턴스 생성에 의해 값이 변동되거나 할일이없다는 것이다.</p>
<p>인스턴스의 상태와는 독립적이고, 의존하지않고 클라스 메소드를 시키므로서 모든 인스턴스에 영향을 준게된다.</p>
<p><strong>더 상세한 예)</strong></p>
<pre><code>class CoffeeShop:
    total_coffee_sold = 0

    def __init__(self, name, price):
        self.name = name
        self.price = price
        self.sold = 0

    def sell_coffee(self, count):
        self.sold += count
        CoffeeShop.total_coffee_sold += count

    @classmethod
    def total_sales(cls):
        return cls.total_coffee_sold

    @classmethod
    def set_discount(cls, percentage):
        cls.discount = percentage / 100

    def get_discounted_price(self):
        return self.price * (1 - getattr(self, &#39;discount&#39;, 0))

espresso = CoffeeShop(&quot;Espresso&quot;, 3000)
latte = CoffeeShop(&quot;Latte&quot;, 4000)

espresso.sell_coffee(5)
latte.sell_coffee(3)

print(f&quot;Total coffee sold: {CoffeeShop.total_sales()} cups&quot;)  # 출력: Total coffee sold: 8 cups

CoffeeShop.set_discount(10)  # 클래스 메서드를 통해 전체 커피에 10% 할인 적용

print(f&quot;Discounted price of Espresso: {espresso.get_discounted_price()} won&quot;)  # 출력: Discounted price of Espresso: 2700.0 won
print(f&quot;Discounted price of Latte: {latte.get_discounted_price()} won&quot;)  # 출력: Discounted price of Latte: 3600.0 won
</code></pre><p>total_coffee_sold는 클래스 변수로써 모든 CoffeeShop 인스턴스에 의해 공유됩니다.
sell_coffee는 인스턴스 메서드로써 특정 커피 상품의 판매량을 증가시킵니다. 
그런데 이 메서드 안에서 클래스 변수 total_coffee_sold도 증가시키는데, 이는 모든 인스턴스가 공유하므로 전체 커피 판매량에 영향을 줍니다.</p>
<p>(개별적인 부분을 볼수있는게, 메소드 내 따른 메소드를 불러내어 클라스메소드화시킨것.)
total_sales는 클래스 메서드로써 현재까지 판매된 전체 커피의 수를 반환합니다.
=&gt; 이 메서드는 인스턴스의 상태에는 의존하지 않습니다.
set_discount는 또 다른 클래스 메서드로써 모든 커피에 대한 할인률을 설정합니다.
=&gt; 이는 클래스 레벨에서 할인률을 설정하므로 모든 인스턴스에 영향을 줍니다.</p>
<p>이 예제를 통해 볼 수 있듯이, 클래스 메서드는 인스턴스의 상태와 독립적으로 클래스 레벨에서 작동하며 클래스 변수나 다른 클래스 메서드와의 상호 작용에 주로 사용됩니다.</p>
<hr>
<p>그렇다면 다시 돌아와서 내가 추가해준부분이 밑에 부분인데,</p>
<p>@classmethod를 사용하는 이유는 다음과 같다.</p>
<p>인스턴스 독립성: </p>
<p>이 메서드는 특정 인스턴스의 상태에 의존하지 않습니다. 즉, 특정 사용자 객체의 상태나 속성에 의존하지 않고 전체 User 모델 데이터에서 이메일 닉네임 등 중복 여부를 확인합니다.
따라서, 인스턴스에 종속적이지 않은, 클래스 레벨에서의 연산이 필요하기 때문에 클래스 메서드를 사용합니다.</p>
<p>장고 QuerySet API 접근: 
cls.objects.filter()와 같은 형식은 장고의 QuerySet API를 사용하여 데이터베이스 쿼리를 수행합니다. 이는 모델 클래스 레벨에서 수행되는 작업이므로, 인스턴스 메서드보다는 클래스 메서드가 더 적합합니다.</p>
<p>재사용성: 
클래스 메서드를 사용하면, 이 메서드는 인스턴스를 생성하지 않고도 언제든지 이메일 중복 검사를 수행할 수 있습니다. 
예를 들어, 사용자 등록 과정에서 중복 이메일 검사를 수행하기 위해 User.is_email_duplicated(email)와 같은 방식으로 간편하게 호출할 수 있습니다.</p>
<p>요약하면, is_email_duplicated 메서드는 특정 User 인스턴스와는 독립적으로 작동하며,
장고의 QuerySet API를 통해 데이터베이스 연산을 수행하기 때문에 @classmethod를 사용하는 것이 적절하다.</p>
<ol>
<li><p>시리얼라이저에서 이메일 부분 중복확인 체크하고 update()부분에서 이미 excluse시켜주는 걸 모르고 추가적으로 기능을 부여했다.
그렇기에 마지막final코드에는 불필요한 코드이다 보니 삭제.
 @classmethod
 def is_email_duplicated(cls, email, user_id=None):</p>
<pre><code> print(f&quot;models: {email}&quot;)
 origin_email = cls.objects.filter(email=email)
 print(origin_email)
 if user_id:
     origin_email = origin_email.exclude(id=user_id)
 return origin_email.exists()</code></pre></li>
<li><p>닉네임 중복여부 확인 -&gt; 불필요하여 삭제
 @classmethod
 def is_nickname_duplicated(cls, nickname, user_id):</p>
<pre><code> origin_nickname = cls.objects.filter(nickname=nickname)
 if user_id:
     origin_nickname = origin_nickname.exclude(id=user_id)
 return origin_nickname.exists()</code></pre></li>
<li><p>이전 패스워드가 현재바꿀것과 동일한지 아닌지 확인 -&gt; 추후 이건 common_utils.py로 옮김
 @classmethod
 def is_password_same_as_previous(cls, password, user_id):</p>
<pre><code> user = get_object_or_404(User, id=user_id)
 if user.check_password(password):
     return True
 else:
     return False</code></pre></li>
</ol>
<pre><code>class User(AbstractBaseUser):
    &quot;&quot;&quot;
    사용자 모델을 정의하는 클래스입니다.

    - email(필수) : 로그인 시 사용할 사용자의 이메일 주소입니다.
        - 다른 사용자의 이메일과 중복되지 않도록 설정합니다. (Unique)
    - password(필수) : 사용자의 비밀번호입니다.
    - nickname(필수) : 사용자의 활동 아이디입니다.
        - 다른 사용자의 닉네임과 중복되지 않도록 설정합니다. (Unique)
    - intro : 사용자의 소개글입니다.
    - subscribe : 사용자 간 구독(팔로우) 관계입니다.
    - is_admin : 관리자 권한 여부입니다.
        - True 혹은 False를 저장할 수 있으며, 기본값으로 False를 저장하도록 설정합니다.
    &quot;&quot;&quot;

    email = models.EmailField(&quot;이메일&quot;, max_length=255, unique=True)
    password = models.CharField(&quot;비밀번호&quot;, max_length=255)
    nickname = models.CharField(&quot;활동 아이디&quot;, max_length=30, unique=True)
    intro = models.CharField(&quot;소개글&quot;, max_length=500, null=True, blank=True)
    subscribe = models.ManyToManyField(
        &quot;self&quot;,
        verbose_name=&quot;구독&quot;,
        symmetrical=False,
        related_name=&quot;subscribers&quot;,
        blank=True,
    )
    is_admin = models.BooleanField(&quot;관리자 여부&quot;, default=False)

    objects = UserManager()

    USERNAME_FIELD = &quot;email&quot;
    REQUIRED_FIELDS = [
        &quot;nickname&quot;,
    ]

    def __str__(self):
        return self.email

    def has_perm(self, perm, obj=None):
        return True

    def has_module_perms(self, app_label):
        return True

    @property
    def is_staff(self):
        return self.is_admin

    &quot;&quot;&quot;클래스 변수에(user필드) 접급해야하여 클래스 메소드 사용&quot;&quot;&quot;

    @classmethod
    def is_email_duplicated(cls, email, user_id=None):
        print(f&quot;models: {email}&quot;)
        origin_email = cls.objects.filter(email=email)
        print(origin_email)
        if user_id:
            origin_email = origin_email.exclude(id=user_id)
        return origin_email.exists()

    @classmethod
    def is_nickname_duplicated(cls, nickname, user_id):
        origin_nickname = cls.objects.filter(nickname=nickname)
        if user_id:
            origin_nickname = origin_nickname.exclude(id=user_id)
        return origin_nickname.exists()

    @classmethod
    def is_password_same_as_previous(cls, password, user_id):
        user = get_object_or_404(User, id=user_id)
        if user.check_password(password):
            return True
        else:
            return False

    class Meta:
        db_table = &quot;user&quot;
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 user 부분 view.py]]></title>
            <link>https://velog.io/@stella_k/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-user-%EB%B6%80%EB%B6%84-view.py-fnhknk3l</link>
            <guid>https://velog.io/@stella_k/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-user-%EB%B6%80%EB%B6%84-view.py-fnhknk3l</guid>
            <pubDate>Tue, 24 Oct 2023 16:01:57 GMT</pubDate>
            <description><![CDATA[<pre><code>from django.shortcuts import get_object_or_404
from rest_framework.views import APIView
from rest_framework_simplejwt.views import TokenObtainPairView
from user.models import User
from user.serializers import UserSerializer, LoginSerializer, PasswordSerializer
from rest_framework.response import Response
from rest_framework import status, permissions
from user.common_utils import lets_check_password

class RegisterView(APIView):
    def post(self, request):
        &quot;&quot;&quot;사용자 정보를 받아 회원가입 합니다.&quot;&quot;&quot;
        serializer = UserSerializer(
            data=request.data, context={&quot;profile_img&quot;: request.FILES}
        )
        print(request.FILES)
        if serializer.is_valid():
            serializer.save()
            return Response({&quot;message&quot;: &quot;회원가입 성공&quot;}, status=status.HTTP_201_CREATED)
        else:
            return Response(
                {&quot;message&quot;: serializer.errors}, status=status.HTTP_400_BAD_REQUEST
            )


class EmailCheckView(APIView):
    def post(self, request):
        &quot;&quot;&quot;이메일 중복 검사를 위한 클래스 뷰입니다.&quot;&quot;&quot;
        email = User.objects.filter(email=request.data[&quot;email&quot;])
        # print(request.data[&#39;email&#39;])
        # print(email)
        if email:
            return Response(
                {&quot;message&quot;: &quot;해당 이메일은 이미 사용 중입니다.&quot;}, status=status.HTTP_409_CONFLICT
            )
        else:
            return Response({&quot;message&quot;: &quot;해당 이메일은 사용 가능합니다.&quot;}, status=status.HTTP_200_OK)


class NicknameCheckView(APIView):
    def post(self, request):
        &quot;&quot;&quot;닉네임 중복 검사를 위한 클래스 뷰입니다.&quot;&quot;&quot;
        nickname = User.objects.filter(nickname=request.data[&quot;nickname&quot;])
        # print(request.data[&#39;nickname&#39;])
        # print(nickname)
        if nickname:
            return Response(
                {&quot;message&quot;: &quot;해당 닉네임은 이미 사용 중입니다.&quot;}, status=status.HTTP_409_CONFLICT
            )
        else:
            return Response({&quot;message&quot;: &quot;해당 닉네임은 사용 가능합니다.&quot;}, status=status.HTTP_200_OK)

&quot;&quot;&quot;common.utils에다가 빼놓았습니다.&quot;&quot;&quot;
# class PasswordCheckView(APIView):
#     def post(self, request, user_id):
#         user = get_object_or_404(User, id=user_id)
#         if request.data[&quot;password&quot;] is None:
#             return Response(
#                 {&quot;message&quot;: &quot;입력된값이 없습니다.&quot;}, status=status.HTTP_404_NOT_FOUND
#             )
#         if user.check_password(request.data[&quot;password&quot;]):
#             message = f&quot;반갑습니다.{request.user.nickname}님&quot;
#             return Response({&quot;message&quot;: message}, status=status.HTTP_200_OK)
#         else:
#             return Response(
#                 {&quot;message&quot;: &quot;입력된값이 일치하지 않습니다.&quot;}, status=status.HTTP_409_CONFLICT
#             )


class LoginView(TokenObtainPairView):
    &quot;&quot;&quot;
    사용자 정보를 받아 로그인 합니다.
    DRF의 JWT 토큰 인증 로그인 방식에 기본 제공된는 클래스 뷰를 커스터마이징하여 재정의합니다.
    &quot;&quot;&quot;

    serializer_class = LoginSerializer


class UserInfoView(APIView):
    permission_classes = [permissions.IsAuthenticated]
    &quot;비밀번호 확인해서 수정페이지 access&quot;
    def post(self,request,user_id):
        return lets_check_password(request,user_id)


    def get_user(self, user_id):
        return get_object_or_404(User, id=user_id)

    def get(self, request, user_id):
        &quot;&quot;&quot;사용자의 회원 정보를 보여줍니다.&quot;&quot;&quot;
        serializer = UserSerializer(self.get_user(user_id))
        return Response(serializer.data, status=status.HTTP_200_OK)

    def patch(self, request, user_id): 
        &quot;&quot;&quot;사용자의 정보를 받아 회원 정보를 수정합니다.&quot;&quot;&quot;
        serializer = UserSerializer(
            self.get_user(user_id),
            data=request.data,
            context={&quot;profile_img&quot;: request.FILES, &quot;user_id&quot;: user_id},
            partial=True,
        )
        if serializer.is_valid():
            serializer.save()
            print(serializer.data)
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:
            return Response(
                {&quot;message&quot;: &quot;회원정보를 수정할 수 없습니다.&quot;, &quot;errors&quot;: serializer.errors},
                status=status.HTTP_400_BAD_REQUEST,
            )

    &quot;&quot;&quot;밑에 클라스 주석시켜놓은걸 빼서 가져왔습니다. 
    위에 patch가이 있어 put으로하고 partial=True지정해줬는데
    patch랑 다를바 없어서 언제 put과 patch그냥 아무거나 사용해도되나요?
    위에 patch에 비번도 같이 한번에 보내서 바꿔주고 싶었는데 unique=True속성때매 
    같이 그 필드 인풋값을 보내주라고 떠가지고 따로 put patch로 넣어줬습니다. 한 클라스안에 &quot;&quot;&quot;         
    def put(self, request, user_id):
        &quot;&quot;&quot;사용자의 비밀번호만을 수정합니다.&quot;&quot;&quot;
        serializer = PasswordSerializer(
            self.get_user(user_id),
            context={&quot;user_id&quot;: user_id},
            data=request.data,
            partial=True,
        )
        print(f&#39;put에서: {user_id}&#39;)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:
            return Response(
                {&quot;message&quot;: &quot;비밀번호를 수정할수없습니다.&quot;, &quot;errors&quot;: serializer.errors},
                status=status.HTTP_400_BAD_REQUEST,
            )


# class UserPasswordInfoView(APIView):
#     def patch(self, request, user_id):
#         &quot;&quot;&quot;사용자의 비밀번호를 수정합니다.&quot;&quot;&quot;
#         serializer = PasswordSerializer(
#             self.get_user(user_id),
#             context={&quot;user_id&quot;: user_id},
#             data=request.data,
#             partial=True,
#         )
#         if serializer.is_valid():
#             serializer.save()
#             return Response(serializer.data, status=status.HTTP_200_OK)
#         else:
#             return Response(
#                 {&quot;message&quot;: &quot;비밀번호를 수정할수없습니다.&quot;, &quot;errors&quot;: serializer.errors},
#                 status=status.HTTP_400_BAD_REQUEST,
#             )</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[serializerMethodField()가 뭐일까]]></title>
            <link>https://velog.io/@stella_k/serializerMethodField%EA%B0%80-%EB%AD%90%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@stella_k/serializerMethodField%EA%B0%80-%EB%AD%90%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Thu, 19 Oct 2023 01:09:23 GMT</pubDate>
            <description><![CDATA[<p>일단 공식문서에서 이를 어케 설명하는지 봐보려한다.</p>
<p>serializerMethodField는 Miscellaneous fields 속에 속해져있다. 기타 항복..필드라는 그런 개념인데, readonlyfield나 hiddenfield등 같은 개념이 속해있다.</p>
<p>읽으면서 약간 헷갈린 개념이 속성
(attribute)과 필드(Field)의 개념이었다.</p>
<p>알고있는데 내가 이해한 개념과는 살짝 다르다고해야하나. 범위가 넓다해야하나. 그래서 다시 명확히 정리해두려한다.</p>
<blockquote>
<p>Django에서의 속성 (Attribute),모델 속성 (Model Attribute),필드 (Field)의 개념::</p>
</blockquote>
<p>즉, 속성이란:
Python객체에서 어떤 값을 표현하는 상수나 변수를 의미한다. 
예를들어, apple이란 변수가 fruit이라는 모델에 들어가있으면 
모델이 가지는 변수나 함수 =&gt; 모델의 속성이라고 할수있고,
모델이 결국 클래스타입으로 정의되기 때문에 =&gt; 클래스 속성이라고도 할 수 있겠다.</p>
<p>그럼 모델속성은 무엇인가?</p>
<p>장고에서 데이터베이스의 테이블과 연결되는 클래스 =&gt; 모델, 그리고 
위에서 언급했다시피 모델클라스 내에 정의된 변수나 함수를 의미한다.</p>
<p>그렇다면 필드란?</p>
<p>위의 모델 속성 중 데이터베이스의 컬럼(테이블컬럼)에 직접연결되는 속성을 =&gt; Field필드라 칭한다.</p>
<p>이 컬럼을 나타내는 변수, 즉 필드를 CharField, IntegerField, ForeignKey의 타입클래스를 통해 변수를 정의한다.</p>
<p>예) models.CharField or serializers.CharField </p>
<p>여기서 헷갈렸던 부분은 ?</p>
<p>=&gt;  바로 속성 = 필드라고 생각했던 부분이다. 
속성의 개념의 넓이는 넓기 때문에 @property로 정의된 메소드 또한 python의 속성으로 속성으로 취급되지만 필드는 아닌것. 
모델의 &#39;필드&#39;는 데이터베이스의 컬럼을 나타내는 반면, &#39;속성&#39;은 모델의 변수나 함수를 일반적으로 의미 즉, 데이터베이스에는 저장이되지않으면 필드가 아닌것이다.</p>
<pre><code>ReadOnlyField
A field class that simply returns the value of the field without modification.

This field is used by default with ModelSerializer when including field names that relate to an attribute rather than a model field.

Signature: ReadOnlyField()

For example, if has_expired was a property on the Account model, then the following serializer would automatically generate it as a ReadOnlyField:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = [&#39;id&#39;, &#39;account_name&#39;, &#39;has_expired&#39;] </code></pre><p>이렇게 설명되어진 부분에서 
This field is used by default with ModelSerializer when including field names that relate to an attribute rather than a model field. =&gt; 이부분이 헷갈렸는데</p>
<p>즉, has_expired 같은 속성이 Account 모델의 필드가 아니라 (즉, 데이터베이스의 컬럼이 아니라) 그냥 Python의 속성 (예를 들면, @property로 정의된 메서드)인 경우, DRF의 ModelSerializer는 이를 ReadOnlyField로 자동으로 처리한다는것이다. </p>
<p><strong>여기서 @property데코레이터를 사용하여 메서드 정의시 -&gt; 속성처럼 호출할수있다, 허나 데이터베이스애는 저장이되지않음.</strong></p>
<blockquote>
<p>그렇다면 여기서 SerializerMethodField는??</p>
</blockquote>
<p>SerializerMethodField는 Django Rest Framework (DRF)의 시리얼라이저 내에서 사용되는 특별한 필드 유형으로서, 이 필드를 사용하면 시리얼라이저 내에서 특정 메서드의 반환 값을 필드 값으로 사용할 수 있다. </p>
<p>즉, 모델 필드가 아닌 사용자 정의 로직을 기반한 값을 시리얼라이저의 출력에 포함 시킬수 있다.</p>
<p>사용방법은?</p>
<p>시리얼라이저 내에서 SerializerMethodField를 정의하고, 해당 필드와 관련된 메서드를 시리얼라이저 내에 구현합니다. 이 메서드의 이름은 get_<field_name> 형식을 따라야한다. </p>
<p>예시:</p>
<p>from rest_framework import serializers
from .models import User</p>
<p>class UserSerializer(serializers.ModelSerializer):
    full_name = serializers.SerializerMethodField()</p>
<pre><code>class Meta:
    model = User
    fields = (&#39;id&#39;, &#39;username&#39;, &#39;full_name&#39;)

def get_full_name(self, obj):
    return f&quot;{obj.first_name} {obj.last_name}&quot;</code></pre><p>위의 예시에서 full_name 필드는 SerializerMethodField로 정의되어있다. 따라서 get_full_name 메서드를 시리얼라이저 내에서 정의하였고, 이 메서드는 User 모델의 first_name과 last_name을 조합하여 전체 이름을 반환하게 커스터마이징을 해주었다.</p>
<p>이와 같이 SerializerMethodField는 표준 모델 필드나 시리얼라이저 필드가 아닌, 사용자 정의 로직을 사용하여 값을 반환할 때 유용하게 사용한다.</p>
<p>다른예시</p>
<pre><code>
class ArticleListSerializer(serializers.ModelSerializer):

    user = serializers.SerializerMethodField()
    def get_user(self, obj):
        # obj는 현재 직렬화되고 있는 Article 객체의 인스턴스
        # (serializer = ArticleListSerializer(instance=articles,many=True))
        # 즉, obj는 이 articles 쿼리셋 내의 개별 Article 인스턴스를 나타냄
        #obj 해당 아티클. 
        return obj.user.email
    class Meta:
        model = Article
        fields = (&#39;pk&#39;,&#39;title&#39;,&#39;image&#39;,&#39;updated_at&#39;,&#39;user&#39;) </code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[DRF: serializer 사용 - login/signup  ]]></title>
            <link>https://velog.io/@stella_k/serializer-%EC%82%AC%EC%9A%A9-drf</link>
            <guid>https://velog.io/@stella_k/serializer-%EC%82%AC%EC%9A%A9-drf</guid>
            <pubDate>Wed, 11 Oct 2023 17:23:22 GMT</pubDate>
            <description><![CDATA[<pre><code>from rest_framework.views import APIView
from rest_framework import status,permissions
from rest_framework_simplejwt.views import (
    TokenObtainPairView
)
from users.serializers import UserSerializer,CustomTokenObtainPairSerializer
from rest_framework.response import Response

# Create your views here.

class UserView(APIView):
    def post(self, request):
        # serializer = UserSerializer (data=request. data)
        # serializer.is_valid(raise_exception=True)
        # serializer.save()
        #밑에것과 다를것 없지만 따로 조건문을 넣어주면 유도리 있게 그 사이에 더 필용한 작업들을 실행할 수 있다.
        serializer = UserSerializer(data = request.data) 
        # db상에는 저장이 아직되어있지 않지
        # request.data는 브라우저에서 받아온 body값 json형태로 받아온 데이터를 가지고있다.
        # 여기서 request로 받아와 지면서  drf에 의해 python 딕셔너리상태로 변형되어가져오게된다.
        # 즉,JSON으로 받아져오잖아? json.stringfy → 이걸 Python 사전(dict) 바꿔줘서 서버에 넘기고 → 다시 JSON으로의 변환과정, 그리고 그 사이에 여러 유효성 검사와 처리
        # serializer를 통해 json 직렬화 시키는 것 뿐만아니라 여러 역할을 수행한다.

        ##즉, api는 클라이언트-&gt;서버 그리고 서버-&gt; 클라이언트 두가지 방향을 다 컨트롤하는 곳이기에
        # 클라이언트-&gt;서버 (디코딩)
        #현재는 post로 프론트에서 받아온 body부분의 json을 -&gt; python객체로 서버에서 디코딩화 시켜는데,
        # DRF에서는 서버에서 이 디코딩 과정이 자동으로 처리해주고, request.data에는 파이썬 딕셔너리 형태의 데이터가 저장된다는 말.

        #서버-&gt; 클라이언트 (인코딩)
        # 그렇게 서버가 처리한 결과를 클라이언트에게 보낼 때, 해당 결과는 JSON 형식의 문자열로 변환 (인코딩) 해서 전송됩니다.
        # 이 인코딩 과정 역시 Django REST framework의 Serializer를 통해 수행될 수 있습니다. 
        # Serializer의 .data를 응답으로 전송하면, 그 데이터는 JSON 형식으로 클라이언트에게 전달됩니다.


# Serializer의 주요 역할은 다음과 같습니다:
# 데이터 유효성 검사 (Validation): 받아온 데이터가 우리의 기대와 일치하는지, 
# 예를 들어 필수 필드의 존재, 데이터 형식, 길이 제한 등의 유효성을 검사합니다.

# 복잡한 데이터 구조의 처리: ORM의 관계 (예: ForeignKey, ManyToMany)를 쉽게 JSON과 같은 형태로 표현하거나, 
# 반대로 복잡한 JSON 데이터를 ORM 객체로 변환하는 작업을 돕습니다.

# ORM과의 연계: 파이썬 사전 형태로 파싱된 데이터를 모델의 인스턴스로 변환하거나, 
# 반대로 모델의 인스턴스나 쿼리셋을 JSON 형태로 변환하는 작업을 수행합니다.




        if serializer.is_valid():
            serializer.save()
            #이렇게 해서는 비번 해싱이 안됌.
            return Response({&quot;message&quot;: &quot;가입완료&quot;},status=status.HTTP_201_CREATED)
        else: 
            return Response({&quot;message&quot;: f&quot;{serializer.errors}&quot;}, status=status.HTTP_400_BAD_REQUEST)

class CustomTokenObtainPairView(TokenObtainPairView):
    serializer_class = CustomTokenObtainPairSerializer
    #drf에서 사용하는 serializer클라스를 지정하기위한 클래스 변수이다.
    #클래스 변수는 그 클래스의 (만들어진 무수한)인스턴스들이 공유하는 변수 
    # CustomTokenObtainPairSerializer 클라스로 들어가면 Return값이 있기 떄문에 따로 필요가 없음
    # TokenObtainPairView안에보면 다 serialize_class변수 라던지 post요청이 있다.
    # payload부분에 추가적인 데이터를 넣어준는것.

#get요청 토큰인증확인
class mockView(APIView):
    permission_classes=[permissions.IsAuthenticated]#인증된사용자만 뷰에 접근(api)
    #permissions.안에 내장된 기존 메소드들이 있고 현재는 인증이 유효한지에 관련한 클라스 메소드를 불러들이고 있다.
    def get(self, request):
        print(request.user)
        #이부분은 포스트로보내는게 맞는데 일단 확인차 테스팅하기위해 겟에 넣어준것뿐임
        #그래도 반영이 되어서 디비들어가서 보면 요청이 변경된것을 볼 수 있다. 
        user = request.user
        user.is_admin = True # true로 변경
        user.save()
        #
        return Response(&quot;get요청&quot;)
    #이때 포스트맨으로 체크하는데 로긴하고 나서의 토큰이 잘 갔는지 테스팅을 해주는거라 
    # 헤더에 실어서 보낼꺼라 헤더부분에 키 Authorization 값 Bearer accesstokennumber해줘야한다 bearer는 들고있는 사람
    # 왜냐하면 요청시 header에 토큰정보를 담아 넣어줘야 그값이 확인하고 인증하기때문에  

    # 포스트맨에서는 위와같이했다면 이제 frontend에서 자바스크립트로 fetch api써서 보내줄것.




    # =====================================================================================


# HTTP GET:

#     목적: 주로 서버로부터 정보를 검색하기 위해 사용됩니다.
#     데이터 전송: 데이터는 URL의 쿼리 매개변수로 전송됩니다. 
#             (예: https://example.com/data?param1=value1&amp;param2=value2)
#     크기 제한: 대부분의 웹 브라우저와 웹 서버는 URL의 길이에 제한이 있으므로 크기가 큰 데이터를 전송하는 데에는 적합하지 않습니다.
#     보안: 데이터가 URL에 포함되어 있기 때문에 비밀번호나 기타 중요한 정보를 전송하는 데 사용되어서는 안 됩니다.
#     캐싱: 일반적으로 웹 브라우저에 의해 캐싱될 수 있습니다.



# HTTP POST:

#     목적: 서버로 데이터를 전송하기 위해 사용됩니다.
#             이 데이터는 새로운 리소스를 생성하거나 기존 리소스를 업데이트하기 위한 것일 수 있습니다.
#     데이터 전송: 데이터는 HTTP 메시지의 본문에 포함되어 전송됩니다. 따라서 큰 데이터를 전송하는 데에 적합합니다.
#     크기 제한: 본문의 크기에 따라 웹 서버 설정에 따라 제한이 있을 수는 있지만, 
#                 일반적으로 GET보다 큰 데이터를 전송하는 데 더 적합합니다.
#     보안: 데이터가 HTTP 메시지 본문에 포함되어 있으므로 URL에 노출되지 않습니다. 
#             그러나 전송된 데이터는 여전히 암호화되지 않을 수 있으므로 중요한 정보를 전송할 때는 HTTPS와 같은 보안 프로토콜을 사용해야 합니다.
#     캐싱: 일반적으로 캐싱되지 않습니다.
#     이런 차이점들을 기반으로, GET은 정보를 조회할 때 주로 사용되며, POST는 서버에 정보를 제출할 때 주로 사용됩니다.
#     그러나 POST 요청의 응답으로 서버가 데이터를 반환하는 것도 가능합니다.
#     예를 들어, 새로운 리소스를 생성한 후 해당 리소스의 세부 정보를 반환할 수 있습니다.

# 즉, POST를 사용하여 데이터를 서버에 보내면서 동시에 서버로부터 정보를 받아올 수 있습니다. ::  주고받음의 역할
# 하지만 GET이 데이터를 조회하는 데 더 일반적으로 사용된다는 점을 이해하는 것이 중요합니다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[장고 심화 과정- 프로젝트 api문서 및 erd 그리고 wirelessframe]]></title>
            <link>https://velog.io/@stella_k/%EC%9E%A5%EA%B3%A0-%EC%8B%AC%ED%99%94-%EA%B3%BC%EC%A0%95-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-api%EB%AC%B8%EC%84%9C-%EB%B0%8F-erd-%EA%B7%B8%EB%A6%AC%EA%B3%A0-wireless</link>
            <guid>https://velog.io/@stella_k/%EC%9E%A5%EA%B3%A0-%EC%8B%AC%ED%99%94-%EA%B3%BC%EC%A0%95-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-api%EB%AC%B8%EC%84%9C-%EB%B0%8F-erd-%EA%B7%B8%EB%A6%AC%EA%B3%A0-wireless</guid>
            <pubDate>Thu, 05 Oct 2023 14:54:42 GMT</pubDate>
            <description><![CDATA[<p>팀원들과 주제를 얘기하다,
지도를 불러와 중간지점을 선택하여 그 주변 맛집을 공유해주고 다이렉팅까지해주는 그런 서비스를 만들어 보자! 해서 주제를 그것으로 선택했다.</p>
<p>물론 리뷰게시글을 만들어서 추천기능을 넣어준 후 관련 가게에 포스팅을 모아 볼수도 있는 기능을 해 주려고 한다.</p>
<h3 id="프로젝트-이름은-주제부주제식으로-">프로젝트 이름은 주제:부주제식으로 :</h3>
<p>&lt; 당장만나! : 어디서볼까? &gt; 이렇게 정해보았다.</p>
<h3 id="wirelessframe">Wirelessframe</h3>
<p><img src="https://velog.velcdn.com/images/stella_k/post/ba432dbc-65c7-41b8-951e-2fbb1e1ea4cf/image.png" alt=""></p>
<p>이렇게 간단하게 어떻식으로 페이지를 구성하고 기능을 넣어줄지에 대해서 rough하게 만들어 보았다.</p>
<h3 id="erd">ERD</h3>
<p>ERD Cloud를 이용해서 모델링을 해보았다.</p>
<p>일단 최대한 생성해 놓을 부분은 해놓고 필요하지않으면 쓰지 않던가하는게, 추후에 추가해서 넣어주는것보다는 나을것 같아서 구성했다.</p>
<blockquote>
<p>FK -&gt; 1:many 관계있을때 
one있는 곳이 아닌 many모델entity에 fk필드가 들어가있는것을 볼 수 있다!</p>
</blockquote>
<blockquote>
<p>리뷰entity를 중심으로 (-&gt;&gt; : one -&gt;&gt; many:FK)</p>
</blockquote>
<blockquote>
<p>user -&gt;&gt; review : 유저는 많은 리뷰 생성가능/반면에하나의포스트안에 많은유저 불가능
user -&gt;&gt; comments : 유저는 많은 댓글 생성가능
user -&gt;&gt; likes :  유저는 많은 좋아요(리뷰당) 생성가능</p>
</blockquote>
<blockquote>
<p>review -&gt;&gt; likes : 리뷰는 많은 좋아요 가질수 있고
review -&gt;&gt; comments : 리뷰는 많은 댓글 가질 수 있고
review -&gt;&gt; photos : 리뷰는 많은 사진을 추가해 넣을 수 있다.</p>
</blockquote>
<blockquote>
<p>restaurant -&gt;&gt; review :  하나의 레스토랑이 많은 리뷰에 언급되니 많은리뷰를 가질수 있음</p>
</blockquote>
<blockquote>
<p>catagories -&gt;&gt; restaurant :  카테고리 (중식당, 양식당,카페...)는 레스토랑 태그안에 여러개 들어가질 수 있게 정해둠.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/stella_k/post/4ee64d43-a9e7-4533-9237-d2024b22ab33/image.png" alt=""></p>
<h3 id="api-명세서">API 명세서:</h3>
<p>일단 간단하게 KAKAO MAPS API를 불러오는거 제외하고 만들어 보았다.
DRF를 사용하고 serializer를 사용하다보니 여러 모듈을 상속시켜 api를 만들어 어느것을 어떻게 올려야하나 고민을했는데,</p>
<p>간단하게 postman활용해서 보내주고 확인했던 부분들, urls,views.py안에 있는 것들을 api 명세서에 넣어주면 되는것으로 보았다.</p>
<p>우리는 GUI가 깔끔한 GITBOOK을 apiDocs를 만들었다.</p>
<p>&lt;Api 명세서&gt;
<a href="https://800-1.gitbook.io/api-docs/">https://800-1.gitbook.io/api-docs/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[save()메서드 인자(force_insert/force_update/commit/using) 그리고 다중데이터베이스]]></title>
            <link>https://velog.io/@stella_k/save%EB%A9%94%EC%84%9C%EB%93%9C-%EC%9D%B8%EC%9E%90forceinsertforceupdatecommitusing-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EB%8B%A4%EC%A4%91%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@stella_k/save%EB%A9%94%EC%84%9C%EB%93%9C-%EC%9D%B8%EC%9E%90forceinsertforceupdatecommitusing-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EB%8B%A4%EC%A4%91%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Sun, 01 Oct 2023 07:21:13 GMT</pubDate>
            <description><![CDATA[<p>save()는 Model에서 제공된느 장고의 메서드로서 객체를 데이터베이스에 저장 또는 업데이트 할때 사용.</p>
<p>다양한 인자를 받게되는데,</p>
<h4 id="force_insert-기본값-false">force_insert (기본값: False):</h4>
<p>만약 True로 설정될 시 이 객체를 데이터베이스에 새로운 레코드로 삽입.
즉, 기존에 있는 데이터레코드인지 확인을 해서 존재한다하여도 그 존재하는 레코드의 갱신은 시도되지않고 새로운 id를 가지는 데이터로 생성됩니다.
마지막 id가 16이였으면 17로 새로 생성되겠죠?</p>
<pre><code>new_user = User(username=&quot;new_user&quot;, email=&quot;new_user@example.com&quot;)
new_user.save(force_insert=True)
</code></pre><p>반면에</p>
<h4 id="force_update-기본값-false">force_update (기본값: False):</h4>
<p>만약 True로 설정되면, 장고는 이 객체를 데이터베이스의 존재하는 레코드로 갱신하려 할 것입니다. 즉, 존재하는 데이터 중에서 업데이트를 시도하는데, 해당객체의 기존레코드가 없으면 오류가 발생하며, 새 레코드의 삽입은 시도하지 않습니다.</p>
<pre><code>existing_user = User.objects.get(username=&quot;existing_user&quot;)
existing_user.email = &quot;updated_email@example.com&quot;
existing_user.save(force_update=True)
</code></pre><blockquote>
<p>여기서 commit=False의 경우는 뭐가 다른가?</p>
</blockquote>
<blockquote>
<p>일단 commit=False는 모델의 save() 메서드나, 모델 폼의 save() 메서드에서도 사용되지만 force_update와는 다르게 실제 데이베이스에 저장되지 않고 객체를 메모리에만 저장한다. 그렇게 사용하여 따로 실제 데이터베이스에 저장하지 않고 필요한 추가적인 사항을 적용한 후에 객체를 수동으로 저장하여 사용한다.</p>
</blockquote>
<h4 id="using">using:</h4>
<p>여러 데이터베이스가 있는 경우 특정 데이터베이스에 연결하여 연산을 실행할 수 있습니다.
예를 들어, 프로젝트에 default와 archive 라는 두 개의 데이터베이스 설정이 있다고 가정합니다. 다음은 archive 데이터베이스에 객체를 저장하는 방법입니다.</p>
<pre><code>user = User(username=&quot;archived_user&quot;, email=&quot;archived_user@example.com&quot;)
user.save(using=&quot;archive&quot;)
</code></pre><p>해당 데이터베이스의 alias가 self._db에 할당되어 user.save(using=self._db)를 호출하면, self._db에 저장된 데이터베이스 alias를 사용하여 해당 사용자 객체를 저장된다.</p>
<blockquote>
<h4 id="장고에서-다중데이터베이스-설정">장고에서 다중데이터베이스 설정</h4>
</blockquote>
<h3 id="1-데이터베이스-설정을-추가">1. 데이터베이스 설정을 추가</h3>
<p>settings.py 파일 내에서 DATABASE설정에 새로운 데이터베이스를 추가</p>
<pre><code>DATABASES = {
    &#39;default&#39;: {
        &#39;ENGINE&#39;: &#39;django.db.backends.sqlite3&#39;,
        &#39;NAME&#39;: BASE_DIR / &quot;db.sqlite3&quot;,
    },
    &#39;archive&#39;: {
        &#39;ENGINE&#39;: &#39;django.db.backends.sqlite3&#39;,
        &#39;NAME&#39;: BASE_DIR / &quot;archive_db.sqlite3&quot;,
    }
}
</code></pre><p>setting.py에 만들어놓은 라우터 파일을 연결시켜줘야한다.</p>
<pre><code>DATABASE_ROUTERS = [&#39;app이름.파일이름.ArchiveRouter&#39;]
</code></pre><p>여기서는 archive라는 이름을 가진 새로운 데이터를 생성하고, ENGINE을 SQLite로 설정했지만 엔진 부분에는 다른 데이터 베이스 백엔드 (예: PostgreSQL, MySQL)로도 설정할 수 있다.</p>
<h3 id="2-선택사항으로-데이터베이스-라우팅로직-세팅">2. 선택사항으로 데이터베이스 라우팅로직 세팅</h3>
<p>특정앱의 모델을 archive 데이터베이스에만 저장되어야 하는 경우를 예를 들어 설명하면, 이 로직은 routers.py 라는 별도의 파일에 저장할 수 있고, 프로젝트의 구조나 개인의 선호에 따라 models.py 또는 database_routers.py 등의 다른 파일에도 저장할 수 있다.</p>
<blockquote>
<p>먼저 라우팅이 무엇인가?</p>
</blockquote>
<p>라우팅이란 네트워킹에서 데이터 패킷이 소스에서 목적지까지 어떻게 이동할지 결정하는 프로세스인데, 한마디로 데이터의 길찾기? 라고 보면된다.</p>
<blockquote>
<h4 id="데이터-패킷-큰-데이터나-메세지를-여러개의-작은데이터-조각으로-나누어-보내는데-이것이-데이터-패킷이다-출발지-목적지주소-순서-정보등-여러정보가-포함되어있다">데이터 패킷: 큰 데이터나 메세지를 여러개의 작은데이터 조각으로 나누어 보내는데 이것이 데이터 패킷이다. 출발지 목적지주소, 순서 정보등 여러정보가 포함되어있다.</h4>
</blockquote>
<h4 id="소스--데이터-패킷의-시작지점으로-웹-서버가-브라우서의-웹페이지요청에-응답하기-위해-데이터-패킷을-보내기-시작하는-지점이-소스">소스 : 데이터 패킷의 시작지점으로 웹 서버가 브라우서의 웹페이지요청에 응답하기 위해 데이터 패킷을 보내기 시작하는 지점이 소스</h4>
<h4 id="목적지-패킷의-최종-도착지점으로-브라우저의-웹페이지-요청의-데이터를-받는-목적지가-된다">목적지: 패킷의 최종 도착지점으로 브라우저의 웹페이지 요청의 데이터를 받는 목적지가 된다.</h4>
<p>데이터 패킷이 소스에서 목적지까지 전송될 때는 다양한 네트워크 장비들(라우터, 스위치, 게이트웨이 등)을 거치게 되며, 각 장비는 패킷의 헤더 정보를 기반으로 다음 목적지로 패킷을 전송하고, 이 과정에서 라우팅 로직이 패킷을 어디로 전송할지 결정하는 역할을 한다.</p>
<p>웹개발의 맥락에서 라우팅을 바라보면, 라우팅은 URL을 특정 처리 로직이나 함수에 매핑하는 메커니즘을 지칭한다. </p>
<p>예를 들어 사용자가 웹 브라우저의 주소창에 특정 URL을 입력하면 그 URL과 연관된 함수나 뷰를 호출하는 역할을 하는것으로 보면된다.</p>
<p><em><strong>flask를 예를 들어보면 @app.route(&#39;/&#39;)를 통해 매핑시키는 역할을 볼수있다.</strong></em></p>
<pre><code>from flask import Flask
app = Flask(__name__)

@app.route(&#39;/&#39;)
def home():
    return &quot;Home Page&quot;

@app.route(&#39;/about&#39;)
def about():
    return &quot;About Page&quot;
</code></pre><blockquote>
<p>라우팅로직은?</p>
</blockquote>
<p> 다중 데이터베이스를 사용할때 어떤 데이터베이스에 쿼리를 보내고 어느 데이터베이스에 데이트를 저장할 것인지 결정하는 방법이 필요한데, 이것을 위해 사용되는게 라우팅 로직이다.</p>
<p> 라우팅 로직을 사용하면 어플레케이션의 복잡한 db구조와 요구사항 및 데이터를 여러 db에 분산 시키며 성능 향상, 데이터 분리, 백업전략 다양한 목적으로 다중 데이터 베이스 구성을 활용 할 수 있다.</p>
<p> 장고의 데이터베이스 라우터는 여러 메서드를 통해 라우팅로직을 제공한다.</p>
<pre><code>
**db_for_read(model, hints): 주어진 모델에 대한 읽기 쿼리를 수행할 데이터베이스를 결정합니다.

**db_for_write(model, hints): 주어진 모델에 대한 쓰기 쿼리를 수행할 데이터베이스를 결정합니다.

**allow_relation(obj1, obj2, hints): 두 개의 객체 (또는 모델 인스턴스) 간의 관계 (예: ForeignKey)가 허용되는지를 결정합니다.

**allow_migrate(db, app_label, model_name=None, hints): 특정 데이터베이스에 마이그레이션을 적용할 수 있는지 결정합니다.
</code></pre><p>예를 하나씩 들어볼게요! </p>
<h4 id="db_for_read와-db_for_write">db_for_read와 db_for_write:</h4>
<p>read 와 write같은경우는 어디더 읽고 저장할 것인지를 판단하는 과정이라고 보면된다.</p>
<pre><code>class Database1Router: </code></pre><p>클라스 </p>
<pre><code>def db_for_read(self, model, **hints):
    if model._meta.app_label == &#39;myapp&#39;:
        return &#39;database1&#39;
    return &#39;default&#39;

def db_for_write(self, model, **hints):
    if model._meta.app_label == &#39;myapp&#39;:
        return &#39;database1&#39;
    return &#39;default&#39;
</code></pre><p>myapp에서 읽어온거면 database1에 보내주고, 그 myapp에서 잃어들어온걸 database1에다 써줘, 그외에는 기본 SQLite에 저장할게</p>
<p>라는 의미이다.</p>
<h4 id="allow_relation">allow_relation:</h4>
<p> 두 모델 간의 관계 (예: ForeignKey 또는 ManyToManyField)가 가능한지를 결정하는 데 사용되는데, 구현되어 있지 않는다면 모든 모델 간의 관계를 설정하는 것을 허용한다. 즉, 어떤 앱의 모델이든 다른 앱의 모델을 참조하거나 연관 짓는 것이 가능하게 된다는 것이다.</p>
<p> 예를 들어서 
 &#39;app1&#39;이라는 앱의 모델이 &#39;db1&#39;에,
 &#39;app2&#39;라는 앱의 모델이 &#39;db2&#39;에 저장된다고 가정하면,</p>
<p> &#39;app1&#39;의 모델이 &#39;app2&#39;의 모델을 ForeignKey로 참조하려고 할 때, 이 참조가 실제로 가능한지를 결정하는 것이 allow_relation의 역할이다.</p>
<p> 해주고 싶으면 TRUE 싫다면 FALSE를 해주면 된다.</p>
<p> True: 관계 설정 허용
 False: 관계 설정 금지
 None: 라우터의 결정이 없는 경우 (다음 라우터나 기본 설정 따름)</p>
<p><em>여기서 obj는 모델 인스턴스로 생각하면 된다.</em></p>
<pre><code>def allow_relation(self, obj1, obj2, **hints):
    if obj1._meta.app_label == &#39;myapp&#39; or obj2._meta.app_label == &#39;myapp&#39;:
        return True
    return None</code></pre><p>위에서는 특정 앱에서만 관계를 제한해주고 싶을때, 즉 myapp이란 같은 모델에서만 가능케 꼭 명시해 주고 싶을때는 필요한 메소드이지만 굳이 안해줘도 기본적으로 모든 앱은 fk가 가능한 상태라 제한이 필요한 상황에서 써주면 될것 같다.</p>
<pre><code>def allow_relation(self, obj1, obj2, **hints):
    if obj1._meta.app_label == &#39;app1&#39; or obj2._meta.app_label == &#39;app2&#39;:
        return True
    return None
</code></pre><p>여기서는 다른 앱 app1 app2의 모델이 관련된 관계를 맺을때 허용하겠다는 메서드라고 보면 되겠다.</p>
<blockquote>
<p>hints는 딕셔너리 형태의 추가적인 정보를 제공하는 역할이라고 생각하면된다. 선택적인 정보여서 필요할때만 넣으면 되고 제공되는 hints딕셔너리 키가 모든상황에서 같은게 아니라서 어떤 키가 있는지 확인하는 로직을 추가하는게 좋다.</p>
</blockquote>
<p>기본적으로 model,instance라는 키가 들어가있고,
  model: 현재 작업 중인 모델 클래스입니다.
  instance: 현재 작업 중인 모델 인스턴스입니다. (일부 메서드에서만 사용 가능)</p>
<pre><code>def allow_relation(self, obj1, obj2, **hints):
    if &#39;instance&#39; in hints:
        instance = hints[&#39;instance&#39;]
        if isinstance(instance, MyModel):
            # 로직...
            pass
    return True
</code></pre><p>hints 딕셔너리 내에 &#39;instance&#39; 키가 있는지 확인
만약 &#39;instance&#39; 키가 있다면 그 값을 instance 변수에 할당하고,
해당 인스턴스가 MyModel(모델클라스이름)의 인스턴스인지 확인해주는 것.</p>
<h4 id="allow_migrate">allow_migrate:</h4>
<p>특정 데이터베이스에 대한 마이그레이션을 허용하거나 금지하려는 경우 allow_migrate를 사용한다.</p>
<p>이것도 기본적으로 별도의 라우팅 로직을 구현하지 않으면 모든 앱의 모델은 DATABASES 설정의 default 데이터베이스에 마이그레이션된다.</p>
<p>&#39;myapp&#39; 앱의 모델에 대한 마이그레이션을 &#39;database1&#39;에서만 허용하도록 설정해주는 예의 코드를 작성해보겠다.</p>
<pre><code>def allow_migrate(self, db, app_label, model_name=None, **hints):
    if app_label == &#39;myapp&#39;:
        return db == &#39;database1&#39;
    return None
</code></pre><blockquote>
<p>마이그래이션 연산은 테이블 생성, 삭제 및 특정모델과 직접적인 연관이 있는 것도 있지만, 데이터베이스 인덱스 추가라던지, sql 구문실행과 같이 특정 모델과 직접적인 연관이 없을 수 있기 때문에 그냥 기본 인자값으로  model_name=None처리 해주는것이다.</p>
</blockquote>
<p>makemigration이나 migrate 관련 장고 명령 사용시, 
장고에서는 내부적으로 이 함수를 호출하면 필요한 인자값을 전달하기 때문에,
model_name=None이어도 model_name은 None값이 아니라 
해당 마이그레이션과 관련된 모델의 이름이 문자열로 전달되어 
함수 내부에서 특정 모델에 대한 마이그레이션 허용 여부 결정을 해주기 위해 model_name을 활용할 수 있다.</p>
<pre><code>def allow_migrate(self, db, app_label, model_name=None, **hints):
    if app_label == &#39;myapp&#39; and model_name == &#39;mymodel&#39;:
        return db == &#39;database1&#39;
    return None
</code></pre><h3 id="3-데이터베이스-마이그레이션">3. 데이터베이스 마이그레이션</h3>
<p>새로운 데이터베이스에 마이그레이션을 수행한다!</p>
<p>특정 앱의 모델 변경을 기반으로 마이그레이션 파일을 생성</p>
<pre><code>python manage.py makemigrations app_name
</code></pre><p>특정 데이터베이스에 마이그레이션을 적용</p>
<pre><code>python manage.py migrate --database=archive
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[RelatedManager란?::Queryset메소드들>filter()/exclude()/annotate()/aggregate()/count()/distinct() ]]></title>
            <link>https://velog.io/@stella_k/RelatedManager%EB%9E%80Queryset%EB%A9%94%EC%86%8C%EB%93%9C%EB%93%A4filterexcludeannotateaggregatecountdistinct</link>
            <guid>https://velog.io/@stella_k/RelatedManager%EB%9E%80Queryset%EB%A9%94%EC%86%8C%EB%93%9C%EB%93%A4filterexcludeannotateaggregatecountdistinct</guid>
            <pubDate>Mon, 25 Sep 2023 14:50:50 GMT</pubDate>
            <description><![CDATA[<h3 id="relatedmanager">RelatedManager</h3>
<p>RelatedManager는 FK나 ManyToManyField, OneToOneField와 같은 관계의 필드에서 반대쪽 모델로의 역참조를 관리할 때 사용되는 매니저이다.</p>
<p>이 메니저는 Queryset메서드를 지원하여 데이터베이스 쿼리를 작성할때 유용하게 사용된다.</p>
<p>대표적인 메서드를 예를 들어 설명하겠다!</p>
<p>이전 역참조와 정참조에서 사용했던 그 예시로 같지만 간단하게 모델을 재정의 하도록 하겠다.</p>
<p>&lt;모델&gt;</p>
<pre><code>from django.db import models

class User(models.Model):
    username = models.CharField(max_length=20)

class Post(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(User, related_name=&quot;posts&quot;, on_delete=models.CASCADE)
    publish_date = models.DateField()
    comments = models.ManyToManyField(&quot;users.User&quot;, related_name=&quot;comment&quot;, through=&quot;Comments&quot;,verbose_name=&quot;댓글&quot;)
    category = models.CharField(max_length=200)  # 카테고리를 문자열 필드로 표현
    length = models.PositiveIntegerField(default=0) # 포스트의 내용의 길이를 정수 필드로 표현, 예를 들어, 문자의 수 =&gt; 현재는 content라는 필드가 있으면 그것의 길이, 없으니 일반적으로 그냥 본문의 길이. 같은의미다</code></pre><p>만약 length에 관련해서 조건을 넣어주고 싶다면,
모델에는 length필드를 제거하고, 밑에 함수를 추가하여 그 값을 가져올수 있지만</p>
<p>필드로서 length는 명시되지 않았기 떄문에 데이터베이스에 저장되지는 않는다.
따라서 데이터베이스에는 이에 해당하는 컬럼이 존재하지 않을 것이다.</p>
<p> 예) content    필드가 있으면 content 문자의 수 , 없으면 title문자의수 만</p>
<pre><code> @property
    def length(self):
        if self.content:
            return len(self.content)
        else:
            return len(self.title)</code></pre><p>  <strong>@property</strong>를 사용하면, 클래스 내의 함수를 해당 클래스의 인스턴스를 통해 &quot;함수 호출&quot;(() 사용) 없이 접근할 수 있게 됩니다.</p>
<p>  원래</p>
<pre><code>  post = Post(필드값....) # 모델 인스턴스 생성
  print(post.length()) #이렇게는 못하니까
  length = post.length()
  print(length) #이렇게 해줘야한다</code></pre><p>그게 번거롭기때문에** @property**를 사용하면
=&gt; 메서드를 마치 인스턴스 변수/속성처럼 (즉, 함수 호출 () 없이) 접근할 수 있게되는것</p>
<pre><code>  post = Post(필드값....) 
  print(post.length)</code></pre><p>  이렇게 <strong>바로 접근이 가능</strong>하기 때문에 데이터베이스에는 저장되지않아도 값을 api에서는 불러와서 사용할수는 있다.</p>
<blockquote>
<p> 모델 필드에도 따로 저장하고 조건 또한 넣으려고 한다면, 추가적인 로직이 필요하다.</p>
</blockquote>
<h3 id="1-저장-메서드-오버라이드">1. 저장 메서드 오버라이드:</h3>
<pre><code>    class Post(models.Model):
    # ... 기타 필드 ...
    content_length = models.PositiveIntegerField(default=0)

    def save(self, *args, **kwargs):
        self.content_length = len(self.content) if self.content else len(self.title)
        # super(Post, self).save(*args, **kwargs) #파이썬2
        #현재는 python3활용하므로 밑으로쓰기
        super().save(*args, **kwargs) # 파이썬3
</code></pre><p>Post 클래스의 경우, 직접적으로 보이지는 않지만 models.Model을 상속받고 있습니다.</p>
<p>view.py </p>
<pre><code>post = Post.objects.get(id=post_id)
post.save() </code></pre><p>post.save()불러오게되면, 우리가 기존에 데이터베이스에 저장하는 메소드인 models.Model의 save 메서드를 호출하는 것이 아니라, Post 클래스 내에서 새로히 만든 save 메서드를 불러오게 되는것으로, 기존 save()에 오버라이드하여 추가적인 작업을 수행할 수 있도록 한 뒤에, super(Post, self).save(<em>args, *</em>kwargs)호출하며 원래 models.Model의 save 메서드를 호출하여 실제 저장 작업을 수행하게 만든것입니다.</p>
<p>그럼 저 super(Post, self).save(<em>args, *</em>kwargs)는 ?</p>
<p>super에 관련한 설명은 이전 포스팅에서도 했지만 좀더 간단하게 설명하는 겸, 추가적인 설명을 더 들어주겠다.</p>
<p>일단 여기서 super(class,self)의 인자는 python2에서 쓰던 방식이고 현재는 python3로 많이 사용되기에 이제는 인자 값이 필요없는 super()해주면된다.</p>
<pre><code>super().save(*args, **kwargs) # 파이썬3
</code></pre><blockquote>
<p><em><strong>&lt;그럼 여기서 잠깐!&gt;</strong></em></p>
</blockquote>
<p>super()라는 함수는 파이썬에 내장된 함수로 상속된 상위 클래스의 메서드를 호출하기 위해 사용하는데. 여기서 간혹 상속인자를 여러개 주게 되는 경우시에 이게 충돌이 일어날 수 있기 때문에  MRO(Method Resolution Order)를 기반으로 다음 클래스의 메서드를 호출한다.</p>
<p>예를 들어줄게요:</p>
<pre><code>class A:
    def hello(self):
        print(&quot;Hello from A class&quot;)

class B(A):
    def hello(self):
        super().hello()
        print(&quot;Hello from B class&quot;)

class C(A):
    def hello(self):
        super().hello()
        print(&quot;Hello from C class&quot;)

class D(B, C):
    def hello(self):
        super().hello()
        print(&quot;Hello from D class&quot;)

d = D()
d.hello()
</code></pre><p>그럼 내가 d의 hello함수를 호출하게 됐는데?</p>
<p>이것 값은 ??</p>
<p>Hello from A class
Hello from C class
Hello from B class
Hello from D class</p>
<p>이렇게 나오는데 왜 이렇게 나오지 &gt; 라는 생각을 하게된다.</p>
<p>먼저 b다 끝내고 c다끝내고 A로 돌아와야하는게 아닌가?</p>
<p>여기서 문제점은 우리가 생각하는 방식으로 MRO짜져 있지 않다는것이다. </p>
<p>클래스의 MRO를 확인하려면 클래스명.mro() 함수를 사용하면 되는데,</p>
<pre><code>D.mro()

=&gt; [D, B, C, object]와 같은 출력 결과
</code></pre><p>D에서 hello 호출을 시작으로 super().hello() 상위 hello()가 호출되는데, 우리는 모델에서 받은 인자 A를 기준으로 생각해 그곳으로 들어가서 결과값이 반환된다고 생각하는데 다중으로 받았을때는 A가 아니라 그 다음에 주었던 인자값이 상위 클라스로 인지되어 올라가진다고 보면 된다. </p>
<p>B.hello()의 super().hell0()는 C.hello()고 
C.hello()의 super().hell0()는 A클라스로 A클라스는 상위로 갈게 없어 상위클라스의 destination찍고,
이게 끝이아니라 다시 올라온순서대로 꺼꾸로 가서 마지막 프린트 할것 나 나가야한다.</p>
<p>그래서 </p>
<p>Hello from A class -&gt; 정점찍고
Hello from C class -&gt; c로 꺼구로 돌아와서 print()실행
Hello from B class
Hello from D class</p>
<p>이러한 값이 나오는것.</p>
<blockquote>
<p>&lt;여기서 잠깐 <em>args, *</em>kwargs의 인자&gt;</p>
</blockquote>
<p>전에 언패킹에 관에 설명하면서 * =&gt; 리스트, ** =&gt;딕셔너리를 위해 사용한다고 말했던것 기억나나?</p>
<p>이때 args는 arguments로 인자들이라는 의미를 가지고있고,
kwargs도 마찬가지로 key arguments라는 의미로 딕셔너리형태의 인자를 말하는것이다.</p>
<p>그래서 인자들안에 그러한 두 형태를 가지고 있을 수 있다는 의미로  <em>args,*</em>kwags를 넣는것. 순서는 그냥 보편적으로 <em>args 다음 *</em>kwargs순으로 쓴다. </p>
<p>주로 args인자에 값을 넣는 경우는 거의 없다. </p>
<h3 id="2선택적-django-시그널-pre_save사용">2.(선택적) Django 시그널 pre_save사용:</h3>
<pre><code>from django.db.models.signals import pre_save
from django.dispatch import receiver

@receiver(pre_save, sender=Post)
def update_content_length(sender, instance, **kwargs):
    instance.content_length = len(instance.content) if instance.content else len(instance.title)
</code></pre><p>Django의 pre_save 시그널을 사용하여 Post 객체가 저장되기 전에 content_length 값을 업데이트할 수도 있는 방법이 바로 위에 있는 방법이데, 이 방법은 1번에서 보여준 메서드 오버라이딩 대신 모델의 저장 로직 외부에서 추가 로직을 처리할 수 있게 해줍니다.  </p>
<h4 id="pre_save">pre_save</h4>
<p>장고모델의 save()메서드가 호출 되기 직전에 발생하는 신호로 데이터베이스 저장전 객체 필드를 수정하거나 유효성 검사를 수행하는데 사용된다.</p>
<h4 id="receiver">@receiver</h4>
<p>이 함수 데코레이터는 신호와 연결될 함수를 정의하고 그 신호를 처리할 함수를 연결하는 역할을 함으로써 연결된 함수가 신호 발생시마다 호출된다.</p>
<p>Post 모델의 객체가 데이터베이스에 저장되기 전에 pre_save의 신호를 호출하여 def update_content_length()함수를 호출해 실행한다. 그 후에 알아서 저장된다.</p>
<p>따라서 이 함수는 model.py안에 해달 클라스 모델안에 넣어주거나, 많은 신호관련 로직이 있다면 signal.py를 따로 생성해 모아주는게 답이지만, 현재로서는 큰 프로젝트를 진행하는것이 아니니 model.py 해당 클래스안에 적어주는게 옳다고 본다.</p>
<blockquote>
<p>드디어! 이제 RelatedManger 주요 메서드를 설명해주겠다!</p>
</blockquote>
<ol>
<li>filter(): 여러개의 데이터를 뽑아오고 싶을때 쓴다</li>
</ol>
<p>특정 조건에 맞는 포스트들만 필터링합니다.</p>
<pre><code>user = User.objects.get(id=1)
posts_from_2023 = user.posts.filter(publish_date__year=2023)
</code></pre><p>여기서 쿼리체이닝이라는것을 필터 매니저로 보여주면 :</p>
<pre><code># 2023년에 발행된 포스트 중에서, &quot;Django&quot;라는 단어를 제목에 포함하는 포스트만 필터링
specific_posts = Post.objects.filter(published_date__year=2023).filter(title__contains=&quot;Django&quot;)
</code></pre><ol start="2">
<li>exclude(): 
특정 조건을 제외한 포스트들만 필터링할 때 사용한다.</li>
</ol>
<pre><code>user = User.objects.get(id=1)
posts_not_from_2023 = user.posts.exclude(publish_date__year=2023)
</code></pre><ol start="3">
<li>annotate():
각 포스트에 대한 추가 정보를 주기 위해 주석을 달 때 사용
즉, 주석이라해서 내 주석이아닌 장고 ORM의 주석이라보면된다.</li>
</ol>
<p>각 레코드에 대한 추가정보를 &quot;주석&quot;으로 추가하는 경우에 사용되는것이다. </p>
<p>즉, 추가로 필드를 넣어주는 거라고 보면된다.
그렇지만 이 것이 실제로 모델에 영구적으로 추가된다거나 데이터베이스에 반영되는 것은아니다.
그저 임시적으로 추가정보를 제공하기위해 QuertSet에 메타 데이터를 첨부하는것이다.
QuerySet의 생애 주기 동안만 존재하고, 평가된 후에는 정보는 사라진다.</p>
<p>여기서 num_comments 라는 임시 필드를 주석으로 처리하고, Count()라는 집계함수를 넣어 해당 모델의 comments 필드의 갯수를 센것!</p>
<pre><code>from django.db.models import Count
user = User.objects.get(id=1)
posts_with_comment_count = user.posts.annotate(num_comments=Count(&#39;comments&#39;))
</code></pre><p>결과값 출력:</p>
<pre><code>for post in posts_with_comment_count:
    print(post.title, post.num_comments)
</code></pre><blockquote>
<p>여기서 잠시 메타 데이터는 뭔가?</p>
</blockquote>
<p>&quot;메타 데이터&quot; ==&gt; &quot;데이터에 대한 데이터&quot;라는 의미
기본 데이터의 구조, 성격, 특성, 방식 등에 대한 정보를 제공하는 데이터</p>
<p>예) 디지털 사진 파일을 생각해보면:</p>
<p>기본 데이터: 사진의 픽셀, 색상, 이미지 자체의 내용
메타 데이터: 촬영 날짜, 카메라 모델, GPS 위치, ISO 설정, 셔터 속도 등</p>
<p>즉, 메타 데이터는 주 데이터의 컨텍스트나 특성을 설명하는 추가 정보를 제공하는 역할을 한다.</p>
<p>모델에서도 많이 사용하지 &gt; class Meta :</p>
<p>4.aggregate():
포스트의 그룹에 대한 정보를 집계해준다. </p>
<p>밑의 함수는 현재 특정사용자의 집합을 모아서 length(문자수)라는 post모델의 필드를 가르키고 집계함수의 Avg()를 불러와 평균값을 계산해주고있다.</p>
<pre><code>from django.db.models import Avg
user = User.objects.get(id=1)
average_post_length = user.posts.aggregate(Avg(&#39;length&#39;))
</code></pre><p>출력값을 가지고 나오려할때 여기서 length__avg는 키다.
자동적으로 원래필드이름과 집계함수의 이름(소문자)과 연결하여 생성된다.
Sum(&#39;length&#39;)라면 length__sum이 될것.</p>
<pre><code>avg_length = average_post_length[&#39;length__avg&#39;]
print(avg_length)
</code></pre><p>5.count():
포스트의 수를 카운트! &gt; 이게 바로 전에 템플릿에서 우리가 사용했던 하나의 매니저다!</p>
<pre><code>user = User.objects.get(id=1)
number_of_posts = user.posts.count()
</code></pre><ol start="6">
<li>distinct():
중복되지 않은 값을 가진 포스트만 반환한다.</li>
</ol>
<p>다시말하자면, 만약 한 사용자가 여러 포스트를 다양한 카테고리에 작성시,
같은 카테고리에 여러 포스팅을 했을 수 있잖아?
그럼 같은 카테고리가 같은게 여러게 나올꺼아니야? 
중복값을 없애고 카테고리를 단 한번씩만 나타내는 리스트를 반환하는것을 말하는것!</p>
<pre><code>user = User.objects.get(id=1)
unique_post_categories = user.posts.values(&#39;category&#39;).distinct()
</code></pre><blockquote>
<p>여기서 <strong>year는 뭐냐하면 바로 lookuptype이라고 하는 장고ORM에서 사용하는 기능이다.
이것은 데이터베이스 쿼리 작성시 필드값에 대한 특정조건을 나타내기 위해서 사용되어지는데 : Lookup type은 필드 이름과 이중 밑줄(__</strong><strong>)</strong> 뒤에 나오며, 사용하려는 조건에 따라 다양한 lookup type이 있다.</p>
</blockquote>
<blockquote>
<p>자주사용하는 것을 예시로 들어주겠다.</p>
</blockquote>
<ol>
<li>exact:
정확하게 일치하는 값을 찾습니다.<pre><code>entries = Entry.objects.filter(title__exact=&quot;Hello World&quot;)
</code></pre></li>
</ol>
<pre><code>2. iexact:
대소문자를 구분하지 않고 정확하게 일치하는 값을 찾습니다.</code></pre><p>entries = Entry.objects.filter(title__exact=&quot;Hello World&quot;)</p>
<pre><code>3. contains:
필드 값 내에 지정된 부분 문자열이 포함되어 있는지 찾습니다.
</code></pre><p>entries = Entry.objects.filter(title__contains=&quot;Hello&quot;)</p>
<pre><code>4.icontains:
대소문자를 구분하지 않고 필드 값 내에 지정된 부분 문자열이 포함되어 있는지 찾습니다.</code></pre><p>entries = Entry.objects.filter(title__icontains=&quot;hello&quot;)</p>
<pre><code>5.gt, gte, lt, lte:
필드 값이 지정된 값보다 큰, 크거나 같은, 작은, 작거나 같은지를 찾습니다.

gt (Greater Than):필드의 값이 지정한 값보다 큰 레코드를 필터링
gte (Greater Than or Equal to): 필드의 값이 지정한 값보다 크거나 같은 레코드를 필터링
lt (Less Than): 필드의 값이 지정한 값보다 작은 레코드를 필터링
lte (Less Than or Equal to): 필드의 값이 지정한 값보다 작거나 같은 레코드를 필터링
</code></pre><p>entries_from_past = Entry.objects.filter(pub_date__lte=date.today())
#date.today()값보다 작거나 같은 레코드를 불러온다.</p>
<pre><code>6.year, month, day:
DateField 또는 DateTimeField의 연도, 월, 일을 기준으로 필터링합니다.
</code></pre><p>entries_from_2023 = Entry.objects.filter(pub_date__year=2023)</p>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[QuerySet vs QueryDict 차이점]]></title>
            <link>https://velog.io/@stella_k/QuerySet-vs-QueryDict-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EA%B7%B8%EB%A6%AC%EA%B3%A0-RelatedManager%EB%9E%80Queryset%EB%A9%94%EC%86%8C%EB%93%9C%EB%93%A4filterexcludeannotateaggregatecountdistinct</link>
            <guid>https://velog.io/@stella_k/QuerySet-vs-QueryDict-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EA%B7%B8%EB%A6%AC%EA%B3%A0-RelatedManager%EB%9E%80Queryset%EB%A9%94%EC%86%8C%EB%93%9C%EB%93%A4filterexcludeannotateaggregatecountdistinct</guid>
            <pubDate>Fri, 22 Sep 2023 12:37:35 GMT</pubDate>
            <description><![CDATA[<p>먼저 QuerySet vs QueryDict 의 차이점부터 알아보려한다.</p>
<p>이 두가지는 장고에서 제공하는 중요한 클래스로, 다른 목적과 특성을 가지고 있다.</p>
<h3 id="queryset"><a href="QuerySet:">QuerySet:</a></h3>
<p><strong>목적:</strong> 
-데이터베이스 쿼리의 결과를 나타내는 객체
-주로 Django ORM을 사용하여 데이터베이스에서 데이터를 조회할 때 사용한다. 
    예)all_posts = Post.objects.all()
        =&gt; QuerySet 반환
즉, ORM을 사용하는 코드(Objects)는 다 queryset을 반환한다고 보면된다.</p>
<p><strong>특성:</strong></p>
<ol>
<li><p>QuerySet은 게으른 평가(lazy evaluation)를 사용한다고 말하는데, 이 의미는 바로 데이터를 가져오는 것이 아니라 QuerySet을 가져오고 그것을 루프나 특정 메소드를 실행하였을때 실제 데이터에 접근하여 데이터를 가져온다는 의미입니다. 즉, 필요한 순간까지 데이터베이스 쿼리의 실행을 연기하는 방식으로 생가가면 될것 같습니다.</p>
</li>
<li><p>filter(), exclude(), annotate() 등 다양한 메서드를 제공하여 데이터베이스 쿼리를 체인(chain)으로 연결할 수 있다. 
=&gt; 이 체인이라는 의미는 그냥 추가로 덧붙혀 필요한 쿼리를 만들어 낸다는 의미로 최종적으로 원하는 조건의 데이터만 불러올 수 있게 한다는 의미로 받아들이면 편할 것 같습니다.</p>
</li>
<li><p>QuerySet은 내부적으로 캐시기능을 지원합니다. 즉, 이미 불러온 결과는 객체 내 캐시되어 캐시된 queryset객체에 다시 접근 = 동일한 쿼리 결과를 재사용하려할때 이미 캐시된 결과를 재사용하는 것입니다</p>
</li>
<li><p>데이터베이스의 모델 레코드(인스턴스)들의 리스트와 유사하게 동작합니다.
Queryset은 리스트라 칭하기는 어렵지만 리스트와 같은 형태의 객체로 그 모델들의 인스턴스의 집합은 queryset의 형태로 반환되니 그리 칭하는것.</p>
</li>
</ol>
<h4 id="예시를-들어보겠습니다"><em>예시를 들어보겠습니다.</em></h4>
<p>먼저 쿼리셋을 생성하기위해 모델 인스턴스를 만들건데요</p>
<pre><code>posts = Post.objects.filter(published_date__year=2023).exclude(title__contains=&quot;Draft&quot;).order_by(&#39;-published_date&#39;)
</code></pre><p>여기서 이것을 불러왔다고 하여 실제로 데이터가 불러온것이아니라,쿼리셋의 상태로만 되어있습니다.</p>
<p>여기서 </p>
<p>QuerySet을 반복할 때 (예: for post in posts:)
QuerySet의 길이를 조회할 때 (예: len(posts))
QuerySet을 명시적으로 평가할 때 (예: list(posts))
QuerySet의 특정 아이템에 접근할 때 (예: posts[0])</p>
<p>와 같이 어떠한 메소드들이나 루프를 통해서 접근하므로써 실행이 되어,
데이터를 불러온는 것이라고 설명할 수 있겠습니다.</p>
<pre><code>posts = Post.objects.all()  
# 이 시점에서는 아직 데이터베이스에 쿼리를 실행하지 않았다.

for post in posts:  
# 여기에서 QuerySet이 평가되고, =&gt; late evaluation
# 데이터베이스에 쿼리가 실행됩니다. =&gt; 데이터 가져오는것
    print(post.title)

first_post = posts[0] 
# 인덱싱을 사용하여 특정 레코드에 접근할 수 있습니다.
</code></pre><p>&lt;슬라이싱 사용해서 해당 부분만 가져오기&gt;</p>
<pre><code>recent_posts = Post.objects.all()[:5]  # 처음 5개의 포스트만 가져옵니다.</code></pre><p>&lt;명시적으로 쿼리셋 리스트로 변환&gt;</p>
<pre><code>posts_list = list(Post.objects.all())
</code></pre><h3 id="querydict"><a href="QueryDict:">QueryDict:</a></h3>
<p><strong>목적:</strong></p>
<p>QueryDict는 HTTP 요청에서 전달된** GET 또는 POST 데이터를 처리<strong>하기 위한 **특수한 딕셔너리</strong>입니다.</p>
<p><strong>특성:</strong>
HTTP 요청에서 하나의 키에 여러 개의 값을 가질 수 있습니다 (예: ?key=value1&amp;key=value2). <strong>=&gt; MultiValueDict()</strong></p>
<p>QueryDict는 이러한 여러 값을 처리할 수 있습니다.</p>
<p>QueryDict는 immutable(불변)로 기본 설정되어 있습니다. 
값을 변경하려면 copy() 메서드를 사용하여 mutable(변경 가능한) 복사본을 만들어야 합니다.</p>
<p>get(), getlist(), setdefault() 등의 특수한 메서드를 제공하여 여러 값을 처리할 수 있습니다.</p>
<p>주로 request.GET 또는 request.POST를 통해 접근합니다.</p>
<p>예로 저번 포스팅에 사용된 것을 그대로 불러와서 다시 사용해 설명해 보겠습니다.</p>
<pre><code>post = Post.objects.get(id = post_id) 
# QueryDict 반환
post_data = request.POST.copy()
# request.POST는 QueryDict 타입으로 immutable(불변)이므로 복사해서 사용

post_data[&#39;post_id&#39;] = post.id
post_data[&#39;text&#39;] = &quot;추가적으로 넣고 싶은 값&quot;
#이렇게 직접적으로 불러와 변경 

image_form = ImageForm(data=post_data, files=request.FILES)</code></pre><p>음 좀더 자세히 설명하면</p>
<p>*<em>1.HTTP요청에서 QueryDict사용 *</em></p>
<p>url 요청 :</p>
<pre><code>http://example.com/
?name=John&amp;hobby=reading&amp;hobby=traveling 
#이 밑에 줄에 있는게 넘어간다 request 요청으로 </code></pre><p>request.GET으로 받아와서 </p>
<pre><code>def example_view(request):
    names = request.GET.get(&#39;name&#39;) 
    # &#39;John&#39;
    hobbies = request.GET.getlist(&#39;hobby&#39;) 
    # [&#39;reading&#39;, &#39;traveling&#39;] 값을 반환하겠지
    ...
</code></pre><p><strong>2. QueryDict 직접 생성하기</strong></p>
<pre><code>from django.http import QueryDict

data = QueryDict(&#39;name=John&amp;hobby=reading&amp;hobby=traveling&#39;)
name = data.get(&#39;name&#39;)  # &#39;John&#39;
hobbies = data.getlist(&#39;hobby&#39;)  # [&#39;reading&#39;, &#39;traveling&#39;]
</code></pre><p>쿼리딕을 불러와서 직접 쿼리값을 전달해주는것.</p>
<p><strong>3. QuertDict는 불변이다.</strong> </p>
<p>이 값은 딕셔너리 같은 객체이지 딕셔너리는 아닙니다.
안전하고 실수로 원본요청 데이터의 변경을 방지하기 위한 특성입니다. </p>
<pre><code>mutable_data = data.copy()
mutable_data[&#39;name&#39;] = &#39;Jane&#39;
</code></pre><p>그렇기에 한 키에 대한 여러값을 처리할 수 있는 기능을 제공하면서, 
기본적으로 불변성을 가집니다.</p>
<h3 id="요약">&lt;요약&gt;</h3>
<p>한마디로</p>
<p>   *<em>Queryset: *</em>
   장고의 ORM을 사용할때 반환되는 값의 형태로, 리스트의 형태로 받아오지만 리스트는 아니고, 바로 데이터를 가져오지않고 어떠한 루프나 메소드로 직접 실행되어야 데이터값을 가져오는 느린평가의 기능을 가지고 있고, 캐시기능이 내장되어 성능저하없이 효율적으로 작동.</p>
<pre><code> from your_app.models import YourModel
 queryset = YourModel.objects.all()
 queryset

==&gt; 값 : &lt;QuerySet [&lt;YourModel: ModelObject1&gt;, &lt;YourModel: ModelObject2&gt;, ...]&gt;</code></pre><p> *<em>QuertDict: *</em>
    딕셔너리형태와 비슷하지만 딕셔너리는 아니고, 불변성을 가지며, 한 키에 여러값을 넣을 수 있는 MultiValueDict()로, 주로 HTTP요청처리할때 사용한다.</p>
<pre><code>직접 쿼리딕을 객체생성시

from django.http import QueryDict
q = QueryDict(&#39;name=John&amp;hobby=reading&amp;hobby=traveling&#39;)
print(q)

==&gt; 값 : &lt;QueryDict: {&#39;name&#39;: [&#39;John&#39;], &#39;hobby&#39;: [&#39;reading&#39;, &#39;traveling&#39;]}&gt;

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[정참조와 역참조 (FK로서의 예시와 ManytoManyField의 예시) 그리고 모델 클라스 사용법 설명  및 관련 MultiValueDict과 언패킹 관련 요약.]]></title>
            <link>https://velog.io/@stella_k/%EC%A0%95%EC%B0%B8%EC%A1%B0%EC%99%80-%EC%97%AD%EC%B0%B8%EC%A1%B0-FK%EB%A1%9C%EC%84%9C%EC%9D%98-%EC%98%88%EC%8B%9C%EC%99%80-ManytoManyField%EC%9D%98-%EC%98%88%EC%8B%9C-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EB%AA%A8%EB%8D%B8-%ED%81%B4%EB%9D%BC%EC%8A%A4-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%84%A4%EB%AA%85-%EB%B0%8F-%EA%B4%80%EB%A0%A8-MultiValueDict%EA%B3%BC-%EC%96%B8%ED%8C%A8%ED%82%B9-%EA%B4%80%EB%A0%A8-%EC%9A%94%EC%95%BD</link>
            <guid>https://velog.io/@stella_k/%EC%A0%95%EC%B0%B8%EC%A1%B0%EC%99%80-%EC%97%AD%EC%B0%B8%EC%A1%B0-FK%EB%A1%9C%EC%84%9C%EC%9D%98-%EC%98%88%EC%8B%9C%EC%99%80-ManytoManyField%EC%9D%98-%EC%98%88%EC%8B%9C-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EB%AA%A8%EB%8D%B8-%ED%81%B4%EB%9D%BC%EC%8A%A4-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%84%A4%EB%AA%85-%EB%B0%8F-%EA%B4%80%EB%A0%A8-MultiValueDict%EA%B3%BC-%EC%96%B8%ED%8C%A8%ED%82%B9-%EA%B4%80%EB%A0%A8-%EC%9A%94%EC%95%BD</guid>
            <pubDate>Thu, 21 Sep 2023 07:09:21 GMT</pubDate>
            <description><![CDATA[<p>정참조(Forward Relation)와 역참조(Reverse Relation)는 관계형 데이터베이스 모델링에서 주로 사용되는 용어이다.</p>
<p>FK 나 OneToOneField 그리고 ManyToManyField를 사용할때 사용되어지는 말이라고 볼 수 있다.</p>
<p>즉, 관계의 정의를 위한 용어인것.</p>
<blockquote>
<p><strong>그럼 정참조는 무엇인가?</strong></p>
</blockquote>
<p><strong>정참조</strong>란 모델 필드에서 다른 모델을 직접 참조하는 것을 의미한다.</p>
<pre><code>class User(AbstractUser):
    class Meta :
        db_table = &quot;user_list&quot;

    follow = models.ManyToManyField(
        &#39;self&#39;, #내 자신을 연결 User

        symmetrical=False, 
        #서로 동일하지 않게 해주겠다 
        #=&gt; 서로 팔로워가 되어있지않아도 ㄱㅊ아요.

        through = &quot;Follow&quot;, #중간 모델 직접정의
        related_name=&#39;follower&#39;,#역참조
        )


class Post(models.Model):
    class Meta:
        db_table = &quot;post_list&quot;

    author = models.ForeignKey(
        &quot;users.User&quot;,
        verbose_name=&quot;글쓴이&quot;,
        null=True,  # on_delete해주기위해
        related_name=&quot;authors&quot;,
        on_delete=models.SET_NULL,
    )
    title = models.CharField(&quot;제목&quot;, max_length=50)
    content = models.TextField(&quot;내용&quot;, max_length=8000)
    comment = models.ManyToManyField(
        &quot;users.User&quot;, related_name=&quot;comment&quot;, through=&quot;Comments&quot;
    )
</code></pre><p>위에의 Post모델에서는 User모델을 ForeignKey로, Comment모델을 ManyToMany필드로 불러내어 참조하는 것을 볼 수 있는데, 이렇게 모델안에 FK가 있거나 OneToOne, 그리고 ManyToMany가 직접 사용되어 참조할 때는 정참조라고 한다.</p>
<h4 id="그럼-만약-이-포스트를-작성한-글쓴이는-누구인가-를-찾고-싶을때">그럼 만약 이 포스트를 작성한 글쓴이는 누구인가? 를 찾고 싶을때:</h4>
<p>*<em>post = post.object.get(post_id = post_id)
post.author.Fullname *</em></p>
<blockquote>
<p>여기서 하나 추가로 말씀드리고 싶은것은, 
매개변수의 형태에 _id를 붙혀주면 따로 객체를 생성할 필요가 없이 
다이렉트로 연결 시킬수 있다는 것이다.</p>
</blockquote>
<p><strong>id가 1번인 &quot;글쓴이&quot;가 쓴 모든 포스트를 불러와줘</strong> </p>
<p><strong>&lt;case.1&gt;</strong></p>
<blockquote>
<p>auth = User.objects.get(id=1)
Post.objects.filter(author = auth) : author는 User모델의 객체이름</p>
</blockquote>
<p><strong>&lt;case.2&gt;</strong>
<strong>다른 author_id의 명시적 방법을 통해 부러주는것.</strong></p>
<blockquote>
<p>Post.object.filter(author_id = 1)</p>
</blockquote>
<p>이렇게 바로 불러줄 수 있다.</p>
<hr>
<blockquote>
<p><strong>그럼 역참조는 무엇인가?</strong></p>
</blockquote>
<blockquote>
<p>역 관계 :oreignKey나 OneToOneField, ManyToManyField와 같은 관계 필드를 정의할 때 자동으로 생성되는 관계로</p>
</blockquote>
<p>역참조는 반대로 위의 예를 똑같이 말하면, User에서 반대로 Post를 불러올때를 말한다고 설명할 수 있다.</p>
<p>이러한 역관계의 생성시 <strong>relate name 이 없으면</strong> 기본적으로 
=&gt; <strong>소문자로 클라스이름변경 _set</strong> : follow_set
역관계 이름이 생성되어진다. </p>
<p><strong>related_name 을 활용</strong>하면 이름을 바꿔서 쉽게 명칭을 정해
모델의 각 데이터를 불러 올 수 있다.
=&gt; 인스턴스이름, 모델이름.related_name.all()</p>
<p>예) user1_following = user1.followers.all()</p>
<h4 id="그럼-만약-유저에서-특정-사용자가-작성한-포스팅들을-보여줘">그럼 만약 유저에서 특정 사용자가 작성한 포스팅들을 보여줘?</h4>
<p>user = User.objects.get(id=1)</p>
<p><strong>related_name없으면 :</strong> 자동으로 소문자 변경하고 _set붙혀  =&gt; 
user.post_set.all()</p>
<p>*<em>related_name이 있으면 : *</em>
posts_by_user = user.authors.all()</p>
<p>즉, user모델에서 부터 Post모델에 있는 author필드를 통해 연결되어진 User모델이기때문에 Post이 모든 필드에 접근이 가능한것</p>
<p>그럼 여기서 manytomany필드에서느 어떻게 참조되는 것인가? 그리고 왜 self를 매개변수로 넣어주는 것인가에 대해서 설명!</p>
<pre><code> follow = models.ManyToManyField(
        &#39;self&#39;, #내 자신을 연결 User

        symmetrical=False, 
        #서로 동일하지 않게 해주겠다 
        #=&gt; 서로 팔로워가 되어있지않아도 ㄱㅊ아요.

        through = &quot;Follow&quot;, #중간 모델 직접정의
        related_name=&#39;follower&#39;,#역참조
        )</code></pre><p>이 파트에서의 self는 말 그대로 내 자신을 연결, 즉, 현 User모델을 참조하는 다른 객체를 생성하라는 말로 </p>
<pre><code>class Follow(models.Model):
    class Meta:
        db_table = &quot;follow_list&quot;

    follower = models.ForeignKey(User, related_name=&quot;followers&quot;,on_delete=models.CASCADE)
    followee = models.ForeignKey(User,related_name=&quot;followees&quot;, on_delete=models.CASCADE)
    followed_at =models.DateTimeField(&quot;팔로워한날&quot;,auto_now_add=True)
</code></pre><p>자기자신을 manytomany로 연결해서 두개의 fk가 유저에서 나오는것을 볼 수 있다!</p>
<p>현재는 through로 인해 따로 중간 브릿지 모델을 직접생성해주는것을 볼 수 있는데 이때는 그럼 어느것을 참조하여 역참조와 정참조가 진행이 되나?</p>
<p>** through가 없을떄는  &quot;[app_label]<em>[model_name]</em>[field_name]&quot;
형태로 이름이 생성
예) like = models.ManyToManyField(settings.AUTH_USER_MODEL, verbose_name=&#39;좋아요&#39;, related_name=&#39;likes&#39;)  가 User 앱 안의 Post모델에 들어가 있는 필드명이라면</p>
<p>User_Post_like 가 된다.</p>
<ol>
<li>정참조</li>
</ol>
<p>사용자가 다른 사용자를 친구로 추가할때</p>
<pre><code>user1 = User.objects.get(id=1)
user2 = User.objects.get(id=2)
</code></pre><ol start="2">
<li>역참조</li>
</ol>
<p>특정 사용자에게 친구 요청을 보낸 모든 사용자를 찾을때 :</p>
<pre><code>follow_requested = user1.followers.all()
</code></pre><p>특정 사용자가 친구요청을 보낸 모든 사용자를 찾을때 :</p>
<pre><code>sent_follow_request = user2.followees.all()
</code></pre><blockquote>
<h3 id="추가-설명--모델-인스턴스-생성관련-설명">추가 설명 : 모델 인스턴스 생성관련 설명</h3>
</blockquote>
<pre><code>from django.db import models

class User(AbstractUser):
    class Meta :
        db_table = &quot;user_list&quot;

    follow = models.ManyToManyField(
        &#39;self&#39;, #내 자신을 연결 User

        symmetrical=False, 
        #서로 동일하지 않게 해주겠다 
        #=&gt; 서로 팔로워가 되어있지않아도 ㄱㅊ아요.

        through = &quot;Follow&quot;, #중간 모델 직접정의
        related_name=&#39;follower&#39;,#역참조
        )

class Follow(models.Model):
    class Meta:
        db_table = &quot;follow_list&quot;

    follower = models.ForeignKey(User, related_name=&quot;followers&quot;,on_delete=models.CASCADE)
    followee = models.ForeignKey(User,related_name=&quot;followees&quot;, on_delete=models.CASCADE)
    followed_at =models.DateTimeField(&quot;팔로워한날&quot;,auto_now_add=True)
</code></pre><p>인스턴스 생성 후에 저장해줘야 디비에 저장이되는데,</p>
<pre><code>friendship = Follow(followers=user1, followees=user2, since=date.today())
friendship.save()
</code></pre><p>왜 모델을 부르는데 매개변수의 값이 들어가나 떠올렸을때 
(폼에서는 직접 ({}, {}) 딕셔너리 형태로 인자를 따로따로 넣어줬음 :  이부분은 밑에서 다시 설명하겠다.)</p>
<p>import해서 불러와 모델링 할때 사용하는 models.Model기억하나?</p>
<p>그안에 내부적으로 &#39;<strong><strong>init</strong></strong>&#39;이라는 메서드를 가리고 있는데, 사용자가 명시적으로 모델 클라스안에 <strong><strong>init</strong></strong>을 만들어주지 않아도 장고에서 제공하는 Model로 인해 해당 메서드는 구현이 되어있다.</p>
<p>그렇기에 인자로 값을 바로 
=&gt;Friendship(from_user=user1, to_user=user2, since=date.today()) </p>
<p>이렇게 넣어줄 시에 바로 init메서드는 자동으로 호출되면서 초기화 작업을 수행하게 되는것! =&gt; 직접 할당해주게 되는것</p>
<p>그리고나서 save!!!시켜주면! 저장된다.</p>
<p>이 방법이 아닌 
만약 딕셔너리 방식으로 수행해주고 싶다면,</p>
<pre><code>data = {
    &#39;from_user&#39;: user1,
    &#39;to_user&#39;: user2,
    &#39;since&#39;: date.today()
}

friendship = Friendship(**data)
</code></pre><p>이런식으로 해주면 되는데, 이때 꼭 <strong>** double asterisks 를 해줘서 언패킹으로 접근해줘야한다. (딕셔너리기때문에 &#39;**</strong>&#39;언패킹 연산자  사용해줘야해)-----&gt; 밑에서 추가로 설명하겠다.</p>
<p>아까 위에 폼에서는 인자 두개를 딕셔너리 형태로 넣어줬었는데? 뭐가 다른가 하면 폼자체에서는 매개변수의 인자를 2개 받을 수 있게되어 있는 구조이다.</p>
<p>그 이전 프로젝트 했을때 imageform을 생성해줬었는데 </p>
<pre><code>image_form = ImageForm({&quot;post&quot;: post.id}, {&quot;image&quot;: img_file})
</code></pre><h3 id="form-의-주요-인자-2개">&lt;form 의 주요 인자 2개&gt;</h3>
<p>이렇게 넣을 수 있는 이유는 폼은 두개의 주요인자를 받는데 :</p>
<h4 id="1-data">1. data:</h4>
<blockquote>
<p>폼의 일반 필드에 바인딩 될 데이터를 담고 있는 딕셔너리 또는 유사한 객체로서, 대체로 request.POST에서 가져오게된다.</p>
</blockquote>
<p>일반필드란 그냥 char,text,integer....등등의 필드를 말한다</p>
<h4 id="2-files">2. files:</h4>
<blockquote>
<p>files 인자는 파일 업로드를 처리하기 위한 데이터를 담고 있는 딕셔너리 또는 유사한 객체로, 주로 request.FILES에서 가져옵니다.</p>
</blockquote>
<p>한마디로 이미지파일 및.. file 형태의 것들을 가져오는것.</p>
<p>==================================================</p>
<h4 id="사용예">사용예</h4>
<p>일반적으로 request의 전체 값을 넣어주는게 보편적이다.</p>
<pre><code>image_form = ImageForm(data=request.POST, files=request.FILES, 옵션들...)
</code></pre><p>특정 필드만 전달하고 싶다하면 아까처럼 </p>
<h4 id="1-딕셔너리-방식으로-인자를-넣어주던지">1. 딕셔너리 방식으로 인자를 넣어주던지,</h4>
<p>여기서는 예를 더 들어 text값을 더 넣어준것으로 하면</p>
<pre><code>post = PostForm(request.POST, request.FILES)
#여기선 이미 폼안에 id값이 저장되어있다는 가정.
image_form = ImageForm({&quot;post&quot;: post.id,&quot;text&quot; : &quot;mo&quot;}, {&quot;image&quot;: img_file})
</code></pre><p>이렇게 되는것이다.</p>
<h4 id="2-request-값들-복사해서-사용하기">2. request. 값들 복사해서 사용하기</h4>
<pre><code>post = Post.objects.get(id = post_id)
post_data = request.POST.copy()
# request.POST는 QueryDict 타입으로 immutable(불변)이므로 복사해서 사용

post_data[&#39;post_id&#39;] = post.id
post_data[&#39;text&#39;] = &quot;추가적으로 넣고 싶은 값&quot;

image_form = ImageForm(data=post_data, files=request.FILES)
</code></pre><p>이렇게 넣어주게 되는데... 사실 그냥 딕셔너리방식으로 넣어주는게 가장 깔끔한거같다.</p>
<blockquote>
<p><strong>MultiValueDict은 ????</strong></p>
</blockquote>
<p>여기서 request.Post 및 request.FILES 은 MultiValueDict의 인스턴스로 위에 언급했다 싶이 querydict이 형태로 불변이다.</p>
<p>MultiValueDict은 Django에서 리스트 형태의 여러 값들을 하나의 키에 대응하여 저장하고 처리할 수 있도록 도와주는 딕셔너리와 유사한 데이터 구조이다.</p>
<p>특정한 키에 대해 여러개의 값을 가진 데이터를 수동으로 만들어 주고 싶을때 
<em>MultiValueDict*</em>을 사용하는데...
현재는 사용자한테 값을 받아 사용하므로 필요가 없는 기능이다. </p>
<p>그치만 예를 보여주겠다.</p>
<pre><code>from django.utils.datastructures import MultiValueDict

data = MultiValueDict({
    &#39;key1&#39;: [&#39;value1&#39;],
    &#39;key2&#39;: [&#39;value2a&#39;, &#39;value2b&#39;]
})
</code></pre><p>이렇게 위에 utils에서 기능을 받아와야하고</p>
<p>그리고 data라는 인스턴스를 만들고 MultiValueDict()의 형태로 딕셔너리 값을 넣어주되, 한개의 키 값이 여러개의 값을 넣어줄수 있는 리스트 형태로 들어갈수 있다.</p>
<blockquote>
<h3 id="언패킹unpacking---or-">언패킹(unpacking) : * or **</h3>
</blockquote>
<p>python에서 지원하는 언패킹 연산자 2개이다.</p>
<h4 id="--리스트나-튜플과-같은-순차적-데이터-타입의-요소들을-언패킹"><strong>* : 리스트나 튜플과 같은 순차적 데이터 타입의 요소들을 언패킹</strong></h4>
<p>먼저 리스트의 구조 및 활용을 잠시 보면</p>
<pre><code>numbers = [1, 2, 3]
a, b, c = numbers
print(a)  # 출력: 1
print(b)  # 출력: 2
print(c)  # 출력: 3
</code></pre><p>이렇게 순서에 따라 변수를 적어주고 리스트를 대입하면 그 순서에 맞춰 값이 대입되는 것을 볼 수 있다.</p>
<p><strong>&lt;일단 언패킹 * 사용할때를 예로 들면&gt;</strong></p>
<pre><code>def func(a, b, c):
    return a + b + c

args = [1, 2, 3]
result = func(*args)  # 같은 효과: func(1, 2, 3)
print(result)  # 출력: 6
</code></pre><p><em><strong>리스트 형식의 arguments가 있고 그것을 매개변수로 넣어줄때 그 []를 풀어주는 역할을 하는것이 &#39;*&#39;연산자이다.</strong></em></p>
<h4 id="--딕셔너리와-같은-매핑-데이터-타입의-키-값-쌍을-언패킹">&lt;** : 딕셔너리와 같은 매핑 데이터 타입의 키-값 쌍을 언패킹&gt;</h4>
<pre><code>def func(a=0, b=0):
    return a + b

args = {&#39;a&#39;: 1, &#39;b&#39;: 2}
result = func(**args)  # 같은 효과: func(a=1, b=2)
print(result)  # 출력: 3
</code></pre><p>매개변수를 대입식으로 값을 직접 지정하여 디폴트로 메소드에 넣어줬다.</p>
<p>이럴때는 당연히 딕셔너리 타입을 사용할 수 밖에 없고,
arguments의 값은 딕셔너리의 타입으로 받아 fuc()안에 넣어지게 되는것이다.</p>
<h4 id="여기서-연산자를-사용해야-딕셔너리-가-사라져-언패킹되어-나온다고-생각하면-편할것이다">여기서 &#39;**&#39;연산자를 사용해야 딕셔너리 {}가 사라져 언패킹되어 나온다고 생각하면 편할것이다.</h4>
<p>이 두 연산자는 주로 함수 인자를 전달할 때나 변수에 값을 할당할 때 사용됩니다.</p>
<p>C언어의 pointer같다고 생각하면서도 다른점은 ***와 같은 연산자는 Python의 현재 버전에서는 정의되어 있지 않다는것. </p>
<blockquote>
<p>즉 타입에 따라 주 사용 연산자가 정해져 있다는 것을 기억해 주면 된다!
&#39;<em>&#39; :  리스트 형태의 정열
&#39;*</em>&#39;: 딕셔너리 형태</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[장고 첫번째 팀프로젝트 :: 게시판 CRUD관련 코드 리뷰 - 상세페이지에 댓글 생성 삭제 및 편집/ 게시물 좋아요/ 댓글 좋아요]]></title>
            <link>https://velog.io/@stella_k/%EC%9E%A5%EA%B3%A0-%EC%B2%AB%EB%B2%88%EC%A7%B8-%ED%8C%80%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B2%8C%EC%8B%9C%ED%8C%90-CRUD%EA%B4%80%EB%A0%A8-%EC%BD%94%EB%93%9C-%EB%A6%AC%EB%B7%B0-%EC%83%81%EC%84%B8%ED%8E%98%EC%9D%B4%EC%A7%80%EC%97%90-%EB%8C%93%EA%B8%80-%EC%83%9D%EC%84%B1-%EC%82%AD%EC%A0%9C-%EB%B0%8F-%ED%8E%B8%EC%A7%91-%EA%B2%8C%EC%8B%9C%EB%AC%BC-%EC%A2%8B%EC%95%84%EC%9A%94-%EB%8C%93%EA%B8%80-%EC%A2%8B%EC%95%84%EC%9A%94</link>
            <guid>https://velog.io/@stella_k/%EC%9E%A5%EA%B3%A0-%EC%B2%AB%EB%B2%88%EC%A7%B8-%ED%8C%80%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B2%8C%EC%8B%9C%ED%8C%90-CRUD%EA%B4%80%EB%A0%A8-%EC%BD%94%EB%93%9C-%EB%A6%AC%EB%B7%B0-%EC%83%81%EC%84%B8%ED%8E%98%EC%9D%B4%EC%A7%80%EC%97%90-%EB%8C%93%EA%B8%80-%EC%83%9D%EC%84%B1-%EC%82%AD%EC%A0%9C-%EB%B0%8F-%ED%8E%B8%EC%A7%91-%EA%B2%8C%EC%8B%9C%EB%AC%BC-%EC%A2%8B%EC%95%84%EC%9A%94-%EB%8C%93%EA%B8%80-%EC%A2%8B%EC%95%84%EC%9A%94</guid>
            <pubDate>Wed, 20 Sep 2023 08:18:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>포스트 좋아요 모음 메소드 : 
    모든 좋아요를 다가져와서 templates에 보내줍니다.</p>
</blockquote>
<pre><code>                {% if post.liked_post.count &gt; 0 %}
                &lt;br&gt;
                &lt;br&gt;
                &lt;p&gt;추천: {{ post.liked_post.count }}&lt;/p&gt;
                &lt;div class=&quot;btn-group&quot;&gt;
                    &lt;a href=&quot;{% url &#39;post:like&#39; post.id %}&quot; class=&quot;btn btn-info&quot;&gt;추천인 명단&lt;/a&gt;
                &lt;/div&gt;
                {% else %}
                &lt;br&gt;
                &lt;p&gt;추천이 없습니다&lt;/p&gt;
                {% endif %}</code></pre><p>  .count는 내장된 장고의 기능으로 id를 세어저 반환해준다.
  그래서 모델안에 따로 COUNT관련 값을 넣어주지 않은것. =&gt; 충돌이 일어날 확률이 높아.</p>
<pre><code>def like_list(request, post_id):
    list = PostLike.objects.filter(post_id=post_id)
    return render(request, &quot;post/like_list.html&quot;, {&quot;list&quot;: list})</code></pre><blockquote>
<p>코멘트 좋아요 모음 메소드:</p>
</blockquote>
<p>위의 포스트 좋아요와 같은 방식으로 활용</p>
<pre><code>
def commentlike_list(request, comment_id):
    list = CommentLike.objects.filter(comment_id=comment_id)
    return render(request, &quot;post/like_list.html&quot;, {&quot;list&quot;: list})</code></pre><blockquote>
<p>코멘트 생성 메소드:</p>
</blockquote>
<pre><code>def detail(request, post_id):
    post = get_object_or_404(Post, id=post_id)
    comments = Comments.objects.filter(post_id=post_id)
    #댓글 모델에서 그 포스트에 있는 모든 댓글을 가져오게함

    if request.method == &quot;GET&quot;:
        form = CommentForm(request.POST)
        return render(
            request,
            &quot;post/detail.html&quot;,
            {
                &quot;post&quot;: post,
                &quot;comments&quot;: comments,
                &quot;form&quot;: form,
                &quot;MEDIA_URL&quot;: settings.MEDIA_URL,
            },
        )

    #포스트 
    else:
        #template안에 폼으로 요청하는 것들이 많아서 따로 NAME을 설정해서 받아주었다.
        #따로 Url설정하고 메소드 생성해서 하는것도 더 깔금 했을것

        #댓글 저장
        if request.method == &quot;POST&quot; and &quot;wow12&quot; in request.POST:
            if request.user.is_authenticated:
                form = CommentForm(request.POST)
                if form.is_valid:
                    comment = form.save(commit=False)
                    comment.author = request.user
                    comment.post = post
                    comment.save()
                    return redirect(&quot;post:detail&quot;, post_id=post.id)
                else:
                    form = CommentForm()
                    return render(request, &quot;post/detail.html&quot;, {&quot;form&quot;: form})
            else:
                return redirect(&quot;post:detail&quot;, post_id=post.id)

        #포스트 좋아요 저장
        if request.method == &quot;POST&quot; and &quot;wow11&quot; in request.POST:
            if request.user.is_authenticated:
                # 이미 좋아요 누른 게 있느지 확인해서 있으면 똑같은 페이지로 돌아가기
                if request.user in post.liked_post.all():
                    return redirect(&quot;post:detail&quot;, post_id=post.id) #중복방지                
                # 아니면 좋아요 눌러주는거 저장
                else:
                    like = PostLike.objects.create(user=request.user, post=post)
                    like.save()
                    return redirect(&quot;post:detail&quot;, post_id=post.id)
            else:
                return redirect(&quot;post:detail&quot;, post_id=post.id)

        #댓글 좋아요 저장
        if request.method == &quot;POST&quot; and &quot;wow13&quot; in request.POST:
            if request.user.is_authenticated:
                comment_id = request.POST.get(&quot;comment_id&quot;)
                comment = Comments.objects.get(id=comment_id)
                #이미 좋아요 누른사람은 그대로 돌아가
                if request.user in comment.liked_comments.all():
                    return redirect(&quot;post:detail&quot;, post_id=post.id)
                #아니면 좋아요 저장
                else:
                    commentlike = CommentLike.objects.create(
                        user=request.user, comment=comment
                    )
                    commentlike.save()
                    return redirect(&quot;post:detail&quot;, post_id=post.id)
            else:
                return redirect(&quot;post:detail&quot;, post_id=post.id)
</code></pre><blockquote>
<p>코멘트 수정 메소드:
똑같이 instance로 받아와서 저장</p>
</blockquote>
<pre><code>@login_required(login_url=&quot;login&quot;)
def comment_update(request, post_id, comment_id):
    post = get_object_or_404(Post, id=post_id)
    comments = Comments.objects.filter(post=post)
    comment = get_object_or_404(Comments, id=comment_id)

    #권한 설정
    if request.user != post.author:
        messages.error(request, &quot;수정권한이 없습니다&quot;)
        return redirect(reverse(&quot;post:detail&quot;, args=[post.id]))#kwargs={,}

    if request.method == &quot;POST&quot;:
        comment_form = CommentForm(request.POST, instance=comment)

        if comment_form.is_valid():
            form = comment_form.save(commit=False)
            form.author = request.user
            form.post = post

            #원래는 밑에 있는 comment가 스스로 저장이 되어야 하는데
            #데이터 베이스에 저장이 안되서 직접 일입력해줌.
            #아무래도 직접적인 post_id가 연결이 안되어서인건지 확실치 않지만,
            # 앞으로 데이터베이스가 저장안될때는 직접 넣어줘야한다
            form.comment = request.POST.get(&quot;content&quot;)
            form.save()

            #reverse(&quot;url&quot;, args= [], 또는 kwargs={딕셔너리형태=&gt; key: 인수})
            #reverse(&quot;post:detail&quot;, kwargs={}
            return redirect(reverse(&quot;post:detail&quot;, args=[post.id]))

        else:
            comment_form = CommentForm(instance=comment)
            return HttpResponse(&quot;Invalid comment_form&quot;, status=405)


    #해당 글의 자신이 쓴 댓글에만 수정버튼을 달게하기 위해서 따로 생성
    user_comments = Comments.objects.filter(post=post, author=request.user)

    return render(
        request,
        &quot;post/detail_update.html&quot;,
        {&quot;post&quot;: post, &quot;comment&quot;: comment, &quot;user_comments&quot;: user_comments},
    )
</code></pre><blockquote>
<p>코멘트 삭제 메소드:
    포스트 삭제와 다를바 없지만
    comment = get_object_or_404(Comments, id=comment_id)
    커멘트에서 받아오는 인스턴스를 하나 추가해준것</p>
</blockquote>
<pre><code>@login_required(login_url=&quot;users:login&quot;)
def comment_delete(request, post_id, comment_id):
    comment = get_object_or_404(Comments, id=comment_id)
    post = get_object_or_404(Post, id=post_id)
    if request.user != post.author:
        messages.error(request, &quot;삭제권한이 없습니다&quot;)
        return redirect(&quot;post:detail&quot;, post_id=post.id)
    else:
        comment.delete()
        return redirect(&quot;post:detail&quot;, post_id=post.id)
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[장고 첫번째 팀프로젝트 :: 게시판 CRUD관련 코드 리뷰 - 게시판 생성 삭제 및 편집]]></title>
            <link>https://velog.io/@stella_k/%EC%9E%A5%EA%B3%A0-%EC%B2%AB%EB%B2%88%EC%A7%B8-%ED%8C%80%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B2%8C%EC%8B%9C%ED%8C%90-CRUD%EA%B4%80%EB%A0%A8-%EC%BD%94%EB%93%9C-%EB%A6%AC%EB%B7%B0-%EA%B2%8C%EC%8B%9C%ED%8C%90-%EC%83%9D%EC%84%B1-%EC%82%AD%EC%A0%9C-%EB%B0%8F-%ED%8E%B8%EC%A7%91</link>
            <guid>https://velog.io/@stella_k/%EC%9E%A5%EA%B3%A0-%EC%B2%AB%EB%B2%88%EC%A7%B8-%ED%8C%80%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B2%8C%EC%8B%9C%ED%8C%90-CRUD%EA%B4%80%EB%A0%A8-%EC%BD%94%EB%93%9C-%EB%A6%AC%EB%B7%B0-%EA%B2%8C%EC%8B%9C%ED%8C%90-%EC%83%9D%EC%84%B1-%EC%82%AD%EC%A0%9C-%EB%B0%8F-%ED%8E%B8%EC%A7%91</guid>
            <pubDate>Tue, 19 Sep 2023 08:07:24 GMT</pubDate>
            <description><![CDATA[<p>임포트 할 모듈</p>
<pre><code>from django.http import HttpResponse, HttpResponseNotAllowed
from django.shortcuts import redirect, render, get_object_or_404
from django.contrib.auth.decorators import login_required
from post.models import Post, Comments, PostLike, CommentLike
from django.conf import settings  # media url
from .forms import PostForm, ImageForm, FileForm, CommentForm
from django.contrib import messages
from django.urls import reverse</code></pre><p>인덱스함수 : </p>
<pre><code>def index(request):
    posts = Post.objects.all().order_by(&quot;-created_at&quot;)
    #꺼꾸로 최신글 부터 위로 오게 설정
    if request.method == &quot;GET&quot;:
        return render(
            request,
            &quot;post/index.html&quot;,
            {&quot;posts&quot;: posts, &quot;MEDIA_URL&quot;: settings.MEDIA_URL},
        )
    elif request.method == &quot;POST&quot;:
        pass
    else:
        return HttpResponse(&quot;Invalid request method&quot;, status=405)
</code></pre><p>게시물 생성 함수 : </p>
<p>만약 한개의 이미지만 가져오고 싶었을때와 폼 없이 저장하고 싶었을때:</p>
<pre><code>@login_required(login_url=&quot;/users/login/&quot;)
# 즉, 로그인 되어있지 않을시 로그인 페이지로 옮겨
#if request.user.is_authenticated:해준 모든것을 알아서 처리해준다.

def create(request):
     #if request.user.is_authenticated:
        if request.method == &quot;POST&quot;:
            title = request.POST[&#39;title&#39;]
            author =request.user
            content =request.POST[&#39;content&#39;]

            #한개의 이미지
            image =request.FILES.get(&#39;image&#39;)
            file = request.FILES.get(&#39;file&#39;)

            Post.objects.create(
                title = title,
                author =author,
                content=content,
                image = image,
                file = file,
                )

            #여러개의 이미지 : 폼을 안쓰고 
            #1번쨰 방식
            for img_file in request.FILES.getlist(&#39;images&#39;):
                Image.objects.create(post=post, image=img_file)

            for file in request.FILES.getlist(&#39;files&#39;):
                File.objects.create(post=post, file=file)

            #2번쨰 방식
            # for f in file:
            #     file_instance = File(file=f) # 클라스 인스턴스 생성
            #     file_instance.save()

            # for img in image:
            #     img_instance = Image(image=img)
            #     img_instance.save()


            return redirect(&#39;/post/&#39;)
        elif request.method ==&quot;GET&quot;:
            return render(request,&#39;post/create.html&#39;)
        else:
            return HttpResponse(&quot;Invalid request method&quot; , status=405)
  #  else:  # if not request.user.is_authenticated:
    # 로그인전 새로만들기 눌렀을때 로그인페이지로 next값 저장해주고 넘어가게해주기위해 만든것
    # login_url = f&quot;/user/login/?next={request.path}&quot;
    # return redirect(login_url)</code></pre><p>form은 모델과 연동을 통해 여러 파일,사진등을 자동적으로 데이터의 생성, 수정, 유효성 검사, 에러 메시지 표시 등에 관련 기능이 풍부하기 떄문에 사용.</p>
<p>이번 프로젝트 에서는 더 templates와 연결할때 사용할 수 있는 편리한 FORM기능을 사용하지는 않음. 왜냐면 따로 &lt;input ~~ multiple&gt;같은 옵션을 걸어줘야할때는 사용할 수 없기 때문에.</p>
<p>허나 유효성검사 및 데이터 생성 수정 연결....해줌!</p>
<pre><code>
@login_required(login_url=&quot;/users/login/&quot;)
def create(request):
    # if request.user.is_authenticated:
        if request.method == &quot;POST&quot;:
            post_form = PostForm(request.POST, request.FILES)  # 폼 인스턴스 생성
            if post_form.is_valid(): # 유효성 검사
                post = post_form.save(commit=False) 
                #commit=False해줄 시 바로 디비에 저장되지않고 폼만 형성되어 대기.
                #해주는 이유는 templates에서 받지 못한 값을 따로 저장해 주기 위해서 
                post.author = request.user
                post.save()

                for img_file in request.FILES.getlist(&quot;image&quot;):
                #getlist를 통해서 request된 FILES의 모든 이미지를 받아 오는것
                #for문으로 하나씩 돌려
                    image_form = ImageForm({&quot;post&quot;: post.id}, {&quot;image&quot;: img_file}) #정확히 어떤 위치의 포스트에 어떤 파일을 지정해주는것.
                    if image_form.is_valid():
                        image = image_form.save(commit=False)
                        image.post = post
                        image.save()
                    else:
                        #print(image_form.errors) #디버깅
                        #이렇게 에러메세지를 터미널에 볼수 있어 FORM에러메세지 기능
                        image_form = ImageForm()
                       # 처음 방문할 때 폼 객체가 존재하지 않기 때문에 생기는 오류를 방지
                       #템플릿에서 {{ form.as_p }} 등의 폼 렌더링 메서드를 사용할 경우 오류가 발생할 수 있기때문에 =&gt; 애초에 사용하지않았지만 해준다.
            # context =  {&#39;post_form&#39;: post_form, &#39;image_form&#39; : image_form , &#39;file_form&#39;:file_form}

                        return render(
                            request, &quot;post/create.html&quot;, {&quot;image_form&quot;: image_form}
                        )

                for file in request.FILES.getlist(&quot;file&quot;):
                    file_form = FileForm({&quot;post&quot;: post.id}, {&quot;file&quot;: file})
                    if file_form.is_valid():
                        file = file_form.save(commit=False)
                        file.post = post
                        file.save()
                    else:
                        #print(f&quot;file{file_form.errors}&quot;)
                        file_form = FileForm()
                        return render(request, &quot;post/create.html&quot;, {&quot;file_form&quot;: file_form})
                return redirect(&quot;/post/&quot;)
            else:
                print(post_form.errors)
                post_form = PostForm()
                return render(request, &quot;post/create.html&quot;, {&quot;post_form&quot;: post_form})

        elif request.method == &quot;GET&quot;:
            posts = Post.objects.all()
            return render(request, &quot;post/create.html&quot;, {&quot;posts&quot;: posts})
        else:
            return HttpResponseNotAllowed([&quot;GET&quot;, &quot;POST&quot;])
    #else:
     #로그인전 새로만들기 눌렀을때 로그인페이지로 next값 저장해주고 넘어가게해주기위해 만든것
        #login_url = f&quot;/user/login/?next={request.path}&quot;
        #return redirect(login_url)

</code></pre><p>위에는 폼을 사용해주었고 참고로 폼셋도사용해봤지만 TEMPLATES와 연결해주는것에 에러가 자꾸 나서 폼으로 사용해 주었다.</p>
<p>폼셋을 예를 들어주면:</p>
<blockquote>
<p>ImageFormSet = modelformset_factory(
        Image, form=ImageForm, extra=0)  # extra : 추가적인 빈폼 갯수
    form = ImageFormSet(request.POST, request.FILES, queryset=post.images.all())
    # post.images.all()는 특정 post와 연결된 모든 Image 객체를 반환합니다. 따라서 이 폼셋은 해당 post와 관련된 이미지만을 표시하고 편집
    # queryset는 폼셋의 초기 상태를 정확하게 제어하기 위해 사용되며, 이를 통해 사용자가 특정 데이터만을 볼 수 있도록 하거나 특정 데이터에 대한 작업만을 수행하도록 제한.
    # 안해주면 데이터베이스에 있는 모든 이미지에 대한 폼이 표시</p>
</blockquote>
<p>폼으로 여러개 파일이미지 업뎃 및 그 이전 파이 불러와 삭제 해주는 기능:</p>
<p>여기서 삭제기능은 폼을 설정할때 boolean타입으로 IMAGE 및 file의 위젯을 변경해주었다.</p>
<pre><code>@login_required(login_url=&quot;/users/login/&quot;)
def update(request, post_id):

    post = get_object_or_404(Post, pk=post_id)

    current_images = post.images.all() #저장된 이미지를 모두 불러와.
    current_files = post.files.all()

    post_form = PostForm(request.POST, instance=post) 
    #instance=post는 이전의 저장된 포스트를 불러오는것.
    #즉, instance 인자를 불러오지않으면 새로운 객체를 생성하느것이고, INSTANCE를 부를시 그 객체를 불러와 수정한다는 의미이다.

    # 로그인한자와 글쓴이가 같은지 
    if request.user != post.author:
        messages.error(request, &quot;삭제권한이 없습니다&quot;)
        return redirect(&quot;post:detail&quot;, post_id=post.id)
    if request.method == &quot;POST&quot;:
        # 이미지 수정 및 삭제
        if post_form.is_valid():
            post = post_form.save(commit=False)
            post.author = request.user
            post.save()
           # print(post_form.instance.id) 디버깅 체크
        else:
            post_form = PostForm(instance=post)
            # 
            return redirect(&quot;post:home&quot;)

        # 기존의 저장된 파일 들을 삭제할것인지 
        for img in current_images:  
            image_form = ImageForm(
                request.POST, request.FILES or None, instance=img, prefix=str(img.id)

                #request.FILES or None을 해주는 이유는 만약 아무런 데이터가 없어 즉, 이미지를업로드 시키지 아니아하였을때 .is_valid()유효성 체크시 None =&gt; false가 나올수 있기때문에 넣어준다.
            )
            if image_form.is_valid():
                if f&quot;delete_{img.id}&quot; in request.POST:  
                # template에서 전해주는 name을 확인해서 넘겨준다.
                #&lt;label&gt;삭제하기: &lt;input type=&quot;checkbox&quot; name=&quot;delete_{{ form.instance.id }}&quot; value=&quot;{{ form.instance.id }}&quot; /&gt;&lt;/label&gt;
                #위의 체크박스를 넣어주고 그걸 연결시켜 값을 보내주는것.

                    img.delete()
            else:
                image_form = ImageForm(instance=img)
                return HttpResponse(&quot;Invalid image&quot;, status=405)

        for file in current_files:
            file_form = FileForm(
                request.POST, request.FILES or None, instance=file, prefix=str(file.id)
            )
            if file_form.is_valid():
                if f&quot;delete_{file.id}&quot; in request.POST:
                    file.delete()
            else:
                file_form = FileForm(instance=file)
                return HttpResponse(&quot;Invalid file&quot;, status=405)



        #새롭게 추가하여 더해줄 파일들
        for img_file in request.FILES.getlist(&quot;image&quot;):
            new_image_form = ImageForm({&quot;post&quot;: post.id}, {&quot;image&quot;: img_file})
            if new_image_form.is_valid():
                print(&quot;imageform &quot;)
                image = new_image_form.save(commit=False)
                image.post = post
                image.save()
            else:
                new_image_form = ImageForm()
                return HttpResponse(&quot;Invalid image&quot;, status=405)

        for f_file in request.FILES.getlist(&quot;file&quot;):
            new_file_form = FileForm({&quot;post&quot;: post.id}, {&quot;file&quot;: f_file})
            print(&quot;fileform &quot;)
            if new_file_form.is_valid():
                file = new_file_form.save(commit=False)
                file.post = post
                file.save()
            else:
                new_file_form = FileForm()
                return HttpResponse(&quot;Invalid file&quot;, status=405)

        return redirect(&quot;post:detail&quot;, post_id=post.id)

    # 위에 다 수정을 해줫으면 새로히 수정된 내용 저장[] 해주고 
    # 이름에 의해 일어날 충돌을 막기 위해서 앞에 이름을 덧 붙혀준것 =&gt; prefix=str(~.id)
    # 다 이미지 폼으로 생성되면서 충돌이 생길까봐.

    image_forms = [
        ImageForm(prefix=str(img.id), instance=img) for img in current_images
    ]  

    file_form = [FileForm(prefix=str(file.id), instance=file) for file in current_files]

    return render(
        request,
        &quot;post/update.html&quot;,
        {
            &quot;post&quot;: post,
            # &#39;post_from&#39; : post_form,
            &quot;image_forms&quot;: image_forms,
            &quot;file_form&quot;: file_form,
        },
    )
</code></pre><blockquote>
<p>게시물을 삭제하는 메소드:</p>
</blockquote>
<pre><code>@login_required(login_url=&quot;users:login&quot;)
def delete(request, post_id):
    post = get_object_or_404(Post, id=post_id)
    if request.user != post.author: 
        messages.error(request, &quot;삭제권한이 없습니다&quot;)
        return redirect(&quot;post:detail&quot;, post_id=post.id)
    else:
        post.delete()
        return redirect(&quot;post:home&quot;)

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[장고 첫번째 팀프로젝트 :: 게시판 CRUD관련 코드 리뷰 - 모델링편]]></title>
            <link>https://velog.io/@stella_k/%EC%9E%A5%EA%B3%A0-%EC%B2%AB%EB%B2%88%EC%A7%B8-%ED%8C%80%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B2%8C%EC%8B%9C%ED%8C%90-CRUD%EA%B4%80%EB%A0%A8-%EC%BD%94%EB%93%9C-%EB%A6%AC%EB%B7%B0-%EB%AA%A8%EB%8D%B8%EB%A7%81%ED%8E%B8</link>
            <guid>https://velog.io/@stella_k/%EC%9E%A5%EA%B3%A0-%EC%B2%AB%EB%B2%88%EC%A7%B8-%ED%8C%80%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B2%8C%EC%8B%9C%ED%8C%90-CRUD%EA%B4%80%EB%A0%A8-%EC%BD%94%EB%93%9C-%EB%A6%AC%EB%B7%B0-%EB%AA%A8%EB%8D%B8%EB%A7%81%ED%8E%B8</guid>
            <pubDate>Tue, 19 Sep 2023 05:52:46 GMT</pubDate>
            <description><![CDATA[<p>먼저 나의 담당 중 하나는 모델링이었다.</p>
<p>post관련 모델링은 </p>
<pre><code>from django.db import models</code></pre><p>먼저 디비에 모델을 연결해주는 모듈 임포트</p>
<blockquote>
<p>포스트 모델</p>
</blockquote>
<pre><code>class Post(models.Model):
    class Meta:
        db_table = &quot;post_list&quot;

    author = models.ForeignKey(
        &quot;users.User&quot;,
        verbose_name=&quot;글쓴이&quot;,
        null=True,  # on_delete해주기위해
        related_name=&quot;author&quot;,
        on_delete=models.SET_NULL,
    )
    title = models.CharField(&quot;제목&quot;, max_length=50)
    content = models.TextField(&quot;내용&quot;, max_length=8000)
    comment = models.ManyToManyField(
        &quot;users.User&quot;, related_name=&quot;comment&quot;, through=&quot;Comments&quot;
    )
    liked_post = models.ManyToManyField(
        &quot;users.User&quot;, through=&quot;PostLike&quot;, related_name=&quot;liked_posts&quot;
    )
    # image = models.ImageField(upload_to = &#39;posts/images/&#39;,null=True,blank=True)
    # file = models.FileField(upload_to = &#39;posts/files/&#39;,null=True,blank=True)
    # 여러 파일을 넣으려면 파일 이미지 모델 따로 분리해서 폼 생성.
    created_at = models.DateTimeField(&quot;생성일&quot;, auto_now_add=True)
    updated_at = models.DateTimeField(&quot;수정일&quot;, auto_now=True)

    # 여기서 User모델이 참조되는 인스턴스들이 많아서 =&gt; post_set으로 다 지정되다보니 혼동이 온다
    # 그래서 related_name꼭 넣어주기
    def __str__(
        self,
    ):  # admin 페이지에 content를 대표로 보여주는것 , 그리고 실제 페이지애서 반영은 좀더 크기가 키워져 나온다.
        return self.content
</code></pre><blockquote>
<p>이미지 / 파일 모델을 따로 모델링 해준이유는 따로따로 여러개를 저장해주고 싶어서이다. </p>
</blockquote>
<pre><code>class Image(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name=&quot;images&quot;)
    image = models.ImageField(upload_to=&quot;posts/images/&quot;, null=True, blank=True)


class File(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name=&quot;files&quot;)
    file = models.FileField(upload_to=&quot;posts/files/&quot;, null=True, blank=True)</code></pre><blockquote>
<p>피드의 좋아요 모델 : 좋아요 count를 설정해주지 않은것은 장고 내 자체기능으로 알아서 .COUNT해주면 id 가 count 되기 때문에 해주지 않았습니다. 즉, Postlike.count하면 Postlike의 모든id를 다 세어준다.</p>
</blockquote>
<pre><code>class PostLike(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, verbose_name=&quot;좋아요한 포스트&quot;)
    user = models.ForeignKey(
        &quot;users.User&quot;, on_delete=models.CASCADE, verbose_name=&quot;포스트 좋아요한 유저&quot;
    )
    # likes_count = models.IntegerField(&quot;좋아요 수&quot;,default=0)


&gt; #  좋아요 수는 해당 포스트 또는 댓글과 연결된 PostLike 또는 CommentLike 객체의 수로 계산가능
</code></pre><blockquote>
<p>comment를 위한 모델 생성 :: 다 원래 있는 모델 참조해준것이고 댓글 내용을 담아주기 위해서 through옵션으로 따로 이름 생성하여 추가해준것.</p>
</blockquote>
<pre><code>class Comments(models.Model):
    author = models.ForeignKey(
        &quot;users.User&quot;,
        related_name=&quot;comment_author&quot;,
        verbose_name=&quot;댓글 쓴 유저&quot;,
        on_delete=models.CASCADE,
    )
    post = models.ForeignKey(
        &quot;Post&quot;,
        related_name=&quot;comment_post&quot;,
        verbose_name=&quot;댓글달린 포스트&quot;,
        on_delete=models.CASCADE,
    )
    comment = models.TextField(&quot;댓글&quot;, max_length=1500, null=True, blank=True)

    # textfield는 말그대로 문자열만 나타내주기때문에 다른 모델과의 관계를 정의하지 않는다, 고로 on_delete옵션이 없음
    # 그리고 이미 author나 post가 지워지면 코멘트도 사라지게되어있다. on_delete지워주기
    # 모든 참조한 데이터들 on_delete로 사라지는 기능


    liked_comments = models.ManyToManyField(
        &quot;users.User&quot;,
        verbose_name=&quot;댓글좋아요&quot;,
        related_name=&quot;liked_comments&quot;,
        through=&quot;CommentLike&quot;,
    )


class CommentLike(models.Model):
    comment = models.ForeignKey(
        Comments, on_delete=models.CASCADE, verbose_name=&quot;좋아요한 댓글&quot;
    )
    user = models.ForeignKey(
        &quot;users.User&quot;, on_delete=models.CASCADE, verbose_name=&quot;댓글 좋아요한 유저&quot;
    )
    # likes_count = models.IntegerField(&quot;좋아요 수&quot;,default=0)

</code></pre><p>이렇게 모델링을 다로 해주고 나면 어드민 페이지에 보여주고 설정하려면 
admin.py에 들어가서 </p>
<pre><code>from django.contrib import admin
from post.models import Post, Image, File, Comments

# Register your models here.


class ImageInline(admin.TabularInline):
    model = Image


class FileInline(admin.TabularInline):
    model = File

class CommentsInline(admin.TabularInline):
    model = Comments

class PostLikeInline(admin.TabularInline):
    model = PostLike

class PostAdmin(admin.ModelAdmin):
    inlines = [
        ImageInline,
        FileInline,
        CommentsInline,
        PostLikeInline
    ]</code></pre><p>이렇게 해주어야 어드민에 생성되지만 어드민을 따로 사용하지 않을 것이라면 굳이 해줄 필요없다.</p>
<p>나는 폼을 사용하였는데 이미지와 파일을 위해서 사실 폼관련 기능을 별로 사용하지않아서 안써줘도 될법도 했지만 유효성 체크라도 하기 위해서 폼을 사용해 주었다.</p>
<pre><code>Django에서 폼(forms)를 사용하는 것은 여러 가지 이유로 유용합니다. 아래는 폼을 사용하는 주요 이유들입니다:

데이터 검증 및 정제: 폼은 사용자로부터 입력받은 데이터의 유효성 검사와 정제를 자동화해 줍니다. 각 필드에 대한 규칙과 제약 조건을 설정하면, 데이터가 이러한 규칙에 맞게 제출되었는지 자동으로 확인합니다.

보안: 폼은 여러 보안 위협, 특히 SQL 인젝션 및 크로스 사이트 스크립팅(XSS)과 같은 공격을 방지하는 데 도움이 됩니다. 폼은 자동으로 입력을 이스케이프하여 이러한 공격을 방지합니다.

HTML 렌더링: Django 폼은 자동으로 HTML 필드를 렌더링할 수 있습니다. 이를 통해 입력 필드, 레이블, 유효성 검사 오류 메시지 등을 쉽게 출력할 수 있습니다.

DRY (Don&#39;t Repeat Yourself) 원칙: 폼은 데이터 처리 로직을 중앙화하여 코드의 중복을 최소화합니다. 예를 들어, 여러 뷰나 템플릿에서 동일한 입력 처리 및 유효성 검사 로직을 사용해야 하는 경우, 하나의 폼을 재사용하여 중복을 피할 수 있습니다.

모델 통합: ModelForm을 사용하면 Django 모델과 폼을 직접 연결할 수 있습니다. 이를 통해 데이터베이스 모델의 인스턴스를 쉽게 저장하고 업데이트 할 수 있습니다.

에러 메시지 관리: 폼은 각 필드에 대한 오류 메시지를 자동으로 처리합니다. 사용자가 잘못된 데이터를 입력하면, 해당 필드에 대한 에러 메시지를 쉽게 표시할 수 있습니다.

다국어 및 지역화: 폼은 다국어 및 지역화 지원과 통합되어 있어, 다양한 언어 및 지역 설정에 맞게 메시지와 데이터 포맷을 쉽게 처리할 수 있습니다.

요약하면, Django 폼은 웹 애플리케이션에서 데이터 처리, 유효성 검사, 보안 및 사용자 인터페이스와 관련된 많은 공통 작업을 간소화하고 표준화하는 데 도움을 줍니다.

라고 한다.</code></pre><p>위에는 나중에 추후 배울 drf에서 사용하는 serializer와 같이 유효성검사 및 여러 다양한 기능을 가지고있는 모듈이라할수있다.</p>
<pre><code>from django import forms
from .models import Post, Image, File, Comments, PostLike


class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        exclude = (&quot;author&quot;,)
        fields = (&quot;author&quot;, &quot;title&quot;, &quot;content&quot;)


class ImageForm(forms.ModelForm):
    image = forms.ImageField(widget=forms.ClearableFileInput())
    #이미지 삭제 버튼칸을 제공하기 위해서 추가해준 것

    class Meta:
        model = Image
        fields = (&quot;image&quot;,)


class FileForm(forms.ModelForm):
    file = forms.FileField(widget=forms.ClearableFileInput())
    #파일 삭제 버튼칸을 제공하기 위해서 추가해준 것
    class Meta:
        model = File
        fields = (&quot;file&quot;,)


class CommentForm(forms.ModelForm):
    class Meta:
        model = Comments
        fields = [&quot;comment&quot;]
</code></pre><p> 이부분은  image = forms.ImageField(widget=forms.ClearableFileInput())</p>
<p> 이미지 삭제 버튼칸을 제공하기 위해서 추가해준 것이기도 하지만 모델필드 커스트 마이징 한것 ::  특별한 위젯 설정을 적용하기위해</p>
<pre><code>#widget:
#폼 필드를 HTML로 렌더링할 때 어떤 형태로 렌더링될 것인지를 결정하는 컴포넌트

#간단히 말해, 위젯은 데이터 입력을 위한 HTML 요소(예: &lt;input&gt;, &lt;textarea&gt;, &lt;select&gt; 등)를 생성
# widget=forms.ClearableFileInput: ImageField의 기본 위젯을 
#ClearableFileInput으로 변경 =&gt; 업로드된 파일 삭제하는 지우기옵션 제공
# attrs={&#39;multiple&#39;: True} : 위젯의 속성을 설정하여, 사용자가 한 번에 여러 이미지 파일을 선택할 수 있게 해줌 
#=&gt; html &lt;input type=&quot;file&quot; name=&quot;image&quot; multiple&gt; mutilple 속성과 같음
#하지만 애초에 그 속성이 가능한 위젯이 있기에 알아서 설정. 
#ClearableFileInput 여기서는 멀티플 속성이 가능하지않다.
#그렇기 때문에 templates에서 따로 번거럽게 옵션 설정을 해줘야한다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[소수 n째 자리까지 출력하기 (round(), %, format(), f-string)]]></title>
            <link>https://velog.io/@stella_k/%EC%86%8C%EC%88%98-n%EC%A7%B8-%EC%9E%90%EB%A6%AC%EA%B9%8C%EC%A7%80-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0-round-format-f-string-s6td58z1</link>
            <guid>https://velog.io/@stella_k/%EC%86%8C%EC%88%98-n%EC%A7%B8-%EC%9E%90%EB%A6%AC%EA%B9%8C%EC%A7%80-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0-round-format-f-string-s6td58z1</guid>
            <pubDate>Mon, 18 Sep 2023 14:40:21 GMT</pubDate>
            <description><![CDATA[<h3 id="소수-n째-자리까지-출력하기">소수 n째 자리까지 출력하기</h3>
<p>몇가지의 방법으로 소수점 출력을 print에서 제어할 수 있다.</p>
<p><strong>1. round()함수</strong></p>
<pre><code>print(&quot;결과 : {}&quot;.format(solution(121)))# =&gt;결과 : 144.0
</code></pre><p>원래의 반환값이 144.0이다</p>
<pre><code>print(round(solution(121))) =&gt;144</code></pre><p>round()안에 매개변수가 오직 value값만 들어가 있다면 정수 부분만 출력</p>
<p>원래의 반환값이 144.0으로 끝난다면, 아무리 해도 반환된값까지만 나온다.</p>
<pre><code>print(round(solution(121),1)) =&gt; 144.0

print(round(solution(121),0)) =&gt; 144.0

print(round(solution(121),3)) =&gt; 144.0</code></pre><p>이렇게 메소드가 아니라 수를 넣어줬을때를 보면 이해가 더 잘 갈것이다.</p>
<pre><code>print(round(3.12345)) =&gt; 3
print(round(3.12345,1)) =&gt; 소수점 1번쨰 까지 =&gt; 3.1
print(round(3.12345,0)) =&gt; 소수점을 1번째를 0으로 만들어 반환 =&gt; 3.0 
print(round(3.12345,3)) =&gt; 소수점 3번쨰 까지 =&gt; 3.123
</code></pre><p>다른 함수와 다르게 바로 문자열없이 함수자체로 표기가 가능하고,
만약 텍스트와 함께 프린트 하길 원한다면 f-string 방식으로 사용하면된다.</p>
<pre><code>print(f&quot;이렇게 {round(3.12345,3)} 사용하면 됩니다.&quot;)</code></pre><p>나머지 방법은 </p>
<p><strong>2.f-string</strong> </p>
<p>f&quot;{받을 값이나 함수를 넣고 : 소수점 몇번째.nf} 식으로 작성해주면된다.</p>
<pre><code>print(f&quot;{solution(121):.4f}&quot;) #=&gt;144.0000
print(f&quot;{0.43123:.4f}&quot;) #=&gt;0.4312</code></pre><p><strong>3.format()</strong></p>
<p>기본적인 활용법은 </p>
<p>print( &quot;{0}{1}{2}&quot; 텍스트 안의 중괄호 안에 인덱스를 부여하고 .format()을 붙혀 그안에 넣어줄 값을 순서대로 &#39;,&#39;로 넣어 주면된다.</p>
<pre><code>print(&quot;결과 : {0} {2} {1}&quot;.format(solution(121),&quot;나옵니다&quot;,&#39;이렇게&#39;))
# =&gt;결과 : 144.0 이렇게 나옵니다</code></pre><p>소수점 출력제어의 사용법은</p>
<p>{index:.nf}=&gt; 인덱스 값의 몇번째 소수점.n(=0.n)까지 f를 붙혀주는 것이다</p>
<p>{:.2f}=&gt; format안의 인수가 1나라면 굳이 index를 쓰지 않아도 된다.</p>
<pre><code>print(&quot;결과 : {}&quot;.format(solution(121)))# =&gt;결과 : 144.0
print(&quot;{:0.0f}&quot;.format(solution(121)))# =&gt;144
print(&quot;{0:0.0f},{1:0.2f}&quot;.format(solution(121),2.567))# =&gt;144,2.57</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[2023-09-15 장고 첫번째 팀 프로젝트 회고]]></title>
            <link>https://velog.io/@stella_k/2023-09-15-%EC%9E%A5%EA%B3%A0-%EC%B2%AB%EB%B2%88%EC%A7%B8-%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@stella_k/2023-09-15-%EC%9E%A5%EA%B3%A0-%EC%B2%AB%EB%B2%88%EC%A7%B8-%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 15 Sep 2023 10:21:36 GMT</pubDate>
            <description><![CDATA[<p>&lt;<strong>사실(Fact):</strong> 일어난 일에 대한 객관적인 기록&gt;</p>
<blockquote>
<p>기본적인 CRUD를 만들어 보는 과제였다. 
    <em>(+는 기본과제 추가로 만들어 본 기능)</em></p>
</blockquote>
<pre><code>-로그인/로그아웃/회원가입 
+ 이메일/패스워드 정규화
+ 패스워드 수정

-게시판/글수정/글생성 
+ 여러개 이미지 및 파일 추가
+ 댓글생성/댓글수정
+ 팔로우 기능
+ 좋아요 기능

-마이페이지
+팔로우 및 내 게시글 보기</code></pre><p>&lt; <strong>느낌(Feeling):</strong> 상황에 대한 감정적인 반응&gt;</p>
<p>이미 개인과제를 통해 기본적인 기능관련해서는 구현을 해봤기때문에
    좀더 몸집이 커진 프로젝트였지만 가능할것이라는 믿음이 있었다.</p>
<p>&lt; <strong>교훈(Finding):</strong> 경험을 통해 배울 수 있었던 것&gt;</p>
<p>여러 에러를 통해 좀더 에러를 해결해 나가며 어떨때 이러한 에러가 생기는구나 하는 감을 잡게된것 같다.</p>
<p>   와이어프레임, ERD 및 API문서 생성을 처음 해보면서 좀더 구체적으로 프로젝트를 어떻게 구현해야겠다는 프레임이 생겼다.</p>
<p>   =&gt; 비록 처음 생성된것에서 벗어나 수정하게 된 부분 및 추가된 부분이 있었지만 처음 이렇게 팀끼리 모여 소통하면서 만들어 놓은게 모델링 및 기본 API구성 및 페이지를 브라우저에 어떻게 구성해야겠다는 기획이 뚜렷하게 있어 쉽게 한발자국씩 나아갈수 있었던거같다.</p>
<p>&lt;<strong>향후 행동(Future action):</strong> 향후 할 수 있는 개선된 행동?</p>
<p>이 와이어프레임 ERD 그리고 API문서를 얼마나 뚜렷하게 기획을 하냐에 따라서 프로젝트를 팀끼리 배정하기도 좋고 쉽게 헤매지 않고 단계별로 해나갈수 있을거같아 좀더 생각하고 기획하는 시간을 가져야겠다는 생각을 했다.</p>
<p>첫째하루에 몇시간 투자한게 다였기 때문에 바로 기능구현에 들어가 더이상 좀더 깊게 소통해서 기획하지 못했던 부분이 아쉽다는 생각이프로젝트 끝나고 들었다.</p>
<p>다른 팀에서 발표때 말한 API문서에 =&gt; 1단계 2단계 3단계 ...이렇게 단계별로 나눠서 만들게 되면 좋을거 같다는 생각을 하게 되었다.</p>
<p>먼저 그이휘에 1단계 작성하고 그 기능을 구현한후에 머지하고
2단계를 또 고려해 작성하고 구현하고  머지....</p>
<p>이런 방식은 어떨까? 라는 생각을 했다.</p>
<p>&lt;<strong>Keep:</strong> 좋은 결과를 만들었고, 계속 유지해나가야 할 것&gt;</p>
<p>유지해야될 부분은 앞으로도 포기하지않고 에러와 맞서 싸우면서, 조급해하지않고 하나하나 에러를 깨트려나가는 자세가 유지되어야 한다는 것</p>
<p>&lt;<strong>Problem:</strong> 아쉬운 결과를 만들었고, 앞으로 개선되어야 할 것&gt;</p>
<p>시간에 촉박하여 100프로 나만의 것으로 만들지 못했던게 아쉽다.</p>
<p>나의 코드는 어떤이유에서 이 코드와 로직을 썼는지는 알겠지만 그것 이외는 내가 보지않고 다시 구현하라 하면 머뭇거릴것 같다.</p>
<p>&lt;<strong>Try:</strong> 문제를 파악하고, 이를 해결하기 위한 구체적인 개선방안&gt;</p>
<p>-ERD 꼼꼼히 작성 -&gt; 이걸 베이스로 모델링
-기획 API 단계별로 작성 -&gt; 팀원가 후의 머지를 고려해서 역할 분담
-중간중간 머지하며 팀 소통
-css-javascript따로 공부해야할 것 같다.</p>
<p>좀 더 실력을 쌓고 꾸준히 쌓다보면 이해도도 높아지고 부족한 시간도 좀더 나아지지 않을까? </p>
<p>내가 구현한 코드들 과정 및 에러사항들 개선은 추후 작성하여 태그하겠다:</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ERD사용시 테이블 관계 연결할때 식별자와 비식별자 선택? 무슨 차이점이 있을까?]]></title>
            <link>https://velog.io/@stella_k/ERD%EC%82%AC%EC%9A%A9%EC%8B%9C-%ED%85%8C%EC%9D%B4%EB%B8%94-%EA%B4%80%EA%B3%84-%EC%97%B0%EA%B2%B0%ED%95%A0%EB%95%8C-%EC%8B%9D%EB%B3%84%EC%9E%90%EC%99%80-%EB%B9%84%EC%8B%9D%EB%B3%84%EC%9E%90-%EC%84%A0%ED%83%9D-%EB%AC%B4%EC%8A%A8-%EC%B0%A8%EC%9D%B4%EC%A0%90%EC%9D%B4-%EC%9E%88%EC%9D%84%EA%B9%8C</link>
            <guid>https://velog.io/@stella_k/ERD%EC%82%AC%EC%9A%A9%EC%8B%9C-%ED%85%8C%EC%9D%B4%EB%B8%94-%EA%B4%80%EA%B3%84-%EC%97%B0%EA%B2%B0%ED%95%A0%EB%95%8C-%EC%8B%9D%EB%B3%84%EC%9E%90%EC%99%80-%EB%B9%84%EC%8B%9D%EB%B3%84%EC%9E%90-%EC%84%A0%ED%83%9D-%EB%AC%B4%EC%8A%A8-%EC%B0%A8%EC%9D%B4%EC%A0%90%EC%9D%B4-%EC%9E%88%EC%9D%84%EA%B9%8C</guid>
            <pubDate>Fri, 08 Sep 2023 14:11:30 GMT</pubDate>
            <description><![CDATA[<p>먼저 ERDcloud에 들어가서 로그인한 후 ERD를 만드는데!
팀이라면 권한을 주어 다같이 작성할 수 있게 된다!
<img src="https://velog.velcdn.com/images/stella_k/post/ea711d42-ed6d-4ac0-996c-18c0c90b071a/image.png" alt="">
그 이후에 ERD맨 오른쪽에 보면 설정칸이 있는데, 거기 눌러 들어가면
<img src="https://velog.velcdn.com/images/stella_k/post/7ada906a-85e7-4f6a-a8c1-5fa48242a2d2/image.png" alt="">
프로젝트 이름 및 테이블구조를 설정할 수 있는데 나는 논리+물리를 선택하고 domain과 type만 설정했다.</p>
<p>다 설정해주는게 여러모로 좋은것 같다는 생각은 작성후에 들기 시작했는데, 처음이라 설정할때 굳이 다 넣어줘야하나라고 생각했지만 생각보다 null값이 들어가는 데이터도 많고, 커멘트를 붙여줬는면 더 편하게 알아볼거 같다는 생각이 들었다.</p>
<blockquote>
<p>테이블 구조</p>
</blockquote>
<blockquote>
<p>논리 테이블명 : 테이블명에 대한 설명 (한글)
물리 테이블명 : 실제 테이블명 (영문)
논리 필드명 : 필드명에 대한 설명 (한글)
물리 필드명 : 실제 필드명 (영문)
도메인 : 필드명에 대한 주제를 적는 곳 (이것도 일종의 코멘트와 같다)
타입 : int, varchar, date 필드 타입을 써주는 곳
NULL 유무 : NULL / NOT NULL 중에 하나
기본값 : default 값을 써주는 곳
코멘트 : 말 그대로 부가설명이 필요할 경우 써주는 곳이다
논리값은 그냥 주석, 부가설명 으로 이해하면 된다. 실제 쿼리에는 포함되지 않아 한글로 설명을 쓰면 된다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/stella_k/post/6f88f82f-fad8-4abe-857e-4808406e9566/image.png" alt=""></p>
<p>여기서 이 관계도를 그리는 선에서 비식별자 관계인지 식별자 관계인지를 구분하라고 나왔을때, 이게 무슨뜻인가 했다.</p>
<p>Entity(테이블)간의 타입의 연결을 나타내는데 사용하는 방법으로,</p>
<blockquote>
<ol>
<li>식별관계 (Identifying Relationship):</li>
</ol>
</blockquote>
<ul>
<li>식별 관계는 부모 엔터티의 기본 키(PK, Primary Key)가 자식 엔터티의 기본 키로도 사용되는 관계<pre><code>  =&gt;이러한 관계에서 자식 엔터티는 부모 엔터티 없이는 존재할 수 없다.</code></pre></li>
<li>ERD에서는 실선으로 표시되며, 자식 엔터티의 PK에 부모 엔터티의 PK가 포함되어 있음을 나타낸다.</li>
</ul>
<blockquote>
<p>풀이하자면, 한마디로 자식 PK가 FK로 그 부모의 PK를 받아오는것. 그렇게 되면 그 자식은 게시물로 예를 든다면 하나의 유저가 하나의 게시물만 작성하게되는것입니다. 1:N이 관계에서는 사용하면 안되는것이죠. OnetToOneField에서는 그래서 궅이 식별자로 나눌필요도 없게되는것인데. 말 그대로 부모의 PK를 받아오는 것이라 부모가 없으면 자식은 존재할 수 없습니다.</p>
</blockquote>
<p>예)OneToOneField관계를 가진 user와 profile테이블의 관계</p>
<pre><code>class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
    .
    .
    .</code></pre><p>근데 굳이 저 primarykey를 넣어줘서 식별자로 만들 필요까지 있을까??
라는 생각을 해본다. </p>
<p>해줬을때 달라지는것은 pk가 user=pk를 가지게되는것과,
유저가 없으면 프로필이 없다는것인데....</p>
<p>profile_id데이터를 안넣어줘 메모리 세이브하는 정도의 이득이 있다 보지만...? 
그렇게 크지도 않은 데이터를 위해 식별을 해줬다가 
나중에 큰코! 다치는 일이있을수 있기때문에...</p>
<p>onotoone이라면 써도 되지만 그 외에는 식별 관계를 사용할지, 비식별 관계를 사용할지는 
특정 애플리케이션의 요구사항, 데이터의 특성, 그리고 기대되는 확장성 등 여러 요소를 고려해야한다.</p>
<p>여기서 식별자를 사용하려할때 기본키를 고유하게 식별 할 수 있게 하기 위해서,
때로는 두개이상의 필드를 조합하여 <strong>복합 키(composite key)</strong> 를 기본 키로 사용한다.</p>
<p>PK는 오직 테이블 당 한개만 존재할 수 있다고 하였는데, 이 <strong>복합키로 두개의 pk를 가진</strong>다고 생각 하면 된다.
이렇게 만들어 주는 이유는 자식테이블이 부모테이블에 강하게 의존하는 경우를 강조할 때 사용하는것.</p>
<blockquote>
<p>예: 주문(Order)과 주문 항목(Order Item) 간의 관계에서 주문 항목의 기본 키는 (Order ID, Item ID)와 같이 주문의 ID를 포함할 수 있습니다. 여기서 주문 항목은 주문 없이는 존재할 수 없으므로 식별 관계에 있습니다.</p>
</blockquote>
<p>주문항복은 특정주문이 없이는 존재 할 수 없다.
주문은 외래키면서 동시에 복합 기본키의 일부로 주문항목은 복합 기본키의 다른부분입니다.</p>
<pre><code>class Order(models.Model):
    order_id = models.AutoField(primary_key=True)
    order_date = models.DateTimeField(auto_now_add=True)

class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE, primary_key=True)
    item_sequence = models.PositiveIntegerField()
    item_name = models.CharField(max_length=255)

    class Meta:
        unique_together = (&#39;order&#39;, &#39;item_sequence&#39;)</code></pre><p>Meta 클래스의 unique_together 옵션은 order와 item_sequence의 조합이 OrderItem 테이블에서 유일하게 유지되도록 한다. 
이렇게 함으로써 OrderItem 테이블의 복합 기본 키를 구성하는것.</p>
<blockquote>
<ol start="2">
<li>비식별 관계 (Non-Identifying Relationship):</li>
</ol>
</blockquote>
<ul>
<li>비식별자는 우리가 일반적으로 사용하는 방식의 관계로서,</li>
<li>자식테이블의 기본키가 부모테이블의 기본키를 포함하지 않는 다는것.
   =&gt;즉, 부모 테이블의 키본키는 외래키로만 사용되고, 자식테이블은 부모테이블과 독립적으로 존재할 수 있다.</li>
<li>ERD에서는 점선으로 표시.</li>
</ul>
<blockquote>
<p>예: 직원(Employee)과 부서(Department) 간의 관계에서 직원은 부서에 속할 수도 있고 속하지 않을 수도 있습니다. 여기서 직원의 기본 키는 직원 ID만을 포함하고, 부서는 외래 키로만 참조됩니다. 따라서 이는 비식별 관계에 있습니다.</p>
</blockquote>
<pre><code>from django.db import models

class Department(models.Model):
    department_name = models.CharField(max_length=255)

class Employee(models.Model):
    employee_name = models.CharField(max_length=255)
    department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True, blank=True)
</code></pre><p>추후 부서는 모델의 인스턴스로 생성해서 </p>
<pre><code>hr_department = Department(department_name=&quot;인사과&quot;)
hr_department.save()

support_department = Department(department_name=&quot;지원관&quot;)
support_department.save()
</code></pre><p>이렇게 저장해주면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[장고]FK (foreignkey), ManyToManyField, OneToOneField형식.]]></title>
            <link>https://velog.io/@stella_k/%EC%9E%A5%EA%B3%A0FK-foreignkey-ManyToMany-OneToOne-%ED%95%84%EB%93%9C%ED%98%95%EC%8B%9D</link>
            <guid>https://velog.io/@stella_k/%EC%9E%A5%EA%B3%A0FK-foreignkey-ManyToMany-OneToOne-%ED%95%84%EB%93%9C%ED%98%95%EC%8B%9D</guid>
            <pubDate>Wed, 06 Sep 2023 06:43:46 GMT</pubDate>
            <description><![CDATA[<p>PK와 FK란? : <a href="https://velog.io/@stella_k/PKPrimary-Key%EC%99%80-FK-Foreign-Key%EA%B7%B8%EA%B2%8C-%EB%AD%94%EB%8D%B0">링크텍스트</a></p>
<p>저번시간의 PK와 FK를 설명했는데, 좀더 Fk와 그 외의 설명이 필요할거 같아 다시 새로운 글을 팠다.</p>
<p>Django의 모델 필드는 데이터베이스 테이블의 열(Column)을 정의하는데 사용되며, 웹 애플리케이션의 데이터 모델을 구성한다. </p>
<h3 id="그렇다면-먼저-모델의-필드는-어떤-종류가-있을까">그렇다면 먼저 모델의 필드는 어떤 종류가 있을까?</h3>
<p>참조:[링크텍스트](Model field reference | Django documentation | Django (djangoproject.com))</p>
<p>대표적인것만 보자면,</p>
<blockquote>
<p><strong>CharField:</strong></p>
</blockquote>
<p>CharField는 짧은 문자열을 저장하기 위한 필드입니다.
max_length 매개변수로 최대 문자열 길이를 지정해야 합니다.
예: models.CharField(max_length=100)</p>
<blockquote>
<p><strong>IntegerField:</strong></p>
</blockquote>
<p>IntegerField는 정수 값을 저장하는 데 사용됩니다.
예: models.IntegerField()</p>
<blockquote>
<p><strong>FloatField:</strong></p>
</blockquote>
<p>FloatField는 부동 소수점 수를 저장하는 데 사용됩니다.
예: models.FloatField()</p>
<blockquote>
<p><strong>DateField:</strong></p>
</blockquote>
<p>DateField는 날짜를 저장하는 데 사용됩니다.
예: models.DateField()</p>
<blockquote>
<p><strong>TimeField:</strong></p>
</blockquote>
<p>TimeField는 시간을 저장하는 데 사용됩니다.
예: models.TimeField()</p>
<blockquote>
<p><strong>DateTimeField:</strong></p>
</blockquote>
<p>DateTimeField는 날짜와 시간을 저장하는 데 사용됩니다.
예: models.DateTimeField()</p>
<blockquote>
<p><strong>BooleanField:</strong></p>
</blockquote>
<p>BooleanField는 참(True) 또는 거짓(False) 값을 저장하는 필드입니다.
예: models.BooleanField(default=False)</p>
<blockquote>
<p><strong>EmailField:</strong></p>
</blockquote>
<p>EmailField는 이메일 주소를 저장하는 데 사용됩니다. 입력 값이 유효한 이메일 형식이어야 합니다.
예: models.EmailField(max_length=100)</p>
<blockquote>
<p><strong>ImageField:</strong></p>
</blockquote>
<p>ImageField는 이미지 파일을 저장하는 데 사용됩니다. 파일 업로드 기능과 함께 사용됩니다.
예: models.ImageField(upload_to=&#39;images/&#39;)</p>
<blockquote>
<p><strong>ForeignKey:</strong></p>
</blockquote>
<p>ForeignKey는 다른 모델과의 관계를 표현하는 필드입니다. 다른 모델의 인스턴스와 관련되어 있음을 나타냅니다.
예: models.ForeignKey(related_name=&#39;posts&#39;, to=&#39;Author&#39;)</p>
<blockquote>
<p><strong>ManyToManyField:</strong></p>
</blockquote>
<p>ManyToManyField는 다대다(Many-to-Many) 관계를 표현하는 필드입니다. 여러 모델 인스턴스 간의 관계를 나타냅니다.
예: models.ManyToManyField(related_name=&#39;tags&#39;, to=&#39;Tag&#39;)</p>
<blockquote>
<p><strong>SlugField:</strong></p>
</blockquote>
<p>SlugField는 URL에 사용할 수 있는 문자열을 저장하는 필드입니다. 주로 블로그 글이나 게시물의 제목을 URL 친화적인 형태로 저장할 때 사용됩니다.
예: models.SlugField(max_length=50, unique=True)</p>
<blockquote>
<p><strong>AutoField:</strong></p>
</blockquote>
<p>AutoField는 자동으로 증가하는 숫자 필드입니다. 주로 데이터베이스의 기본 키(primary key)로 사용됩니다.</p>
<p>좀더 추가적으로 설명할 필드들은 바로 FK와 연결이 많이된 ManyToManyField 와 OnetoOneField이다.</p>
<h3 id="fk-알고-있으면-도움될-것들">FK 알고 있으면 도움될 것들</h3>
<blockquote>
<h3 id="1-to속성">1. to속성</h3>
</blockquote>
<p>다른 앱에서 UserModel(모델이름) 불러올때 from user.models import UserModel 해줘야한다.</p>
<pre><code>  from user.models import UserModel

author = models.ForeignKey(user.UserModel, verbose_name = &quot;글쓴이&quot;, on_delete=models.CASCADE)
</code></pre><p>직접적인 모델 임포트 없이 모델의 이름을 문자열로 참조할 수 있다. 
이 방법은 순환 참조 문제를 방지하는 데 유용.  </p>
<pre><code>author = models.ForeignKey(&#39;user.UserModel&#39;, verbose_name = &quot;글쓴이&quot;, on_delete=models.CASCADE)</code></pre><blockquote>
<h3 id="2-필드의-parameter-구조">2. 필드의 parameter 구조</h3>
</blockquote>
<p>model. CharField/... 다양한 필드들이 있고,
그 값 속성에 이미 파라미터의 키값이 설정되어져 있다.</p>
<p> 이게 무슨말이냐 하면, option키 눌러서 charfield들어가서 option키 눌러 <strong><strong>init</strong></strong>함수로 들어가면 이미 </p>
<pre><code> def __init__(
        self,
        verbose_name=None,
        name=None,
        primary_key=False,
        max_length=None,
        unique=False,
        blank=False,
        null=False,
        db_index=False,
        rel=None,
        default=NOT_PROVIDED,
        editable=True,
        serialize=True,
        unique_for_date=None,
        unique_for_month=None,
        unique_for_year=None,
        choices=None,
        help_text=&quot;&quot;,
        db_column=None,
        db_tablespace=None,
        auto_created=False,
        validators=(),
        error_messages=None,
        db_comment=None,
    ):
</code></pre><p>이러한 순서로 함수구조가 짜여진걸 볼 수가 있다.</p>
<p> 순서대로 짠다면 키값없이 바로 value값을 넣어주면 된다.
**     title = model.CharField(&quot;이름&quot;,&quot;title&quot;,False,16)**</p>
<p> 순서가 아니라면(굳이 써줄 필요가 없다던가) 키값 명시하면된다.
<strong>title = model.CharField(&quot;이름&quot;,max_length=16)</strong></p>
<h3 id="_--하지만-foreignkey-만-다르다-_">_  <strong>하지만 ForeignKey 만 다르다.</strong> _</h3>
<p>  verbose_name이란 속성이 아에 구조에서 없기때문에</p>
<pre><code>    verbose_name: 
    모델의 필드가 어떻게 레이블링 될지를 보다 명확하게 지정하고 싶을 때 사용한다.
    어드민이라든지 페이지에 나타나고, 
    코딩시 이해가 빠르게 되어 적어주는 편이 나을때도 있다.</code></pre><p>  to = &#39;모델이름&#39;or &#39;app이름. 모델이름&#39; 가 첫번째 속성이기때문에 UserModel이 바로들어간다.
      ```
author = models.ForeignKey(UserModel, verbose_name = &quot;글쓴이&quot;, on_delete=models.CASCADE) </p>
<pre><code>





&gt; #### 3.ManyToMany 필드

ManyToManyField를 사용하면 Django는 자동으로 &quot;브릿지 테이블&quot; 또는 &quot;중간 테이블&quot;(intermediate table)을 생성한다.

이 테이블은 두 모델 간의 다대다 관계를 저장하는 데 사용하는데,
(한마디로 두테이블이 서로서로를 필요로 하고 다대다 관계에 있으면 
즉, 한 저자가 여러 책을 출판할수도 있고, 북에서는 여러 작가가있을 수 있다.)
</code></pre><p>class Author(models.Model):
    name = models.CharField(max_length=100)</p>
<p>class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)</p>
<pre><code>

이렇게 만들어진 테이블 이름은 &#39;book__authors&#39;로 **&lt;첫 번째 모델 이름 소문자&gt;&lt;두 번째 모델 이름 소문자&gt;형식**으로 지정된다.

 book_id와 author_id라는 두 개의 외래 키가 포함된다.

 중간 테이블에 추가적인 정보를 저장하고 싶다면, through 옵션을 사용하여 직접 중간 모델의 이름을 정의하여 모델링을 해주면 된다.

</code></pre><p>from django.db import models</p>
<p>class Student(models.Model):
    name = models.CharField(max_length=100)</p>
<p>class Course(models.Model):
    title = models.CharField(max_length=100)
    students = models.ManyToManyField(Student, through=&#39;Enrollment&#39;)</p>
<p>class Enrollment(models.Model):
    student = models.ForeignKey(Student, on_delete=models.CASCADE)
    course = models.ForeignKey(Course, on_delete=models.CASCADE)
    date_enrolled = models.DateField()
    grade = models.DecimalField(max_digits=5, decimal_places=2)</p>
<pre><code>
 1. Course 모델 내의 ManyToManyField에 through=&#39;Enrollment&#39; 옵션을 지정 =&gt; 중간 테이블로 Enrollment을 사용하겠다고 선언.
2. Enrollment는 Student와 Course에 대한 외래 키를 포함 + 
    추가적으로 date_enrolled 및 grade 필드를 포함

=&gt; 학생이 강좌를 수강할 때마다 Enrollment 객체를 생성하여 
        해당 학생, 강좌, 등록 날짜 및 성적을 저장

####  여기서 몇가지 속성이 있는데 related_name, to 속성이다.

 **to**: 이 속성은 필드가 연결될 다른 모델을 지정 

** related_name**: 이 속성은 역관계에서 사용될 이름을 제공
     역관계란 현재 모델이 아닌 다른 모델(이 경우 Tag 모델)에서 현재 모델로의 접근할 이름을 넣어주면 현재모델의 모든 객체에 접근할 수 있게된다.

</code></pre><p>class Article(models.Model):
    title = models.CharField(max_length=100)
    tags = models.ManyToManyField(related_name=&#39;articles&#39;, to=&#39;Tag&#39;)</p>
<p>class Tag(models.Model):
    name = models.CharField(max_length=30)</p>
<pre><code>여기서 Article 모델은 여러 Tag와 연결될 수 있고, 하나의 Tag는 여러 Article과 연결될 수 있습니다.

related_name=&#39;articles&#39; 속성 덕분에 Tag 객체에서 연결된 모든 Article 객체에 직접 접근할 수 있습니다.

</code></pre><p>tag = Tag.objects.get(name=&quot;Python&quot;)
related_articles = tag.articles.all()  # 이 태그와 관련된 모든 기사를 가져옵니다</p>
<pre><code>여기서는 views.py에 있는 함수 중 모델 값을 가지고 올때 사용된다.
relate_name을 통해 더 직관적인 모델관계를 표현한다.




&gt; ### 4. OneToOneField(일대일 관게)

 한 모델의 인스턴스가 다른 모델의 한 인스턴스와만 연결될 수 있게되는 것을 말한다.
 오직 서로가 서로에게 참조되는 것.

 예)하나의 사용자(User)가 하나의 프로필(Profile)만 가질 수 있다
</code></pre><p>class User(models.Model):
    name = models.CharField(max_length=100)</p>
<p>class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.TextField()</p>
<pre><code>
그리고 사실 OneToOne은 따로 명시하지 않는 이유는 필드의 속성중 unique가 있는데 ForeignKey 값이 unique=True인것과 같은것을 뜻하기 때문이다.

unique=True 그 값을 고유하게 유지시켜주겠다는 소리, 
즉 참조된 그 값은 현재테이블과의 고유참조가 된것.


예)</code></pre><p>class User(models.Model):
    name = models.CharField(max_length=100)</p>
<p>class Profile(models.Model):
    user = models.ForeignKey(User, unique=True, on_delete=models.CASCADE)
    bio = models.TextField()</p>
<pre><code>

&gt; ### 4. On_delete 속성

 이 속성은 관련된 객체가 삭제될 때 어떤 동작을 해야하는지를 정의한다.

  여기서 on_delete 속성에 대해서 얘기하자면 6가지의 속성이 있고,

  OneToOneField과 ForeignKey에서 사용되는 속성이다.

  ManyToManyField에서 한 객체를 삭제해도 다른 모델의 객체에는 영향을 주지않고(이어져 있는 다른 2개의 모델들), 오직 중간테이블에서 해당객체와 관련된 모든 레코드(연결, 열)만 제거되기 때문에 이속성 자체가 필요가 없다. 




 필드에서 제일 많이쓰는 models.CASCADE 와 models.SET_NULL 관해 설명하자면,



#### models.CASCADE:
 관련된 객체가 삭제될 때, ForeignKey나 OneToOneField로 연결된 객체도 삭제된다.

 즉, 다른 연결된 객체도 삭제되는것.

 예) 글쓴이 - 블로그
   =&gt; 글쓴이가 삭제되면 글쓴이가 쓴 모든 포스팅도 삭제된다. 

#### models.SET_NULL:
 관련된 객체가 삭제될 때 ForeignKey 필드의 값을 NULL로 설정합니다. 이 옵션을 사용하려면 null=True 속성이 필드에 설정되어 있어야 한다.

 즉, 다른 연결된 객체는 남아있고 삭제된 해당 객체만 NULL상태로 연결된것.

  예) 글쓴이 - 블로그
   =&gt; 글쓴이가 삭제되면 글쓴이가 쓴 모든 포스팅에서 글쓴이가 삭제된다. 

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[PK(Primary Key)와 FK (Foreign Key)그게 뭔데? ERD 개념]]></title>
            <link>https://velog.io/@stella_k/PKPrimary-Key%EC%99%80-FK-Foreign-Key%EA%B7%B8%EA%B2%8C-%EB%AD%94%EB%8D%B0</link>
            <guid>https://velog.io/@stella_k/PKPrimary-Key%EC%99%80-FK-Foreign-Key%EA%B7%B8%EA%B2%8C-%EB%AD%94%EB%8D%B0</guid>
            <pubDate>Tue, 05 Sep 2023 12:09:54 GMT</pubDate>
            <description><![CDATA[<p>ERD 개념 : <a href="https://velog.io/@stella_k/ERD">https://velog.io/@stella_k/ERD</a></p>
<h3 id="pk와-fk는-뭐고-이것의-상관관계는-무엇인가">pk와 fk는 뭐고 이것의 상관관계는 무엇인가?</h3>
<blockquote>
<p>PK(primary key)는 데이터베이스 테이블에서 각 레코드(행)를 고유하게 식별하기 위한 열(속성)</p>
</blockquote>
<p>주로 정수 형식의 일련번호로(ID)표현 =&gt; 장고를 사용해서 데이타 값을 넣어주면 알아서 id값을 부여해주는데 이때의 예가 될 수 있다.</p>
<p>테이블 내에 중복된 값을 가질 수 없고, NULL값을 가질 수 없다.</p>
<p>즉, 오직 하나만이 pk가 될 수있고, 값이 없어서는 안되는것이 특징이다.
unique=True처럼 다른 고유한 필드값은 있을수 있지만, 고유하다고 해서 PK가 되는것이 아니라는것.</p>
<p>주요 대표가 되는 오직 고유한 PK가 테이블에 한개 존재한다.</p>
<p>예) 사용자 -&gt; 사용자 id번호 사용 =&gt; 몇번째 회원으로 가입되었는지. 또는 아이디 중복이 안되니까 말그대로의 유저네임이 pk가 될 수 도 있겠죠?</p>
<blockquote>
<p>FK(foreign key)는 한 데이터의 테이블의 열에서 다른 테이블의 PK를 참조하는 열을 말한다.
    여기서 ForeignKey는 일대다 관계라고 생각하면된다.</p>
</blockquote>
<p>이 FK를 통해 두 테이블의 관계를 설정하고 연결 할 수 있다.</p>
<p>부모테이블(참조되는 PK가 있는 테이블)의 값을 자식 테이블에서(참조하는 테이블) 사용</p>
<p>예) 주문테이블에 사용자테이블의 PK참조하는 FK가 있으면 각 주문이 어떤 사용자에 의해 생성되었는지 추적할 수 있다.</p>
<pre><code>    python

    from user.models import UserModel

    class TweetModel(models.Model):
        class Meta:
            db_table = &quot;tweet&quot;

        author = models.ForeignKey(UserModel, on_delete=models.CASCADE) 
        content = models.CharField(max_length=256)
        created_at = models.DateTimeField(auto_now_add=True)
        updated_at = models.DateTimeField(auto_now=True)
</code></pre><pre><code>    SQL코드


    CREATE TABLE Users (
        user_id INT PRIMARY KEY,
        username VARCHAR(50),
        email VARCHAR(100)
    );

    CREATE TABLE Orders (
        order_id INT PRIMARY KEY,
        user_id INT,  -- FK referencing Users table
        order_date DATE,
        FOREIGN KEY (user_id) REFERENCES Users(user_id)
    );
</code></pre><p>이런 느낌의 테이블이 생성되는것.</p>
<blockquote>
<p>추가적 예시</p>
</blockquote>
<p>도서관이라고 생각하고 ERD를 구성한다 생각할때:</p>
<ol>
<li><p>사용자 테이블 :
 각 사용자의 고유한 회원번호 =&gt; PK =&gt; 다른사용자와 중복 불가
 회원 이름, 나이, 전화번호, 이메일, 주소 등등</p>
</li>
<li><p>도서 테이블 : 
 각 도서는 고유한 ISBN번호를 가진다. =&gt; PK
 도서의 제목, 작가 ,출판사</p>
</li>
<li><p>대출 테이블:
 각 대출건 마다 고유한 대출 번호 =&gt; PK
 대출한 도서의 번호와 사용자의 회원 번호를 저장 =&gt; 도서번호, 사용자 회원번호 =&gt;FK 사용
 대출 날짜, 반납날짜, 연체체크 등</p>
</li>
</ol>
<p>여기서 보면 사용자 - 도서 테이블은 N:N의 관계이다.
그럼 대출테이블은 뭐냐? 바로 FK사용으로 두 테이블의 관계를 이어주는 브릿지 테이블의 역할을 하고 있다고 보면 된다. 더 효과적으로 관리하기 위한 테이블이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ERD란? 그리고 PK와 FK?]]></title>
            <link>https://velog.io/@stella_k/ERD</link>
            <guid>https://velog.io/@stella_k/ERD</guid>
            <pubDate>Mon, 04 Sep 2023 14:56:01 GMT</pubDate>
            <description><![CDATA[<h2 id="erd란">ERD란?</h2>
<p>Entity-Relationship Diagram의 약자로,
필드에 들어가 프로젝트를 시작할때 데이타베이스의 구조와 관계를 그래픽적으로 표현하는 도구이다. </p>
<p>ERD는 데이터베이스의 논리적 구조 파악하는데 매우 유용하며, 데이터 설계 시에 커뮤니케이션 도구로도 활용된다.</p>
<h4 id="entity">Entity:</h4>
<pre><code>데이터베이스에서 표현하려는 정부의 주요 객체난 개념을 나타냄
예) 사용자, 주문, 상품 등
ERD에서는 보통 직사각형으로 표시</code></pre><h4 id="attribute">Attribute:</h4>
<pre><code>엔터티의 세부정보나 특징을 나타냄
예) 사용자 엔터티의 속성은 이름/이메일/전화번호 등
ERD에서는 보통 타원형으로 표시되고, 해당 앤터티에 연결</code></pre><h4 id="relationship">Relationship:</h4>
<pre><code>두 엔터티의 관계를 나타냄
예) 사용자와 주문의 관계 =&gt; 주문하기
ERD에서는 라인과 관계의 이름을 표시, 연관된 엔터티 간에 그려짐</code></pre><h4 id="cardinality">Cardinality:</h4>
<pre><code>엔터티 간 관계의 수를 나타냄 =&gt; 각 엔터티의 인스턴스 수
예) 사용자 - 여러 주문 가능
    주문 - 여러 사용자에게 가능
    M:N

 1:1(one-to-one):
     한 엔터티의 인스턴스가 다른 엔터티의 하나의 인스턴스만 참조 가능

    사용자 한명당 하나의 프로필 가능
    프로필 하나당  한 사용자에게만 가능

    각 사원당 하나의 주민등록 가능
    하나의 주민번호 한 사원에게만 속함

1:N (one-to-many) or N:1 (many-to-one):
    한 엔터티와 다른 엔터티 간의 관계에서 한 쪽 엔터티는 여러 개의 연결을 가질수 있고, 다른 쪽 엔터티는 한번만 연결</code></pre><p>ERD를 참고하고 기획하는데 도움이 되는 툴:<a href="https://www.erdcloud.com/">https://www.erdcloud.com/</a></p>
<p>pk와fk 란? : <a href="https://velog.io/@stella_k/PKPrimary-Key%EC%99%80-FK-Foreign-Key%EA%B7%B8%EA%B2%8C-%EB%AD%94%EB%8D%B0">https://velog.io/@stella_k/PKPrimary-Key%EC%99%80-FK-Foreign-Key%EA%B7%B8%EA%B2%8C-%EB%AD%94%EB%8D%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[git 간단명료 사용법/ 브랜치 설명/git branch/git merge/git rebase/git init/ git error/ git branch삭제/git 커밋삭제/git revert/ git reset/ HEAD개념/git 파일 삭제/collaborator]]></title>
            <link>https://velog.io/@stella_k/git-%EA%B0%84%EB%8B%A8%EB%AA%85%EB%A3%8C-%EC%82%AC%EC%9A%A9%EB%B2%95-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%84%A4%EB%AA%85git-branchgit-mergegit-rebasegit-init-git-error-git-branch%EC%82%AD%EC%A0%9Cgit-%EC%BB%A4%EB%B0%8B%EC%82%AD%EC%A0%9Cgit-revert-git-reset-HEAD%EA%B0%9C%EB%85%90git-%ED%8C%8C%EC%9D%BC-%EC%82%AD%EC%A0%9Ccollaborator</link>
            <guid>https://velog.io/@stella_k/git-%EA%B0%84%EB%8B%A8%EB%AA%85%EB%A3%8C-%EC%82%AC%EC%9A%A9%EB%B2%95-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%84%A4%EB%AA%85git-branchgit-mergegit-rebasegit-init-git-error-git-branch%EC%82%AD%EC%A0%9Cgit-%EC%BB%A4%EB%B0%8B%EC%82%AD%EC%A0%9Cgit-revert-git-reset-HEAD%EA%B0%9C%EB%85%90git-%ED%8C%8C%EC%9D%BC-%EC%82%AD%EC%A0%9Ccollaborator</guid>
            <pubDate>Fri, 01 Sep 2023 10:01:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>먼저 맨 처음 repository생성 후</p>
</blockquote>
<p>주의:readme.md를 만들었다면 git init할 수 없다.
    이미 커밋이 되어 버린상태라 병합 수 없기에
    git clone을 해야한다.</p>
<h3 id="git-init">git init</h3>
<h3 id="git-remote-add-origin-주소">git remote add origin 주소</h3>
<pre><code>여기서 origin은 컨벤션에 따라 이름이 지정된것이지
이름을 따로 지정해줄 수는 있다. 그냥 이 git원격저장소의 별명이다.</code></pre><blockquote>
<p>git에 다른 작업이 있거나 할때</p>
</blockquote>
<p>git clone 주소 =&gt; 폴더 전체로 클론
git clone 주소 . =&gt; 파일들만 따로 뽑아 클론</p>
<blockquote>
<p>원격에 올릴때</p>
</blockquote>
<h3 id="git-add-">git add .</h3>
<p>현재폴더의 모든 것 더해주는것
원칙적으로는 git add file file file.... 하나하나 더해주는게 좋다.</p>
<h3 id="git-reset-파일명">git reset 파일명</h3>
<p>add해준거 stage에서 뺴기</p>
<h3 id="git-status">git status</h3>
<p>git에 잘 올라가졌는지 파일의 현상태를 알려준다. 
초록이면 올라갔고, 빨강이면 add안됌.</p>
<h3 id="git-commit--m-무엇을-누가-추가했다-이정도-내용">git commit -m &quot;무엇을 누가 추가했다 이정도 내용&quot;</h3>
<p>커밋도 마찬가지로 새로 추가한 내용이 있을때 마다 커밋해주는 것이 좋고</p>
<p>이렇게 커밋을 하고나면 히스토리가 남는데 그것을 확인할 수있는것</p>
<h3 id="git-log">git log</h3>
<p>커밋 히스토리
=&gt; git log -n 10 하면 10번쨰전까지만 보여준다.</p>
<p>그 log를 이용해서 HEAD나 커밋해쉬를 알아내 그 단계로 넘어가거나 삭제 할 수 있다</p>
<blockquote>
<h4 id="git-commit-삭제하기">git commit 삭제하기</h4>
</blockquote>
<h4 id="head">HEAD</h4>
<p>일단 HEAD가 뭐냐면 현재 체크아웃된 커밋을 가리키는 포인터이다.
HEAD는 일반적으로 현재작업중인 브랜치의 최신 커밋
HEAD<del>1(HEAD^) 한 커밋전, HEAD</del>2 두커밋전을 가리킨다.</p>
<p>A -&gt; B -&gt; C 순으로 커밋이 있고 현재 HEAD는 C를 가리킨다고 가정</p>
<h2 id="git-reset">git reset</h2>
<p>최근 커밋을 되돌릴 수 있다 =&gt; 커밋 히스토리를 변경시킨다. 주의필요</p>
<blockquote>
<p>git reset HEAD는 아무런 일을 하지 않는다. 왜냐? reset은 포인터를 움직이는 방식으로 동작하는데 포인터가 현재를 가르키고 있기 때문에. 
반면에 git revert HEAD는 가능한데 그 이유는 그 HEAD커밋에 반대되는 다른 커밋을 새로 생성하기 때문이다. 그렇기에 그 직전으로 되돌아가는것이기때문에 되돌아가는것이 가능하다. 히스토리도 지워지지않고.</p>
</blockquote>
<p><strong>1.</strong> <strong>soft reset:</strong>
git reset --soft HEAD~1
=&gt; 최근의 커밋을 되돌린다. 변경된 파일들은 스테이징 영역에 그대로 남음.</p>
<p>git status를 확인하면 해당 파일들이 여전히 &quot;staged&quot; 상태로 나타나는 것을 볼 수 있다는 것</p>
<p><strong>2.Mixed Reset (기본):</strong>
git reset --mixed HEAD<del>1  # or git reset HEAD</del>1
=&gt; 최근의 커밋을 되돌리고, 변경된 파일들은 스테이징 영역에서 제거된다 (하지만 작업 디렉토리에는 그대로 남아있다).</p>
<p><strong>3.Hard Reset:</strong>
git reset --hard HEAD~1
=&gt; 최근의 커밋을 되돌리고, 스테이징 영역과 작업 디렉토리의 변경사항까지 모두 제거한다. 이 방법은 변경사항을 완전히 삭제하므로 주의가 필요.</p>
<h3 id="git-revert">git revert</h3>
<p>git revert HEAD
git revert [커밋해시]</p>
<p>=&gt; git revert는 새로운 커밋을 생성하여 이전 커밋의 변경사항을 취소하여, 커밋 히스토리를 변경하지 않으므로 다른 사람들과 함께 작업하는 저장소에서 안전하게 사용할 수 있다.</p>
<p>헷갈리기 때문에 좀 더 설명하자면::
먼저 기본 개념:</p>
<p>스테이징 영역은 커밋될 준비가 된 변경사항들의 집합입니다.
<strong>git revert</strong>는 특정 커밋의 변경을 되돌리는 새로운 커밋을 생성하는 명령입니다.</p>
<p>사용할 수 있는 두가지 방법을 풀이::</p>
<h4 id="기본-git-revert-사용">기본 git revert 사용:</h4>
<p>git revert HEAD/[커밋해시]를 실행하면
=&gt; 해당 커밋을 되돌리는 변경사항을 바로 생성하고 <strong>커밋합니다</strong>.
이렇게 하면 스테이징 영역에는 아무런 변경사항도 남아있지x.
<strong>WHY?</strong> 모든 변경사항이 즉시 새로운 커밋으로 저장되기 때문.</p>
<h4 id="-n-또는---no-commit-옵션을-사용하여-git-revert-실행">-n 또는 --no-commit 옵션을 사용하여 git revert 실행:</h4>
<p>이 옵션을 사용하여 git revert -n HEAD/[커밋해시]를 실행하면, 해당 커밋을 되돌리는 변경사항만 준비한다. 
이 변경사항은 스테이징 영역에 추가됩니다, 하지만 아직 커밋되지는 않게 되는것.</p>
<p>이 시점에서 git status를 확인하면 스테이징 영역에 변경사항이 존재.</p>
<p>이후에 git commit -m &quot;Revert some commit&quot;와 같은 명령으로 수동으로 커밋을 생성가능.</p>
<p>간단히 말하면, -n 또는 --no-commit 옵션을 사용하면 git revert는 그전 단계로 되돌아가면서 사용자는 스스로 커밋을 생성할 수 있는 기회를 갖게된다.</p>
<p>그렇기에 원격저장소에 푸시한 커밋을 되돌리려면 git revert를 사용하는게 훨씬 권장되는 사용법이다.</p>
<h3 id="git-push-origin다른별칭으로-remote-add했으면-그것-브랜치-이름">git push origin(다른별칭으로 remote add했으면 그것) 브랜치 이름</h3>
<p>여기서 푸시하고 난 후에 나오는 에러들이 있는데 그것들은 여길 참조:<a href="https://velog.io/@stella_k/git-errors-%EB%AA%A8%EC%9D%8C">https://velog.io/@stella_k/git-errors-%EB%AA%A8%EC%9D%8C</a></p>
<p>여기서 잠시!
git push -u origin main에서 -u 옵션은 &quot;upstream&quot;의 약자로서 </p>
<p>-u를 쓰면 좋은 장점: 
git push 또는 git pull만 입력하면 된다.</p>
<p>이 뜻은 현재 로컬브랜치에서 원격 origin/main브랜치를 upstream으로 연결해 서로의 status를 확인할 수 있게해주며 git push 또는 git pull만 입력하면되서 간결성이 있다.</p>
<p>로컬브랜치란 그냥 기본main이의 내가 따로 브랜치를 만들어서 push 하기이전의 것을 로컬 브랜치라한다.</p>
<p>그래서 일반적인 워크플로에서는 같은 이름을 가진 로컬 브랜치와 원격 브랜치를 추적하도록 설정하는 것이 일반적인데(즉 처음으로 push 하게되면 원격저장소가 생성되고 추적되겠지), 특수한 사항에서 바꿔야 되는 상황이면 밑의 코드에 따라 추적위치를 다른곳으로 바꿔 줄 수 있다.</p>
<pre><code>git branch --set-upstream-to=origin/dev-featureB featureA
</code></pre><p>이 명령은 featureA 브랜치가 origin/dev-featureB를 추적하도록 설정한다.</p>
<h3 id="git-branch">git branch</h3>
<p>브랜피의 현상태:: 나는 지금 어디에있는지 어떤 브랜치들이 있는지</p>
<h3 id="git-brach-새로만들브랜치이름">git brach 새로만들브랜치이름</h3>
<p>그 이름으로 브랜치 생성</p>
<h3 id="git-branch--m-바꿀이름">git branch -m 바꿀이름</h3>
<p>브랜치이름을 바꾸고 싶을때는 현 위치에 그 브랜치를 두고 이 명령어 실행</p>
<h3 id="브랜치-삭제">브랜치 삭제</h3>
<ol>
<li><p>삭제할 해당 브랜치에서 벗어나야하기 떄문에</p>
<pre><code>git checkout main</code></pre></li>
<li><p>로컬브랜치 삭제</p>
<pre><code>git branch -d [브랜치명]</code></pre></li>
<li><p>브랜치 변경 사항이 아직 병합이 안되어있으면 -d로 삭제 불가 -&gt; -D강제로 삭제</p>
<pre><code>git branch -D [브랜치명]</code></pre></li>
<li><p>원격 브랜치 삭제</p>
<pre><code>git push origin --delete [브랜치명]</code></pre></li>
</ol>
<p>*. 브랜치 삭제 전 해당 브랜치 커밋이 중요하고 나중에 참조해야하면 삭제전 백업해주기 </p>
<ol>
<li>새로운 브랜치 만들기
git checkout <branch_to_backup>
git branch <backup_name></li>
</ol>
<p>삭제하려는 브랜치와 동일한 위치에 새로운 브랜치를 만들어 주면 커밋 히스토리 들을 다 가지게 된다. 공유하는것
2.태그 사용하기
git checkout <branch_to_backup>
git tag <backup_tag_name></p>
<p>태그는 좀더 필요하면 나중에 더 알아가면 좋겠고,</p>
<p>일단 여기서는 branch와는 다르게 tag는 한번 지정해주면 움직일수 없다.
북마크처럼 사용된다고 생각하면되는데, 특정 커밋을 참조하는것이다.
삭제될 브랜치라 더이상의 커밋이 없다는 가정하에 태그를 사용하는 것.</p>
<p>이러한 <strong>백업 방법</strong> 을 사용하면 원래의 브랜치를 안전하게 삭제하고,
필요할 때 백업한 브랜치나 태그를 참조하여 
<strong>커밋 히스토리를 복구하거나 조회</strong>할 수 있다.</p>
<h3 id="브랜치관련설명">브랜치관련설명</h3>
<p>여기서 브랜치를 가지치라고 생각하게되면 무언가 하나의 공간이라고 생각할 수있는데 그게아니다.</p>
<p>시간의 포인터라고 보면된다.</p>
<p>만약 A세계가있다.
A는 따로 시간이 흐를 B시간대를 주고싶어.
그래서 그 만들어주고 싶은 시간대에 포인터를 가리켜 B를 동시간대를 가질 수있게해. 하지만 그 이전의 A의 히스토리를 다 공유는 하고 있고, 그 이후의 벌어지는 사건들은 공유가 안되는거지. 왜냐? 동시간대라 A도 자기만의 사건이 벌어질테니까.</p>
<p>예)</p>
<p>A세계 :: #A-B-*C-R-T 
*B세계 :: A-B-C-D-&amp;E-K-O
&amp;C세계 :: A-B-C-D-E-P-U
#D세계 :: A-P-J</p>
<p>A의 A커밋에 포인터D를 넣어주고 계속 알아서 자시 시간을 가져가지만 그 이전 히스토리 A는 가지고 있지
B도 마찬가지로 A의 C단계까진 히스토리 공유하지만 그때 branch를 해주어서 자기만의 시간이 나가는거고 
C는 B의 E타임라인에서 부터 다른 자기만의 동시간대를 형성한것.</p>
<p>여기서 brach니까 계속 커밋이 생성하면서 최근 커밋으로 움직이게되지만
tag를 사용하면 그 지점에 계속 움직이지 않고 있게된다고 생각하면돼. 더이상의 커밋저장은 하지않는거지.</p>
<p>그렇기때문에 실제로 커밋이나 데이터가 중복되어 복사되는게 아니라, 해당 커밋을 가리키는 포인터가 생성 =&gt; 저장소의 크기가 급격히 커지지 않게된다.</p>
<h3 id="서브모듈">서브모듈</h3>
<p>Git 서브모듈은 하나의 Git 저장소 내에 다른 Git 저장소를 포함시키는 기능.</p>
<p>예를 들어, 여러 프로젝트에서 공통으로 사용되는 라이브러리를 별도의 Git 저장소로 관리하고, 이 라이브러리를 메인 프로젝트의 서브모듈로 포함시키는 경우.</p>
<h3 id="git-switchcheckout-브랜치이름">git switch/checkout 브랜치이름</h3>
<p>그 브랜치 이름으로 브랜치가 이동되는데. 
switch는 새로생긴 언어다 
checkout이 4가지 기능을 하는데 하나는 이전커밋이동, 브랜치 이동인데 헷갈리다는 평이 많아 switch를 새로 만들었다.</p>
<blockquote>
</blockquote>
<p>한번에 생성하고 위치를 변경해는 옵션을 추가해주면 한번에 할 수있다.
git switch -c 이름
git checkout -b 이름</p>
<h3 id="git-checkout-4가지-기능">git checkout 4가지 기능</h3>
<ol>
<li><p><strong>다른브랜치로 전환: git checkout 브랜치명</strong>
 git 2.23버전 업데이트이후:
 git switch 브랜치명</p>
</li>
<li><p><strong>새로운 브랜치 생성-&gt;그 브랜치 전환:git checkout -b 브랜치명</strong>
 git switch -c 브랜치명</p>
</li>
<li><p><strong>이전 커밋으로 이동:git checkout [커밋해시]    **
 커밋해시는 Git에서 각 커밋을 고유하게 식별하기 위한 SHA-1 해시:: git log 에서의 커밋번호 앞에서 7자리까지만 처도 식별가능
 이렇게하면 detachedHEAD상태로 전환=&gt; 필요한 작업완료 후 새로운 브랜치 생성하거나 기존 브랜치로 돌아가. 
 _</strong>why?**_ detachedhead상태에서 커밋하면 브랜치에 연결이 되지않은 커밋이 생성되서 추후 접급하기 어려움.</p>
</li>
<li><p><strong>특정 파일을 이전버전으로 되돌리기:git checkout [커밋해시] -- [파일명]</strong>
 만약 한 커밋에서 여러 파일의 변경사항이 있었고, 그 후에 다른 커밋을 수행하여 여러 파일을 수정했는데, 그 중 한 파일만 특정 커밋 시점의 상태로 되돌리고 싶을 때 사용하는 것.
 git restore --source=[커밋해시] [파일명]</p>
</li>
</ol>
<blockquote>
<p>여기서 잠깐! 브랜치가 원격 저장소에만 존재하는 경우: 로컬 저장소에는 없지만 원격 저장소에는 있는 브랜치인 경우
1.git fetch - 최신정보 불러와서
2.git branch -a 명령을 사용하여 모든 브랜치(로컬 및 원격)를 확인
3.git checkout -b 가져올브랜치이름 origin/가져올브랜치이름 : 즉 따로 리모트에 있는 브랜치를 로컬에 만들어줘야해</p>
</blockquote>
<h3 id="브랜치위치는-가져올곳--git-pull-origin별칭-가져올내용이담긴브랜치">브랜치위치는 가져올곳 : git pull origin(별칭) (가져올내용이담긴브랜치)</h3>
<p>이것은 <strong>git fetch origin 브랜치 + git merge origin 브랜치 합친것</strong> 인데, 새로운 브랜치에서 정보를 받아온다거나 할때는 git pull 하는게 빠르지만</p>
<p>만약 데이터가 달라져있는게 로컬에 있다면 git fetch 하고 merge하는게 좋은상황일때도 있다.</p>
<p>허나 pull을 하는 것이 대부분인것이 애초에 다른 값이 들어있다면(로컬과 원격브랜치가 divergent발산) 병합이 되지않기에 그것을 확인해주는 작업을 하게된다.</p>
<blockquote>
<p>발산이란?</p>
</blockquote>
<p>로컬 브랜치와 원격 브랜치가 서로 다른 지점에서 변경이 발생하여 각각 다른 방향으로 진행되었다는 것으로 ::
처음에는 로컬과 원격브랜치 모두 같은 커밋A에서 시작,
로컬에서 작업을 진행해서 새로운 커밋 B를 생성
근데 동시에 다른 개발자가 작업을 진행해서 새로운 커밋 C를 생성하고 푸시</p>
<p>이렇게 되면 로컬브랜치와 원격브랜치는 서로 다른지점에서 변경이 발생
이것을 발산이라합니다.</p>
<p>발산되지 않은 상태라 하면 단순진행(fast-foward)인 상황이라 할 수 있다.
원격브랜치가 로컬 브랜치보다 앞선 커밋을 가지고 있지만, 로컬 브랜치는 추가적인 커밋이 없는 상태를 말한다. 즉, 최신 커밋으로 직진하는 형태에는 발산이라 칭하지 않고 merge를 하더라도 커밋이 생성되지 않는다.</p>
<p><strong>추가적으로 단순진행(fast-foward) 더 예를 들자면:</strong></p>
<ol>
<li>다른사람의 작업 가져올때:
 내 로컬에는 새로운 커밋이 없는 상태(일정기간 작업이 없는 등등)에서 최신 변경사항을 가져올때</li>
</ol>
<ol start="2">
<li><p>원격저장소에서 직접 작업한 내용 가져올때 :
 Git-Hub같은 웹 인터페이스를 통해 직접 파일을 수정하거나, readme갱신, 또는 머지된 pull-request의 내용을 로컬로 가져올때</p>
<p>그렇기에 <strong>git pull --ff-only로 명시적으로 지정</strong> 하여 단순진행 머지를 실행하면, 벼롣의 병합커밋없이 히스토리가 깔끔히 유지된 상태에서 원격 브랜치의 상태로 갱신이 가능합니다. 히스토리 추적이나 이해하기가 더 쉽다는 장점이 있다.</p>
</li>
</ol>
<h3 id="git-fetch-origin-브랜치명">git fetch origin 브랜치명</h3>
<p>원격 저장소의 최신 변경사항(커밋, 브랜치, 태그 등)을 로컬에 가져오지만, 로컬의 작업 디렉토리에는 아무런 변경이 보이지 않는다.</p>
<p>허나 머지전 내용을 확인하거나 충돌을 예방을 위해 사용하고 그 이후 git rebase를 활용해 커밋없이 머지 시키고 싶을때 사용할 수 있다.</p>
<p>git merge origin/[원격브랜치명] :현재 브랜치에 원격의 변경사항을 병합
git rebase origin/[원격브랜치명] :현재 브랜치의 커밋들을 원격 브랜치 위로 재배치 =&gt; 커밋히스토리가 달라짐.</p>
<p>이 차이점을 보려하면 </p>
<p><strong>1. log로 차이점 보기</strong>
main 브랜치에는 없고 origin/main 브랜치에만 있는 커밋들을 보여줍</p>
<pre><code>git log main..origin/main</code></pre><p>반대로,
origin/main 브랜치에는 없고 main 브랜치에만 있는 커밋들을 보여줌</p>
<pre><code>git log origin/main..main</code></pre><p><strong>2. git diff로 차이점 보기</strong>
이건 코드를 직접 보여주기때문에 : 달라진점 초록색글씨에 빨간색커서</p>
<pre><code>  git diff main..origin/main &gt; diff_output.txt</code></pre><p>  이렇게 파일로 아웃풋 시켜 원본과 비교해줄 수 있다.</p>
<p><strong>3. vscode Extention 사용하기</strong>
 Source Control 패널 =&gt; 변경사항 섹션에서 파일을 클릭하여 변경 내용을 확인확인.
특정 브랜치 간의 차이점을 보려면, VSCode의 터미널에서 git checkout을 사용하여 원하는 브랜치로 전환한 후 위의 절차를 따르면 된다.</p>
<p><strong>git fetch -p</strong>/<strong>git fetch --prune</strong>
(-p 옵션은 원격에서 삭제된 브랜치를 로컬에서도 정리)</p>
<p>로컬과 다르게 원격에서 삭제된 브랜치가 있을때, 일반 git fetch 만 사용하면
로컬의 원격 추적 브랜치 목록과 실제 원격 저장소의 브랜치 목록이 동기화되지 않을 수 있다.</p>
<p>로컬의 원격 추적 브랜치:
로컬에서 원격을 추척하고 비교하기위한 참조 역할인데,
origin/branch-name 형태로 표현하고 fetch실행시 업데이트되는데 
제대로 동기화 안되면 활용하기 힘들다.
git diff main..origin/main 과 같은 문구 활용할때 문제 생길 수 있다. 메인이라 그렇진 않겠지만 다른 삭제된 브랜치라면.</p>
<h3 id="git-merge">git merge</h3>
<p>원격 저장소의 변경사항을 현재 체크아웃된 로컬 브랜치와 병합</p>
<p>git fetch로 가져온 변경사항들을 merge로 병합하려면 
git fetch로 가져온 최신커밋을 가르키는 특별한 포인터인 FETCH-HEAD를 사용해야한다.</p>
<blockquote>
<p>git merge FETCH_HEAD</p>
</blockquote>
<p>Fast-forward 병합: 로컬 브랜치가 원격 브랜치의 직접적인 이전 상태일 때, Git은 단순히 로컬 브랜치의 포인터를 원격 브랜치의 최신 커밋으로 이동시킵니다. 이 경우 새로운 커밋은 생성되지 않습니다.</p>
<p>3-way 병합: 로컬과 원격 브랜치가 발산(diverged)한 경우, Git은 마지막 공통 조상(커밋)과 이 두 브랜치의 최신 커밋을 사용하여 병합을 수행하는 방식으로두 브랜치의 변경사항을 병합하여 새로운 &quot;Merge commit&quot;을 생성합니다.</p>
<p>a-b-c(branch 1)
a-d-e(branch 2)</p>
<p>즉, 마지막 공통 조상 커밋 a 확인 -&gt; 브랜치들 커밋확인 -&gt; 병합시도 
3단계를 거친다는 의미.</p>
<h3 id="git-rebase-브랜치-명">git rebase 브랜치 명.</h3>
<blockquote>
<p>git rebase전단계로 취소해서 돌아가기 
git reset --hard HEAD@{1}</p>
</blockquote>
<p>리베이스를 사용하려면 일단
당연히 합쳐질 브랜치에 fetch를 해야한다
그 이후에 rebase를 사용하는데,
그다음에 push</p>
<p>git rebase를 사용하면, 현재 브랜치의 커밋들이 일시적으로 제거되고, 대상 브랜치의 모든 커밋이 먼저 적용되면서 커밋히스토리가 선형화된다.</p>
<p>예를 들면::</p>
<p>A-B-C (m branch)
 <br>  D-E (y branch)
현재 y브랜치에서 git rebase m 하면
일시적으로 y브랜치의 변경사하인 D E를 제거하고,
m브랜치의 A- B -C가 적용되고 
순차적으로 y브랜치의 D - E가 적용 </p>
<p>그후엔</p>
<p>A-B-C (m branch)
     <br>      D-E (y branch)</p>
<p>이렇게 변경.</p>
<pre><code>비선형::merge할 경우 커밋이 두개의 부모커밋을 가지게되면서 복잡해지거나, 동시에 여러 사용자가 다른브랜치에 작업하면 커밋히스토리가 나뉘어져 매우 복잡해진다.

선형:: 근데 리베이스를 사용하면 병합커밋없이 연속적인 일련의 변경사항으로 재배치해 =&gt; 단순하고 가독성있게 유지하면서 동인한 변경내용을 포함.</code></pre><h4 id="rebase-충돌">rebase 충돌</h4>
<p>리베이스를 하다 충돌이 발생할 수있는데 충돌이 발생하면 충돌을 해결 한 후
:: git rebase --continue를 하여 rebase과정을 계속 진행해준다.</p>
<p>그리고 나서 push를 하게되는데 이때 문제가 발생할 확률이 높다.</p>
<h4 id="push-충돌">push 충돌</h4>
<p>git rebase를 사용하면 커밋의 내용이나 순서가 바뀌면 해당 커밋의 해시값도 변경되기때문에, 리베이스된 로컬 브랜치의 커밋 히스토리와 원격 저장소의 브랜치의 커밋 히스토리가 서로 다르게 될 수 있다. </p>
<p>그렇게되면 push는 변경사항을 원격에 반영할 수 없게되는데 
이상황에서 원격에 반영하려면 
=&gt; git push --force
=&gt; git push --force-with-lease
   이건 단지 하나의 브레이크를 더 주는것 뿐인데,
   원격 브랜치의 상태가 로컬의 마지막 알려진 상태와 일치하는지 확인.
   =&gt; 다시 말해, 그 사이에 다른 변경사항이 푸시되지 않았는지 확인해주는것.</p>
<p>   그렇다고 해서 다른 팀원이 이전 커밋을 pull해서 push 할 계획이 있는지에 대한 정보가 없기에 최소한의 브레이크이다.</p>
<p>이렇게 강제로 push 하면 추후에 팀원들의 최신 변경사항을 가져올때 문제가 발생할 확률이 높아 팀프로젝트를 할때는 권장되는 방법은 아니다.</p>
<h4 id="pull충돌">pull충돌</h4>
<p>이것은 git pull할때도 마찬가지인데, 
git fetch + merge이니까 merge작업을 할때 
merge , rebase, fast-forward 할 것인지 정하라 하는데 
그 중 
git config pull.rebase False(merge)/ 
git config pull.rebase True(rebase)/ 
git pull --ff-only(fast-forward) 
리베이스를 선택하게 되면, 원격저장소의 변경사항을 가져와서 현재 브랜치 커밋들을 그위로 재배치하게되면 이것도 커밋히스토리가 재구성되어 권장되지 않는다.</p>
<p>그렇기에 위에서 언급했다 시피 팀 프로젝트보다는 혼자 개인프로젝트를 할때나 혼자 작업하는 브랜치에서는 리베이스를 사용하여 커밋 히스토리를 깔끔하게 유지하는것이 좋을 수 있다.</p>
<p>이것 왜에도 pull이 당겨지지 않을 때가 있는데,
이 이유는 이미 현재 상태에서 수정이 되어있고 그 것과 다른 또 수정된 무언가를 pull로 땡겨오려하면 충돌이 생겨 풀이 안될때가 있다. 그럴땐 =&gt;
현재 수정하고 있는 브랜치에 먼저 커밋을 해주고 난 다음에 주로 master에서 가져오니 가져와야할 브랜치에서 pull해주면 내것과 pull된 자료의 차이점을 fetch처럼 보여주고 그것을 보며 수정해준다음에 push해주면 된다.</p>
<h4 id="문제들의-예">문제들의 예:</h4>
<p>먄약 커밋을 원격에 했던 상태 (다른사람이 올렸든,내가 이전에 올렸든)
이미 원격과 달라지게된다 rebase를 해서 push하면</p>
<p>내가 rebase해서 커밋히스토리를 변경해 push한 후에 다른 팀원이 이전커밋의 히스토리를 가지고 있는 변경사항을 push하려할때 커밋히스토리가 다르기에 push에 문제가 생기게된다.(git push --force-with-lease 체크해서 푸시해도)</p>
<blockquote>
</blockquote>
<p>git rebase -i / git rebase --interactive라는 대화형 리베이스도 있는데 커밋히스토리를 텍스트 에디터에서 직접 수정할 수있다.
예)
커밋 순서 변경
커밋 메시지 수정
커밋 합치기 (squash)
커밋 분리 (split) 등등</p>
<h3 id="파일삭제">파일삭제</h3>
<p>먼저 로컬에서 파일삭제
<strong>git rm &lt;파일명&gt;</strong></p>
<p>커밋
<strong>git commit -m &quot;파일 삭제: &lt;파일명&gt;&quot;</strong></p>
<p>그리고 변경사항 원격저장소 푸시
<strong>git push origin &lt;브랜치명&gt;</strong></p>
<p>헷갈려 하는 부분들이 있는데:</p>
<h4 id="git-rm---cached-파일명">git rm --cached &lt;파일명&gt;</h4>
<p>git rm --cached &lt;파일명&gt;만을 사용하면 해당 파일은 로컬 Git의 추적 목록에서만 제거하는 것이지 원격저장소에 있는 파일을 삭제시키는 것이 아니다.</p>
<p>git 추적 목록에서 제거되고 untracked상태가 되지만 아직 이 변경을 커밋도지않아서 git status 하면 changes to be committed 영역에 나열된다. 
즉, 커밋을 하지않으면 로컬이력에는 아무 변화가 반영되지 않는다 
=&gt; 변경사항을 stage영역에 추가했다하더라고 커밋히스토리에는 추가하지않아 영구적으로 기록하지 않아 현재 변경이 된 상태는 아닌것이라 할 수 있다.</p>
<h4 id="사용법">사용법::</h4>
<p>잘못 추적하고 있는 파일 제거: 예를 들어, 기본설정파일 등등 .gitignore에 포함되어야 하는데 잘못해서 Git에 추가된 파일을 추적에서 제거하고 싶을 때 사용한다.</p>
<p>그냥 .gitignore에 추가하면 되는거 아냐? 하겠지만 </p>
<p>기본적으로 새로운 파일은 untracked 상태로 존재.
아직 그 파일의 변경사항을 추적하고 있지 않다는 것을 의미한다.
git add실행시 git의 추적은 시작되고 해당 파일은 staged 상태로 변경된다. 커밋을 하면 staged상태의 모든 변경사항이 커밋에 포함되고, 커밋 후 해당 파일 git에 의해 tracked상태로 관리.</p>
<p>그렇기에 그이후에 
.gitignore에 추가하는 것만으로는 이미 추적 중인 파일이 Git의 추적에서 제거되지는 않기때문에 그 파일의 변경사항은 계속해서 Git에 감지될것이기 때문.</p>
<h4 id="git-rm---cached-파일명--파일을-git의-추적-목록에서-제거">git rm --cached 파일명:  파일을 Git의 추적 목록에서 제거</h4>
<h4 id="gitignore에-파일-추가---변경사항">.gitignore에 파일 추가 :  변경사항</h4>
<h4 id="git-commit--m-변경사항-커밋--로컬-저장소의-git히스토리에는-해당파일이-삭제-git-push-안하고-멈추면-원격저장소에-반영-안되-해당파일-존재">git commit -m &quot;변경사항 커밋&quot;:  로컬 저장소의 git히스토리에는 해당파일이 삭제 (git push 안하고 멈추면 원격저장소에 반영 안되 해당파일 존재)</h4>
<h4 id="git-push---원격저장소-해당-파일-삭제">git push :  원격저장소 해당 파일 삭제</h4>
<p>하는 순으로 해줘야 원격에 올라간 파일도 삭제되고 ignore상태가 된다.</p>
<h3 id="collaborator">collaborator</h3>
<p><strong>팀 레포지토리 만들기.</strong></p>
<p>먼저 레포지토리 만들어 =&gt; 설정들어가 =&gt; collaborator들어가서 팀원들 깃헙 유저네임이나 이메일로 invitation보내주고 팀원들이 허락하면 끝</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[python : :캡슐화 정의/ 상속의 정의/ super().__init__() 사용법]]></title>
            <link>https://velog.io/@stella_k/python-%EC%BA%A1%EC%8A%90%ED%99%94-%EC%A0%95%EC%9D%98-%EC%83%81%EC%86%8D%EC%9D%98-%EC%A0%95%EC%9D%98-super.init-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@stella_k/python-%EC%BA%A1%EC%8A%90%ED%99%94-%EC%A0%95%EC%9D%98-%EC%83%81%EC%86%8D%EC%9D%98-%EC%A0%95%EC%9D%98-super.init-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Thu, 31 Aug 2023 03:53:34 GMT</pubDate>
            <description><![CDATA[<p>클라스 속성을 배우다보면, 상속하는 형태의 클라스들을 볼 수 있다.</p>
<h3 id="상속-inherit">상속 (inherit)</h3>
<p>상속은 이미 정의된 클래스에서 속성과 메서드를 물려받아 새로운 클래스를 생성하는 것을 말합니다. 
=&gt; 메소드와 속성을 재사용 =&gt; 코드 중복을 줄이고 모듈성을 높인다.</p>
<pre><code># 기본 클래스 (부모 클래스)
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass

# 파생 클래스 (자식 클래스)
class Dog(Animal):
    def speak(self):
        return f&quot;{self.name} says Woof!&quot;

### class Cat(Animal):
    def speak(self):
        return f&quot;{self.name} says Meow!&quot;

# 객체 생성 및 사용
dog = Dog(&quot;Buddy&quot;)
cat = Cat(&quot;Kitty&quot;)

print(dog.speak())  # 출력: Buddy says Woof!
print(cat.speak())  # 출력: Kitty says Meow!
</code></pre><p>Animal 이라는 클라스를 상속받아 그것의 속성인 name 과 메소드를 재사용한것.</p>
<h3 id="super">super()</h3>
<p>현재 클래스의 부모클래스를 참조 -&gt; 일종의 프록시 객체를 반환합니다.</p>
<p>간단히 말해 프록시 객체: 
동일한 데이터베이스 사용, 하지만 python 코드 상에서의 행동을 변경하고 싶을 때 사용 =&gt; 커스터마이징</p>
<p>여기서 프록시 객체 프록시 모델은 위의 설명과 같게:</p>
<p>기존모델과 동일한 데이터베이스를 사용하지만,
그 모델의 python레벨에서의 행동(메타,옵션,메서도,매니저 등의 python코드상에서의 행동)만을 수정하는것이라고 말할 수 있다.</p>
<p>사용사례:</p>
<ol>
<li>같은 데이터에서 다른 phython레벨에서의 동작이 필요하다던가</li>
<li>기본 쿼리셋 변경이 필요할때 : (relatedmanager와 같은)모델 메니저를 사용하여 변경</li>
</ol>
<p>예) 장고에서의 프록시 모델</p>
<pre><code>from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

class MyPerson(Person):
    class Meta:
        proxy = True

    def do_something(self):
        # 여기에 특정 동작을 정의
        pass
</code></pre><p>위의 Person모델과 같은 데이터베이스 테이블을 사용하지만, 추가적인 do_something 이라는 메서드를 가지고 있다.</p>
<p>프록시 모델을 사용하려면, Meta 클래스 내에서 proxy 옵션을 True로 설정해야 한다.</p>
<p>이렇게 프록시 모데를 사용할때는 </p>
<h4 id="프록시">프록시</h4>
<p>프록시 패턴은 객체 지향 설계 패턴 중 하나로,</p>
<p>-제어목적(접근 제어, 호출), 
-지연로딩(일부분 초기화, 필요한 시점에 전체객체 로딩), 
-로그,보안및 검증(메서드 호출 로그를 기록, 보안검사 수행,입력검증 등), 
-분산시스템(원격프록시 사용 -&gt; 원격객체의 로컬 인터페이스를 제공) </p>
<p>다양한 목적으로 프록시를 사용하여 기능을 제어하거나 확장할 수 있도록 합니다.</p>
<p>따라서 super().<strong>init</strong>() 형태로 부 초모 클래스의기화 메서드를 호출할 수 있습니다.</p>
<pre><code>class BaseClass:
    def __init__(self):
        print(&quot;BaseClass의 __init__ 메서드 호출&quot;)

class DerivedClass(BaseClass):
    def __init__(self):
        super().__init__()  # 여기서 BaseClass의 __init__ 메서드를 호출
        print(&quot;DerivedClass의 __init__ 메서드 호출&quot;)

obj = DerivedClass()


#출력값
BaseClass의 __init__ 메서드 호출
DerivedClass의 __init__ 메서드 호출
</code></pre><p>코드를 보면 DerivedClass().<strong>__</strong>init______함수안에 부모클라스인 BaseClass()의 메소드인 <strong>__</strong>init______()을 호출하고 그 다음것을 실행 print(&quot;DerivedClass의 <strong>init</strong> 메서드 호출&quot;) 해주고 있는것.</p>
<h3 id="캡슐화-encapsulation">캡슐화 (Encapsulation)</h3>
<p>객체지향 프로그래밍에서는 데이터와 그 데이터를 다루는 메서드들을 하나로 묶어 캡슐화합니다.</p>
<p>캡슐화된 객체는 외부에서 직접적으로 접근할 수 없으며, 메서드를 통해서만 데이터를 조작할 수 있습니다.</p>
<p>이를 통해 데이터의 보안성과 안정성을 높일 수 있습니다.</p>
<pre><code>class Example:
    def __init__(self):
        self.__private_var = &quot;I am private&quot;

obj = Example()
# print(obj.__private_var)  # 이 코드는 오류를 발생시킵니다.

# 그러나 다음과 같이 이름 변경된 속성에 접근하는 것은 가능합니다.
print(obj._Example__private_var)  # &quot;I am private&quot;를 출력합니다.
</code></pre><p>캡슐화는 객체의 상태와 기능을 숨기고 (즉, 세부 구현을 숨기고) 공개 인터페이스만을 제공하는 개념입니다.</p>
<p><strong>__ 접두사를 사용하여 변수나 메서드를 &quot;비공개&quot;로 설정하면 이름 변경(name mangling)이 발생하여 직접적인 접근이 어려워</strong></p>
<pre><code>=&gt; __(더블언더스코어)접두사가 붙은 변수나 메소드는 이름 변경(name mangling)이 발생.
이 이름 변경의 결과로 실제 변수나 메서드 이름은
_클래스이름__변수명 또는
_클래스이름__메서드명 형태로 바뀝니다.</code></pre><p>즉 위에 print(obj._Example______private_var)는 <strong>__</strong>priavate_var를 비공개 처리하면서 이름이 변경이 발생되어서=&gt; _Example__private_var로 바뀌었기 때문에 접근이 가능해 프린트 되어진것이다.</p>
]]></description>
        </item>
    </channel>
</rss>