관계형 데이터베이스에서의 외래 키 속성을 사용해 모델간 N:1 관계 설정하기
RDB(관계형 데이터 베이스)에서는 각 행에서 고유하게 식별 가능한 기본 키가 있고 외래 키(FK)를 사용해 각 행에서 서로 다른 테이블 간의 관계를 만드는 데 사용할 수 있음
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
CASCADE
: 부모 객체(참조된 객체)가 삭제 됐을 때 이를 참조하는 객체도 삭제 PROTECT, SET_NULL, SET_DEFAULT...
등 여러 옵션 존재 모델 변경 및 추가 했으니까
python manage.py makemigrations
python manage.py migrate
article.comment
형식으로는 참조할 수 없음comment_set
manager를 자동으로 생성해서 article.comment_set
형태로 댓글 객체를 참조 가능 comment.article
형태로 작성 가능class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name = 'conmments')
from .models import Article, Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = '__all__'
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" vlaue="작성">
</form>
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
# fields = '__all__'
exclude = ('article',)
path('<int:pk>/', views.detail, name='detail'),
# article/urls.py
path('<int:pk>/comments/', views.comments_create, name = 'comments_create'),
# articles/views.py
def comments_create(request,pk):
article = Article.objects.get(pk=pk)
comment_form = CommentForm(request.POST)
if comment_form.is_valid():
# article 객체는 언제 저장할 수 있을까?
comment_form.save()
return redirect('articles:detail', article.pk)
<!-- articles/detail.html -->
<form action="{% url 'article:comments_create' article.pk %}" method = "POST">
{% csrf_token %}
{{comment_form}}
<input type="submit" vlaue="작성">
</form>
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)
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)
<hr>
<h5>Comments</h5>
<h4>댓글 목록</h4>
<ul>
{% for comment in comments %}
<li> {{comment.content}} </li>
{% endfor %}
</ul>
<hr>
<form action="{% url 'articles:comments_create' article.pk %}" method = "POST">
{% csrf_token %}
{{comment_form}}
<input type="submit" vlaue="작성">
</form>
# articles/urls.py
path('<int:article_pk>/comments/<int:comment_pk>/delete/', views.comments_delete, name = 'comments_delete'),
# articles/views.py
def comments_delete(request, article_pk, comment_pk):
comment = Comment.objects.get(pk=comment_pk)
comment.delete()
return redirect('articles:detail', article_pk)
{% 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 %}
length
사용{{ comments|length }}
{{ article.vomment_set.all|length }}
{% if comments %}
<p><b>{{ comments|length }}개의 댓글이 있습니다</b></p>
{% endif %}
count()
사용{{ comments.count }}
{{ article.comment_set.count }}
for empty
활용하기 {% 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 %}
# articles/models.py
class Article(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=30)
content = models.TextField()
image = models.ImageField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f'{self.id}번째글 - {self.title}'
$ python manage.py makemigrations
You are trying to add a non-nullable field 'user' to article without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit, and let me add a default in models.py
Select an option:
python manage.py makemigrations
이후 첫 화면 Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>>
ArticleForm 출력을 확인해보면 create 템플릿에서 불필요한 user 필드가 출력됨
이전에 CommentForm에서 외래 키 필드 article이 출력되는 상황과 동일
user필드에 작성해야 하는 user객체는 view함수의 request 객체를 활용해야 함
ArticleForm의 출력 필드 수정
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
# fields = '__all__'
exclude = ('user',)
@login_required
@require_http_methods(['GET','POST'])
def create(request):
if request.method == 'POST':
form = ArticleForm(request.POST, request.FILES)
if form.is_valid():
article = form.save(commit=False)
article.user = request.user
article.save()
return redirect('articles:detail', article.pk)
else:
form = ArticleForm()
context = {'form': form}
return render(request, 'articles/create.html', context)
@require_POST
def delete(request, pk):
article = Article.objects.get(pk=pk)
if request.user.is_authenticated:
if request.user == article.user:
article.delete()
return redirect('articles:index')
return redirect('articles:detail', article.pk)
@login_required
@require_http_methods(['GET','POST'])
def update(request, pk):
article = Article.objects.get(pk=pk)
if request.user == article.user:
if request.method == 'POST':
form = ArticleForm(request.POST, request.FILES, instance=article)
if form.is_valid():
form.save()
return redirect('articles:detail', pk=article.pk)
else:
form = ArticleForm(instance=article)
context = {'form': form, 'article': article}
return render(request, 'articles/update.html', context)
else:
return redirect('articles:detail', pk=article.pk)
{% if request.user == article.user %}
<a href="{% url 'articles:update' article.pk %}">수정하기</a>
<form action="{% url 'articles:delete' article.pk %}" id="delete-form">
{% csrf_token %}
<input type="submit" value="삭제하기" id="delete-btn" />
</form><br>
<hr>
{% endif %}
<p><b>작성자 : {{article.user}} </b></p>
<p>글 번호 : {{article.pk}} </p>
<p>글 제목 : {{article.title}}</p>
<p>글 내용 : {{article.content}}</p>
<p>생성시각 : {{article.created_at}}</p>
<p>수정시각 : {{article.updated_at}}</p>
# articles/models.py
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
python manage.py makemigrations
python manage.py migrate
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
# fields = '__all__'
exclude = ('article','user',)
# articles/views.py
@require_POST
def comments_create(request,pk):
if request.user.is_authenticated:
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.user = request.user
comment.save()
return redirect('articles:detail', article.pk)
return redirect('accounts:login')
@require_POST
def comments_delete(request, article_pk, comment_pk):
if request.user.is_authenticated:
comment = Comment.objects.get(pk=comment_pk)
if comment.user == request.user:
comment.delete()
return redirect('articles:detail', article_pk)