[Django] M:N 모델링

작성:    

업데이트:

카테고리:

태그: ,

예시

병원 진료 기록 시스템을 예시로, N:M 모델링을 살펴보자


  • 한 환자가 여러 의사에게 진료를 받고, 한 의사가 여러 환자를 진료한다.
  • 즉, 환자와 의사의 관계는 서로 대등하며, N:M의 관계이다.


방식 A : 1:N 방식

기존의 ForeignKey 방식으로 model을 정의해보자.

# hospitals/models.py
from django.db import models

class Doctor(models.Model):
    name = models.TextField()

    def __str__(self):
        return f'{self.pk}번 의사 {self.name}'


class Patient(models.Model):
    doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
    name = models.TextField()

    def __str__(self):
        return f'{self.pk}번 환자 {self.name}'


의사 2명과 환자 2명 생성

In [1]: doctor1 = Doctor.objects.create(name='justin')

In [2]: doctor2 = Doctor.objects.create(name='eric')

In [3]: patient1 = Patient.objects.create(name='tony', doctor=doctor1)

In [4]: patient2 = Patient.objects.create(name='harry', doctor=doctor2)

In [5]: doctor1
Out[5]: <Doctor: 1번 의사 justin>

In [6]: doctor2
Out[6]: <Doctor: 2번 의사 tony>

In [7]: patient1
Out[7]: <Patient: 1번 환자 tony>

In [8]: patient2
Out[8]: <Patient: 2번 환자 harry>

In [9]: patient3 = Patient.objects.create(name='james', doctor=doctor1, doctor2)

SyntaxError: postitional argument follows keyword argument
  • 한 환자(patient3)가 doctor1, doctor2의 여러 의사들에게 진료를 받아야 한다.
  • 하지만, 외래키는 2개의 데이터를 넣을 수 없기 때문에 에러 발생
  • 1:N의 한계!


방식 B : 중개 모델

from django.db import models


class Doctor(models.Model):
    name = models.TextField()

    def __str__(self):
        return f'{self.pk}번 의사 {self.name}'


# 외래키 삭제
class Patient(models.Model):
    name = models.TextField()

    def __str__(self):
        return f'{self.pk}번 환자 {self.name}'

# 중개모델 작성
class Reservation(models.Model):
    doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
    patient = models.ForeignKey(Patient, on_delete=models.CASCADE)

    def __str__(self):
        return f'{self.doctor_id}번 의사의 {self.patient_id}번 환자'
  • 각 모델을 1:N 참조하는 Reservation 모델 생성
  • Patient 모델의 외래키가 사라지고, Reservation 모델의 외래키가 2개
  • 즉, 각 모델을 1로 하고, 자신은 N으로 하여 예약과 관련된 정보들을 모두 모으는 중개 모델 Reservation

migration 실시


중개 테이블을 통한 예약

# 의사와 환자가 각각 생성
In [1]: doctor1 = Doctor.objects.create(name='justin')

In [2]: patient1 = Patient.objects.create(name='tony')

# 중개 테이블에 예약 레코드 추가
In [3]: Reservation.objects.create(doctor=doctor1, patient=patient1)
Out[3]: <Reservation: 1번 의사의 1번 환자>


# 의사와 환자 각각에 대한 역참조
In [4]: doctor1.reservation_set.all()
Out[4]: <QuerySet [<Reservation: 1번 의사의 1번 환자>]>

In [5]: patient1.reservation_set.all()
Out[5]: <QuerySet [<Reservation: 1번 의사의 1번 환자>]>


# 새 환자 생성 및 예약 추가
In [6]: patient2 = Patient.objects.create(name='harry')

In [7]: Reservation.objects.create(doctor=doctor1, patient=patient2)
Out[7]: <Reservation: 1번 의사의 2번 환자>


# 의사와 환자 각각에 대한 역참조
In [8]: doctor1.reservation_set.all()
Out[8]: <QuerySet [<Reservation: 1번 의사의 1번 환자>, <Reservation: 1번 의사의 2번 
환자>]>

In [9]: patient2.reservation_set.all()
Out[9]: <QuerySet [<Reservation: 1번 의사의 2번 환자>]>


방식 C : ManyToManyField

  • 다대다 관계(M:N) 설정 시 사용하는 모델 필드
  • 하나의 필수 위치인자(M:N 관례로 설정할 모델 클래스) 필요
  • 작성 위치는 Doctor 또는 Patient 모두 작성 가능


# hospital/models.py
from django.db import models


class Doctor(models.Model):
    name = models.TextField()

    def __str__(self):
        return f'{self.pk}번 의사 {self.name}'


class Patient(models.Model):
    # ManyToManyField 작성
    doctors = models.ManyToManyField(Doctor)
    name = models.TextField()

    def __str__(self):
        return f'{self.pk}번 환자 {self.name}'


무슨 차이가 생긴거죠?

  • 중개 모델(Reservation)이 삭제
  • ManyToManyField가 환자 쪽에 작성
  • M과 N 그 자체에는 차이가 없다. ForeignKey도 없다. Field 하나가 중개 테이블 하나를 만든다!


ManyToManyField가 Doctor에 있으면 어떻게 되나요?

  • 1:N은 종속 관계이기 때문에 ForeignKey Field는 확실히 N쪽에 있어야 한다.
  • M:N은 대등한 관계이기 때문에 괜찮다.
  • M과 N 자체의 변화는 없다.
  • 테이블 이름이 바뀔 것이다. 하지만 의미는 없다.
  • 다만 참조와 역참조의 입장이 바뀌긴 할 것
    • ManyToMany Field가 있는 model이 상대 model을 참조, 반대는 역참조
    • 1:N에서도 ForeignKey가 있는 쪽이 없는 쪽을 참조, 반대는 역참조


# 의사와 환자 정보 생성
In [1]: doctor1 = Doctor.objects.create(name='justin')

In [2]: patient1 = Patient.objects.create(name='tony')

In [3]: patient2 = Patient.objects.create(name='harry')


# 1번 환자 입장에서 1번 의사 추가
In [4]: patient1.doctors.add(doctor1)

# 1번 환자 입장에서 의사 확인(참조)
In [5]: patient1.doctors.all()
Out[5]: <QuerySet [<Doctor: 1번 의사 justin>]>

# 1번 의사 입장에서 환자 확인(역참조)
In [6]: doctor1.patient_set.all()
Out[6]: <QuerySet [<Patient: 1번 환자 tony>]>


# 1번 의사 입장에서 2번 환자 추가
In [7]: doctor1.patient_set.add(patient2)

In [8]: doctor1.patient_set.all()
Out[8]: <QuerySet [<Patient: 1번 환자 tony>, <Patient: 2번 환자 harry>]>

In [9]: patient2.doctors.all()
Out[9]: <QuerySet [<Doctor: 1번 의사 justin>]>

In [10]: patient1.doctors.all()
Out[10]: <QuerySet [<Doctor: 1번 의사 justin>]>


# 1번 의사가 1번 환자에 대한 예약 취소
In [11]: doctor1.patient_set.remove(patient1)

In [12]: doctor1.patient_set.all()
Out[12]: <QuerySet [<Patient: 2번 환자 harry>]>

In [13]: patient1.doctors.all()
Out[13]: <QuerySet []>

# 2번 환자가 1번 의사에 대한 예약 취소
In [15]: patient2.doctors.remove(doctor1)

In [16]: patient2.doctors.all()
Out[16]: <QuerySet []>

In [17]: doctor1.patient_set.all()
Out[17]: <QuerySet []>
  • 어느 입장에서 중개 테이블의 데이터를 삭제해도 상관이 없다.
  • add, remove() 등 set에서 사용되는 메소드 모두 사용 가능


releated_name 사용

doctor_set, patient_set 불편합니다!

  • releated_name을 활용한다.
  • 1:N에서 지양했던 이유는 M:N에서 활용하기 위함이었음
  • 1:N과 M:N을 구분하는 큰 지표


  • target model(관계 필드를 가지지 않은 모델; Doctor)이 source model(관계 필드를 가진 모델; 환자)을 참조, 즉 역참조할 때 사용할 manager의 이름을 설정

  • patients, doctors 처럼 복수형으로 사용


from django.db import models


class Doctor(models.Model):
    name = models.TextField()

    def __str__(self):
        return f'{self.pk}번 의사 {self.name}'


class Patient(models.Model):
    # ManyToManyField - related_name 작성
    doctors = models.ManyToManyField(Doctor, related_name='patients') 🔅
    name = models.TextField()

    def __str__(self):
        return f'{self.pk}번 환자 {self.name}'


In [1]: doctor1 = Doctor.objects.get(pk=1)

In [2]: doctor1
Out[2]: <Doctor: 1번 의사 justin>

In [3]: doctor1.patient_set.all()
AttributeError: 'Doctor' object has no attribute 'patient_set'

In [4]: doctor1.patients.all()
Out[4]: <QuerySet []>
  • 기본적으로 사용하던 역참조 manager인 patient_set은 사용 불가
  • related_name인 ‘patients’를 사용


django M:N에서 중개 테이블 작성

  • django는 ManyToManyField를 통해 중개 테이블을 자동 생성


중개 테이블을 직접 작성할 수는 없을까?

  • through 옵션 사용
  • 중개 테이블에 추가 데이터를 사용하는 경우 사용


ManyToManyField

  • 다대다(M:N, many-to-many) 관계 설정 시 사용하는 모델 필드
  • 하나의 필수 위치인자(M:N 관계로 설정할 모델 클래스) 필요
  • 모델 필드의 RelatedManager를 사용하여 관련 개체를 추가(add), 제거(remove) 가능


Arguments

related_name

  • 역참조시 사용할 manager의 이름 설정
  • ForeignKey의 related_name과 동일


through

  • 중개 테이블을 직접 작성하는 경우, 중개 테이블을 나타내는 Django 모델을 지정 가능
  • 중개 테이블에 추가 데이터를 사용하는 다대다 관계와 연결하는 경우 사용


symmetrical

  • 연결된 모델 인스턴스끼리의 참조를 동기화하는 것
  • 기본값은 True (동기화를 한다는 것)
  • 후술


  • 1:N 또는 M:N 관련 context에서 사용되는 manager
  • 같은 이름의 메서드여도 각 관계에 따라 다르게 사용 및 동작
    • 1:N : target 모델 인스턴스만 사용 가능
    • M:N : 두 객체 모두 사용 가능
  • add(), remove(), create(), clear(), set() 등이 있음


add()

  • 지정된 객체를 관련 객체 집합에 추가
  • 이미 존재하는 관계에 사용 시 복제 X
  • 모델 인스턴스, 필드 값(PK)을 인자로 허용


remove()

  • 관련 객체 집합에서 지정된 모델 객체 제거
  • 내부적으로 QuerySet.delete()를 사용하여 관계 삭제
  • 모델 인스턴스, 필드 값(PK)을 인자로 허용


through

from django.db import models


class Doctor(models.Model):
    name = models.TextField()

    def __str__(self):
        return f'{self.pk}번 의사 {self.name}'


class Patient(models.Model):
    doctors = models.ManyToManyField(Doctor, through='Reservation') 🔅
    name = models.TextField()

    def __str__(self):
        return f'{self.pk}번 환자 {self.name}'


class Reservation(models.Model):
    doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
    patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
    symptom = models.TextField() 🔅
    reserved_at = models.DateTimeField(auto_now_add=True) 🔅

    def __str__(self):
        return f'{self.doctor.pk}번 의사의 {self.patient.pk}번 환자'
  • model들에 정의된 속성들 외의 **추가 필드를 사용하고 싶은 경우
  • symptom(증상), reserved_at(예약 시간) 필드를 추가


# 의사 및 환자 정보 생성 및 저장
In [1]: doctor1 = Doctor.objects.create(name='justin')

In [2]: patient1 = Patient.objects.create(name='tony')

In [3]: patient2 = Patient.objects.create(name='harry')


# reservation 인스턴스 생성
In [4]: reservation1 = Reservation(doctor=doctor1, patient=patient1, symptom='headache') 🔅

In [5]: reservation1
Out[5]: <Reservation: 1번 의사의 1번 환자>

# reservation 인스턴스 저장
In [6]: reservation1.save()

# 예약 확인
In [7]: doctor1.patient_set.all()
Out[7]: <QuerySet [<Patient: 1번 환자 tony>]>

In [8]: patient1.doctors.all()
Out[8]: <QuerySet [<Doctor: 1번 의사 justin>]>


# 환자에 의한 예약 추가
In [9]: patient2.doctors.add(doctor1, through_defaults={'symptom': 'flu'}) ⭐

In [10]: doctor1.patient_set.all()
Out[10]: <QuerySet [<Patient: 1번 환자 tony>, <Patient: 2번 환자 harry>]>

In [11]: patient2.doctors.all()
Out[11]: <QuerySet [<Doctor: 1번 의사 justin>]>
  • through_defaults 옵션


중개 테이블의 필드 생성 규칙

source model 및 target model 모델이 다른 경우

  • id
  • _id
  • _id


ManyToManyField가 동일한 모델을 가리키는 경우

  • id
  • from__id
  • to__id


Like 기능

from django.db import models
from django.conf import settings

# Create your models here.
class Article(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    like_users = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='like_articles') 
    title = models.CharField(max_length=10)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title


class Comment(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    content = models.CharField(max_length=200)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.content
  • like_users 필드 생성 시 자동으로 역참조는 .article_set 매니저를 생성
  • 이전 1:N(User:Article) 관계에서 이미 해당 매니저를 사용중이어서 migration 에러
  • User와 관계된 ForeignKey 또는 ManyToManyField 중 하나에 related_name 추가 필요
  • ‘like_articles’로 이름 재지정


현재 User-Article간 사용 가능한 DB API

article.user

게시글을 작성한 유저 - 1:N


article.like_users

게시글을 좋아요한 유저 - M:N


user.article_set

유저가 작성한 게시글(역참조) - 1:N


users.like_articles

유저가 좋아요한 게시글(역참조) - M:N



# articles/urls.py
from django.urls import path
from . import views


app_name = 'articles'
urlpatterns = [
    path('', views.index, name='index'),
    path('create/', views.create, name='create'),
    path('<int:pk>/', views.detail, name='detail'),
    path('<int:pk>/delete/', views.delete, name='delete'),
    path('<int:pk>/update/', views.update, name='update'),
    path('<int:pk>/comments/', views.comment_create, name='comment_create'),
    path('<int:article_pk>/comments/<int:comment_pk>/delete/', views.comment_delete, name='comment_delete'),
    path('<int:article_pk>/likes/', views.likes, name='likes'), 🔅
]


좋아요를 눌렀다면 좋아요 취소, 아니라면 좋아요

가. 좋아요/취소 기능에 충실한 코드

# articles/views.py
def likes(request, article_pk):
    article = get_object_or_404(Article, pk=article_pk)
    
    # 이 게시글의 좋아요를 누른 유저 목록에 현재 요청한 유저가 있다면, 좋아요 취소
    if request.user in article.like_users.all():
        article.like_users.remove(request.user)
    
    # 이전에 좋아요를 누르지 않은 경우
    else:
        article.like_users.add(request.user)
    return redirect('articles:index')
  • variable routing을 통해 전달받은 article_pk를 이용해 article 조회
  • article에 좋아요를 누른 user들 queryset 중에 request.user가 있는지 확인
  • 있다면 좋아요 취소이므로 article의 like_users queryset에서 request.user 제거(remove)
  • 아니라면 article의 like_users queryset에 request.user 추가(add)


{% extends 'base.html' %}

{% block content %}
  <h1>Articles</h1>
  {% if request.user.is_authenticated %}
    <a href="{% url 'articles:create' %}">CREATE</a>
  {% else %}
    <a href="{% url 'accounts:login' %}">[새 글을 작성하려면 로그인 하세요]</a>
  {% endif %}
  <hr>
  {% for article in articles %}
    <p>작성자: {{ article.user }}</p>
    <p>글 번호: {{ article.pk }}</p>  
    <p>글 제목: {{ article.title }}</p>
    <p>글 내용: {{ article.content }}</p>
    <div>
      <form action="{% url 'articles:likes' article.pk %}" method="POST"> 🔅
        {% csrf_token %}
        <input type="submit" value="좋아요"> 🔅
      </form>
    </div>
    <a href="{% url 'articles:detail' article.pk %}">DETAIL</a>
    <hr>
  {% endfor %}
{% endblock content %}

좋아요 버튼 form 추가


문제점 발견

image

  • Article 생성에 사용된 ArticleForm에 like_users 추가
  • model을 Article을 참조하는데, Article에 like_users가 정의되었기 때문
  • 이를 조정해보자.


ArticleForm 수정

# articles/forms.py
from django import forms
from .models import Article, Comment

class ArticleForm(forms.ModelForm):

    class Meta:
        model = Article
        exclude = ('user', 'like_users',) 🔅

exclude에 like_users를 추가해 rendering에서 제외한다.


좋아요 취소 기능 추가

<!-- articles/index.html -->
...
<form action="{% url 'articles:likes' article.pk %}" method="POST">
  {% csrf_token %}
  {% if user in article.like_users.all %}
    <input type="submit" value="좋아요 취소">
  {% else %}
    <input type="submit" value="좋아요">
  {% endif %}
</form>
  • article.like_users.all queryset API를 이용
  • template에서 if else문 사용으로 버튼의 value 표시 다르게 하기


QuerySet API : exist()

  • QuerySet에 결과가 포함되어 있으면 True 반환, 그렇지 않으면 False 반환
  • 규모가 큰 QuerySet의 컨텍스트에서 특정 개체 존재 여부와 관련된 검색에 유용
  • 고유한 필드(ex. primary key)가 있는 모델이 QuerySet의 구성원인지 여부를 찾는 가장 효율적인 방법


위의 vieww 함수를 exist()를 활용해 개선해보자

# articles/views.py
def likes(request, article_pk):
    article = get_object_or_404(Article, pk=article_pk)
    
    # 이 게시글의 좋아요를 누른 유저 목록에 현재 요청한 유저가 있다면, 좋아요 취소
    # if request.user in article.like_users.all():
    if article.like_users.filter(pk=request.user.pk).exists(): 🔅
        article.like_users.remove(request.user)
    
    # 이전에 좋아요를 누르지 않은 경우
    else:
        article.like_users.add(request.user)
    return render('articles/index.html')


Profile Page

urls

# accounts/urls.py
from django.urls import path
from . import views

app_name = 'accounts'
urlpatterns = [
    ...
    # ⭐ 문자열로 시작하는 variable routing이므로 위치에 신경 쓰기
    path('<username>/', views.profile, name='profile'),
]
  • string variable routing으로 각 유저에 대한 profile 페이지 url 정의


views

# accounts/views.py
from django.contrib.auth import get_user_model

def profile(request, username):
    person = get_object_or_404(get_user_model(), username=username)
    context = {
        'person': person,
    }
    return render(request, 'accounts/profile.html', context)
  • user의 이름을 variable routing으로 받아 함수 내로 전달
  • 이 username을 이용해 DB에서 특정 user를 찾아 person에 저장


templates

<!-- accounts/profile.html -->
{% extends 'base.html' %}

{% block content %}
  <h1>{{ person.username }}님의 프로필</h1>
  <hr>

  {% comment %} 이 사람이 작성한 게시글 목록 {% endcomment %}
  <h2>{{ person.username }}님이 작성한 게시글 목록</h2>

  {% for article in person.article_set.all %}
    <p>{{ article.title }}</p>
  {% endfor %}

  {% comment %} 이 사람이 작성한 게시글 댓글 목록 {% endcomment %}
  <h2>{{ person.username }}님이 작성한 게시글 댓글 목록</h2>
  {% for comment in person.comment_set.all %}
    <p>{{ comment.content }}</p>
  {% endfor %}

  {% comment %} 이 사람이 좋아요를 누른 게시글 목록 {% endcomment %}
  <h2>{{ person.username }}님이 좋아요를 누른 게시글 목록</h2>

  {% for article in person.like_articles.all %}
    <p>{{ article.title }}</p>
  {% endfor %}

{% endblock content %}
  • person이 가지고 있는 article_set, comment_set, like_articles queryset에서 각각의 article, comment, article 들을 추출


Follow

User와 User간 참조이기 때문에 ManyToManyField에 self를 참조

# 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')


symmetrical

  • ManyToManyField가 동일한 모델(on self)을 가리키는 정의에서만 사용
  • symmetrical=True(기본값)일 경우 Django는 person_set 매니저를 추가하지 않음
  • 한 쪽에서 추가되면 상대적인 쪽도 추가되므로 역참조가 필요가 없어진다.

  • source 모델의 인스턴스가 target 모델의 인스턴스를 참조하면, target 모델 인스턴스도 source 모델 인스턴스를 자동으로 참조하도록 함

  • 내가 당신의 친구라면, 당신도 내 친구가 되는 것
  • 대칭을 원치 않는다면 symmetrical 옵션을 False로 설정


migration 결과

image

자기 자신을 참조할 때에는 필드명을 from과 to로 지정


urls

# accounts/urls.py
from django.urls import path
from . import views

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


views : follow/unfollow 기능

# accounts/views.py
def follow(request, user_pk):
    # person은 상대방
    you = get_object_or_404(get_user_model(), pk=user_pk)
    me = request.user
    
    # unfollow
    if me in you.followers.all():
        you.followers.remove(me)
        
    else:
        you.followers.add(me)
    return redirect('accounts:profile', you.username)


templates

<!-- accounts/profile.py -->
...
<div>
  <form action="accounts:follow" person.pk method="POST">
    {% csrf_token %}
    {% if user in person.followers.all %}
      <input type="submit" value="언팔로우">
    {% else %}
      <input type="submit" value="팔로우">
    {% endif %}
  </form>
</div>
...


결과

image


코드 개선 : 내가 나를 팔로우하지 못하게!

<!-- accounts/profile.py -->
...
<div>
  {% if user != person %} 🔅
    <form action="accounts:follow" person.pk method="POST">
      {% csrf_token %}
      {% if user in person.followers.all %}
        <input type="submit" value="언팔로우">
      {% else %}
        <input type="submit" value="팔로우">
      {% endif %}
    </form>
  {% endif %}
</div>
...
  • 내꺼는 팔로우 못하도록, user(request.user)가 person(대상 user)이 아닌 경우에만 팔로우/언팔로우 form이 보이도록 한다.


최종 코드

likes view 함수

# articles/views.py
@require_POST
def likes(request, article_pk):
    if request.user.is_authenticated:
        article = get_object_or_404(Article, pk=article_pk)
        
        # 이 게시글의 좋아요를 누른 유저 목록에 현재 요청한 유저가 있다면, 좋아요 취소
        # if request.user in article.like_users.all():
        if article.like_users.filter(pk=request.user.pk).exists():
            article.like_users.remove(request.user)
        
        # 이전에 좋아요를 누르지 않은 경우
        else:
            article.like_users.add(request.user)
        return redirect('articles:index')
    return redirect('accounts:login')


follow view 함수

# accounts/views.py
@require_POST
def follow(request, user_pk):
    if request.user.is_authenticated:
        you = get_object_or_404(get_user_model(), pk=user_pk)
        me = request.user
        
        if me != you:
            # unfollow
            # if me in you.followers.all():
            if you.followers.filter(pk=me.pk).exists():
                you.followers.remove(me)
            else:
                you.followers.add(me)
        return redirect('accounts:profile', you.username)
    return redirect('accounts:login')


profile html 코드

<!-- accounts/profile.py -->
<h1>{{ person.username }}님의 프로필</h1>

<hr>
<div>팔로워 : {{ person.followers.all|length }} / 팔로우 : {{ person.followings.all|length }}</div> 🔅

<div>
  {% if user != person %}
    <form action="{% url 'accounts:follow' person.pk %}" method="POST">
      {% csrf_token %}
      {% if user in person.followers.all %}
        <input type="submit" value="언팔로우">
      {% else %}
        <input type="submit" value="팔로우">
      {% endif %}
    </form>
  {% endif %}
</div>

{% comment %} 이 사람이 작성한 게시글 목록 {% endcomment %}
<h2>{{ person.username }}님이 작성한 게시글 목록</h2>

{% for article in person.article_set.all %}
  <p>{{ article.title }}</p>
{% endfor %}

{% comment %} 이 사람이 작성한 게시글 댓글 목록 {% endcomment %}
<h2>{{ person.username }}님이 작성한 게시글 댓글 목록</h2>
{% for comment in person.comment_set.all %}
  <p>{{ comment.content }}</p>
{% endfor %}

{% comment %} 이 사람이 좋아요를 누른 게시글 목록 {% endcomment %}
<h2>{{ person.username }}님이 좋아요를 누른 게시글 목록</h2>

{% for article in person.like_articles.all %}
  <p>{{ article.title }}</p>
{% endfor %}

댓글남기기