7월 1주차에 급하게 Django 를 써야할 상황이 생겨서 이틀간 Django를 압축적으로 학습할 일이 생겼다.
그간 학습한 내용들을 간단하게 정리하도록 하겠다.
개인적으로 빠르게 학습하면서 (빠르게라기 보다는 급하게가 맞겠다.) 적은 내용들이라 다소 형식이 올바르지 못하더라도 필요한 내용들만 압축해서 정리해놓았다.
목차:
- 서버 구동 및 초기 설정
- MTV 패턴
- view (HTTP request, HTTP resposne)
- model (클래스 필드에 대하여, CharField, IntegerField, EmailField)
- DB migration, migrate
- DB 엔진 설정 (Mysql 연동 방법)
- Model api 사용법 (장고 기본 지원 ORM 사용법 - insert, select, update, delete)
- URL 매핑
- nginx 사용법, gunicorn 사용법, Django의 serializer 에 대해서
- Django rest framework
Django로 백엔드 개발시 사용가능한 옵션들(api생성, 라이브러리 등등):
1. django rest swagger: ← 사실 제일 편함. 근데 정확하지 않다고 하는데 이건 조금 정성들이면 충분히 명세해서 정확하게 쓸 수 있음.
2. drf-yasg ← api 문서 만들때 좋다고 함. (그냥 swagger 쓰자.)
3. simplejwt ← 이거 jwt 만들때 좋다고 함. (원래 djangorestframework-jwt가 있었는데 지금은 업데이트되지 않고 있음.)
에러사항:
가상환경 설정시 \Scripts\Activate.ps1 파일을 로드할 수 없습니다. 와 같은 에러를 만날 수 있음.
- + CategoryInfo : 보안 오류: (:) [], PSSecurityException + FullyQualifiedErrorId : UnauthorizedAccess
이 경우 아래 블로그 참조하면 해결 가능함.
https://infinitt.tistory.com/43
Django에서 처음 프로젝트 생성할때 아래 command 를 터미널에 입력하면 됨. startproject? 즉 프로젝트를 새로 만든다는 뜻.
django-admin startproject booksite
프로젝트를 만들었다면 이제 django APP을 만들어야할 차례다.
python manage.py startapp <app이름>
이렇게 입력하면 app이름으로 디렉토리가 마구 생길 것이다.
그리고 manage.py를 통해서 application을 실행할 수 있다. 즉 manage.py 파일을 실행해서 Django관련 수많은 동작들을 할 수 있다.
python manage.py runserver 를 타이핑하면 서버를 구동할 수 있다.
이때 runserver를 하게 되면 default port가 8000포트임. 그래서 runserver 뒤에 8080이라고 명시해줄 수 있다.
즉 python manage.py runserver 8080 이라고 하면 된다는 소리이다.
python manage.py makemigrations ← models.py 에서 변경된 필드, 스키마 등등 변경 사항을 파일로 만들어줌. (실제 파일 생기는거 확인할 수 있음.)
python manage.py migrate ← 실제 디비에 변경된 사항들을 적용시켜줌. (아마 springboot 에서의 ddl-auto: update 와 같다고 보면 될듯.) makemigrations와 migrate는 서로 세트라고 생각해주면 됨. 따로따로 분리해서 쓸일은 없을거임. (같이 씀.
질문 사항?
1. migrate 라는게 있는데 이 동작 전에 db 붙여준 적도 없는데 어떻게 migrate ? 어디에 migrate 한다는 거임?
(답변: 연동 전에는 그냥 Django 내에서 처리함. 근데 연동하고 난 뒤에는? 내가 연동한 디비 쪽으로 migrate 될거임. <- 답변받고 나서 좀더 확인해보니 Django내에 있는 dbsqlite3를 내부적으로 쓰고 있는 것 같다.)
2. 통상적으로 viewset 을 많이 사용하는지? viewset 안쓰고 그냥 view 쓰는 방법도 있던것같은데 ?
(답변: 통상적이란건 잘 모르겠음. 통상적이라기보다는 그냥 본인 취향 차이임. 쓰면쓰고 안쓰면 안쓰는 거임.)
3. project / project_app 이랑 서로 무슨차이가 있는지? 이 두 사이를 왔다갔다 하면서 짜는것 같던데 ?
(답변: 이게 추상적 개념으로 나뉘어있는 단위임. 큰 프로젝트 내에서 추상적 개념으로 단위를 나누고 싶다면 app 단위로 잘라서 나누면 됨. 프로젝트와 앱의 개념임. 앱이 모여서 프로젝트를 구성한다.)
Django 에서의 view 는 controller 의 역할을 한다고 보면 됨.
view들은 Django 안 app 의 views.py에 정의하는데 각 함수가 하나의 view 를 정의함. (물론 function based view 로 짰을 경우에 한정됨.)
각 view 는 HTTP request 를 입력으로 받고 HTTP response 를 리턴한다. (사실 리턴 타입은 무수히 많은 종류가 있으니 따로 검색해봐도 좋다.)
Django 에서의 MTV 패턴이란 무엇일까?
Model, Template, View 를 말한다. → 거의 MVC와 유사하다고 보면 됨. 하는일이 비슷하다고 보면 된다.
그럼 여기서 Model은 뭘까?
하나의 모델 클래스는 DB에서 하나의 테이블로 표현된다. (Spring 쓸때 JPA에서 엔티티 클래스 정의할때 떠올리면 됨. 개인적으로 그거랑 완벽히 똑같다고 생각함.)
그럼 View는 뭘까?
http request를 받아서 결과인 http response 를 리턴하는 컴포넌트이고 Model 로부터 데이터를 읽거나 저장할 수 있음. 또는? Template 을 호출해서 데이터를 UI에 표현할 수 있음. (그냥 Controller라고 생각하면 된다. 처음에 이름이 view여서 처음 Django 배울때 살짝 헷갈렸었다. 하지만 Django에서의 view는 controller개념이라고 생각하면 된다.)
그럼 Template은 뭘까?
presentation logic 만을 갖는 HTML 을 생성하는 것을 목적으로 하는 컴포넌트라고 보면됨. Django 에서의 template은 기존의 다른 프레임워크의 MVC 중 view 라고 보면 됨.
Model에 대해서 조금 더 살펴보자.
데이터서비스를 제공하는 Layer 이다. Django App 안에 기본 생성되는 models.py 에 정의(작성)하면 된다. 그리고 그 모듈 안에 하나 이상의 모델 Class 를 정의하고 이 모델 Class 가 디비에서의 하나의 테이블로 매핑된다.
Django 모델은 django.db.models.Model 의 파생 클래스이다. 모델의 field를 클래스의 Attribute 로 표현한다고 보면된다. 그리고 이게 디비 컬럼이라고 보면 된다. (JPA랑 정말 똑같다.)
(만약 모델 클래스 Attribute 중에서 Primary key 를 지정하지 않으면 모델에 primary key 역할을 하는 id 필드가 자동으로 추가되어 Db테이블 생성할때 자동으로 id 컬럼이 생성된다고 보면 된다.)
그
런데 만약 primary key 를 명시적으로 주고 싶다면? primar_key = True 를 주면 됩니다. 해당 필드 옵션에 적어주면 된다.
모델 Class Attribute 세팅할때 EmailField라는게 있다. 이메일 검사해주는건데 내부적으로 EmailValidator 가 이 일을 함. 그래서 ‘@’ 랑 ‘.’ 을 검사해준다. 참고로 max_length 는 extra로 세팅해줘야됨.
그래서 JPA에서 db pk 설정해줄때처럼 하려면
user_id = models.AutoField(primary_key=True)
이렇게 하면 됩니다. 아래 링크 참조하면 이해가 빠를거다. 참고로 오토필드는 auto increment 임.
https://hoorooroob.tistory.com/entry/해설과-함께-읽는-Django-문서-Models-Automatic-primary-key-필드
model class Attribute 설정할때 EmailField라는게 있는데 이게 이메일 형식 유효성 검사를 해주는것 같은데 형식 안맞는거 들어오면 EmailValidator 에서 ValidationError 보내니까 이거 잡아서 에러체킹해주면 될듯싶다.
아 참고로 CharField 는 max_length 지정해주는거 필수이다. TextField는 필수가 아니다.
필드 타입에 대한 더 자세한 정보는 아래 링크에서 확인하면 좋다.
https://docs.djangoproject.com/en/1.11/ref/models/fields/#field-types
DB Migration 이란 무엇일까?
모델 클래스의 수정 및 생성을 DB에 적용하는 과정을 Migration 이라고 부른다.
이건 Django에서 기본적으로 제공하는 ORM 서비스를 통해서 진행된다.
장고는 모델 클래스로부터 테이블을 생성하기 위해서 크게 아래 2가지로 나뉜다.
- Migration 을 준비하는 과정
- 이를 적용하는 과정
구체적인 과정은 아래와 같다.
- settings.py 파일 안의 INSTALLED_APPS 리스트에 Django app 을 추가한다. (본인 app 이름을 적어주면 된다.)
- 모델 클래스로부터 테이블 스키마를 생성 , 수정하기 위해 아래 명령어를 실행한다.
(manage.py makemigrations)
이게 실행되면 Django App 안에 migrations 라는 서브 폴더가 만들어지고 테이블 생성 수정을 위한 python 마이그레이션 파일을 생성함. (python manage.py makemigrations)
- 이게 실제 디비에 테이블 생성, 수정하기 위해서 아래 명령을 실행한다. 실제 적용됨.
(manage.py migrate)
Migration에 의해 생성되는 테이블은 App명_ModelClass명 의 형식으로 생성된다.
manage.py dbshell 을 통해서 db 관련 정보들을 확인할 수도 있다. (이건 직접 써보진 않았다.) 아래 링크 참조.
http://pythonstudy.xyz/python/article/309-DB-설정과-Migration
DB 설정은 어떻게 할까?
기본적인 db 세팅은 settings.py 에서 한다. (사실상 프로젝트 혹은 앱과 관련된 의존성, 외부 기능등은 모두 settings.py에서 한다고 보면 된다.)
Django에서는 default db로 sqlite3 를 사용한다.
settings.py 의 DATABASES에는 반드시 디폴트 설정이 존재해야 한다!
이후에 다른설정을 추가 가능하다.
다른 db엔진을 쓰고 싶으면 ENGINE 키에 해당 db엔진 값을 정하면 된다.
- django.db.backends.postgresql
- django.db.backends.mysql
- django.db.backends.sqlite3
- django.db.backends.oracle
한가지 예시로 mysql 에 대한 예시를 볼 수 있다. (참고로 db엔진마다 서로 다른 옵션을 지정한다.)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'MyDB',
'USER': 'user1',
'PASSWORD': 'pwd',
'HOST': 'localhost',
'PORT': '3306',
}
}
Django 에서 Model api 들을 지원하는데 쿼리별로 살펴보면 아래와 같다. (내용이 꽤나 디테일하게 지원해서 들어가서 보는게 좋다.)
http://pythonstudy.xyz/python/article/310-Django-모델-API
참고로 ORM api 를 써서 값을 불러오면 QuerySet이 리턴된다. 그래서 이 querySet으로 체이닝을 할 수 있다. 여러 조건들을 겹쳐서 줄 수 있다. 그런데 이러한 여러 조건들이 얽혀있어도 결국 DB에는 쿼리 하나 나가게 된다. (Django에서의 db lazy-loading이라는 내용도 찾아보면 재밌다.)
Django App URL 매핑?
하나의 프로젝트 내에 여러 Django App이 존재한다면, 프로젝트 폴더 내의 메인 urls.py 파일 하나로 모든 URL 매핑을 하기보다는 각각의 Django App 안에 urls.py 파일을 만들고 메인 urls.py 파일에서 각 Django App의 urls.py 파일로 URL 매핑을 위탁하게 할 수 있다. 이러한 URL 매핑 방식은 특히 다수의 App들을 포함하는 큰 프로젝트의 경우 편리한 방식이다.
URL 패턴?
django.urls.path() 함수를 사용한다.
Django 에서의 WSGI ?
질문사항:
이전 프로젝트 Deploy 할때 apache ? nginx ? 같은걸 사용했었는지? spring 하면서 톰캣만 써봐서 nginx 를 써본 적이 없는데 nginx쓰는이유가 웹페이지 처리하기 위함이라고 들어서 이걸 혹시 우리 프로젝트에서는 안쓰는게 맞다고 생각하는데 어떻게 생각하시는지?
(답변: nginx를 프록시 서버처럼 쓸거고 gunicorn을 톰캣같은 내장서버로 쓰는게 좋다.)
gunicorn 이 많이 사용된다고 한다. 유닉스 계열 WSGI 서버임. 쓰기 편하다고 한다. 아래 링크를 참조하면 좋다.
http://pythonstudy.xyz/python/article/316-Site-Deployment-Nginx
nginx는 뭘까?
apache server 보다 가벼운 웹 서버다. 정적 웹 컨텐츠 html, csss, js 등을 처리해주는 웹 서버라고 보면 된다. python 을 이용한 동적 웹 페이지 핸들링은 gunicorn, uWSGI 같은 WSGI 서버를 통해서 한다고 한다.
아래 두가지 예시를 들어놨다. 파이썬에서 어플리케이션 구동하고 서버 돌린다하면 아래 1번 케이스로 돌린다고 생각하면된다. 그리고 이에 비교되는게 웹어플리케이션(react + springboot) 돌린다고 가정했을때의 2번케이스랑 비슷하다고 보면 된다.
- 1번: nginx - 구니콘
- 2반: nginx - 톰캣
참고로 Django 쓸때는 gunicorn 쓰면 된다.
serializer라는건 뭘까?
이건 DRF 에서 사용하는 개념이다. DRF 공부할때 같이 공부하면 된다. 참고로 pure Django를 쓴다면 필요없는 개념이다.
팁:
settings.py 에 DEBUG=True가 있는데 실서비스 배포할때는 이거 꺼두고 배포해야한다. 이게 개발할때 브라우저 상에 에러 로그를 전부 찍어서 개발자에게 보여주는데 이걸 True로 해두고 운영하면 공격자에게 내부 구조를 들킬 위험이 있다. 사용자가 이런 개발관련 로그를 보면 안되므로 배포할때는 False로 해두고 배포하자.
순수한 Django로는 개발하는게 권장되지 않는다고 한다. 템플릿쪽으로 QuerySet이 전달되는데 이게 권장되지 않는 방식이라는 의견이 있었다. 프론트단으로 쿼리셋이 아니라 Json 이 전달되야한다고 한다. 즉 Serializer 사용해서 json 으로 바꿔서 전달해야한다고 한다. (페이지랑 붙여서 개발하지 말라는 소리)
DRF를 쓸때 어떻게 request에서 데이터를 뽑아낼까?
DRF 에서 api 파라미터부에 request가 전달될텐데 어떤 타입인지 알고 싶으면 request.method 쓰면 되고 데이터 얻고 싶으면 request.data 를 쓰면 된다.
request.body와 request.data 중 어떤걸 쓸까?
기능은 똑같다. request.data 를 쓰라고 한다. 이게 좀더 유연하다는 의견이 있었다.
아래 설명 참고하면 좋다.
- request.body is bytes, which is always available, thus there is no limit in usage
- request.data is a "property" method and can raise an exception, but it gives you parsed data, which are more convenient
디비에서 특정 객체를 뽑아내서 쓰고 싶을때 filter 로 뽑으려고 하지마라.
filter로 뽑으면 queryset 이 리턴된다. 그래서 여기서 직접 값을 뽑아낼 수 가 없다. 방법을 찾아봤는데 찾지못했다. 그래서 만약 디비에서 특정 객체를 뽑아내서 쓰고 싶다면 get을 사용해서 객체를 뽑아내는게 좋다. 그렇게 하면 그냥 dot product를 통해서 바로 뽑아낼 수 있음. <변수명>.<필드명> 이렇게 써서 바로 뽑아낼 수 있다.
Django에서는 jwt를 굉장히 간단하게 지원해준다. (참고로 simple-jwt사용해라. 이게 최근에 쓰이는 라이브러리임.)
windows에서 gunicorn을 사용하려고 할때
gunicorn을 사용하려고 하는데 아래와 같은 에러가 발생할 수 있다.
ModuleNotFoundError: No module named 'fcntl’
이유: 참고로 gunicorn 은 unix environment에서 작동하게끔 세팅되어있기 때문에 window local 개발 환경에서는 gunicorn을 사용할 수가 없다. 즉 window 로컬에서 개발하면서 gunicorn을 쓸 수 없다는 소리다.
해결방법은?
gunicorn말고 다른걸 사용하면 되겠지만 그건 해결방법이 아니다. 그러므로 어떻게든 gunicorn을 사용할 수 있는 방법이어야하는데 윈도우에서 리눅스 쉘을 쓸수 있는 wsl 을 활용한다면 해결할 수 있다.
wsl 을 사용하는 방법은 아래와 같다.
https://www.lainyzine.com/ko/article/how-to-install-wsl2-and-use-linux-on-windows-10/
참고로 wsl 사용하려고 이것저것 만지다가 로컬에서 좀 고생할 수 있다. 원래 개발에서 문제생기는것보다 환경설정에서 문제생기는게 몇배는 더 고통스럽다. 그러니 위 방법을 하기 싫으면 gunicorn 대신에 waitress 를 사용하면됨. waitress는 window에서 쓸 수 있다고 한다. (참고로 난 써보지 않았다.)
윈도우에서 wsl2 설치 방법 (참고로 wsl까지 설치하고 그냥 쓰지 말고 wsl2로 업데이트까지 하는걸 권장한다. 성능과 기능차이가 상당히 크다고 한다.)
https://hacktiming.tistory.com/15
Django 에서 Test Code 작성하는법:
참고로 메소드 테스트는 TestCase를 상속한 클래스를 정의해주고 그 안에 메소드를 정의해주면 된다.
Http요청을 통해서 API를 테스트하고 싶다면 Client객체를 불러서 사용하면 된다. 아래 그 방법이 있다.
https://twpower.github.io/24-test-in-django-by-using-django-client
에러 조치사항:
we can't do that (the database needs something to populate existing rows).
이라는 에러가 발생하면 어떻게 할까?
우선 Model class에서 필드에 defualt 값을 주던, null=True로 바꾸던 적용시키면 된다.
그런데 defualt 값을 준상태로 model 을 바꿔주고 migrations한 이후에 다시 default값을 부여하는 구문을 삭제해서 migrations 하면 위 에러없이 바로 적용가능하다.
Django에서 json.dumps를 사용하면 python 객체를 json으로 바꿔서 post요청으로 보낼 수 있다.
https://velog.io/@hj8853/Django-Unit-Test
만약 User모텔을 확장시키고 싶다면?
User Model class를 정의한 다음에 AbstractUser 를 상속하면 된다. 이를 통해 얻는 이점은 Django에서 제공하는 사용자 관련 다양한 기능들을 활용할 수 있다는 것이다. 비밀번호도 자동으로 해쉬화해서 저장해준다. 이외에 기본 필드들도 많이 제공해준다. 다만 이 과정에서 자잘한 문제들이 발생할 수 있다. (이것때매 꽤나 고생했다. 덕분에 강제로 User모델 확장에 대해 잘 이해하게 되었다. 흑)
참고로 Abstractuser 를 사용하려고 생각하지 않았었는데 프로젝트가 시작되고 시간이 흐른 뒤에야 적용하려고 한다면 db.sqlite3부터 migrations내의 내역들 + pycache까지 모두 지워주고 난 다음에 적용해야한다.
그리고 나서 다시 makemigrations + migrate 를 해줘야 한다. 참고로 migrations폴더 내에 있는 pycache까지 지워야 한다는 걸 명심해야 한다.
그렇게 해야 모든걸 삭제하고 다시 진행할 수 있다. (애초에 처음부터 설계를 잘 짜는게 가장 좋다.)
jwt
djangorestframework-jwt 는 현재 유지보수되고 있지 않고 djangorestframework-simplejwt가 권장된다.
Django에서의 Cannot create consistent method ordering 발생
다중상속을 받을때 죽음의 다이아몬드가 형성되었을때 발생하는 문제이다.
따라서 AbstractUser로 들어가보면
AbstractBaseUser를 상속받고 있음을 알 수 있다.
그리고 AbstractBaseUser에 들어가게 되면
위 사진과 같이 이미 AbstractBaseUser에서 models.Model을 상속받고 있음을 알 수 있다.
따라서 다이아몬드가 형성되므로 Warning이 발생하는 것이다.