댓글 기능 구현을 통해 관계형 데이터베이스에서 외래 키 속성을 사용해 모델간 N:1 관계를 설정하는 방법에 대해 알아보자.
이번 포스팅에서는 먼저 Comment 모델과 Article 모델간 관계를 설정해보자!
OneToOneField()
: A one-to-one relationship
ForeignKey()
: A many-to-one relationship
ManyToManyField()
: A many-to-many relationship
여기서 우리가 사용할 것은 ForeignKey()
model class
on_delete
옵션# articles/models.py
class Comment(models.Model):
article = models.ForeignKey(Article, 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
on_delete
CASCADE
: 부모 객체(참조된 객체)가 삭제 됐을 때 이를 참조하는 객체도 삭제models.py 에서 모델에 대한 수정사항이 발생했기 때문에 migration 과정 진행
$ python manage.py makemigrations
migrate 진행
$ python manage.py migrate
$ python manage.py shell_plus
# Comment 클래스의 인스턴스 comment 생성
>>> comment = Comment()
# 인스턴스 변수 저장
>>> comment.content = 'first comment'
# DB에 댓글 저장
>>> comment.save()
# 에러 발생
IntegrityError: NOT NULL constraint failed: articles_comment.article_id
# articles_comment 테이블의 ForeignKeyField, article_id 값이 저장시 누락되었기 때문
# 게시글 생성 및 확인
>>> article = Article.objects.create(title='title', content='content')
>>> article
<Article: 2번째글 - title>
# 외래 키 데이터 입력
# 다음과 같이 article 객체 자체를 넣을 수 있음
>>> comment.article = article
# 또는 comment.article_id = article.pk 처럼 pk 값을 직접 외래 키 컬럼에
# 넣어 줄 수도 있지만 권장하지 않음
# DB에 댓글 저장 및 확인
>>> comment.save()
>>> comment
<Comment: first comment>
>>> comment.pk
1
>>> comment.content
'first comment'
# 클래스 변수명인 article로 조회 시 해당 참조하는 게시물 객체를 조회할 수 있음
>>> comment.article
<Article: 2번째글 - title>
# article_pk는 존재하지 않는 필드이기 때문에 사용 불가
>>> comment.article_id
2
# 1번 댓글이 작성된 게시물의 pk 조회
In [13]: comment.article.pk
Out[13]: 2
# 1번 댓글이 작성된 게시물의 content 조회
In [14]: comment.article.content
Out[14]: 'content'
>>> comment = Comment(content='second comment', article=article)
>>> comment.save()
>>> comment.pk
2
>>> comment
<Comment: second comment>
>>> comment.article_id
2
objects
라는 매니저를 통해 queryset api를 사용했던 것 처럼 related manager를 통해 queryset api를 사용할 수 있게 됨article.comment_set.method()
모델명_set
이름 규칙으로 만들어짐$ python manage.py shell_plus
article = Article.objects.get(pk=2)
>>> dir(article)
[...
'comment_set',
'content',
'created_at',
'date_error_message',
'delete',
'from_db',
'full_clean',
...
]
>>> article.comment_set.all()
<QuerySet [<Comment: first comment>, <Comment: second comment>]>
>>> comments = article.comment_set.all()
>>> for comment in comments:
>>> print(comment.content)
first comment
second comment
related_name
# articles/models.py
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
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
# articles/admin.py
from django.contrib import admin
from .models import Article, Comment
# Register your models here.
admin.site.register(Article)
admin.site.register(Comment)
# articles/forms.py
from django import forms
from .models import Article, Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = '__all__'
# articles/views.py
from .forms import ArticleForm, CommentForm
def detail(request, pk):
article = Article.objects.get(pk=pk)
comment_form = CommentForm()
context = {
'article': article,
'comment_form' : comment_form,}
return render(request, 'articles/detail.html', context)
<!-- articles/detail.html -->
...,
<h5>Comments</h5>
<hr>
<form action="#" method="POST">
{% csrf_token %}
{{ comment_form }}
<input type="submit" value="작성">
</form>
다음과 같이 출력됨
외래 키 필드를 출력에서 제외 후 확인
# articles/forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
exclude = ('article',)
path('<int:pk>/', views.detail, name='detail')
url에 해당 게시글의 pk 값이 사용되고 있음variable routing
사용# articles/urls.py
from django.urls import path
from . import views
app_name = 'articles'
urlpatterns = [
...,
path('<int:pk>/comments/', views.comments_create, name='comments_create'),
]
<!-- articles/detail.html -->
<h5>Comments</h5>
<hr>
<form action="{% url 'articles:comments_create' article.pk %}" method="POST">
{% csrf_token %}
{{ comment_form }}
<input type="submit" value="작성">
</form>
save()
method # articles/views.py
def comments_create(request, pk):
article = Article.objects.get(pk=pk)
comment_form = CommentForm(request.POST)
if comment_form.is_valid():
comment = comment_form.save(commit=False)
comment.article = article
comment.save()
return redirect('articles:detail', article.pk)
# articles/views.py
from .models import Article, Comment
def detail(request, pk):
article = Article.objects.get(pk=pk)
comment_form = CommentForm()
comments = article.comment_set.all()
context = {
'article': article,
'comment_form' : comment_form,
'comments' : comments,
}
return render(request, 'articles/detail.html', context)
<!-- articles/detail.html -->
<h4>댓글 목록</h4>
<ul>
{% for comment in comments %}
<li>{{ comment.content }}</li>
{% endfor %}
</ul>
# articles/urls.py
app_name = 'articles'
urlpatterns = [
...,
path('<int:article_pk>/comments/<int:comment_pk>/delete/', views.comments_delete, name='comments_delete'),
]
# articles/views.py
from .models import Movie, Comment
def comments_delete(request, article_pk, comment_pk):
comment = Comment.objects.get(pk=comment_pk)
comment.delete()
return redirect('articles:detail', article_pk)
<!-- articles/detail.html -->
<h4>댓글 목록</h4>
<ul>
{% for comment in comments %}
<li>
{{ comment.content }}
<form action="{% url 'articles:comments_delete' article.pk comment.pk %}" method="POST">
{% csrf_token %}
<input type="submit" value="DELETE">
</form>
</li>
{% endfor %}
</ul>
length
사용{{ comments|length }}
{{ article.comment_set.all|length }}
count()
사용 {{ comments.count }}
{{ article.comment_set.count }}
<!-- articles/detail.html -->
<h4>댓글 목록</h4>
{% if comments %}
<p><b>{{ comments|length }}개의 댓글이 있습니다.</b></p>
{% endif %}
for empty
활용<!-- articles/detail.html -->
<h4>댓글 목록</h4>
{% if comments %}
<p><b>{{ comments|length }}개의 댓글이 있습니다.</b></p>
{% endif %}
<ul>
{% for comment in comments %}
<li>
{{ comment.content }}
<form action="{% url 'articles:comments_delete' article.pk comment.pk %}" method="POST">
{% csrf_token %}
<input type="submit" value="DELETE">
</form>
</li>
{% empty %}
<p>댓글이 없습니다.</p>
{% endfor %}
</ul>
<hr>