Django insta pj (2)

Haks.·2025년 2월 3일
0

How to use

목록 보기
18/32

인스타 기능 구현 PJ

📝 기본 장고 설정

  1. python manage.py startapp post, installed_app 추가
  2. post/models.py 추가
  • 이미지
  • 작성자
  • 수정일자
  • 작성일자
  1. 작성일자, 수정일자 공통부부분 utils/models.py 생성 후 제작
from django.db import models

class TimestampModel(models.Model):
    created_at = models.DateTimeField('생성일자',auto_now_add=True)
    updated_at = models.DateTimeField('작성일자', auto_now=True)
    
    class Meta: 
        abstract = True # 종속되는 것이기에 abstract
  1. models.py 제작
from django.contrib.auth import get_user_model
from django.db import models
from utils.models import TimestampModel

User = get_user_model()

class Post(TimestampModel):
    content = models.TextField('본문')
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    
    def __str__(self):
        return f'{self.user} post' # User의 __str__을 nickname 으로 지정해놔서 self.user 는 닉네임 호출
    
    class Meta:
        verbose_name = '포스트'
        verbose_name_plural = '포스트 목록'

class PostImage(TimestampModel):
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    image = models.ImageField('이미지', upload_to='post/%Y/%m/%d')
    
    def __str__(self):
        return f'{self.post} image'

    class Meta:
        verbose_name ='이미지'
        verbose_name_plural='이미지 목록'
  1. 이미지 필드 migrate 를 위해 poetry add Pillow -> makemigrations
  2. admin단 먼저 제작해보기
# post/admin.py
from django.contrib import admin
from post.models import PostImange, Post

class PostImageInline(admin.TabularInline): # post 안에 들어가야 하니까 inline
    model = PostImage
    fields=['image']
    extra = 1

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    inlines = [
        PostImageInline,
    ]
  1. admin 페이지 들어가서 post 생성 해보기
  2. 커스텀 유저로 인해 admin페이지 단에서 유저가 나오지 않음
# member/admin.py
from django.contrib import admin

from member.forms import User

@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    ...
  1. admin페이지 에서 들어가 있나 확인
  2. 이미지 경로를 지정해주지 않아서 post 폴더안에 들어가 있다. 경로 media 로 지정되게 해주자, settings,url 작성
# settings.py 
# Media
MEDIA_URL = 'media/'
MEDIA_ROOT = BASE_DIR / 'media'

# urls.py
from django.conf.urls.static import static
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
  1. admin페이지에서 사진 등록시 media 로 들어가는거 확인

📝 포스트 목록페이지 제작 ListView

  1. views.py 제작 및 url 작성시 post는 메인 페이지이기에 include 하지 않고 메인 url 로 연결
# post/views.py
from django.views.generic import ListView
from post.models import Post

class PostListView(ListView):
	queryset = Post.objects.all().select_related('user')
    template_name = 'post/list.html'
    paginate_by = 20
    ordering = ['created_at']

# config/urls.py
	path('', post_views.PostListView.as_View(), name='main'),
<!-- post/list.html  -->
{% extends 'base.html' %}
{% block content %}
    <div>

    </div>
{% endblock %}
  1. TODO의 메인페이지로 redirect로 한것 작업 처리
  2. list.html 작성, content 나오게, html 에서 ForeignKey 관련해서 쉽게 쓰기위해 models PostImage의 post에 related_name ='images'추가
<!-- post/list.html -->
{% extends 'base.html' %}
{% block content %}
    <div class="row">
        <div class="col-10 offset-1 col-lg-6 offset-3">
            {% for post in object_list %}
                    <!-- PostImage 모델에 related_name images 라 적어서 아래와 같이 사용 가능 아니면 post.postimage_set 을써야함 -->
                    {% for image in post.images.all %}
                    <div class="border-1">
                            <img class="img-fluid post-image" src="{{ image.image.url }}" alt="">
                    </div>
                    {% endfor %}
                </div>
            {% endfor %}
        </div>
    </div>
{% endblock %}
  1. 여러개의 이미지를 추가하기 위해 swiper모델을 가져올 거임 구글에서 swiper 검색 -> Get Started -> 카테고리 Use Swiper from CDN 클릭 -> 해당링크 ->헤드부분에 추가, 푸터부분 추가 -> viewport 적용 접속한 디바이스 넓이를 적용해주는 것
<!-- base.html -->
    {% block style %}{% endblock %}
</head>
<script src="{% static 'js/bootstrap.bundle.js' %}"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- </body> 바로위에 이부분 추가 -->
{% block js %}{% endblock %}
</body>
<!-- post/list.html 부분 위아래 추가 -->
{% extends 'base.html' %}
<!-- 이부분 추가 -->
{% block style %}
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"/>
{% endblock %}
{% block content %}
    <div class="row">
        <div class="col-10 offset-1 col-lg-6 offset-3">
            {% for post in object_list %}
                <div class="border-1">
                    <img class="img-fluid" src="{{ post.images.first.image.url }}" alt="">
                </div>
            {% endfor %}
        </div>
    </div>
{% endblock %}
<!-- 아래에 이부분 추가 -->
{% block js %}
    <script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
{% endblock %}
  1. swiper 를 이용해 slide 기능 추가해보기, js 부분 swiper 홈페이지의 Initialize Swiper 부분 가져오기 -> 페이지네이션 넣고 싶으면 홈페이지에서 긁어와서 wrapper 랑 같은 위치에 붙여넣기 -> 이미지 사이즈 수정 위에 style 수정 -> 밑에 가져온거 안쓰는거 지워주고, loop=false 수정
 <!-- post/list.html -->
{% extends 'base.html' %}
{% block style %}
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"/>
    <style>
        .post-image{
            aspect-ratio: 1 / 1;
            object-fit: cover;
        }
    </style>
{% endblock %}
{% block content %}
    <div class="row">
        <div class="col-10 offset-1 col-lg-6 offset-lg-3">
            {% for post in object_list %}
                <div class="swiper" style="max-height: 500px;"> <!-- 메인 swiper -->
                    <div class="border-1 swiper-wrapper"> <!-- 이미지가 여러개 있을거니까 여기에 wrapper -->
                        {% for image in post.images.all %}
                             <div class="swiper-slide">
                                <img class="img-fluid post-image" src="{{ image.image.url }}" alt="">
                             </div>
                        {% endfor %}
                    </div>
                    <div class="swiper-pagination"></div>
                </div>
            {% endfor %}
        </div>
    </div>
{% endblock %}
{% block js %}
    <script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
    <!-- swiper Initialize Swiper 부분 가져오기 -->
    <script>
        const swiper = new Swiper('.swiper', {
          // Optional parameters
          direction: 'horizontal',
          loop: false,

          // If we need pagination
          pagination: {
            el: '.swiper-pagination',
          },
        });
    </script>
{% endblock %}
  1. 아이콘 사용 font awesome 사용하기 구글 font awesome -> start for free -> download -> Free For Web -> 복사 -> static/font-awesome 붙여넣기 -> font-awesome 사이트에서 DOCS -> getting started -> Host Yoursef(Webfonts) -> 링크부분 복사 -> base.html 메타 밑에 붙여넣기 -> 안의 path 수정 -> list 부분에 아이콘 사용을 위해 코드 수정 -><i class="fa-solid fa-user"></i> 이 코드 넣어서 아이콘 모양 쓰기 -> content 나오게 하는데 텍스트의 줄넘김 처리해주는 문법 사용 {{ post.content | linebreaksbr }} ->
<!-- base.html -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- font awesome 적용 -->
    <link href="{% static 'font-awesome/css/fontawesome.css' %}" rel="stylesheet" />
    <link href="{% static 'font-awesome/css/custom-icons.css' %}" rel="stylesheet"/>
    <link href="{% static 'font-awesome/css/sharp-solid.css' %}" rel="stylesheet" />

<!-- list.html -->
{% extends 'base.html' %}
{% block style %}
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"/>
    <style>
        .post-image{
            aspect-ratio: 1 / 1;
            object-fit: cover;
        }
    </style>
{% endblock %}
{% block content %}
    <div class="row">
        <div class="col-10 offset-1 col-lg-6 offset-lg-3">
            {% for post in object_list %}
                <div class="border-bottom my-4">
                    <div class="mb-2">
                        <span class="p-2 border rounded-circle me-2">
                            <i class="fa-solid fa-user" style="width: 16px; padding-left: 1px;"></i>
                        </span>
                        <!-- 작성자 -->
                        {{ post.user.nickname }}
                    </div>
                        <div class="swiper" style="max-height: 500px;"> <!-- 메인 swiper -->
                            <div class="border-1 swiper-wrapper"> <!-- 이미지가 여러개 있을거니까 여기에 wrapper -->
                                {% for image in post.images.all %}
                                    <div class="swiper-slide">
                                        <img class="img-fluid post-image" src="{{ image.image.url }}" alt="">
                                     </div>
                                {% endfor %}
                            </div>
                        <div class="swiper-pagination"></div>
                    </div>
                    <div>
{#      TODO : like            #}
                    </div>
                    <div class="my-2">
                        {{ post.content | linebreaksbr }}
                    </div>
                </div>
            {% endfor %}
        </div>
    </div>
{% endblock %}
{% block js %}
    <script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
    <!-- swiper Initialize Swiper 부분 가져오기 -->
    <script>
        const swiper = new Swiper('.swiper', {
          // Optional parameters
          direction: 'horizontal',
          loop: false,

          // If we need pagination
          pagination: {
            el: '.swiper-pagination',
          },
        });
    </script>
{% endblock %}
  1. views.py prefetch_related 추가
    queryset = Post.objects.all().select_related('user').prefetch_related('images')
# post 가 foreginkey 를 가지고 있으면 select_related 가능 (join)
# 역참조 일때 prefetch_related (파이썬에서 알아서 가져오는것)

📌 무한 스크롤

. waypoint js 검색 -> imakewebthings/waypoints 깃 접속 -> 내려서 shortcuts에 infinite scroll 접속 -> About -> 다운 -> 다시 돌아가서 인피니티 스크롤에서 필요한 2가지 파일을 확인 -> lib 에서 찾아서 static 안에 waypoints 라는 폴더 생성 후 폴더안에 넣기 -> list 페이지에서 무한스크롤 제작 -> script 넣기 -> 상단에 {% load static %} -> swiper 밑에 let infinite = new Waypoint.Infinite({ element: $('.infinite-container')[0] }) 이부분 붙여넣기 -> infinite 컨테이너 넣어주기
2. shell로 포스트 여러개 늘리기

post_list = Post.objects.all()
for _ in range(20):
    for post in post_list:
        images=PostImage.objects.filter(post=post)
        post.id=None
        post.save()
        for image in images:
            image.id = None
            image.post = post
            image.save()
  1. waypoints가 jquery를 사용하기에 jquery 설치
  2. 구글 jquery -> 접속 -> download jquery -> 텍스트 전체 복사 -> static js에 파일 추가 (jquery.min.js) -> <scirpt> 추가 -> jquery란 자바스크립트를 좀더 간단하게 쓸수있게 해주는 패키지
  3. infinit 보면 각각에 infinite-item 을 넣어주라 나와있다. -> 그리고 링크를 넣으라고 나와있다.
  4. offset: 'bottom-in-view' let infinite 설정 element 밑에 추가
{% extends 'base.html' %}
{% load static %}
{% block style %}
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"/>
    <style>
        .post-image{
            aspect-ratio: 1 / 1;
            object-fit: cover;
        }
    </style>
{% endblock %}
{% block content %}
    <div class="row">
        <div class="col-10 offset-1 col-lg-6 offset-lg-3 infinite-container">
            {% for post in object_list %}
                <div class="border-bottom my-4 infinite-item">
                    <div class="mb-2">
                        <span class="p-2 border rounded-circle me-2">
                            <i class="fa-solid fa-user" style="width: 16px; padding-left: 1px;"></i>
                        </span>
                        <!-- 작성자 -->
                        {{ post.user.nickname }} {{ post.id }}
                    </div>
                        <div class="swiper" style="max-height: 500px;"> <!-- 메인 swiper -->
                            <div class="border-1 swiper-wrapper"> <!-- 이미지가 여러개 있을거니까 여기에 wrapper -->
                                {% for image in post.images.all %}
                                    <div class="swiper-slide">
                                        <img class="img-fluid post-image" src="{{ image.image.url }}" alt="">
                                     </div>
                                {% endfor %}
                            </div>
                        <div class="swiper-pagination"></div>
                    </div>
                    <div>
{#      TODO : like            #}
                    </div>
                    <div class="my-2">
                        {{ post.content | linebreaksbr }}
                    </div>
                </div>
            {% endfor %}
        <!-- has_next : 다음페이지가 있는지 확인하는 기능 -->
        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}" class="infinite-more-link d-none"></a>
        {% endif %}
        </div>
    </div>
{% endblock %}
{% block js %}
    <script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
    <script src="{% static 'js/jquery.min.js' %}"></script>
    <script src="{% static 'waypoints/jquery.waypoints.min.js' %}"></script>
    <script src="{% static 'waypoints/infinite.min.js' %}"></script>
    <!-- swiper Initialize Swiper 부분 가져오기 -->
    <script>
        const swiper = new Swiper('.swiper', {
          // Optional parameters
          direction: 'horizontal',
          loop: false,

          // If we need pagination
          pagination: {
            el: '.swiper-pagination',
          },
        });
        let infinite = new Waypoint.Infinite({
          element: $('.infinite-container')[0],
            offset: 'bottom-in-view'

        })
            </script>
{% endblock %}

📝 Post 생성 및 수정 페이지 제작 CreateView, UpdateView

  1. views.py 제작 -> template form.html 제작 -> form 쓰려면 form 제작
# post/forms.py 제작
from django import forms

from post.models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model=Post
        fields='__all__'

# post/views.py
class PostCreateView(LoginRequiredMixin,CreateView):
    model = Post
    template_name = 'post/form.html'
    form_class = PostForm

    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.user = self.request.user # 유저는 입력 부분이 없을테니까 지정
        self.object.save()

        return HttpResponseRedirect(reverse('main'))

# config/urls.py 연결
    path('create/', post_views.PostCreateView.as_view(), name= 'create'),
<!-- post/form.html -->
{% extends 'base.html' %}
{% block content %}
    <div class="row">
        <div class="col-10 offset-1 col-lg-6 offset-lg-3">
            <form action="" method="post">
                {% csrf_token %}
                {{ form.as_p }}
                <button class="btn btn-primary">저장</button>
            </form>
        </div>
    </div>
{% endblock %}
  1. forms.py 수정, 공통적으로 사용할 수 있으니까 utils/forms.py 생성, form-control 을 줄것임 -> 위아래로 길이 조절 방지하려고 static/css/style.css 생성-> 만든것 base.html에 적용
# utils/forms.py 
from django import forms

class BootstrapModelForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args,  **kwargs)
        for name, field in self.fields.items():
            if 'class' in field.widget.attrs: # 클래스가 있으면
                field.widget.attrs['class'] += ' form-control' # 텍스트로 들어가니까 띄어쓰기 해야함
            else:
                field.widget.attrs.update({'class': 'form-control'}) # 없으면 클래스를 넣는것

# post/forms.py 에 상속
from post.models import Post
from utils.forms import BootstrapModelForm


class PostForm(BootstrapModelForm):
    class Meta:
        model=Post
        fields=('content',) # 생성시 user 선택은 있으면 안되니까 제외 하고 content 만 설정
  1. fomr.html 에서 이미지 추가버튼과, 여러개의 이미지를 업로드할 수 있게 구현
  2. jquery fomrset 검색 -> cdnjs jqueryformset/Libraries 접속 -> https: 링크 복사후 주소창에 붙여넣기 -> jqueryformset 라이브러리가 나옴 그거 복사후 -> js에 파일 생성 static/js/jquery.formset 생성후 붙여넣기 -> form.html block js 활용해서 수정
  3. formset은 댓글에서 tabluer 햇던것처럼 안에서 추가할 수 있는 기능
{% block js %}
    <script src="{% static 'js/jquery.min.js' %}"></script>
    <script src="{% static 'js/jquery.formset.js' %}"></script>
{% endblock %}
  1. post/forms.py 에서 context에 넣기위한 formset을 위한 것 코딩
# post/forms.py 
class PostImageForm(BootstrapModelForm):
    class Meta:
        model = PostImage
        fields=('image',) # 이미지만 있으면 되니까

from django.forms import inlineformset_factory
PostImageFormSet = inlineformset_factory( # 다중 이미지 업로드를 위한 FormSet
    #  (부모모델)parent model, (자식모델)현재 모델 , 방금만든 포스트이미지 폼, 몇개를 한번에 넣을건지 , 삭제가능여부
    Post, PostImage, form=PostImageForm, extra=1, can_delete=True
)
  1. get_context_data 생성
# post/views.py
class PostUpdateView(LoginRequiredMixin, UpdateView):
    model = Post
    template_name = 'post/form.html'
    form_class = PostForm

    def get_context_data(self, **kwargs):
        data = super().get_context_data(**kwargs) # 기본 context 가져오기
        data['formset'] = PostImageFormSet(instance=self.object) # formset 이라는 이름으로 PostImageFormSet을 전달
        # instance 현재 수정중인 Post객체와 연결된 Postimage 들을 가져와서
        
        return data
  1. 이것을 페이지단에서 보여주기 위해 html 코드 수정
{% extends 'base.html' %}
{% load static %}
{% block content %}
    <div class="row">
        <div class="col-10 offset-1 col-lg-6 offset-lg-3">
            <form action="" method="post" enctype="multipart/form-data">
                {% csrf_token %}
                {{ form.as_p }}

                <table class="table">
                    {{ formset.management_form }}
                    <thead>
                    <tr>
                        {% for field in formset.forms.0.visible_fields %}
                            <th class="text-xs">{{ field.label }}</th>
                        {% endfor %}
                    </tr>
                    </thead>
                    <tbody>
                        <!-- formset은 PostImageForm 이 리스트 형태로 여러개가 들어가 있는거임 그래서 for문 사용 -->
                        {% for form in formset.forms %}
                            <tr class="formset_row">
                                {% for field in form.visible_fields %}
                                    <td>
                                        {% if forloop.first %}
                                            {% for hidden in form.hidden_fields %}
                                                {{ hidden }}
                                            {% endfor %}
                                        {% endif %}
                                        <!-- 이부분이랑 아랫부분은 form 을 만들어서 쓸때 고정적으로 입력한다 생각 -->
                                        {{ field.errors.as_ul }}

                                        {{ field }}
                                        {% if field.errors %}
                                            {% for error in field.errors %}
                                                <span class="text-danger"></span>
                                            {% endfor %}
                                        {% endif %}
                                    </td>
                                {% endfor %}
                            </tr>
                            <!-- form.as_p애서 자동으로 처리하던것 -->
                            {% if form.non_field_errors %}
                                {% for error in form.non_field_errors %}
                                    <p class="text-danger">{{ error }}</p>
                                {% endfor %}
                            {% endif %}
                        {% endfor %}
                    </tbody>
                </table>
                <button class="btn btn-primary">저장</button>
            </form>
        </div>
    </div>
{% endblock %}

{% block js %}
    <script src="{% static 'js/jquery.min.js' %}"></script>
    <script src="{% static 'js/jquery.formset.js' %}"></script>

    <!-- 버튼추가 -->
    <script>
        $('.formset_row').formset({
            addText: '추가하기',
            deleteText: '삭제',
            prefix: 'images'
        })
    </script>
{% endblock %}
  1. 이미지 valid한지 확인하고 저장하는 코드 작성
# post/views.py PostCreateView
    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.user = self.request.user
        self.object.save()

        # 이미지가 타당한지 확인하는 것
        image_formset = PostImageFormSet(self.request.POST, self.request.FILES, instance=self.object)
        if image_formset.is_valid():
            image_formset.save()

        return HttpResponseRedirect(reverse('main'))
  1. UpdateView 생성 CreateView랑 거의 동일 몇개만 삭제
# post/views.py 
class PostUpdateView(LoginRequiredMixin, UpdateView):
    model = Post
    template_name = 'post/form.html'
    form_class = PostForm

    def get_context_data(self, **kwargs):
        data = super().get_context_data(**kwargs) # 기본 context 가져오기
        # 인스턴스 설정을 하면 ForeignKey로 묶여있는 데이터를 같이 가져와줌
        data['formset'] = PostImageFormSet(instance=self.object) # 어떤 것의 formset인지만 넣어줌
        
        return data

    def form_valid(self, form):
        self.object.save()

        image_formset = PostImageFormSet(self.request.POST, self.request.FILES, instance=self.object)
        if image_formset.is_valid():
            image_formset.save()

        return HttpResponseRedirect(reverse('main'))
    
    # 현재 글 작성유저와 같은 사람만 와야하니 
    def get_queryset(self):
        queryset=super().get_queryset()
        return queryset.filter(user=self.request.user)

# config/urls.py

    path('<int:pk>/update/', post_views.PostUpdateView.as_view(), name='update'),
  1. list.html 에 수정,생성 버튼 생성
{% block content %}
    <div class="row">
    <div class="text-end col-10 offset-1 col-lg-6 offset-lg-3">
        <a class="btn btn-sm btn-info" href="{% url 'create' %}">생성</a>
    </div>

                        <!-- 작성자 -->
                        {{ post.user.nickname }}
                        {% if post.user == request.user %}
                            <a href="{% url 'update' post.pk %}" class="btn btn-warning btn-sm float-end">
                                수정
                            </a>
                        {% endif %}

📝 댓글과 태그 모델 만들기

  1. 태그는 한개의 태그가 여러개의 포스트를 가질수 있다. N:N
# 태그
class Tag(TimestampModel):
    tag = models.CharField('태그', max_length=100)
    # N대 N 관계 ManyToManyField
    posts = models.ManyToManyField(Post, related_name='tags')

# many to many 를 이렇게 풀어서 쓰는 경우도 있음
# class TagPosts(TimestampModel):
#     tag=models.ForeginKey(Tag)
#     post=models.ForeignKey(Post)

# 댓글
class Comment(TimestampModel):
    post=models.ForeignKey(Post, related_name='comments', on_delete=models.CASCADE)
    content = models.CharField('내용', max_length=255)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
  1. python manage.py showmigrations : 현재 migrations 되어있는 항목이 나옴
post
 [X] 0001_initial
 [X] 0002_alter_postimage_post_comment_tag
sessions
 [X] 0001_initial
  1. 만약 post의 2번을 취소하고 싶으면 python manage.py migrate post 0001을 하면 X가 빈채로 나옴
post
 [X] 0001_initial
 [ ] 0002_alter_postimage_post_comment_tag
  1. migrations 폴더에서 잘못된 파일 자체를 삭제하고 showmigrations 해보면 002가 사라져 있다. 그리고 python manage.py makemigrations 해서 다시 하면 된다.
  2. showmigrations로 몇번째로 되돌리고 싶은지 정하면 됨
특징select_relatedprefetch_related
사용 대상ForeignKey, OneToOneField (1:N, 1:1)ManyToManyField, 역참조 (N:N, 1:N 역참조)
쿼리 방식JOIN을 사용하여 한 번의 SQL 쿼리로 가져옴여러 개의 쿼리를 실행한 후, Python이 관계를 매칭
성능JOIN을 사용하기 때문에 SQL 실행이 빠름 (적은 데이터에 유리)여러 개의 개별 쿼리를 실행하기 때문에 대량 데이터에 유리
추가 쿼리 발생 여부관련 객체 접근 시 추가 쿼리 발생하지 않음관련 객체 접근 시 추가 쿼리 발생하지 않음
예제Post.objects.select_related('author').all()Post.objects.prefetch_related('comments').all()

🚀 언제 사용해야 할까?

  • ForeignKey(1:N) 또는 OneToOne(1:1) 관계에서 사용.
  • JOIN을 활용하여 한 번의 SQL 쿼리로 데이터를 가져올 수 있을 때 사용.
  • 데이터 개수가 적거나, 쿼리 성능을 높이고 싶을 때 유리.

📌 예시

Post.objects.select_related('author').all()  # ✅ Post와 User를 JOIN
  • ManyToMany(N:N) 또는 ForeignKey 역참조(1:N 역참조)에서 사용.
  • JOIN이 비효율적인 경우(대량 데이터, 다대다 관계)에서 사용.
  • 여러 개의 개별 쿼리를 실행한 후, Django가 Python에서 관계를 매칭.

📌 예시

Post.objects.prefetch_related('comments').all()  # ✅ Post와 Comment를 별도 쿼리 후 조합

🚀 결론

  • 1:1, 1:N 관계에서는 select_related 사용 (JOIN 활용, 즉시 로드).
  • N:N, 1:N 역참조 관계에서는 prefetch_related 사용 (IN 활용, Python에서 로드).
  • 쿼리 성능을 최적화하려면 데이터의 크기와 관계를 고려하여 선택! 🚀

📌 Tip

0개의 댓글

관련 채용 정보