conda create -n westagram python=3.7
westagram 은 터미널 상에 표시되는 내가 설정한 가상환경의 이름이다. 뒤에 파이썬 버전을 명시해주지 않으면 기본 버전 2.x 대로 설치되어 질 것이므로 유의한다.
conda activate westagram
이렇게 해주면 가상환경이 활성화 상태가 된다. 이 가상환경에 대해서는 영상이나 자료를 봤지만은 음 한번 다시 보자.
pip install django
pip is the package installer for Python. You can use pip to install packages from the Python Package Index and other indexes.
pip 을 터미널에 입력하면 용법과 커맨드 등이 나오므로 참고하자.
django-admin startproject westagram_project
뒤에 프로젝트 명을 적고 보통 상위 루트에서 만든다.
프로젝트를 만들고 나면 ls 로 확인 시에 westagram_project 라는 폴더가 생긴 것을 확인할 수 있다.
그러면 그 프로젝트 폴더로 들어가서 앱을 만들건데 그 앱은 바로 회원가입을 하고 로그인을 하게 될 앱이다.
그럼 앱 이름을 account 로 해주고 만들자.
django-admin startapp account
westagram_project
├── account
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── manage.py
└── westagram_project
├── __init__.py
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py
tree 최상위의 westagram_project 는 겉 껍데기 디렉토리 아래에 account 디렉토리가 생겨난다.
방금 만든 app 을 settings.py 에 있는 INSTALLED_APPS 에 등록해준다.
INSTALLED_APPS = [
# 'django.contrib.admin',
# 'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'account',
]
아 근데 여기서 나는 'account.apps.AccountConfig', 이렇게 등록했었다.
왜냐하면 account 폴더 내의 apps.py 에 들어가보면 저렇게 되어 있어서 그대로 입력했다. 물론 동작하는데는 문제가 없지만 굳이 앱 이름만 써도 되는 것을 저렇게 길게 쓸 필요가 있었을까.
갑자기 장고 orm 에 대해 알아보자면 db를 추상화해서 코드로 조작할 수 있게 하는 기능이라 하며 데이터 추가, 수정, 검색, 삭제가 가능하다고 한다.
좀 더 자세한 설명은 아래와 같다.
장고는 ORM이라는 것을 제공하고 있습니다. ORM? 오알엠이 뭔가요? 라고 물으실것 같아 준비했습니다.
Object Relational Mapping, 객체-관계 매핑
객체와 관계형 데이터베이스의 데이터를 자동으로 매핑(연결)해주는 것을 말한다.
객체 지향 프로그래밍은 클래스를 사용하고, 관계형 데이터베이스는 테이블을 사용한다.
객체 모델과 관계형 모델 간에 불일치가 존재한다.
ORM을 통해 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하여 불일치를 해결한다.
데이터베이스 데이터 <—매핑—> Object 필드
객체를 통해 간접적으로 데이터베이스 데이터를 다룬다.
이와 같이 ORM을 사용하면서 원하는 모델을 작성하고 다루는 곳이 바로 models.py 파일 입니다.
쉽게 말하면 DATABASE를 잘 몰라도 models.py 를 통해 DB를 관리할 수 있다는 사실만 알고 계시면 됩니다.
from django.db import models
class Account(models.Model):
email = models.CharField(max_length=200)
password = models.CharField(max_length=400)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'accounts'
커스터마이징의 경우를 빼고는 (장고의 기능을 가져와쓰지 않고 만드는 과정을 거치는..) 모든 모델 클래스는 models 에 들어 있는 클래스 Model 을 상속받는다. Model 을 상속받아 다 써먹겠다는 의미.
필드는 Field 클래스의 인스턴스이다. Django 는 필드를 통해 다음과 같은 사항을 정의한다.
테이블의 열 저장할 데이터의 유형 (문자열, 정수 등)
폼 필드를 렌더링할 때 사용할 기본 HTML 위젯
관리자 페이지와 자동생성 되는 폼들에 적용될 최소 요구사항
Django 는 기본적으로 아래와 같은 필드 타입들을 제공한다.
관계 정의 필드들도 있지만 그건 나중에 정리해보기로 한다.
DateTimeField는 날짜와 시간을 가져오는 필드인데, auto_now_add = True를 넣어주면 처음 생성(추가)된 시점을 자동으로 기록해주고, auto_now = True를 넣어주면 필드가 업데이트 될 때마다 그 시점을 기록해준다.
class Meta:
db_table = 'accounts'
Meta 클래스 부분은 이 테이블이 생성되었을 때, 테이블 명을 accounts로 만들어지게 만들어준다. 'db세계에서 이름을 accounts로 지정하겠다.' 라는 의미이다. 만약 Meta 클래스를 만들어놓지 않으면 장고가 이름을 알아서 만들어버리기에 account_Account (앱이름__클래스명) 같은 좋지 못한 이름으로 생성된다.
# 바른 예시
# sqlite3 db.sqlite3
sqlite> .tables
accounts django_migrations
django_content_type django_session
python manage.py makemigrations
python manage.py migrate account 0001
# 같은 동일 경로에서 진행하므로 migrate 만 진행해도 된다 한다는데 이부분은 자세히 알아봐야 겠다.
즉, makimigrations는 장고에서 제공하는 모델의 변경사항들을 감지하고 기록하는 역할을 하며 migrate는 그러한 기록된 파일들과 설정값들을 읽어서 그 변경사항을 db에 저장하는 역할을 한다.
여기에 showmigrations 와 sqlmigrate 로 중간중간 위 순서에 섞어서 사용하며 확인한다는 것 같다.
간단하게 말하면 Database에서 전달받은 객체들의 모음(list)이다. DB(SQL)에서는 row에 해당한다.
Python으로 작성한 코드가 SQL로 mapping되어 QuerySet이라는 자료 형태로 값이 넘어온다.
DB에는 column과 row에 데이터가 저장된다. Django에서 column에 해당하는 부분은 모델의 각 클래스안에 지정해준 속성들이며, row에 해당하는 부분은 각 속성에 부여되어 있는 값들이다. 즉 dictionary 가 저장되는 것이다.
따라서, QuerySet안에 있는 객체에 접근할 때에는 value에 접근하는지, dictionary의 요소에 접근하는지 등에 따라서 접근 방식이 다르다.
<클래스이름>.objects.values() :
.values()로 dictionary의 key와 value에 접근이 가능하다.
QuerySet()은 리스트이고, 객체는 dictionary 이므로
<variable name>[index]['key'] 의 형식으로 value값에 접근이 가능하다
<클래스이름>.objects.get(id=1) :
get()은 dictionary의 요소 하나를 반환한다.
해당 조건의 요소가 존재하지 않을때는 DoesNotExist, 여러개 존재할때는
MultipleObjectsReturned 에러가 발생한다.
하나의 객체이기에 반환되기 때문에, dot notation으로 접근이 가능하다.
<variable name>.name
<클래스이름>.objects.filter() or <클래스이름>.objects.all()
filter()는 주어진 parameter에 따라 query의 결과를 필터하며, 결과는 리스트로 반환한다.
all()은 QuerySet안의 모든 객체를 리스트 형태로 return한다.
따라서, <variable name>[index]로 접근이 가능하다.
>>> b = Account.objects.filter(name__startswith="K")
>>> <QuerySet [<Account: Account object (1)>, <Account: Account object (2)>]>
>>> b[0]
<Account: Account object (1)>
>>> c = Account.objects.all()
<QuerySet [<Account: Account object (8)>, <Account: Account object (9)>, <Account: Account object (10)>]>
>>> c[0]
<Account: Account object (8)>
import json
from django.views import View
from django.http import JsonResponse
from .models import Account
뷰는 작성된 모델을 바탕으로, 들어오는 데이터를 어떻게 처리할지에 대한 논리를 맡고 있다.
JSON은 데이터 타입으로 JavaScript Object Notation의 줄임말로,
인터넷에서 자료를 주고 받을때 널리 쓰이는 데이터 타입으로 자리 잡은 형식이다.
django 에서 View 클래스는 직접 생성하지 않고 장고 프레임워크에 내장된 클래스를 상속받아 쓴다.
http를 통해 받은 요청 정보(json)를 파이썬이 읽을 수 있는 형태로 변환해줄 수 있는 모듈인 json 을 임포트 한다.
json 형태로 응답(response) 해줄 수 있는 JsonResponse 모듈을 불러온다. 그리고 뷰에서 처리할 정보가 쌓이는 기준이 되는 Account class를 모델에서 불러온다.
JsonResponse는 서버의 요청에 대한 응답을 Json으로 응답하기 위해 사용한다.
보통 get, post 요청이 들어오는데, get은 순전히 데이터만을 요청할 때, post는 어떠한 데이터를 입력, 수정을 할 때 라고 생각하면 된다. 예를 들어, 로그인도 로그인 완료라는 데이터를 요청해서 get일 것 같지만, 일단 데이터를 입력해서 데이터가 맞는지 확인 받기 때문에 post 메서드를 사용한다.
class SignUpView(View):
def post(self, request):
signup_data = json.loads(request.body) # JSON 데이터를 python으로 파싱해서 data에 저장
try:
if Account.objects.filter(email=signup_data['email']).exists():
return HttpResponse(status=409)
Account.objects.create(
email=signup_data['email'],
password=signup_data['password'],
)
return HttpResponse(status=200)
except KeyError:
return JsonResponse({"message": "INVALID_KEYS"}, status=400)
class SignInView(View):
def post(self, request):
signin_data = json.loads(request.body)
try:
if Account.objects.filter(email=signin_data['email']).exist():
user = Account.objects.get(email=signin_data['email'])
if user.password == signin_data['password']:
return HttpResponse(status=200)
except KeyError:
return JsonResponse({"message": "INVALID_KEYS"}, status=400)
westagram 의 경로를 최초 경로를 관리하는 urls.py 로 들어가 보자.
from django.urls import path, include
urlpatterns = [
path('account', include('account.urls')),
]
이제 include로 넘어온 account 앱의 urls.py 코드를 작성해보자.
from django.urls import path
from .views import SignInView, SignUpView
urlpatterns = [
path('/signup', SignUpView.as_view()),
path('/signin', SignInView.as_view()),
]
들어온 요청 데이터를 처리하는 곳이 views 이다. 그리고 그 안에 만들어뒀던 SignUpView, SignInView를 통해서 데이터를 처리한다.
view 클래스는 내장 함수를 반환하는 as_view() 를 메서드로 제공한다. (모든 클래스 기반 뷰는 이 클래스를 직간접적으로 상속받는다.)
as_view() 는 클래스의 인스턴스를 생성하고, 이 인스턴스의 dispatch() 메서드를 호출한다. dispatch() 메서드에서는 view가 받은 요청을 검사해서 HTTP의 메소드(GET, POST)를 알아낸다. 인스턴스 내에 해당 이름을 갖는 메서드로 요청을 중계한다. 해당 메서드가 정의되지 않으면, HttpResponseNotAllowd 예외를 발생시킨다.
localhost:8000/account/signup email password 로 요청이 들어오면 as_view()가 SignUpView, SignInView 로 인스턴스를 생성하고, dispatch() 메서드를 통해서 들어온 요청 메서드가 뷰 안에서 정의한 POST인지 확인한다. 이 경우 POST가 있으면 정상적으로 SignUpView의 논리에 따라 응답이 처리되서 반환된다.