Lina's Toolbox

Django Model Relationship (1:N) 본문

스파르타 내일 배움 캠프 AI 웹개발 과정/Django framework

Django Model Relationship (1:N)

Woolina 2024. 9. 16. 04:17

 

 

Many to one relationships

굉장히 흔히 사용되는 관계

 

1:N 관계 예시

  • 만약 Article에 Author라는 개념을 둔다면,
    • 하나의 Article은 한 명의 Author를 가질 수 있습니다.
    • 한 명의 Author는 여러개의 Article을 가질 수 있습니다.
  • 만약 Article에 Comment라는 개념을 둔다면,
    • 하나의 Article은 여러개의 Comment를 가질 수 있습니다.
    • 하나의 Comment는 하나의 Article을 가질 수 있습니다.

Foreign Key

  • 외래키를 의미합니다.
  • 관계형 데이터베이스에서 한 테이블(A)의 필드 중 다른 테이블(B)의 행을 유일하게 식별이 가능한 키입니다.
  • 테이블(A)에 설정되는 Foreign Key가 반드시 다른 테이블(B)의 Primary Key일 필요는 없으나 유일하게 식별이 가능해야합니다.

 

이렇게 many to one 관계로 모델링 해보자!


실습 - 댓글 구현하기

 

댓글(Comment) 구현

  • articles/models.py

models.ForeignKey(<참조하는 모델 클래스>, on_delete=<옵션>)

📌 on_delete 옵션

게시글이 삭제되면, 댓글도 같이 삭제해줄 지 결정하는 옵션

✔️ 공식문서 참조 https://docs.djangoproject.com/en/4.2/ref/models/fields/#django.db.models.ForeignKey.on_delete

CASCADE
부모가 삭제되면 이를 참조하는 객체도 같이 삭제

PROTECT
참조하는 객체가 있을경우 예외를 발생시켜 삭제를 못하게함

SET_NULL
참조하는 객체가 삭제될 경우, comment의 게시글 id를 NULL 값으로 바꿔줌

 

📌 <fieldname>_id

 

  • 참조 테이블의 이름뒤에 _id 를 붙여 컬럼을 자동으로 생성합니다.

 

ORM 사용해보기

터미널에서 shell_plus 이용하기

→ 터미널에서 아래의 코드를 입력합니다.

python manage.py shell_plus

comment = Comment()

comment.content = “first comment”

comment.save()

 

1. Comment 생성하기

 

 

article_id 값을 지정하지 않았기때문에 그렇습니다!

comment.article_id = article.pk로 명시하지 않아도 됩니다!

내부적으로 장고의 ORM이 join을 수행하지만, 우리는 할 필요없이 이렇게 바로 접근 가능하다!!!

짱편한데?


 

실습 - 두번째 댓글 생성하기

 

위와 같은 article에 대해 생성해 주세요!

 

 

2. comment → article 접근

만약 comment에서 어떤 article인지 접근하고 싶다면 어떻게 할까요?

그렇다면 이 comment가 작성된 article의 title , content 를 알고 싶다면 어떻게 할까요?

 

  • 정참조 = 정방향참조 (comment에서 article을 참조하는것)
    • Comment(N) → Article(1) 참조 == 정참조
    • 내 Model Class에서 Field가 정의되어 있으므로( Comment는 항상 하나의 참조하는 Article이 있으므로) comment.article 로 아주 쉽게 접근이 가능합니다. 👍

 

3. article → comments 접근

🤔 만약 article에서 작성된 모든 comment를 알고싶다면?

article.???????
Article 모델 클래스에는 Comment에 대한 어떠한 정보도 없어요!
  • 역참조 = 역방향 참조
    • Article(1) → Comment(N) 참조 == 역참조
    • Django는 역참조의 경우 참조하는 클래스명에 _set 을 붙인 manager를 생성합니다.
    1번 Article에 작성된 모든 Comment 조회

OMG … SOOOO EASY !!!

그런데 manager 이름이 맘에들지 않는데요?!

article.commnets.all() 이렇게 쓰고 싶어요!

→ 수정시 migration 필요

 

 

댓글 생성

forms.py

...
class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = "__all__"

 

articles/views.py

 

 

articles/templates/articles/article_detail.html

article을 제외하려면

 

 

 

forms.py (2차)

class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = "__all__"
        exclude = ("article",)

 

 

로직구현

  • articles/urls.py
path("<int:pk>/comments/", views.comments_create, name="comments_create"),

 

articles/views.py

@require_POST
def comment_create(request, pk):
    article = get_object_or_404(Article, pk=pk)
    form = CommentForm(request.POST)
    if form.is_valid():
        comment = form.save(commit=False)
        comment.article = article
        comment.save()
    return redirect("articles:article_detail", article.pk)
  • save(commit=False)
    • 실제 데이터 베이스에 처리되지 않은 상태의 인스턴스를 반환합니다.
    • 저장 전, 로직이 필요한 경우에 사용합니다.

comment = Comment()

comment.content = content

#### 이 지점을 잡아주는 것을 save메소드가 지원을 한다.! ####

comment.save() // 이 때 데이터베이스에 저장을 한다!

→ comment = form.save(commit=False)

→ 이렇게 하면 방금 새로 생성된 comment 인스턴스인데, commit은 되지 않아서 DB에는 아직 들어가지 않은 상태!

 

 

articles/templates/articles/article_detail.html

<hr>
<h3>댓글</h3>
<form action="{% url "articles:comment_create" article.pk %}" method="POST">
    {% csrf_token %}
    {{ comment_form.as_p }}
    <input type="submit" value="댓글작성">
</form>

코멘트를 보여주는건 article detail 페이지를 눌렀을때 보여주기때문에 get은 필요없다!

 

댓글 읽기

articles/views.py

 

 

articles/templates/articles/article_detail.html

...
<ul>
    {% for comment in comments %}
        <li>
            <p>{{ comment.content }}</p>
        </li>
    {% endfor %}
</ul>

템플릿에서 우리가 이미 article을 알고있기 때문에

댓글 목록 보여줄때

 

뷰에서 넘겨서

{% for comment in comments %} 

이렇게 할 필요 없이 그냥

 

 

그런데 사실 이렇게도 가능해요!

⇒ view를 수정하지 않고 바로 해결 👀

<ul>
    {% for comment in articles.comments.all %}
        <li>
            <p>{{ comment.content }}</p>
        </li>
    {% endfor %}
</ul>

도 가능하다!!

 

 

하지만 우리는 order by(-pk) 등을 사용해주기 위해 그냥 comments를 사용해본것.

 


과제 📝

아래 예시 사진처럼 결과가 나올 수 있도록 구현해볼까요?

  • 댓글 삭제를 구현해 보세요.
  • 전체 댓글의 수를 출력하도록 작성해 보세요.