Django

Model Relationship(M:N)

zhelddustmq 2024. 8. 21. 21:46

01. ManyToMany Relationship(M:N)

 

ManyToMany: https://docs.djangoproject.com/en/4.2/ref/models/fields/#manytomanyfield

 

Model field reference | Django documentation

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

  • 다대다(M:N) 관계 설정시 사용하는 모델 필드입니다.
    • 예시) 좋아요
      → 하나의 게시글도 여러명의 유저에게 좋아요를 받을 수 있음
      → 한 명의 유저는 여러개의 게시글을 좋아할 수 있어요!
  • 중계 테이블을 이용해서 관계를 표현(걍 테이블 하나 더 만듦)
  • models.ManyToManyField(<classname>)을 이용해서 설정(이때 장고가 테이블 알아서 야무지게 생성해줌.)
  • M2M 관계가 설정되면 역참조시 사용가능한 _set이름의 RelatedManager를 생성(테이블 만들면 유저나 아티클에서 중계 테이블을 역참조해야함. related_name 으로 변경 가능)
  • add(), remove()를 이용해서 관련 객체를 추가, 삭제할 수 있음

좋아요 구현하기

# articles/models

class Article(models.Model):
    ...
    like_users = models.ManyToManyField(
        settings.AUTH_USER_MODEL, related_name="like_articles"
    )

이렇게 하고 migration하면 놀랍게도 중계 테이블을 안만들어 놔도 장고가 만들어줌

 

user_id와 article_id가 있는 중계테이블

(잉...? 난 얼만큼 좋아하는지 정도를 설정하는 테이블도 넣고 싶은데...? 한다면)

  • 중계테이블을 내가 직접 정의하고 through를 사용하여 중계테이블 지정이 가능

요런식으로~

# articles/urls

urlpatterns = [
    ...
    path("<int:pk>/like/", views.like, name="like"),
    ...
]
# articles/views

@require_POST
def like(request, pk):
    if request.user.is_authenticated:
        article = get_object_or_404(Article, pk=pk)
        if article.like_users.filter(pk=request.user.pk).exists():
            article.like_users.remove(request.user)
        else:
            article.like_users.add(request.user)
    else:
        return redirect("accounts:login")

    return redirect("articles:articles")
#  articles.html

{% for article in articles %}

		...
		
		<form actions="{% url "article:like" article.pk %}" method="POST">
				{% csrf_token &}
				{% if user in article.like_users.all %}
						<input type="sumit" value="좋아요 취소">
				{% else %}
						<input type="sumit" value="좋아요">
				{% endif %}
		</form>
		
{% endfor %}

articles.html

팔로우 생각해보기

 

아래와 같이 팔로우 코드를 짜려고 했는데, ????에 누구랑 연결해야할지 생각해보면

from django.db import models
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    following = models.ManyToManyField(????, related_name='followers')

 

 

⇒ USER - USER 즉, 관계를 맺는것도 내 자신

따라서

following = models.ManyToManyField('self', related_name='followers')

자기자신과 관계를 맺으면 'self'를 넣어줌.

 

※symmetrical

  • M2M Field가 동일한 모델(self)과 관계를 맺는 경우에 사용
  • symmetrical=True인 경우 한 방향에서 관계를 맺으면 반대 관계도 설정됨. (대칭. 디폴드값은 True)
  • 팔로우 기능은 팔로우가 생기면 나도 자동으로 팔로잉 하면 안되기 때문에 False로 지정해줘야함.

팔로우 구현하기

# accounts/models.py

from django.db import models
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    following = models.ManyToManyField(
        "self", symmetrical=False, related_name="followers"
    )

마이그레이션 후,

# users/urls.py

from django.urls import path
from . import views

app_name = "users"
urlpatterns = [
    ...
    path("<int:user_id>/follow/", views.follow, name="follow"),
]
# users/views.py

from django.shortcuts import render, redirect
from django.views.decorators.http import require_POST
from django.shortcuts import get_object_or_404
from django.contrib.auth import get_user_model


def users(request):
    return render(request, "users/users.html")


def profile(request, username):
    member = get_object_or_404(get_user_model(), username=username)
    context = {
        "member": member,
    }
    return render(request, "users/profile.html", context)


@require_POST
def follow(request, user_pk):
    if request.user.is_authenticated:
        member = get_object_or_404(get_user_model(), pk=user_pk)
        if request.user != member:
            if request.user in member.followers.all():
                member.followers.remove(request.user)
            else:
                member.followers.add(request.user)
        return redirect("users:profile", member.username)
    return redirect("accounts:login")
# profile.html 

{% extends 'base.html' %}

{% block content %}
    <h1>{{ member.username }}의 프로필 페이지</h1>

    <div>
        <h2>username : {{ member.username }}</h2>
        <p>
            팔로워 : {{ member.followers.count }}명
            팔로잉 : {{ member.following.count }}명
        </p>
    </div>

    <div>
        {% if request.user != member %}
            <form action="{% url "follow" member.pk %}" method='POST'>
                {% csrf_token %}

                {% if request.user in member.followers.all %}
                <input type="submit" value="언팔로우">
                {% else %}
                <input type="submit" value='팔로우'>
                {% endif %}

            </form>
        {% endif %}
    </div>

    <a href="/index/">홈으로 돌아가기</a>

{% endblock content %}

'Django' 카테고리의 다른 글

백엔드와 Django DRF, RESTful API  (0) 2024.08.23
Django Bootstrap, Fontawesome  (0) 2024.08.22
Custom UserModel(플젝 시작시 유의)  (0) 2024.08.20
Model Relationship (1:N)  (0) 2024.08.19
Admin  (0) 2024.08.19