본문 바로가기
WebFramework/Python Django

[Python Django] 16. Django ORM과 CRUD

by Rosmary 2024. 4. 25.
728x90
반응형

 

 

 

 

이번 포스팅에서는 Django의 Model을 참조하여, sqlite 파일에 생성된 데이터 테이블에 실제 데이터를 생성, 조회, 수정, 삭제(CRUD)하는 방법에 대해 알아보려한다. 원래는 지난 포스팅의 Custom User Model 생성 방법 중 Mapping 방식과 AbstractBasicUser Class에 대해서 작성하려 했으나, Mapping 방식은 Django ORM을 조금이라도 알고 넘어가는게 나을 듯 하고, AbstractBasicUser는 아직 필자가 파악이 덜 끝난(사실 시작도 못한) 상태라, 추후 포스팅에서 소개하고 링크를 연결하려 한다.

 

 

 

1. Django는 어떻게 데이터를 .sqlite 파일에 저장할까?

 

 

필자가 이전에 작성한 Django 관련 포스팅을 보면, 게시판 새 글을 작성하거나 회원 가입을 진행하는 부분에서 별도의 DB Query문을 작성하지 않았음에도 웹 서버는 해당 정보를 무리없이 호출해 화면에 표시하는 것이 가능했음을 알 수 있다. 그럼, Django는 어떻게 사용자 입력값을 DB 쿼리문 없이 바로 DB 파일에 저장하는 것이 가능할까? 

 

https://docs.djangoproject.com/en/5.0/topics/db/managers/

 

 

Django 공식 문서의 Manager에서, Manager는 각 모델에 제공되는 DB Query 수행 인터페이스라고 소개하고 있다. 개별 모델의 Manager는 objects라는 기본 이름으로 지정이 되어 있다고 한다.

 

 

 

django.db 모듈에는  Manager와 BaseManager라는 클래스가 존재하며, 지금까지 봐 왔던 대부분의 내장 클래스와 같이 상속관계로 이루어져 있다. 부모 클래스인 BaseManager에서 소스 코드를 확인해보면, 특정 모델 클래스 객체로부터 add_manager()라는 매서드를 통해 자기 자신(BaseManager 객체)을 Model 클래스에 추가하는 것을 확인할 수 있다. models.py에서 생성하는 모든 클래스는 django.db.models.Model 클래스를 상속받으며, 이 클래스 또한 ModelBase 클래스를 상속받고 있는데, 이 클래스에서 매니저를 붙이는 작업을 자동으로(auto_created=True, 기본값은 BaseManager에서 False로 되어 있다) 진행하는 것도 소스코드로 확인할 수 있다.

 

Manager 클래스의 소스 코드로 다시 돌아와보면, django.db.models.query 모듈 내 존재하는 QuerySet이라는 클래스를 BaseManager의 매서드인 from_queryset()으로 참조하고 있는 것이 확인되는데, 

 

 

 

이 QuerySet이 실제로 DB 쿼리를 수행하는 주체가 된다는 것을 소스 코드를 통해 확인할 수 있다.

 

bulk_update 등등 다양한 매서드를 제공한다. 화면에 다 안들어와서...

 

 

즉, Django의 App 내 models.py에서 models.Model을 상속받아 생성하는 클래스는 기본적으로 objects라는 이름의 Manager를 가지고 있으며, 이 Manager는 다시 내부의 QuerySet 클래스를 사용하여 DB로부터의 정보를 받아온다. 그리고 QuerySet 클래스는 실제로 DB로 쿼리를 날려 CRUD를 진행하게 된다. 

 

물론 QuerySet과 DB 사이에 Engine이나 기타 잡다한 것도 있지만 제외했다.

 

 

정리하자면, Model 내 Manager는 DB 쿼리를 날릴 수 있도록 고안된 일종의 interface라고 보면 된다.

 

"그런데, 지금까지 포스팅 내용을 보면 대부분 form.save()로 데이터 저장 처리를 쉽게 진행할 수 있는데, 굳이 CRUD를 model 클래스로 조작하는 방법도 알아야 하나요?"

 

라고 궁금해하실 분도 있을 듯 한데, 아래의 예시를 보자.

 

 

 

지난 포스팅에서 사용한 Custom User Model인 Accounts 클래스에 last_login_dt라는 fields를 추가했다. 사용자가 로그인을 했을 때, 마지막 접속 일시인 last_login_dt를 최신 일시로 업데이트하기 위해서다. 그런데, 로그인 시 사용하는  AuthenticationForm의 경우, username과 password만 참조하기 때문에 아무리 사용자가 로그인을 정상적으로 진행한다고 해도 last_login_dt는 업데이트되지 않는다. 

 

 

 

 

 

 

2. model 매서드로 DB 조회

 

먼저 데이터 조회부터 진행해보자. DB를 사용하기 위해서는 Model에 붙어있는 Manager가 제공하는 매서드를 사용해야만 한다. 그리고 Manager 매서드는, Model 클래스가 참조하는 DB Table의 정보를 추출하는 매서드를 아래와 같이 제공한다.

 

-  전체 데이터 추출                            :  all()

-  특정 조건을 만족하는 데이터 추출 : filter(<검색조건>)

-  특정 1개 데이터 추출                     : get(<fields_name>=<fields_value>)

 

 

(1) 전체 데이터 추출

 

먼저 전체 회원 정보부터 추출해보자. 추출한 회원 정보는 지난 포스팅에서 사용한 Django 프로젝트의 index.html 페이지에 표시되도록 만들어보려한다. 따라서 views.py를 아래와 같이 수정했다.

 

index.html에서는 context의 "accounts"를 받도록 미리 설정해두었다.

 

 

서버를 구동하고 index.html  페이지로 접속해보면, 아래와 같이 Accounts Model의 Manager가 실행한 all() 매서드에 의해 반환된 QuerySet 객체가 나타나는 것이 확인된다. 

 

 

 

QuerySet 내 각 정보는 Accounts class의 인스턴스로 구성되어 있다. 따라서 index.html에서 이 정보들을 html 태그와 DTL로 꾸미면 아래와 같이 각 계정에 대한 세부 정보도 확인이 가능해진다.

 

 

 

 

(2) 특정 조건의 데이터 추출: filter()

 

다음으로는 특정 조건을 만족하는 데이터만 추출해서 index.html에 출력해보려한다. 조건을 만족하는 Accounts를 QuerySet으로 돌려받기 위해서는 filter 매서드를 사용하며, filter 매서드는 내부에 검색할 조건을 명시해야한다. 그런데, 조건을 명시하는 방식이 일반적인 조건문을 사용하는 것이 아니라 조금 독특하다.

 

 

https://docs.djangoproject.com/en/5.0/ref/models/querysets/#field-lookups

 

QuerySet API reference | Django documentation

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

 

공식 문서의 내용을 보면 알겠지만, 일반적인 함수 인자 입력하듯이 입력하면 정확한 값을 추출하는 것이 어려워진다. '='가 완전히 동일한 것만 찾도록 지정되어 있기 때문이다.

 

 

 

따라서 filter나 exclude, get으로 특정 조건을 명시할 때, Djangodml fields_lookups라 불리는 것을 fields 변수명 뒤에 추가하여 사용한다. 만약 가입일이 2024년 04월 24일 이전인 사용자 정보를 출력하고자 한다면 아래와 같이 filter의 매개변수 뒤에 lookup 변수 __lt를 추가하면 된다.   (less than)

 

 

반대로 2024년 04월 24일 이후 가입자 정보를 확인하고 싶다면 fields 명 뒤의 lookup 변수를 __gt로 변경하면 된다. (greater_than)

 

 

 

filter() 매서드를 사용한 결과 역시, all() 매서드와 마찬가지로 조건과 일치하는 Accounts 인스턴스를 QuerySet 클래스로 반환한다.

 

 

 

(3) 단 하나의 데이터만 반환

 

Model Manager의 get()은 단 하나의 데이터만 반환하도록 설계되어 있다. 따라서 DB 테이블 내 pk 값이나, unique 값을 가지는 fields를 검색할 때 많이 사용한다. 그런데, 현재의 상태에서 get을 사용하게 되면 templates 레벨에서 에러가 발생하는 것을 확인할 수 있다.

 

 

 

오류를 보면 문제가 파악이 되는데, get() 매서드로 index.html에 전달된 데이터는 QuerySet이 아니라 단 하나의 Accounts 인스턴스다. Accounts는 Model 클래스이기 때문에 Iterable 하지 못하다고 확인하라는 것이고 말이다.

 

즉, get()은 앞서 보았던 all()과 filter()와는 달리 Model 클래스가 반환되며, 그렇기 때문에 검색 결과로 2개 이상의 값이 검사되더라도 에러가 발생하게 된다.

 

 

 

따라서 get()은 특정 id를 가지는 데이터의 상세 정보를 나타내어야 할 때 주로 사용한다. 아래와 같이.

 

 

 

혹여나, 단일 데이터를 반환받도록 만들었음에도 에러는 발생할 수 있다. 예를 들어, 현재 필자의 페이지에도 존재하지 않는 계정 ID에 대해 상세 보기 페이지로 이동하면, 아래와 같이 에러가 발생하게 된다.

 

 

이러한 에러를 방지하기 위해, Django는 django.shortcut 모듈에 get_object_or_404라는 매서드를 제공한다. 이 메서드는 DB에서 찾고자 하는 내용이 존재하면 그 값을 돌려주고, 그렇지 않은 경우 예외를 발생시켜, 404 페이지로 redirection하도록 만드는 매서드다.

 

 

 

현재는 settings.py의 DEBUG 값이 True로 설정되어 있어 결과가 큰 차이가 없지만, 만약 DEBUG 값을 False로 지정하면 아래와 같이 get_objects_404를 사용한 것과 그렇지 않은 것의 결과가 확연히 차이가 나게 된다.

 

get_object_or_404 적용 시. redirect status_code인 301 다음 Not Found 404 code가 떨어진다.

 

get_object_or_404 미적용 시, 잘못된 페이지 접속 시 status_code 500이 떨어지는 것을 볼 수 있다.

 

 

(3) 데이터의 추가

 

신규 데이터 추가는 간단하다. Model 클래스의 create() 매서드를 사용하여 생성이 가능하며, 매서드 인자로 Model 클래스의 fields 명과 값을 kwargs나 dictionary 형태로 넣어주면 된다.

 

 

 

기존의 form.save() 그러니까, UserCreationForm의 save() 매서드가 아닌, 지난 번 필자가 생성한 Form의 save() 매서드에서 가입이 이루어지도록 수정한 코드다. 현재는 Custom User Model인 Accounts가 UserCreationForm을 상속받고 있어, create() 매서드가 아닌 create_user()create_superuser()매서드가 제공되지만, forms.ModelForm을 상속받는 일반 Custom Form의 경우, create() 매서드를 사용(Overriding)하여 데이터 조작을 진행하면 된다.

 

결과는 동일하게 나타난다. 

 

 

 

(4) 데이터의 삭제

 

이번에는 방금 생성한 user1을 삭제해보려한다. 삭제는 추가보다 더 쉽다. 삭제하려는 Model의 인스턴스를 호출한 뒤, 단순히 delete()라는 매서드를 사용하면 된다. 

 

회원 탈퇴 버튼을 따로 안만들어 놨어서, 정보 수정 페이지 하단에 별도로 버튼을 생성하고 views.py의 acc_delete와 연결했다. 

 

 

 

회원 탈퇴의 결과는 아래와 같이 나타난다.

 

중간에 잘못해서 DB를 한 번 날렸다...

 

 

 

(5) 데이터의 수정

 

데이터의 수정도 Model Manager가 제공하는 save()라는 매서드를 사용한다. 단, save() 매서드 자체에서 값을 받아 처리하는 것이 아니라, 변경할 데이터를 클래스 인스턴스로 받아, 개별 fields의 값을 수정한 뒤 save() 매서드를 적용해야한다. 데이터 추가와 마찬가지로 Custom Form에 save() 매서드를 생성하여 진행해보려한다. 필자는 testuser의 email을 test1@test.com에서 edittest1@test.com으로 변경해보려한다.

 

 

 

 

 

변경하고자 하는 이메일 외에도 수정일의 일시가 변한 것을 확인할 수 있는데, 이는 Model 내 DateTimeField의 속성이 auto_now=True로 설정되어 있기 때문이다. DateTimeField가 이 속성으로 설정되면, Model Manager에서 save() 매서드가 실행되는 순간 자동으로 해당 field가 save() 매서드 실행 일시로 업데이트 된다.

 

 

 


 

이번 포스팅에서 테스트했던 CRUD는 사실 가장 기본적인 DB 쿼리에 대해서만 실행한 것이라, 만약 조금 더 복잡한 쿼리를 사용하고자 한다면 아래의 공식 문서에서 진행하고자 하는 내용을 찾아 진행하면 된다.

 

Django Model Manager 기본 쿼리 연관 매서드

Django Model Manager DB 함수 연관 매서드

 

 

 

End.

 

 

반응형

댓글