06_django_model_relationship
https://docs.djangoproject.com/ko/3.1/ref/models/fields/#module-django.db.models.fields.related
Foreign Key
개념
외래 키(외부 키)
RDBMS에서 한 테이블의 필드 중 다른 테이블의 행을 식별할 수 있는 키
참조하는 테이블에서 1개의 키(속성 또는 속성의 집합)에 해당하고 이는 참조되는 측의 테이블의 기본 키를 가리킴
참조하는 테이블의 속성의 행 1개의 값은, 참조되는 측 테이블의 행 값에 대응
- 이 때문에 참조하는 테이블의 행에는, 참조되는 테이블에 나타나지 않는 값을 포함할 수 없음
참조하는 테이블의 행 여러 개가, 참조되는 테이블의 동일한 행을 참조할 수 있음
특징
- 키를 사용하여 부모 테이블의 유일한 값을 참조 (참조 무결성)
- 외래 키의 값이 부모 테이블의 기본 키 일 필요는 없지만 유일해야 함
ForeignKey field
A many-to-one relationship
2개의 필수 위치 인자가 필요
- 참조하는 model class
- on_delete 옵션
1:N model manager
# 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
- Article : Comment = 1 : N → 하나의 게시글에는 여러 개의 댓글이 달릴 수 있다.
on_delete
- ForeignKey의 필수 인자이며, ForeignKey가 참조하고 있는 부모(Article) 객체가 사라졌을 때 달려 있는 댓글들을 어떻게 처리할 지 정의
- Database Integrity(데이터 무결성)을 위해서 매우 중요한 설정이다.
possible values for on_delete
CASCADE
: 부모 객체(참조 된 객체)가 삭제 됐을 때 이를 참조하는 객체도 삭제PROTECT
SET_NULL
SET_DEFAULT
SET()
DO_NOTHING
RESTRICT
migration
$ python manage.py makemigrations
# $ python manage.py sqlmigrate articles 0002
$ python manage.py migrate
데이터베이스 표현
- Django는 필드 이름에
_id
를 추가하여 데이터베이스 필드 이름을 만듦
Table 직접 확인하기
article_id
라는 컬럼이 생성- 만약 ForeignKey 를 article 이라고 하지 않고
abcd = models.ForeignKey(..)
형태로 생성 했다면abcd_id
로 만들어진다. - 이렇게되면 모델 관계를 파악하는 것이 어렵기 때문에 부모 클래스명의 소문자(단수형)로 작성하는 것이 바람직하다.
1 : N 관계 manager
Article(1) : Comment(N) :
comment_set
article.comment
형태로는 가져올 수 없다.- 게시글에 몇 개의 댓글이 있는지 Django ORM이 보장할 수 없기 때문
- 본질적으로는 Article 클래스에 Comment 와의 어떠한 관계도 연결하지 않음
Comment(N) : Article(1) :
article
- 댓글의 경우 어떠한 댓글이든 반드시 자신이 참조하고 있는 게시글이 있으므로
comment.article
와 같이 접근할 수 있음
- 댓글의 경우 어떠한 댓글이든 반드시 자신이 참조하고 있는 게시글이 있으므로
related_name
https://docs.djangoproject.com/en/3.1/ref/models/fields/#django.db.models.ForeignKey.related_name
위에서 확인한 것처럼 부모 테이블에서 역으로 참조할 때(the relation from the related object back to this one.)
모델이름_set
이라는 형식으로 참조한다. (역참조)related_name 값은 django 가 기본적으로 만들어 주는
_set
manager를 임의로 변경할 수 있다.# articles/models.py class Comment(models.Model): article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments') ...
위와 같이 변경하면
article.comment_set
은 더이상 사용할 수 없고article.comments
로 대체된다.1:N 관계에서는 거의 사용하지 않지만 M:N 관계에서는 반드시 사용해야 할 경우가 발생한다.
Comment
CREATE
# forms.py
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 = get_object_or_404(Article, pk=pk)
comment_form = CommentForm()
context = {
'article': article,
'comment_form': comment_form,
}
return render(request, 'articles/detail.html', context)
detail page 에 댓글 작성 form 추가
<!-- articles/detail.html --> {% extends 'base.html' %} {% block content %} ... <a href="{% url 'articles:index' %}">back</a> <hr> <form action="" method="POST"> {% csrf_token %} {{ comment_form }} <input type="submit"> </form> {% endblock %}
필드 출력 재설정
# articles/forms.py class CommentForm(forms.ModelForm): class Meta: model = Comment exclude = ('article',)
# articles/urls.py
path('<int:pk>/comments/', views.comments_create, name='comments_create'),
save()
save()
method
https://docs.djangoproject.com/en/3.0/topics/forms/modelforms/#the-save-method
Create, but don't save the new instance.
# articles/views.py @require_POST def comments_create(request, pk): article = get_object_or_404(Article, 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) context = { 'comment_form': comment_form, 'article': article, } return render(request, 'articles/detail.html', context)
<!-- articles/detail.html --> <form action="{% url 'articles:comments_create' article.pk %}" method="POST"> {% csrf_token %} {{ comment_form }} <input type="submit"> </form>
- 댓글을 작성한 후 admin 페이지 혹은 sqlite 확장에서 확인
READ
특정 article에 있는 모든 댓글을 가져온 후 template 으로 전달한다.
# 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)
```html
<!-- articles/detail.html -->
{% extends 'base.html' %}
{% block content %}
...
<a href="{% url 'articles:index' %}">back</a>
<hr>
<h4>댓글 목록</h4>
{% for comment in comments %}
<li>{{ comment.content }}</li>
{% endfor %}
<hr>
...
{% endblock %}
DELETE
# articles/urls.py
path('<int:article_pk>/comments/<int:comment_pk>/delete/', views.comments_delete, name='comments_delete'),
# articles/views.py
@require_POST
def comments_delete(request, article_pk, comment_pk):
comment = get_object_or_404(Comment , pk=comment_pk)
comment.delete()
return redirect('articles:detail', article_pk)
<!-- articles/detail.html -->
{% block content %}
...
<h4>댓글 목록</h4>
{% for comment in comments %}
<li>
{{ comment.content }}
<form action="{% url 'articles:comments_delete' article.pk comment.pk %}" method="POST" class="d-inline">
{% csrf_token %}
<input type="submit" value="DELETE">
</form>
</li>
{% endfor %}
<hr>
...
{% endblock %}
Comment 관련 추가 사항
댓글 개수 출력
# 1. {{ comments|length }}
# 2. {{ article.comment_set.all|length }}
# 3. {{ comments.count }}
<!-- articles/detail.html -->
<h4>댓글 목록</h4>
{% if comments|length %}
<p><b>{{ comments|length }}개의 댓글이 있습니다.</b></p>
{% endif %}
댓글이 없는 경우 다른 문장 출력
for empty
활용<!-- articles/detail.html --> {% for comment in comments %} <li> {{ comment.content }} <form action="{% url 'articles:comments_delete' article.pk comment.pk %}" method="POST" class="d-inline"> {% csrf_token %} <input type="submit" value="DELETE"> </form> </li> {% empty %} <p><b>댓글이 없어요</b></p> {% endfor %}
'Web > Django' 카테고리의 다른 글
django_08_model_relationship (0) | 2021.03.29 |
---|---|
django_07_custom_authentication (0) | 2021.03.25 |
django_05_auth (0) | 2021.03.23 |
django_04_static_media_files (0) | 2021.03.18 |
django_03_form (0) | 2021.03.17 |