DB 中 N:1 (2) - User와 Article(게시글), User와 Comment(댓글) 간의 N:1 관계 정의하기
class Article(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
# User 모델을 직접 import해서 사용하지 않는다.
# 'accounts.User'라는 문자열만 들고 있다가 나중에 모든 모델이
# 다 로드된 이후에 진짜 User 클래스를 찾아서 연결
# ==> '지연 평가' (효율 및 최적화 굿)
# ORM에서도 지연평가 쓰임. Article.objects.all() 이 코드 자체는 DB에 요청을 보내지 않음
# list() 로 형변환 하거나 for로 반복하거나 실제 데이터 활용할 때 평가 진행
title = models.CharField(max_length=10)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
settings.AUTH_USER_MODEL : models.py에서만 사용 (반환 값 : account.User -> 이건 문자열)get_user_model() : models.py를 제외한 다른 모든 위치에서 사용 (반환 값 : User Object -> 이건 객체) $ python manage.py makemigrations
It is impossible to add a non-nullable field 'user' to article without specifying a default.
This is because 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 manually define a default value in models.py.
Select an option:
=> 기존에 테이블이 있는 상황에서 필드를 추가하려 해서 발생하는 것, 모든 필드는 NOT NULL이 기본 제약 조건으로 설정되어 있기에 데이터 없이 새로운 필드를 추가하지 못해서 발생하는 에러다.

# articles/forms.py
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ('title', 'content',)
# exclude = ('user',) 이 방식으로도 가능
이대로 작성 시 user_id 필드에 데이터가 누락되어서 NOT NULL 제약 조건에 위배되기에 에러가 발생함.

from django.contrib.auth.decorators import login_required
@login_required
def create(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save(commit=False)
article.user = request.user
article.save()
return redirect('articles:detail', article.pk)
else:
. . .
<!--> articles/index.html <-->
{% extends 'base.html' %}
{% block content %}
<h1>INDEX</h1>
{% if request.user.is_authenticated %}
<p>Hello, {{ user.username }}</p>
<a href="{% url "articles:create" %}">CREATE</a>
<form action="{% url "accounts:logout" %}" method="POST">
{% csrf_token %}
<input type="submit" value="로그아웃">
</form>
<form action="{% url "accounts:delete" %}" method="POST">
{% csrf_token %}
<input type="submit" value="회원탈퇴">
</form>
<a href="{% url "accounts:update" %}">회원정보 수정</a>
{% else %}
<a href="{% url "accounts:login" %}">로그인</a> |
<a href="{% url "accounts:signup" %}">회원가입</a>
{% endif %}
<hr>
{% for article in articles %}
<div>
<div>작성자: {{article.user.username}} </div>
<div>글 번호: {{ article.pk }}</div>
<div>
글 제목: <a href="{% url "articles:detail" article.pk %}">{{ article.title }}</a>
</div>
</div>
<hr>
{% endfor %}
{% endblock content %}
<!--> articles/detail.html <-->
{% extends 'base.html' %}
{% block content %}
<h1>DETAIL</h1>
<h2>{{ article.pk }} 번 글</h2>
<hr>
<p>제목: {{ article.title }}</p>
<p>내용: {{ article.content }}</p>
<p>작성일: {{ article.created_at }}</p>
<p>수정일: {{ article.updated_at }}</p>
<hr>
{% if request.user == article.user %}
<a href="{% url "articles:update" article.pk %}">EDIT</a>
<form action="{% url "articles:delete" article.pk %}" method="POST">
{% csrf_token %}
<input type="submit" value="DELETE">
</form>
{% endif %}
{% comment %} 댓글 출력 {% endcomment %}
<ul>
{% for comment in comments %}
<li>
{{ comment.user.username }}- {{ comment.content }}
{% if request.user == comment.user %}
<form action="{% url "articles:comments_delete" article.pk comment.pk %}" method="POST">
{% csrf_token %}
<input type="submit" value="삭제">
</form>
{% endif %}
</li>
{% endfor %}
</ul>
<hr>
{% comment %} 댓글 작성 form {% endcomment %}
<form action="{% url "articles:comments_create" article.pk %}" method="POST">
{% csrf_token %}
{{ comment_form }}
<input type="submit">
</form>
<a href="{% url "articles:index" %}">메인 페이지로 돌아가기</a>
{% endblock content %}
# articles/views.py
@login_required
def update(request, article_id):
article = Article.objects.get(pk=article_id)
if request.user == article.user:
if request.method == 'POST':
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
form.save()
return redirect('articles:detail', article.pk)
else:
form = ArticleForm(instance=article)
else:
return redirect('articles:index')
context = {
'form': form,
'article': article,
}
return render(request, 'articles/update.html', context)
<!--> articles/detail.html <!-->
{% extends 'base.html' %}
{% block content %}
<h1>DETAIL</h1>
<h2>{{ article.pk }} 번 글</h2>
<hr>
<p>제목: {{ article.title }}</p>
<p>내용: {{ article.content }}</p>
<p>작성일: {{ article.created_at }}</p>
<p>수정일: {{ article.updated_at }}</p>
<hr>
{% if request.user == article.user %}
<a href="{% url "articles:update" article.pk %}">EDIT</a>
<form action="{% url "articles:delete" article.pk %}" method="POST">
{% csrf_token %}
<input type="submit" value="DELETE">
</form>
{% endif %}
{% comment %} 댓글 출력 {% endcomment %}
<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="삭제">
</form>
</li>
{% endfor %}
</ul>
<hr>
{% comment %} 댓글 작성 form {% endcomment %}
<form action="{% url "articles:comments_create" article.pk %}" method="POST">
{% csrf_token %}
{{ comment_form }}
<input type="submit">
</form>
<a href="{% url "articles:index" %}">메인 페이지로 돌아가기</a>
{% endblock content %}
@login_required
def delete(request, article_id):
article = Article.objects.get(pk=article_id)
if request.user == article.user:
article.delete()
return redirect('articles:index')
# article/views.py
# 댓글 작성 view
def comments_create(request, article_id):
# 1. 어떤 게시글에 작성되는 건지 게시글을 먼저 조회
article = Article.objects.get(pk=article_id)
# 2. 사용자로부터 입력받은 댓글 내용을 확인
comment_form = CommentForm(request.POST)
# 3. 유효성 검사
if comment_form.is_valid():
# 우리에게 필요한건 댓글이 실제 db에 저장되기 전에 외래 키 데이터가 채워져야 함
# 그런데 외래 키 데이터를 넣으려면 댓글 객체가 필요함.
# 하지만 댓글 객체는 save 이후에 생성됨. 그런데 우리는 save 전에 처리를 해야 함
# 그래서 django modelform의 save메서드는 우리에게 옵션(commit)을 하나 줌
# save의 commit옵션을 False로 변경하면
# ==> 우리에게 댓글 객체 인스턴스는 제공은 하되, 아직 db에 최종 저장 요청은 하지 않음
comment = comment_form.save(commit=False)
# 외래 키 데이터를 준비
comment.article = article
comment.user = request.user
# 4. 저장
comment.save()
# 5. 저장 후 detail 페이지로 리다이렉트
return redirect('articles:detail', article.pk)
# 6. 유효성 검사가 실패했다면 에러메세지와 함께 상세 페이지를 다시 응답
context = {
'comment_form': comment_form,
'article': article,
}
return render(request, 'articles/detail.html', context)
<!--> article/detail.html <-->
{% extends 'base.html' %}
{% block content %}
<h1>DETAIL</h1>
<h2>{{ article.pk }} 번 글</h2>
<hr>
<p>제목: {{ article.title }}</p>
<p>내용: {{ article.content }}</p>
<p>작성일: {{ article.created_at }}</p>
<p>수정일: {{ article.updated_at }}</p>
<hr>
{% if request.user == article.user %}
<a href="{% url "articles:update" article.pk %}">EDIT</a>
<form action="{% url "articles:delete" article.pk %}" method="POST">
{% csrf_token %}
<input type="submit" value="DELETE">
</form>
{% endif %}
{% comment %} 댓글 출력 {% endcomment %}
<ul>
{% for comment in comments %}
<li>
{{ comment.user.username }}- {{ comment.content }}
<form action="{% url "articles:comments_delete" article.pk comment.pk %}" method="POST">
{% csrf_token %}
<input type="submit" value="삭제">
</form>
</li>
{% endfor %}
</ul>
<hr>
{% comment %} 댓글 작성 form {% endcomment %}
<form action="{% url "articles:comments_create" article.pk %}" method="POST">
{% csrf_token %}
{{ comment_form }}
<input type="submit">
</form>
<a href="{% url "articles:index" %}">메인 페이지로 돌아가기</a>
{% endblock content %}
# 댓글 삭제 view
def comments_delete(request, article_id, comment_id):
# 1. 몇번 댓글 지우는지 조회
comment = Comment.objects.get(pk=comment_id)
if request.user == comment.user:
# 2. 조회한 댓글 삭제
comment.delete()
# 3. 삭제 후 게시글 상세 페이지로 redirect
return redirect('articles:detail', article_id)
<!--> article/detail.html <-->
{% extends 'base.html' %}
{% block content %}
<h1>DETAIL</h1>
<h2>{{ article.pk }} 번 글</h2>
<hr>
<p>제목: {{ article.title }}</p>
<p>내용: {{ article.content }}</p>
<p>작성일: {{ article.created_at }}</p>
<p>수정일: {{ article.updated_at }}</p>
<hr>
{% if request.user == article.user %}
<a href="{% url "articles:update" article.pk %}">EDIT</a>
<form action="{% url "articles:delete" article.pk %}" method="POST">
{% csrf_token %}
<input type="submit" value="DELETE">
</form>
{% endif %}
{% comment %} 댓글 출력 {% endcomment %}
<ul>
{% for comment in comments %}
<li>
{{ comment.user.username }}- {{ comment.content }}
{% if request.user == comment.user %}
<form action="{% url "articles:comments_delete" article.pk comment.pk %}" method="POST">
{% csrf_token %}
<input type="submit" value="삭제">
</form>
{% endif %}
</li>
{% endfor %}
</ul>
<hr>
{% comment %} 댓글 작성 form {% endcomment %}
<form action="{% url "articles:comments_create" article.pk %}" method="POST">
{% csrf_token %}
{{ comment_form }}
<input type="submit">
</form>
<a href="{% url "articles:index" %}">메인 페이지로 돌아가기</a>
{% endblock content %}
: View 함수의 동작을 수정하거나 추가 기능을 제공하는 데 사용되는 Python 데코레이터
1) Allowed HTTP methods
require_http_methods()from django.views.decorators.http import require_http_methods
@login_required
@require_http_methods(['GET', 'POST'])
def create(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
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_safe()from django.views.decorators.http import require_http_methods, require_safe
@require_safe
def index(request):
articles = Article.objects.all()
context = {
'articles': articles,
}
return render(request, 'articles/index.html', context)
@require_safe
def detail(request, article_id):
article = Article.objects.get(pk=article_id)
comment_form = CommentForm()
# 현재 게시글에 작성된 모든 댓글을 조회 (역참조, 1 ==> N)
comments = article.comment_set.all()
context = {
'article': article,
'comment_form': comment_form,
'comments': comments,
}
return render(request, 'articles/detail.html', context)
require_post@login_required
@require_POST
def delete(request, article_id):
article = Article.objects.get(pk=article_id)
if request.user == article.user:
article.delete()
return redirect('articles:index')
@require_POST
def comments_create(request, article_id):
# 1. 어떤 게시글에 작성되는 건지 게시글을 먼저 조회
article = Article.objects.get(pk=article_id)
# 2. 사용자로부터 입력받은 댓글 내용을 확인
comment_form = CommentForm(request.POST)
# 3. 유효성 검사
if comment_form.is_valid():
# 우리에게 필요한건 댓글이 실제 db에 저장되기 전에 외래 키 데이터가 채워져야 함
# 그런데 외래 키 데이터를 넣으려면 댓글 객체가 필요함.
# 하지만 댓글 객체는 save 이후에 생성됨. 그런데 우리는 save 전에 처리를 해야 함
# 그래서 django modelform의 save메서드는 우리에게 옵션(commit)을 하나 줌
# save의 commit옵션을 False로 변경하면
# ==> 우리에게 댓글 객체 인스턴스는 제공은 하되, 아직 db에 최종 저장 요청은 하지 않음
comment = comment_form.save(commit=False)
# 외래 키 데이터를 준비
comment.article = article
comment.user = request.user
# 4. 저장
comment.save()
# 5. 저장 후 detail 페이지로 리다이렉트
return redirect('articles:detail', article.pk)
# 6. 유효성 검사가 실패했다면 에러메세지와 함께 상세 페이지를 다시 응답
context = {
'comment_form': comment_form,
'article': article,
}
return render(request, 'articles/detail.html', context)
# 댓글 삭제 view
@require_POST
def comments_delete(request, article_id, comment_id):
# 1. 몇번 댓글 지우는지 조회
comment = Comment.objects.get(pk=comment_id)
if request.user == comment.user:
# 2. 조회한 댓글 삭제
comment.delete()
# 3. 삭제 후 게시글 상세 페이지로 redirect
return redirect('articles:detail', article_id)
: 데이터베이스의 구조를 시각적으로 표현하는 도구
구성요소 : 엔티티 / 속성 / 관계


