[DB] Many to Many relationships 2

Jingi·2024년 4월 9일

Web

목록 보기
21/40
post-thumbnail

팔로우 기능 구현

프로필 페이지

  • 각 회원의 개인 프로필 페이지에 팔로우 기능을 구현하기 위해 프로필 페이지를 먼저 구현하기

  • url

    from django.urls import path
    from . import views
    
    app_name = 'accounts'
    urlpatterns = [
        path('login/', views.login, name='login'),
        path('logout/', views.logout, name='logout'),
        path('signup/', views.signup, name='signup'),
        path('delete/', views.delete, name='delete'),
        path('update/', views.update, name='update'),
        path('profile/<username>/', views.profile, name='profile')
    ]
  • views.py

    
    from django.contrib.auth import get_user_model
    def profile(request, username):
        User = get_user_model()
        person = User.objects.get(username=username)
        context= {
            'person' : person,
        }
        return render(request, 'accounts/profile.html', context)
  • profile.html

    <h1> {{ person.username }}님의 프로필</h1>
    <hr>
    
    <h2>{{ person.username }} 가 작성한 게시글</h2>
    {% for article in person.artilce_set.all %}
        <div>{{ article.title }} </div>
    {% endfor %}
    <hr>
    
    <h2> {{person.username }} 가 좋아요한 게시글</h2>
    {% for article in person.like_artilces.all %}
        <div>{{ article.title }} </div>
    {% endfor %}
  • index.html

    <a href="{% url 'accounts:profile' user.username %}"> 내 프로필</a>
    <p>작성자 : <a href="{% url 'accounts:profile' article.user.username %}">{{ article.user }}</a>
    </p>

기능 구현

USER(M) - USER(N)

  • 0명 이상의 회원은 0명 이상의 회원과 관련

    • 회원은 0명 이상의 팔로워를 가질 수 있고, 0명 이상의 다른 회원들을 팔로잉 할 수 있음
  • ManyToManyField 작성

    # accounts/models.py
    from django.db import models
    from django.contrib.auth.models import AbstractUser
    
    # Create your models here.
    class User(AbstractUser):
        followings = models.ManyToManyField('self', symmetrical=False, related_name='followers')
        
  • 참조

    • 내가 팔로우 하는 사람들 (팔로잉, followings)
  • 역참조

    • 상대방 입장에서 나는 팔로워 중 한 명 (팔로워, followers)
  • 바뀌어도 상관없으나 관계 조회 시 생각하기 편한 방향으로 정한 것

  • urls.py

    # accounts/urls.py
    urlpatterns = [
        path('<int:user_pk>/follow/', views.follow, name='follow'),
    ]
  • views.py

    # accounts/views.py
    @login_required
    def follow(request, user_pk):
        User = get_user_model()
        person = User.objects.get(pk=user_pk)
        if person != request.user:
            if request.user in person.followers.all():
                person.followers.remove(request.user)
            else:
                person.followers.add(request.user)
        return redirect('accounts:profile', person.username)
  • profile.html

        <div>
            팔로잉 : {{ person.followings.all|length }} / 팔로워 : {{ person.followers.all|length }}
        </div>
        {% if  request.user != person %}
        <div>
            <form action="{% url "accounts:follow" person.pk %}" mehtod = "POST">
                {% csrf_token %}
                {% if request.user in person.followers.all %}
                    <input type="sumbit" value="Unfollow">
                {% else %}
                    <input type="sumbit" value="follow">
                {% endif %}
            </form>
        </div>
        {% endif %}

Fixtures

Fixtures

  • Django가 데이터베이스로 가져오는 방법을 알고 있는 데이터 모음
    • 데이터는 데이터베이스 구조에 맞추어 작성 되어있음

초기 데이터 제공

  • Fixtures의 사용목적
  • 협업하는 유저가 동일하게 준비된 데이터로 작업해야하기 때문
    • Django에서는 fixtures를 사용해 앱에 초기 데이터를 제공

Fixtures 활용

  • M:N 까지 모두 작성된 Django 프로젝트에서 유저, 게시글, 댓글 등 각 데이터를 최소 2 ~ 3개 이상 생성해두기

fixtures 관련 명령어

  • dumpdata(데이터 추출)
  • loaddata(데이터 입력)

dunmpdata

  • 데이터베이스의 모든 데이터를 추출
  • python manage.py dumpdata -- indent 4 [app_name[.ModelName]app_name[.ModelName] ... ]] > filename.json
  • python manage.py dumpdata -- indent 4 accounts.user > users.json

loaddata

  • Fixtures 데이터를 데이터베이스로 불러오기

Fixtures 파일 기본 경로

  • app_name/fixtures/
    • Django는 설치된 모든 app의 디렉토리에서 fixtures 폴더 이후의 경로로 fixtures 파일을 찾아 load
  • python manage.py loaddata articles.json users.json comments.json

loaddata 순서 주의사항

  • 만약 loaddata를 한번에 실행하지 않고 별도로 실행한다면 모델 관계에 따라 load 순서가 중요할 수 있음
    • commnet는 article에 대한 key 및 user에 대한 key가 필요
    • article은 user에 대한 key가 필요
  • 즉, 현재 모델 관계에서는 user-> article ->comment 순으로 data를 load 해야 오류가 발생하지 않음
    python manage.py loaddata users.json
    python manage.py loaddata articless.json
    python manage.py loaddata comments.json

모든 모델을 한번에 dump 하기

# 3개의 모델을 하나의 json 파일로
python manage.py dumpdata --indent 4 articles.article articles.commnet accounts.user > data.json

# 모든 모델을 하나의 json 파일로
python manage.py dumpdata --indent 4 > data.json

PS. Fixtures 파일을 직접 만들지 말 것

Imporve query

Imporve query

  • query 개선하기
    • 같은 결과를 얻기 위해 DB 측에 보내는 query 개수를 점차 줄여 조회하기

annotate

  • SQL의 GROUP BY를 사용

  • 각 게시글마다 댓글 개수를 반복 평가

  • 문제 원인

    <p> 댓글 개수 : {{ article.comment_set.count }}</p>
  • 문제 해결

    • 게시글을 조회하면서 댓글 개수까지 한번에 조회해서 가져오기

      # views.py
      
      def index_1(request):
          # artilces = Article.objects.order_by('-pk')
          articles = Article.objects.annotate(Count('comment')).order_by('-pk')
          context = {
              'articles' : articles,
          }
          return render(request, 'articles/index_1.html', context)
  • SQL의 INNER JOIN를 사용

    • 1:1 또는 N:1 참초 관계에서 사용
  • 문제 원인

    {% for article in articles %}
        <h3>작성자 : {{ article.user.username }}</h3>
        <p>제목 : {{ article.title }} </p>
        <hr>
    {% endfor %}
  • 문제 해결

    • 11 queries including 10 simialar and 8 duplicates -> 1 query

      # views.py
      
      def index_2(request):
          # artilces = Article.objects.order_by('-pk')
          articles = Article.objects.select_related('user').order_by('-pk')
          context = {
              'articles' : articles,
          }
          return render(request, 'articles/index_2.html', context)
  • M:N 또는 N:1 역참조 관계에서 사용

    • SQL이 아닌 Python을 사용한 JOIN을 진행
  • 문제 원인

    {% for article in articles %}
        <p> 제목 : {{ article.title }} </p>
        <p> 댓글 목록</p>
        {% for comment in article.comment_set.all %}
            <p> {{ comment.content }} </p>
        {% endfor %}
        <hr>
    {% endfor %}
  • 문제 해결

    • 게시글을 조회하면서 참조된 댓글까지 한번에 조회해서 가져오기

    • 11 queries including 10 simialar -> 2 queries

      # views.py
      
      def index_3(request):
          # artilces = Article.objects.order_by('-pk')
          articles = Article.objects.prefetch_related('comment_set').order_by('-pk')
          context = {
              'articles' : articles,
          }
          return render(request, 'articles/index_3.html', context)
  • 문제 원인
    • 게시글 + 각 게시글의 댓글 목록 + 댓글의 작성자를 단계적으로 평가
{% for article in articles %}
    <p> 제목 : {{ article.title }} </p>
    <p> 댓글 목록</p>
    {% for comment in article.comment_set.all %}
        <p> {{ comment.user.username }} : {{ comment.content }} </p>
    {% endfor %}
    <hr>
{% endfor %}
  • 문제 해결
      1. 게시글 조회하면서 참조된 댓글까지 한번에 조회
# views.py

def index_4(request):
    # artilces = Article.objects.order_by('-pk')
    articles = Article.objects.prefetch_related('comment_set').order_by('-pk')
    context = {
        'articles' : articles,
    }
    return render(request, 'articles/index_4.html', context)
- 2. 게시글 + 각 게시글의 댓글 목록 + 댓글의 작성자를 한번에 조회
    
# views.py

def index_4(request):
    # artilces = Article.objects.order_by('-pk')
    # articles = Article.objects.prefetch_related('comment_set').order_by('-pk')
    articles = Article.objects.prefetch_related(Prefetch('comment_set', queryset=Comment.objects.select_related('user'))).order_by('-pk')
    context = {
        'articles' : articles,
    }
    return render(request, 'articles/index_4.html', context)
profile
데이터 분석에서 백엔드까지...

0개의 댓글