티스토리 뷰

일상코딩/노트

Django : Many to one relationship

코딩애벌레 2024. 4. 5. 10:34

관계형 데이터 베이스를 배웠으니 django의 백엔드를 통해 저장하고 데이터를 호출하는 법도 배워야 할 차례다. N:1까지는 할만하니 한번 해보자! (django라서 다루기도 쉽다)


 

Many to one relationships (N:1)

: 한 테이블의 0개 이상의 레코드가 다른 테이블의 레코드 한개와 관련된 관계

=> 예를들어 게시글에 댓글을 생각해보자. 한 개의 게시글에는 여러개의 댓글을 달 수 있을 것이다. 이때, Comment는 N, Article은 1이다. 

Comment Article

id

content

created_at

updated_at


id

title

content

created_at

updated_at

 

현재 위처럼 테이블이 작성되어있다고 하면, Article에 댓글이 여러개 달리기 때문에 N:1인건 이제 모두 알것이다. 그럼 Fk는 어디에 작성되어야 할까? 

 

기준이 Article이 되었다고 생각하면 아래와 같다

Comment에 Article(id)에 대한 외래키가 생성되어야 한다

 

그럼 django에서 ForeignKey를 추가해보자. 

Comment 모델에서 Article을 참조하는 것이기 때문에 첫번째 인자로 모델, 선택인자로 on_delete=models.CASCADE를 넣어준다

 

  • ForeignKey 클래스의 인스턴스 이름은 참조하는 모델 클래스의 소문자 + 단수형으로 작성하는 것을 권장한다
  • ForeignKey 클래스를 작성하는 위치와 관계없이 테이블의 필드 마지막에 생성된다
  • on_delete 는 외래 키가 참조하는 객체가 사라졌을 때, 외래 키를 가진 객체를 어떻게 처리할 지를 정의하는 설정으로 데이터의 무결성을 위해 작성해준다.
  • CASCADE : 부모 객체(참조 된 객체)가 삭제 되었을 때 이를 참조하는 객체도 삭제한다.

article_id 필드가 생성되었다

  • 필드명은 참조하는 대상 클래스 이름 + ' _ ' + '클래스 명(id)'
  • 데이터 활용에 혼동이 없기 위해 models.py에서 클래스 이름의 소문자+단수형으로 작성하는 것을 권장

N : 1 데이터 생성 및 참조

 

이제 database에도 적용했으니 어떻게 데이터를 생성하면 되는지 확인해보자.

create는 save 필요없이 바로 생성하는 명령어!

 

그럼 댓글도 동일하게 생성될까?

 

아까 위에서 만들어진 참조 데이터인 article_id가 누락됨을 알 수 있다.

 

그리고 create를 사용하면 save() 없이 바로 저장할 수 있으나, 데이터 검증이 어렵기 때문에 참조가 생기는 부분부터는 사용을 피해야 한다.

create로 생성할 순 있지만, 과정을 확인하기 위해 위처럼 차례대로 하는 것을 추천한다

 

여기서 궁금증이 생길 수 있다. comment.article = article을 보면 comment의 인스턴스 데이터로 객체 자체를 넣어버리는 것을 확인할 수 있다.

 

어떤게 맞는 표현일까? 사실 둘 다 맞는 표현이다.

 

앞의 방식의 경우 comment의 데이터에 article이라는 객체 자체를 넘긴 상황인데, ORM은 객체와 관형 데이터베이스간의 매핑을 자동으로 처리해주기 때문에 실제로 Article(model) 객체가 할당되고, ORM이 객체를 기반으로 필요한 데이터를 추출한다. 즉, 자동으로 처리해준다..

 

뒤의 방식의 경우 직접 데이터에 접근해 comment.article_id 값에 artticle의 pk(id) 데이터를 할당하는 방식이라 직접적이고 명시적이지만, 객체 지향적인 관점에서는 불편한 경우가 생긴다.

 

일반적으로 ORM을 사용할 경우에는 전자의 방식을 선호한다.

 

모든 데이터를 확인할 수 있다.
id를 직접 연결한 방식에서도 객체는 자동 연결 되는 모습
댓글의 참조 경로를 통해 게시글의 데이터도 확인 가능하다
Comment의 article이 Article model의 통로를 열었다고 이해하면 쉬울 것 같다
데이터 베이스에도 문제없이 채워져있는 모습을 확인

 


역참조(related manager)

 

: N:1 관계에서 1이 N을 참조하거나 조회하는 것

: 'objects' 매니저를 통해 QuerySet API를 사용했던 것처럼 related manager를 통해 QuerySet API를 사용할 수 있다

앞서 만든 데이터는 연결되어있는 통로(comment.article)을 통해 article의 데이터를 조회할 수 있었다. 반대로는 연결된 통로가 없는데 데이터 조회는 어떻게 할 수 있을까?

이때 사용하는 것이 역참조 매니저를 사용한다고 표현하는데, 사용 방법은 아래와 같다.

굉장히 어려워보이지만, 실제로 사용하는데는 어려움이 없으니 쫄지말자

 

= article에서 comment_set(역참조 매니저)를 통해 모든 데이터 조회

 

위를 해석하면 'article에 쓰인 댓글을 모두 조회하겠다'는 의미다. N:1 관계에서 생성되는 Related manager의 이름은 참조하는 '모델명_set' 규칙으로 이름이 정해진다.

 

댓글 2개가 쿼리셋 형태로 반환한다

 

그럼 어떤 데이터들을 출력해 볼 수 있을까?

역참조도 모든 데이터 조회가 가능하다

 

이제 기초 지식이 완료되었으니, 댓글 창을 구현해보자. 

 

CRUDCreate 과정

 

이제 어느정도 django의 흐름을 알고 있을테니, 추가하는 데이터만 보여줄 예정이다.

  • 사용자로부터 댓글 데이터를 입력 받기 위한 CommentForm을 정의한다
  • detail view 함수에서 detail 페이지에 랜더링할  CommentForm 전달 

  • 전달 받은 CommentForm을 detail.html에서 전개
  • 유효성 검사인 csrf_token과 form data의 method를 POST로 설정
  • 데이터 입력을 전달해줄 input (type="submit") 버튼 추가

해당 목록은 ForeignKey인 article 필드가  존재하기 때문이다

 

위의 방법은 사용자 입장에서는 필요없는 과정이다. 이유는 이미 게시글에 들어와서 댓글을 작성하는데, 해당 게시글을 다시 찾아서 댓글달아야 하는점, 다른 게시글이 있다면 다른 페이지에서 다른 게시글에 댓글을 달 수 있는 아이러니한 상황이 놓이게 된다. 즉, 전달될 필요 없이 외래 키 필드 데이터는 사용자로부터 입력 받는 값이 아닌 view 함수 내에서 다른 방법으로 전달 받아 데이터가 저장되어야 하는 것임을 알 수 있다.

 

먼저 form에서 넘겨줄 필요없는 article Foreign Key부터 제외하자.

fields를 명시해주어도 된다. 오른쪽은 적용된 모습

 

일단 급한불은 껐다. 이제 article의 게시글 번호를 불러와야할 방법을 찾아야 되는 것인데 어떻게 가져올 수 있을까? detail 페이지에 포함되는 데이터를 확인해보자.

잘 째려보니 게시글의 pk 값이 사용되고 있다!

 

왜 pk값이 넘어오고 있었나 생각하면 당연하다. 게시글의 detail 페이지를 들어가기 위해 게시글 번호를 넘겨주어야 이동이 가능하기 때문이다. 우리가 원하는 데이터는 게시글의 pk로 comment의 article.pk를 채워주어야 하니 정답이다. 이제 form이 제출되었을때 create되는 부분을 구현하면된다.

여기서 pk는 두가지를 위해 필요하다. 먼저 어떤 게시글에 댓글이 달리는지 참조하기위한 pk 데이터
두번쨰는 detail 페이지로 이동하기 위해 사용된다 (모두 view함수에서 사용된다)
지금까지 작성했던 view함수 방식에서 무엇인가 빠진것 같다. 방금 배웠는데 뭘까?
comment_form을 출력해본 결과 required id가 있다

 

위의 required 속성은 폼 입력 필드가 제출될 때 반드시 필요한 값(id)이 있다는 뜻이다. 사용자가 이 필드를 비어있는 상태로 제출하려고 하면 브라우저가 제출을 막고 사용자에게 값을 입력하도록 요청한다.

 

우리는 Comment model에 aritcle을 참조하기 위한 데이터를 넘겨줘야하는 것을 기억한다! 그렇다면 article을 호출하는 것은 redirect뿐만 아니라, 어떤 게시글에 댓글이 달리는지 데이터를 넣어주어야 한다는 것을 잊지말자. 

 

그러면 comments_form 안의 인스턴스를 필요로하는 article에 article.pk를 직접 할당할 수 있을까? 아쉽게도 불가능하다. save()하면서 comment로 새로 할당받는데, 이때 데이터 틀과 comments_form의 틀이 다르기 때문에 바로 저장할 순 없다. 대신 임시저장을 사용한다. 객체를 연결해줄 수 있는 데이터 형태로 바꾼 다음, 필요한 데이터를 채워넣는 방식이다.

save 함수를 확인해보면 commit이 기본값인 것을 확인할 수 있고, False로 통과하게 될 경우 else 부분으로 가게되어 최종 save가 실행되지 않는다.

 

문제없이 댓글이 잘 작성되는 모습


CRUD Read 과정

 

데이터를 조회하는 과정은 간단하다. 다만, 우리가 detail 함수에서 갖고있는 데이터는 article의 pk값이다. 그럼 어떻게 조회할 수 있을까? 아까 배운 역참조를 사용하면 가능하다! article 객체는 호출되어 있으니 해당하는 통로로 comments들을 모두 조회할 수 있을 것이다.

 

역참조 매니저(모델명_set)의 양식을 따라 comment_set을 사용하고, Query API는 all()을 사용하면 모든 댓글을 할당할 수 있다.

 

이로써 detail 페이지에는 comments 데이터도 전달되어있으니 사용할 수 있다. 모든 댓글을 반복 출력할 예정이니 for문을 통해 전개해주면 되겠다.

반복문을 돌며 li tag 형태로 작성되는 모습

 

CRUDDelete 과정

 

Update는 잠시 보류!

 

이젠 Delete는 빠르게 작성해보자. delete 호출받을 url이 필요하고, 행동할 view함수가 필요하며, 추가 template는 필요없고 detail.html에 삭제 버튼을 추가해주면 된다. 이제는 이렇게 파악할 수 있어야 한다.

근데, comment의 모델을 확인했을때, 어떤 데이터들이 필요할까? 일단, 댓글의 pk는 반드시 필요하다는 것을 알 것이다.  삭제하려면 댓글의 pk를 호출해서 delete()를 해주면 데이터베이스에서 삭제가 가능하니까 말이다. 그러면 게시글의 pk가 필요할까? 이때는 두가지 선택지가 있다. 삭제 되었을 때, detail 페이지를 유지하지 않고 메인 페이지로 이동하거나, 다른 경로로 이동한다면 굳이 게시글 번호는 필요하지 않을 것이다. 하지만 해당 페이지를 유지하려면 detail 페이지인데, 이때 detail 경로에는 게시글 pk가 필요하다. 

우리가 사용하는 대부분 사이트, 클라이언트 입장에서는 댓글이 삭제되었을 때는 보통은 해당 페이지를 유지하는 경우가 많으니 후자를 선택하는 것이 자연스러울 것이다.

그렇다면 필요한 데이터는 article의 pk, 게시글의 pk임을 확인할 수 있다. 이를 토대로 url과 view함수를 정의해보자.

최대한 위에서 작성한 경로들과 유사하게 만들어 주는 것이 약속이다. article_pk는 commet_pk와 구분하기 위해 사용했다.
article_pk는 redirect를 위해, comment_pk는 댓글 조회를 위해 사용된다
삭제는 csrf_token을 필수로 하기 때문에, form을 사용, method = 'POST' 를 지켜준다. input 혹은 button을 통해 form이 제출될 수 있도록 작성한다

 

댓글 삭제 버튼이 생겼고, 제대로 작동하는 모습

 

만약 댓글이 없는 경우에는 댓글의 데이터가 없기 때문에 반복문 for을 돌지 않기 때문에 아무것도 출력되지 않는다. 그러면 댓글이 없다는 텍스트를 출력해주는 것이 자연스러울텐데, 조건문 if를 사용해서 if, else를 하면 되지 않을까? 물론 가능하다. 하지만 조건문 내부에 반복문이 사용되면서 코드의 가독성이 떨어질 수 있다. 이때 사용할 수 있는 for의 empty 옵션이 존재한다. 이는 반복문을 실행하지 않는다(데이터가 비어있다면) 이라는 조건문이 붙어있는 옵션이라고 보면 된다.

for 문 내부에 넣어주면 간단하다. 조건문을 추가해서 조건을 넣어주고 else하는 것보다 훨씬 가독성이 좋다는 것을 알 수 있다
댓글이 없을 경우 대체 텍스트가 출력되는 모습

 

+ 부록

댓글의 개수를 출력하는 방법

물론 view함수에서 파이썬 함수를 사용하여 context에 dictionary로 감싸서 억지로 전달하는 경우도 있겠지만, 이왕이면 html에서 데이터를 조작하는 필터를 사용하면 좋지 않을까?

 

  • DTL filter - '| length' 사용
  • QuerySet API - 'count()' 사용

해당 페이지에서 사용가능한 데이터에 따라 접근하면 된다.
모두 같은 결과의 출력!


이제 N:1 했는데 벌써 참조 역참조 헷갈리기 시작한다.. django라서 쉬운 편인건데 복습하면서도 파고들면 파고들수록 헷갈려지는 부분인 것 같으니 어느정도 암기와 이해력을 갖고 차근차근 이어가면 될 것 같다.

'일상코딩 > 노트' 카테고리의 다른 글

Django : REST API 2  (0) 2024.04.12
Django : REST API 1  (0) 2024.04.11
DB : SQLite JOIN  (0) 2024.04.04
DB : SQLite (DDL, DML) 명령어  (0) 2024.04.03
DB : SQLite (DQL) 명령어  (0) 2024.04.02
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함