-
django ch1 - ch4백엔드 2023. 3. 22. 23:26
개념
HTTP란 컴퓨터들끼리 HTML파일을 주고받을 수 있도록 하는 소통방식 또는 약속(protocol)이다.
클라이언트는 서버에게 request를 보내고, 서버는 response를 클라이언트에게 보낸다.
프로젝트 생성 및 환경 설정
레포지토리를 fork한 후, cmd를 켜 get clone [레포지토리 주소]로 폴더를 생성해준다.
pycharm에서 해당 폴더를 열고 터미널에 git checkout -b [브랜치 이름]를 쳐 branch를 만든다. (오른쪽 하단에서 현재 브랜치 확인 가능)
해당 프로젝트를 위한 가상환경을 세팅해준다.
Pycharm 상단 메뉴바 File - Settings - Project - Project Interpreter
add interpreter을 눌러서 하면 된다. 이때 파이썬 버전 10은 mySQL과 연동이 안되니, 버전을 9로 낮춰서 해야한다.
JW디렉토리에서 터미널에
./venv/Scripts/activate
를 쳐 가상환경을 돌려준다.
터미널에서 (venv)라는 명령어가 뜬다면 가상환경 설정에 성공한 것이다.
그러나 윈도우 설정상 권한 에러가 발생한다.
venv/Scripts/activate : 이 시스템에서 스크립트를 실행할 수 없으므로 ....\PythonWorkspace\test-venv\Scripts\Activate.ps1 파일을 로드할 수 없습니다. 자세한 내용은 about_Execution_Policies(https://go.microsoft.com/fwlink/?LinkID=135170)를 참조하십시오.
일회성 해결방안으로 (pycharm을 킬때마다 쳐야한다.)
Set-ExecutionPolicy Unrestricted -Scope Process
를 쳐봤지만 해결이 되지 않았다.
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
로 권한에러를 해결했다.
이제 보안상 깃허브에 올라가면 안되는 폴더 및 파일(venv/ 와 .idea/)을 .gitignore에 추가해준다.
이는 멘토분께서 다 해두셨다.
장고 버전을
pip install django==3.2.16
으로 깔아준다.
개발 서버
JW 디렉토리에서 터미널에
django-admin startproject mysite
를 치면
mysite/ manage.py mysite/ __init__.py settings.py #현재 장고 프로젝트의 환경 설정 urls.py #URL 선언을 저장 asgi.py #배포 wsgi.py #배포
가 생성된다.
mysite 디렉토리로 이동(CD)하여
python manage.py runserver
를 치면 개발 서버가 시작된다. 이제 본인의 웹 브라우져에서 http://127.0.0.1:8000/ 을 통해 접속할 수 있다.
이때, 윈도우가 8000포트를 연결하지 못한다면
python manage.py runserver 8080
로 포트넘버를 바꿔 연결한다.
앱을 모아둔 것이 프로젝트이다.
앱을 생성하기 위해 manage.py가 존재하는 디렉토리에서
python manage.py startapp polls
를 치면
polls/ __init__.py admin.py apps.py migrations/ __init__.py models.py tests.py views.py
가 생성된다. 이제 설문조사 어플리케이션(앱)을 만들 수 있다.
뷰 작성
《polls/view.py》는 controller의 역할을 한다. 뷰를 호출하기 위해서는 해당 뷰와 연결된 URL이 필요하다.
이때 URLconf가 사용된다. 이를 위해 urls.py라는 파일을 생성한다.
urlpatterns = [ path('', views.index, name='index'), #''시, index뷰로 보낸다. path('polls/', include('polls.urls')), path('admin/', admin.site.urls), #include()를 사용하지 않는 유일한 예외 ]
polls/5/result가 들어오면 include()함수는 5/result를 'polls.urls'로 보낸다. (하위 url)
이제 index 뷰가 URLconf에 연결되었다.
path() 함수에는 2개의 필수 인수인 route와 view, 2개의 선택 인수인 kwargs와 name이 전달될 수 있다.
- route 는 URL 패턴을 가진 문자열이다.
- view는 경로로부터 특정한 view 함수를 호출한다.
- URL에 name을 지으면, 템플릿을 포함한 Django 어디에서나 명확하게 참조할 수 있다.
데이터베이스 설치
mysite/settings.py 파일을 열면 Django 설정을 모듈 변수로 표현한 모듈이 있다.
기본적으로 SQLite를 제공하지만 mySQL로 변경해 진행했다.
pip install mysqlclient
를 깔아주고,
해당 mySQL 폴더에서
CREATE DATABASE jiwon;
을 써 데이터베이스를 설정해 준 후, mysite/settings.py에
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', #기본설정 'NAME': 'jiwon', #데이터베이스 이름 'USER': 'root', #설정한 이름 'PASSWORD': '', #mySQL 비밀번호 'HOST': 'localhost', 'PORT': '3306', #포트번호 } }
데이터베이스 속성값을 수정해준다. 그리고 gitignore 파일에
/JW/mysite/mysite/settings.py
를 추가한다. 이로써 보안까지 신경써서 작업할 수 있게 되었다.
+ 나중엔, .env파일로 뽑아내 import하는 식으로 구현했다.
(작년에 프로젝트할 때, 다른 팀 백엔드에서 준 인가코드(?)를 프론트가 받아 깃허브에 올렸다가 해킹당해 30만원 가량을 물어준 적이 있다. 그래서 gitignore파일은 항상 신경써야 한다.)
그 후,
python manage.py migrate
python manage.py runserver
가 잘 돌아간다면 Django와 mySQL 연동에 성공한 것이다.
물론, 나는 에러가 났다.
JW라는 경로에서 실행했기 때문이다. CD mysite로 들어가 다시 실행해보니
또 에러가 났다.
해당 데이터베이스가 없다고 나온다. 그래서 JW 데이터베이스를 지우고 소문자 jiwon 데이터베이스로 다시 생성하니 잘 돌아간다.
모델 생성
모델이란 데이터베이스의 구조를 말한다. 이 투표 앱에서는 Question 및 Choice 라는 두가지 모델을 클래스로 만든다.
Question 모델은 질문과 출판 날짜를, Choice 모델은 선택 텍스트와 투표 집계라는 필드(컬럼=열)를 가진다.
polls/models.py에
from django.db import models class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) #글자수 200제한 votes = models.IntegerField(default=0) #기본값 0으로 설정
를 써준다. 여기서 models. 뒤에 붙는 CharField나 DateTimeField는 각 필드가 어떤 자료형을 가지는지 알려준다.
cf. CharField는 max_length를 필수로 입력해줘야 한다.
Foreignkey(외래키)는 각각의 Choice가 Question에 관계된다는 것을 알려준다.
모델(스키마)의 활성화
우리는 모델을 이용해
- 데이터베이스 스키마를 생성
- Question과 Choice 객체에 접근하기 위한 Python 데이터베이스 접근 API를 생성
할 수 있다. 즉, 속성 및 속성값을 mySQL에 들어가 작업하지 않아도 된다는 뜻이다.
그 전에 프로젝트에게 polls 앱이 설치되어 있음을 알려야한다.
mysite/setting.py에
INSTALLED_APPS = [ 'polls.apps.PollsConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
를 써주면 된다.
python manage.py makemigrations polls
이 코드를 통해 모델 클래스의 수정 및 생성을 mySQL의 jiwon DB에 적용했다.
모델(models.py)을 수정했다면 장고에게 makemigrations를 통해 알려주고 migrate로 데이터베이스에 반영해야 한다.
- makemigrations: models.py에서 적용한 변경사항이나 추가된 혹은 삭제된 사항들을 감지하여 파일로 생성 => 장고에서 파일만 생성
- migrate: 적용되지 않은 migrations들을(설정값들을) 적용시키는 역할 => mySQL까지 적용
python manage.py sqlmigrate polls 0001
를 치면,
BEGIN; -- -- Create model Question -- CREATE TABLE "polls_question" ( "id" serial NOT NULL PRIMARY KEY, #기본키로 설정 "question_text" varchar(200) NOT NULL, "pub_date" timestamp with time zone NOT NULL ); -- -- Create model Choice -- CREATE TABLE "polls_choice" ( "id" serial NOT NULL PRIMARY KEY, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL, "question_id" integer NOT NULL ); ALTER TABLE "polls_choice" ADD CONSTRAINT "polls_choice_question_id_c5b4b260_fk_polls_question_id" FOREIGN KEY ("question_id") REFERENCES "polls_question" ("id") DEFERRABLE INITIALLY DEFERRED; CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id"); COMMIT;
sqlmigrate 명령어로 금방 저장한 polls라는 migration이 어떤 SQL을 실행하는지 확인할 수 있다.
기본키랑 외래키를 설정한 적은 없는데, sqlmigrate는 자동으로 생성해주는 것인지?
python manage.py migrate
를 쳐,
아직 적용되지 않은 마이그레이션을 실제 데이터베이스에 적용해준다.
데이터베이스 API
필드에 속성값을 넣는 과정으로
python manage.py shell
를 통해 우선 Python shell을 실행시켜야 한다.
>>> from polls.models import Choice, Question >>> Question.objects.all() >>> from django.utils import timezone >>> q = Question(question_text="What's new?", pub_date=timezone.now()) >>> q.save() >>> q.id #1 >>> q.question_text #"What's new?" >>> q.pub_date >>> q.question_text = "What's up?" >>> q.save() >>> Question.objects.all() #<QuerySet [<Question: Question object (1)>]>
이렇게 객체를 생성하면 된다. (모델은 클래스이다.)
그러나, 마지막 줄에
<QuerySet [<Question: Question object (1)>]>
는 객체를 보는데 전혀 도움이 되지 않기 때문에,
polls/model.py에
from django.db import models class Question(models.Model): # ... def __str__(self): return self.question_text class Choice(models.Model): # ... def __str__(self): return self.choice_text
__str__()을 추가해준다.
>>> from polls.models import Choice, Question >>> Question.objects.all() #<QuerySet [<Question: What's up?>]> >>> Question.objects.filter(id=1) #<QuerySet [<Question: What's up?>]> >>> Question.objects.filter(question_text__startswith='What') #<QuerySet [<Question: What's up?>]> >>> from django.utils import timezone >>> current_year = timezone.now().year >>> Question.objects.get(pub_date__year=current_year) >>> Question.objects.get(id=2) #DoesNotExist: Question matching query does not exist. >>> Question.objects.get(pk=1) #<Question: What's up?> id를 pk로 설정해두었다. ... >>> q.choice_set.create(choice_text='Not much', votes=0) #<Choice: Not much> >>> q.choice_set.create(choice_text='The sky', votes=0) #<Choice: The sky>
이제 what's up? 으로 잘 나온다. ORM
뷰(controller) 추가
스프링부트에는 뷰가 templete(html)이었지만, Django에서는 뷰가 controller이다.
그래서 각 뷰는 요청된 페이지의 내용이 담긴 HttpResponse 객체를 반환하거나, 혹은 Http404 같은 예외를 발생하게 해야한다. 프론트(클라이언트)와 백엔드 간의 관계를 간단히 살펴보면,
- 클라이언트로부터 HTTP request 요청을 받으면 URLconf를 이용하여 URL을 분석합니다.
- URL 분석 결과를 통해 해당 URL에 대한 처리를 담당할 View를 결정합니다.
- View는 자신의 로직을 실행하면서, 만일 데이터베이스 처리가 필요하면 Model을 통해 처리하고 그 결과를 반환받습니다.
- View는 자신의 로직 처리가 끝나면, Template을 사용하여 클라이언트에 전송할 HTML 파일을 생성합니다.
- View는 최종 결과로, HTML 파일을 클라이언트에게 보내 HTTP response 응답합니다.
라고 할 수 있다.
polls/views.py 에
def detail(request, question_id): return HttpResponse("You're looking at question %s." % question_id) def results(request, question_id): response = "You're looking at the results of question %s." return HttpResponse(response % question_id) def vote(request, question_id): return HttpResponse("You're voting on question %s." % question_id)
로 뷰를 추가했다.
뷰도 생겼으니 polls.urls 를
from django.urls import path from . import views urlpatterns = [ # ex: /polls/ path('', views.index, name='index'), # ex: /polls/5/ path('<int:question_id>/', views.detail, name='detail'), # ex: /polls/5/results/ path('<int:question_id>/results/', views.results, name='results'), # ex: /polls/5/vote/ path('<int:question_id>/vote/', views.vote, name='vote'), ]
로 고쳐, 뷰와 연결해준다.
브라우저에 /polls/34/를 입력하면 detail() 함수를 호출해 "You're looking at question 34"라는 문장을 출력한다.
뷰 작성 심화
뷰는 데이터베이스의 기록를 읽을 수도 있고 Django나 Python에서 서드파티로 제공되는 템플릿 시스템을 사용할 수 있다.
(뷰는 PDF를 생성하거나, XML을 출력하거나, 실시간으로 ZIP 파일을 만들 수도 있다.)
위 '뷰 추가'에서는 뷰에서 templete의 역할까지 다 하고 있다. 이제 이를 분리해보자.
polls/templates/polls/index.html에
{% if latest_question_list %} <ul> {% for question in latest_question_list %} <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li> {% endfor %} </ul> {% else %} <p>No polls are available.</p> {% endif %}
를 쓰고,
polls/views.py를
### 심화1 ### from django.http import HttpResponse from django.template import loader from .models import Question def index(request): #여기서 request가 어떻게 연결되는 것인지? latest_question_list = Question.objects.order_by('-pub_date')[:5] #질문 개수 template = loader.get_template('polls/index.html') context = { 'latest_question_list': latest_question_list, } return HttpResponse(template.render(context, request))
로 index 뷰를 수정했다.
이제 URLconf가 index함수를 부르면, polls/index.html 템플릿에 context를 전달한다.
request 는 HttpRequest 개체이다.
여기서 request가 어떻게 연결되는 것인지? 프론트에서 준 거.
템플릿에 context를 넣어 HttpResponse객체를 templete에 넘겨주는 구문은 흔하다.
따라서 Django는 render()이라는 단축기능을 제공한다.
### 심화2 ### from django.shortcuts import render from .models import Question def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] context = {'latest_question_list': latest_question_list} return render(request, 'polls/index.html', context)
더 이상 loader와 HttpResponse를 쓰지 않는다.
render() 함수는 request 객체를 첫번째 인수로 받고, 템플릿 이름을 두번째 인수로 받으며, context 객체를 선택적으로 받는다.
404 에러
polls/views.py에
from django.shortcuts import get_object_or_404, render from .models import Question # ... def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/detail.html', {'question': question})
를 작성한다. get_object_or_404() 함수는 Django 모델을 첫번째 인자로 받고, 몇개의 키워드 인수(pk)를 모델 관리자의 get() 함수에 넘긴다. 만약 객체가 존재하지 않을 경우(pk가 존재하지 않을 경우), Http404 예외가 발생한다.
여기서 pk가 없다 == 객체가 없다로 봐도 되는지? 네
템플릿 시스템 사용
polls/detail.html은
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} #질문 개수 반복문 <li>{{ choice.choice_text }}</li> #질문 리스트로 출력 {% endfor %} </ul>
으로 작성되어 있다.
하드 코딩이 뭔지? url 고정시켜 두는 것
URL의 이름공간
해당 프로젝트는 polls라는 앱 하나만을 가지고 진행했다. 하지만 여러개의 앱이 올 수 있기에 이를 구분하기 위해
URLconf에 namespace를 추가한다.
polls/urls.py 를
from django.urls import path from . import views app_name = 'polls' urlpatterns = [ path('', views.index, name='index'), path('<int:question_id>/', views.detail, name='detail'), path('<int:question_id>/results/', views.results, name='results'), path('<int:question_id>/vote/', views.vote, name='vote'), ]
로 수정한다. name이라는 인자를 달아줬다.
이제, polls/index.html 템플릿의 기존 내용을
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
로 네이밍을 명확히 해준다.
Form
polls/detail.html를 수정해 템플릿에 HTML <form> 요소를
<form action="{% url 'polls:vote' question.id %}" method="post"> {% csrf_token %} <fieldset> <legend><h1>{{ question.question_text }}</h1></legend> {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} {% for choice in question.choice_set.all %} <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}"> <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br> {% endfor %} </fieldset> <input type="submit" value="Vote"> </form>
로 추가해준다.
for문을 돌며 데이터베이스에 저장된 선택지의 개수를 라디오 버튼으로 표현한다. 각 라디오 버튼의 name은 choice이며 이 중 하나를 선택하면 form요소는 서버에 choice = #id 을 post한다.
forloop.counter 는 for 태그가 반복한 횟수를 나타낸다.
내부 URL을 대상으로 하는 모든 POST 양식은 템플릿 태그를 사용해야 한다.{% csrf_token %}
polls/urls.py에
path('<int:question_id>/vote/', views.vote, name='vote'),
를 추가하고,
polls/views.py에
from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.urls import reverse from .models import Choice, Question # ... def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST['choice']) #선택된 choice의 ID를 문자열로 반환해 변수에 넣는다. except (KeyError, Choice.DoesNotExist): #choice가 없다면, request.POST['choice'] 는 KeyError가 일어나며 return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select a choice.", }) #에러메세지와 설문조사 폼을 다시 보여준다. else: #제대로 선택이 되었다면, selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results', args=(question.id,))) #reverse()함수는 해당 뷰를 하드코딩하지 않고 가리키게 한다.
추가한다. 이제 어떤 이가 설문조사를 한 후에는 vote() 뷰는 설문조사 결과페이지로 리다이렉트한다.
결과페이지를 만들어보자.
polls/views.py에
from django.shortcuts import get_object_or_404, render def results(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/results.html', {'question': question})
를 추가하고,
polls/results.html에
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li> {% endfor %} </ul> <a href="{% url 'polls:detail' question.id %}">Vote again?</a>
를 추가한다.
제네릭 뷰 사용
우리가 지금까지 작업했던 뷰는 URL에서 전달된 매개변수에 따라 데베에서 데이터를 가져오거나 템플릿을 로드하는 것이다. 이런 과정을 Django는 '제네릭 뷰'라는 시스템으로 지름길을 제공한다.
이는 일반적인 패턴을 추상화하여 앱을 작성할 때 Python코드를 작성하지 않도록 돕는다.
그러기 위해서는 3가지 단계가 필요하다.
- URLconf를 변환
- 불필요한 오래된보기 중 일부를 삭제
- Django의 제너릭 뷰를 기반으로 새로운 뷰를 도입
git ignore
git ignore 파일 확인
git status --ignored
특정 개수만큼의 커밋 기록을 제거
# 가장 최근의 커밋 기록을 1개 제거 (위와 동일) git reset --hard HEAD~1 # 가장 최근의 커밋 기록을 2개 제거 git reset --hard HEAD~2
특정 커밋으로 복구
git reset --hard <commit id>
더 삽질한 건 밑에 정리해두었다^^
Django REST Framework
웹 API를 구축할 수 있는 툴킷으로, Model 을 바탕으로 조건에 맞는 API를 개발할 수 있다.
pip install djangorestframework pip install django-filter #Filtering support
mysite/settings.py을
INSTALLED_APPS = [ ... 'product', 'rest_famework', #추가 ]
로 바꿔준다.
Serializer는 queryset 과 model instance 같은 것들을 쉽게 JSON 또는 XML 의 데이터 형태로 렌더링 할 수 있게 해줍니다. 우리는 Product 모델을 serialize 해줘야 하기 때문에 ModelSerializer를 사용한다.
참조)
'백엔드' 카테고리의 다른 글
[Django] JWT TOKEN(access token, refresh token, crfs, cors error) (0) 2023.05.04 Modeling(데이터베이스 모델링) (0) 2023.04.02 [Git] 커밋 기록 삭제 및 복구 (0) 2023.03.26 스프링 부트와 AWS로 혼자 구현하는 웹 서비스(-78p) (1) 2023.01.06 데이터베이스(MySQL) (0) 2022.08.30