0# quick-install (파이썬 설치, Django 설치, 가상환경 세팅)
•
장고 공식 문서 이용 Click! → www.djangoproject.com
•
파이썬 최신 버전 다운로드 Click! → www.python.org
◦
파이선 설치 확인 방법 : Cmd(명령 프롬프트)에 "python" 입력.
Python 3.x.y ~ ~ ~ on winXX
Type "help", "copyright", "credits" or "license" for more information.
>>>
이와 비슷한 형태의 문구가 나오면 파이썬 설치가 완료되었음을 뜻함.
Shell
복사
•
Django 설치
<<CMD>>
\> pip install virtualenv // 가상 환경 관련 패키지 설치
\> virtualenv myenv // "myenv"라는 이름을 가진 가상 환경 생성
+) \> dir // 현재 위치 디렉터리의 항목들 보기
+) \> cd "dir" // "dir"라는 이름의 디렉터리에 접근
+) \> cd .. // 이전 디렉터리 위치로 이동
<가상 환경 접근 및 구동>
\path\to\myenv\Scripts\activate
\> cd myenv // 생성한 myenv에 접근
\> cd Scripts // Scripts에 접근
\> activate.bat // activate.bat파일 실행
-> (myenv)C:\path\to\myenv\Scripts 가상환경에 접근 완료 및 구동한 상태
\> pip install Django // Django 설치 완료
Shell
복사
<Django 설치 확인 방법>
(myenv)C:\path\to\myenv\Scripts \> python
Python 3.x.y ~ ~ ~ on winXX
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
python입력 후 import django를 입력했을 때 아무런 오류가 안뜨면 정상적으로 설치 완료되었음을 뜻함.
Shell
복사
•
여러 다른 가상 환경마다 서로 다른 개발환경을 구축할 수 있다. 독립적 환경 구축
1# Django app (프로젝트 생성, app 생성, 서버 구동, Django cycle))
간단한 설문조사(Polls) 어플리케이션
•
사람들이 설문 내용을 보고 직접 투표할 수 있는 개방된 사이트
•
관리자가 설문을 추가, 변경, 삭제할 수 있는 관리용 사이트
<<CMD>>
\> py -m django --version // Django의 버전 확인
설치가 제대로 되지 않았다면 -> "No module named django"와 같은 에러 발생
이때는 가상환경에 접근을 해주어야 정상적으로 버전이 출력된다.
\> cd myenv \> Scripts \> activate.bat (가상 환경 접근)
Shell
복사
•
프로젝트 만들기
<<CMD>>
원하는 디렉터리 아무거나 하나(\work)에 접근한 뒤 "mysite"라는 프로젝트 생성
\> cd work \> django-admin startproject mysite
\> cd mysite // "mysite"라는 프로젝트 생성 및 접근
#Visual Studio Code 이용
Visual Studio Code 실행 후 생성한 mysite 프로젝트를 생성해준다
-> 기본적인 Django 프레임워크 생성 완료
Shell
복사
•
프로젝트 서버 동작 확인
<<CMD>>
\> py manage.py runserver // 서버 실행
정상적인 서버 실행의 경우, 다음과 같은 형태의 출력 확인
Performing system checks...
System check identified no issues (0 silenced).
You have unapplied migrations; your app may not work properly until they are applied.
Run 'python manage.py migrate' to apply them.
12월 02, 2019 - 15:50:53
Django version 2.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
-> http://127.0.0.1:8000/ 주소 접근 시 우리의 장고 프로젝트의 동작 확인 가능.
이 서버는 오로지 파이썬으로만 생성된 서버이므로 개발 목적으로만 사용해야함.
상용 목적으로는 nginx, Apache와 같은 웹 서버를 사용하면 된다.
Shell
복사
•
프로젝트 vs 앱
◦
앱은 특정한 기능(블로그나 공공 기록물을 위한 데이터베이스, 간단한 설문조사 앱)을 수행하는 웹 어플리케이션을 말한다.
◦
프로젝트는 이런 특정 웹 사이트를 위한 앱들과 각 설정들을 한데 묶어놓은 것을 말한다.
•
설문조사 앱 만들기
<<CMD>>
mysite라는 프로젝트에 앱을 생성해본다
(myenv)C:\path\to\work\mysite
\> py manage.py startapp polls // polls라는 디렉토리 앱을 생성
-> VSC(Visual Studio Code)에 생성한 프로젝트 내에도 polls 디렉토리가 생성됨
Shell
복사
<<VSC>>
<<polls/views.py>> 파일에 뷰를 생성. 아래 코드 추가
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world.")
위의 뷰를 호출해주기 위해선 urls.py 파일을 만든 뒤 연결시켜주어야 함.
polls 디렉터리에 urls.py파일 생성 후 아래 코드 추가. <<polls/urls.py>>
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
최상위 URL conf라는 mysite 디렉터리의 mysite/urls.py를 polls/urls.py와 연결시켜주어야 함.
아래 코드와 같이 mysite/urls.py를 수정.
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('polls/', include('polls.urls')),
path('admin/', admin.site.urls),
]
// include()함수는 다른 URLconf들을 참조할 수 있도록 도와준다.
최상단 urls.py에서 각 path별 주소를 구별한 뒤 각 path에 따른 앱으로 include()로 분기 시켜준다.
정상적으로 작성을 마치면,
127.0.0.1:8000/polls/ 주소에 접근 시 Hello, world.라는 문구가 정상 출력된다.
Shell
복사
#2-0 git (git 설치, git 사용법, github 사용법)
git을 사용하는 이유 : 코드 작성에 대한 모든 히스토리 관리를 위해 사용
github를 이용해 코드 공유와 협업이 가능하게 됨.
개발자라면 github 계정은 필수
git(코드를 관리하는 형성 관리 도구) 다운로드 Click! → https://gitforwindows.org/
•
git 설치 완료 후 → 설치된 Git Bash 실행
# 리눅스 기반 명령어로 작동됨.
+) $ ls // 현재 위치 디렉터리의 항목들 보기
+) $ cd "dir" // <<CMD>> 와 동일
$ cd work // work 디렉터리에 접근
$ cd mysite // mysite 디렉터리에 접근
$ git init // git을 사용하기 위한 디렉터리 초기화
$ git add . // 현재 위치로부터 모든 디렉터리 파일을 추적
$ git commit // 현재 파일 디렉터리의 내용을 저장
위 commit 과정에서 "***please tell me who you are" 오류 발생 시
아래 명령어를 입력 후 다시 한 번 시도
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
commit이 정상적으로 작동 시, 글자 입력을 위해 "i"키를 눌러주고 작성
"i"클릭 -> "first commit" 입력 -> "esc" 클릭 -> ":wq" 입력
$ git log // 저장한 "first commit" 로그를 볼 수 있음
Bash
복사
•
github 사용
<<github>>
github 회원가입/로그인 후 Create a new repository
Owner:"name"/Repository name:"mysite"
Description:"~~~"
<<gitbash>>
github에 연결하기 위한 코드 작성
git remote add origin https://github.com/"name"/"mysite".git
git push -u origin master
// github에 디렉터리들을 푸쉬할 수 있음 -> github로 코드 공유 가능
Bash
복사
#2-1 database (Django model, model 사용법, api 사용)
앱을 만들 때 데이터를 저장하기 위해선 데이터베이스가 필요함.
데이터베이스는 Oracle, mySQL 등이 있다.
<<VSC>>
mysite/settings.py 에서 DATABASES = { ~ } 내용 변경으로 사용하고자 하는 데이터베이스의 종류를 바꿀 수 있다.
또한, INSTALLED_APPS = [ ~ ] 에 기본적인 어플리케이션들이 제공되며, 필요에 의해 추가하여 사용할 수 있다.
이러한 기본 어플리케이션들 중 몇몇은 최소한 하나 이상의 데이터베이스 테이블을 사용하기 때문에 아래 명령어를 입력하여 실행해본다.
가상 환경이 실행된 상태에서 진행한다.(myenv)
\> py manage.py migrate // 테이블 생성
설문조사 앱을 위해 Question과 Choice라는 두 개의 모델을 만들어본다.
Question은 질문(question), 발행일(publication date) 두 개의 필드를 가짐.
Choice는 선택자(choice), 표(vote) 계산을 위한 두 개의 필드를 가짐.
<<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)
//ForeignKey로 클래스 Question을 참조하는데 하나의 Question에 여러 개의 Choice를 갖는 구조이므로 일 대 다 구조이다.
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
Shell
복사
•
모델의 활성화
Django의 앱들은 "꼈다뺐다" 할 수 있다.
<<mysite/settings.py>>
INSTALLED_APPS = [
'polls.apps.PollsConfig', // 추가
// polls 디렉터리의 apps에 있는 PollsConfig를 추가하는 것
]
Shell
복사
데이터베이스 내의 테이블을 생성할 수 있도록 설계도를 만드는 작업
\> py manage.py makemigrations polls
데이터베이스 내의 실제 테이블을 생성하는 작업
\> py manage.py migrate
Shell
복사
•
API 가지고 놀기
◦
API : 사용자가 필요한 데이터를 뽑아 쓸 수 있도록 만들어 놓은 함수
\> py manage.py shell // shell로 입력받을 수 있도록 설정
\> from polls.models import Choice, Question
// Choice 모델, Question 모델을 사용할 수 있도록 명시함
\> Question.objects.all()
# <QuerySet []> // 등록된 Question이 없으므로 []이 출력됨
\> from django.utils import timezone
// 발행일, 시간을 입력하기 위한 timezone 모델 사용
\> q = Question(question_text="What's new?", pub_date=timezone.now())
\> q.save()
// q라는 Question 생성 후 저장
\> q.id()
# 1 // 명시하지 않아도 장고에서 자동으로 생성해주는 아이디
\> q.question_text
# "What's new?" // question_text 출력
\> q.pub_date
# datetime.datetime(2019, ~~ , tzinfo=<UTC>) // 발행일 출력
\> q.question_text = "What's up?" \> q.save() // 변경 후 저장 가능
\> Question.objects.all()
# <QuerySet [<Question: Question object (1)>]>
Shell
복사
여기서!! <QuerySet [<Question: Question object (1)>]>에서 object(1)이 어떤 객체를 뜻하는지 파악하기 어렵다.
이 때, polls/models.py 파일의 Question 모델을 수정하여 "__str__()" 메소드를 Question과 Choice에 각각 추가해서 사용해볼 수 있다.
\> Question.objects.all()
# <QuerySet [<Question: Question object (1)>]> // 변경 전
<<polls/models.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
개발자가 필요로 하는 커스텀 메소드를 입력해줄 수도 있다.
<<polls/models.py>> 코드 추가 수정
import datetime
from django.db import models
from django.utils import timezone
class Question(models.Model):
# ...
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
변경된 사항을 저장 후, Shell을 다시 실행시켜본다.
\> exit
\> Ctrl-Z
# now exiting InteractiveConsole...
\> cls
\> py manage.py shell //shell 실행
재실행했으므로, 사용에 필요한 모듈을 다시 import 시켜줘야한다.
\> from polls.models import Choice, Question
\> Question.objects.all() // Question의 데이터를 확인하여 변경 전과 비교
# QuerySet [<Question: What's new?>]> // str메소드를 추가하였기에 출력값이 변경됨
\> Question.objects.filter(id=1) // id가 1에 해당하는 값을 불러옴
\> Quesiton.objects.filter(question_text_startswith='what')
// 문장이 'what'으로 시작하는 값을 불러옴
# 이와 같은 것들은 Django 독스 내에 제공하므로 필요한 것을 찾아 쓰면 된다.
# 필요한 것들을 하나하나씩 찾아가며 익히도록 한다. 모른다고 좌절 X
Shell
복사
#2-2 admin (Django admin 사용법)
•
관리자 생성하기
\> py manage.py createsuperuser
# Username (leave blank to use 'lg'):
\> admin
# Email address:
\> admin@example.com
# Password:
\> admin // 비밀번호 입력 시, 입력한 글씨가 안보이므로 주의
# Password (again):
/> admin
# ... Bypass password validation and create user anyway?[y/N]:
\> y
# Superuser created successfully.
생성이 완료되었으면, 명령어를 통해 서버를 시작하면된다.
\> py manage.py runserver
주소 127.0.0.1:8000/admin/ 을 통해 접속 후 아이디, 비밀번호 입력
Shell
복사
관리자 주소인 127.0.0.1:8000/admin/ 을 통해 들어가 로그인을 했지만 우리가 추가한 Question, Choice 등의 객체가 보이지 않는다.
따로 코드를 추가해 관리 사이트에 객체가 관리 인터페이스를 가지고 있다는 것을 알려주면 된다.
<<polls/admin.py>>
from django.contrib import admin
from .models import Question
admin.site.register(Question) // 객체 Question을 추가해준다.
Shell
복사
관리 사이트를 이용하면 객체의 내용을 편집하는 등 여러가지 기능을 적용하여 쉽게 관리할 수 있다.
#3 view (Django view 사용, html 불러오기 및 연결) <함수 기반 뷰>
•
우리가 만드는 poll 어플리케이션에서 다음 네 개의 view를 만들어본다.
◦
질문 "색인" 페이지 - 최근의 질문들을 표시합니다
◦
질문 "세부" 페이지 - 질문 내용과, 투표할 수 있는 서식을 표시합니다.
◦
질문 "결과" 페이지 - 특정 질문에 대한 결과를 표시합니다.
◦
투표 기능 - 특정 질문에 대해 특정 선택을 할 수 있는 투표 기능을 제공합니다.
•
뷰 추가하기
◦
클라이언트로부터 request를 받아 HttpResponse 응답을 해준다.
<<polls/view.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)
Shell
복사
◦
뷰를 호출하기 위한 urls.pu 코드 작성
<<polls/urls.py>>
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/resutls/
path('<int:question_id>/results/'. views.results, name='results'),
# ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]
URL 패턴을 보고 같다면 옆의 views.?를 호출해준다.
*** view.py의 인자 question_id는 urls.py의 question_id 이름과 같아야한다.
Shell
복사
◦
추가적인 뷰 변경
<<polls/views.py>>
from django.http import HttpResponse
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
output = ', '.join([q.question_text for q in latest_question_list])
return HttpResponse(output)
# Leave the rest of the views (detail, results, vote) unchanged
Shell
복사
◦
!!! 디자인을 수정하기 위해선 함수 내부 전체를 수정해야 한다. !!!
◦
!!! 그래서 내부 로직 담당인 뷰와 디자인 담당인 템플릿을 분리해야 한다. !!!
◦
!!! 템플릿 생성 시 템플릿 내에 하나의 앱 이름의 디렉터리를 더 만들어 그 안에 html파일을 넣어주어야 한다. !!!
::
polls/ → 하위 디렉터리 templates 생성
polls/templates/ → 하위 디렉터리 polls 생성
polls/templates/polls/ → 하위 파일 index.html 생성
<<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>>
def index(request):
# 1
# return HttpResponse("Hello, world.")
# 2
# latest_question_list = Question.objects.order_by('-pub_date')[:5]
# output = ', '.join([q.question_text for q in latest_question_list])
# return HttpResponse(output)
# 3
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))
context안에 데이터를 밀어 넣어주고, template을 활용하여 template 내에서 데이터가 보여주도록 한다.
데이터가 잘 전달이 되었는지 확인하고자 하면, 웹 페이지에서 F12을 눌러 확인할 수 있다.
Shell
복사
•
지름길 : render() → 정형화된 작업의 소스코드를 줄이기 위해 제공되는 함수 (위 코드와 동일한 작동)
<<polls/view.py>>
from django.shortcuts import render
def index(request):
# 1
# return HttpResponse("Hello, world.")
# 2
# latest_question_list = Question.objects.order_by('-pub_date')[:5]
# output = ', '.join([q.question_text for q in latest_question_list])
# return HttpResponse(output)
# 3
# 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))
# 4
latest_question_list = Question.objects.order_by('-pub_date')[:5]
context = {'latest_question_list' : latest_question_list }
return render(request, 'polls/index.html', context)
render() 함수를 사용하여 template 작업, HttpResponse의 import 작업 등을 거치지 않고
한 줄로 줄여 표현이 가능하다.
+) Django에는 reloading이라는 기능이 있어 변경된 사항이 생기면 자동으로 업데이트된다.
Shell
복사
•
404 에러 일으키기
<<polls/view.py>>
from django.http import Http404
from django.shortcuts import render
from .models import Question
# ...
def detail(request, question_id):
try:
question = Question.objets.get(pk=question_id)
except Question.DoesNotExist:
raise Http404("Question does not exist")
return render(request, 'polls/detail.html', {'question': question})
// Question에 현재 아이디 값 1만 저장되어있는 상태이다.
// 여기서 존재하지 않는 값(1 제외)을 주소에 적어주었을 때 나타나는 오류를 "Question does not exist" 문장으로 대체해준다.
Shell
복사
•
지름길 : get_object_or_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})
Shell
복사
•
템플릿 시스템 사용하기
<<polls/templates/polls/detail.html>>
<h1>{{ question.question_text }}</h1> question에서 question_text를 받아와 제목으로 사용
<ul>
{% for choice in question.choice_set.all %} // question = "What's new??"
<li>{{ choice.choice_text }}</li> // 모든 choice를 가져와 하나씩 리스트에 작성
{% endfor %}
</ul>
question인 "What's new??"를 외래키로 가지고 있는 모든 Choice를 가지고 오라는 뜻
Shell
복사
•
템플릿에서 하드코딩된 URL 제거하기
<<polls/urls.py>>
from django.urls import path
from . import views
app_name = 'polls' // Name을 명시해준다.
urlpatterns = [
# ex: /polls/
path('', views.index, name='index'),
# ex: /polls/5/
path('<int:question_id>/', views.detail, name='detail'),
# ex: /polls/5/resutls/
path('<int:question_id>/results/', views.results, name='results'),
# ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]
-------------------------------------------------------------------------------------------
<<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/{{ quesiton.id }}/" 이 부분은 *하드코딩된 상태이다.
URL을 템플릿에 하드코딩하게 될 경우, URL 변경 시 템플릿에 있는 코드들도 변경 해주어야 한다.
Django에서는 URL마다 Name을 명시할 수 있다. URL마다 Name을 명시하여 템플릿에 그 Name을 직접 써주어 번거로운 작업을 줄일 수 있다.
"/polls/{{ quesiton.id }}/" ---> {% url 'polls:detail' question.id %}
// detail이라는 polls/urls.py의 Name('polls')을 명시해준다.
다른 앱에서도 이 detail이라는 Name을 사용할 수 있기 때문에 항상 Name을 명시해줄 필요가 있다.
명시해준 이름으로 polls:detail와 같이 작성하여 사용한다.
*하드코딩 -> 데이터를 코드 내부에 직접 입력하는 것.
Shell
복사
#4 from, generic view (Django form 사용, 데이터 받아오는 법, generic view 사용 < 클래스 기반 뷰>
•
#4의 클래스 기반 뷰는 소스코드가 줄어들고 함수 기반 뷰와 동작 방식은 동일하지만 구현방식에 차이가 있다.
•
간단한 폼 만들기
<<polls/templates/polls/detail.html>>
<h1>{{ question.question_text }}</h1>
<!-- <ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul> -->
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
// if error_message가 있을 경우, 에러 메세지 출력. 없는 경우, 출력 X
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
// 사이트간 위조요청 해킹방지를 위한 코드 (전반적인 보안 대비책 마련).
{% for choice in question.choice_set.all %}
// 해당 question을 외래키로 같는 choice들을 모두 가져와서 하나하나씩 반복문을 돌려줌.
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
// 사용자가 submit버튼을 눌렀을 때 데이터는 value값이 넘어간다.
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
<input type="submit" value="Vote">
// 사용자가 input타입인 Vote라는 버튼을 누르게되면 form태그 첫 줄의 url(polls:vote)로 데이터를 보내게된다.
// 해당 url을 따라가 polls/urls.py의 해당 views.vote에서 데이터를 처리하게된다.
// views.vote에서 처리되므로 작동이 이루어질 polls/views.py의 vote함수가 작동된다.
</form>
<<polls/views.py>>
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
from .models import Question, Choice
# ...
def vote(request, question_id):
// urls.py에서 views.vote로 인해 views.py파일 내 vote함수에 question_id를 넘겨받음.
# return HttpResponse("You're voting on question %s." % question_id)
question = get_object_or_404(Question, pk=question.id)
// question_id를 넘겨받아 question을 조회한다.
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
// 조회한 question을 외래키로 갖는 선택지choice를 가져온다.
// 이 때, 조건은 선택지 중에서 pk의 값이 템플릿에서 넘겨받은 값을 조회하게 되는 것이다.
// 즉, detail.html에 있는 Name='choice'라는 선택지에 해당하는 정보를 가져온다.
except (KeyError, Choice.DoesNotExist):
// 위의 selected_choice가 없을 경우 예외가 발생한다.
return render(request, 'polls/detail.html', {
// 예외 발생 시 바로 상세페이지로 response한다.
'question': question,
'error_message': "You didn't select a choice.",
}) // context 데이터로는 question과 error_message를 함께 보내준다. (위처럼 별도의 변수 설정 없이 선언 가능)
else:
selected_choice.votes += 1
selected_choice.save()
// 데이터가 있는 경우 선택지의 votes를 1 증가시켜주고 저장한다.
return HttpResponseRedirect(reverse('polls:results', arg=(question_id, id)))
//HttpResponseRedirect는 POST와 한 세트로 여기면 된다.
//reverse는 하드코딩을 하지 않기 위해 사용하였으며 'app_name:url_name'으로 지정해준다.
// 여기서는 'polls:results'이므로 결과페이지 함수인 views.py의 results()를 호출한다.
def results(request, question_id):
# return HttpResponse(response % question_id)
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
// 위에서 호출한 results()에서 드디어 결과 페이지인 result.html를 보여준다.
// 이때, 동시에 {'question': question}의 question 데이터를 같이 넘겨준다.
<<polls/templates/polls/result.html>>
<h1>{{ question.question_text }}</h1>
// 위에서 같이 넘겨준 question이 여기서 question_text로 보여지게 된다.
<ul>
{% for choice in question.choice_set.all %}
// 넘겨준 question에 대한 모든 선택(choice)값들이 반복문을 통해 리스트로 보여진다.
<li>
{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes||pluralize }}
// "선택지 이름" -- "선택지 투표 수" "vote(s)"의 형식으로 출력된다.
// 여기서 pluralize라는 키워드는 ||앞의 choice.votes가 단수인 경우 vote를 단수 처리(vote), 복수인 경우 복수 처리(votes)해주는 역할을 한다.
// pluralize는 장고에서 제공하는 템플릿 키워드이며 모르는 키워드가 나올 시 구글링으로 알아보자.
</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}"> Vote again?</a>
// Vote again?을 누를시, 초기화면 url인 polls:detail로 돌아가게 된다.
Shell
복사
•
제네릭 뷰 사용하기 <클래스 기반 뷰>
◦
제네릭 뷰는 장고에서 일반적인 패턴을 제공하기 때문에 추상화하여 앱을 작성하기 위해 Python 코드를 많이 작성하지 않아도 된다.
Django에 익숙해질 때까진 클래스 기반 뷰보단 함수 기반 뷰를 많이 사용하는 것을 권장
<<제네릭 뷰(클래스 기반 뷰) 사용하기>>
<<polls/urls.py>>
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
# ex: /polls/
# path('', views.index, name='index'),
path('', views.IndexView.as_view(), name='index'),
// 아래와 전부 동일하게 .as_view()함수를 호출하여 views.???View 뷰를 호출한다.
# ex: /polls/5/
#path('<int:question_id>/', views.detail, name='detail'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
// Detail, Result 동일하게 '<int:pk>/~' 파라미터 이름을 짓고, pk만 명시해주면 된다.
// 어차피 pk가 호출되겠다고 하고 내부에 정의가 되어있는 것이다.
// pk는 DB의 열, 즉 하나의 데이터를 구분하기 위한 값이며 pk값은 중복되지 않는다.
# ex: /polls/5/resutls/
#path('<int:question_id>/results/', views.results, name='results'),
path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
# ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]
<<polls/views.py>>
from django.http import HttpResponseRedirect
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
from django.views import generic
from .models import Question, Choice
# def index(request):
# ...
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
// context에 넘겨주는 이름이 model 이름과 다르다면 새로 context_object이름을 정해주고,
// 필요한 데이터를 get_queryset함수를 통해 작성해주면 된다.
def get_queryset(self):
return Question.objects.order_by('-pub_date')[:5]
# def detail(request, question_id):
# ...
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'
// 클래스 기반 뷰를 사용하면 코드의 길이가 확연히 줄어든다.
// template_name:템플릿 이름과 model:템플릿에서 사용할 데이터 모델을 정해주기만 하면된다.
# def results(request, question_id):
# ...
class ResultsView(generic.DetailView):
model = Question
template_name = 'polls/results.html'
def vote(request, question_id):
# return HttpResponse("You're voting on question %s." % question_id)
... # same as above, no changes needed.
Shell
복사
•
GET, POST 방식 예제
<<polls/templates/polls/detail.html>>
# ...
<form action="{% url 'polls:vote' question.id %}" method="post">
# ...
</form>
// 데이터 전송을 위해 POST 방식을 요청하기 위해선 method="post" POST가 명시된다.
<<polls/templates/polls/index.html>>
# ...
<li><a href={% url 'polls:detail' question.id %}>{{ question.question_text }}</a></li>
# ...
// POST와는 다르게 index.html에서는 데이터 조회를 위해 이와 같은 방식으로 요청한다.
// 이처럼 조회용으로 요청되는 것은 GET 방식이다. POST처럼 명시되지 않은 것을 볼 수 있다.
ex) GET, POST 방식을 따로 분기처리를 할 수 있다.
def vote(request, question_id):
if request.method == 'GET':
do_something()
// GET 방식이 호출될 경우 어떠한 동작을 하게끔 한다.
elif request.mothod == 'POST':
// 위와 달리 POST 방식이 호출되면 아래 동작을 수행한다.
question = get_object_or_404(Question, pk=question_id)
try:
~~
except
~~
Shell
복사
#5 test (Django test 사용, test case 작성, unit test)
•
테스트 코드 : 작성한 코드를 테스트 하기 위해 작성한 코드
◦
수 많은 개발과 버그들을 테스트 하기 위해 필요함
◦
많은 사람들과 협업을 하기 때문에 필요함
◦
점점 스케일이 커지면 일일히 테스트 하기에 오래걸리므로 시간을 절약할 수 있다.
◦
테스트는 문제를 그저 식벼하는 것이 아니라 예방한다.
◦
테스트가 코드를 더 매력적으로 만든다.
▪
"테스트 없는 코드는 설계상 망가져 있는 것이다"
테스트 주도 개발: 테스트 코드를 먼저 작성 후 기능 개발하기
우리는 기초가 필요하므로 기능 개발 후 최대한 빨리 테스트 코드 작성을 한다.
•
버그를 노출하는 테스트 만들기
<<polls/tests.py>>
import datetime
from django.test import TestCase
from django.utils import timezone
from .models import Question
# Create your tests here.
class QuestionModelTests(TestCase):
// import한 TestCase를 클래스 QuestionModelTests에 상속시킨다.
def test_was_published_recently_with_future_question(self):
// 함수명은 맨 앞 "test"로 시작하게 지정해주면 된다.
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)
Shell
복사
- 발생할수 있는 경우에 대해 최대한 테스트 코드를 작성해준다.
- 테스트 코드는 충분할수록 좋다.
- 코드의 양과 상관없이 최대한 작성해 주는 것이 좋다.
<뷰>핵심 -> request를 받고 response해주는 것.
<뷰>테스트는 사용자가 된 것처럼 request를 전달해주고 그에 대한 response를 받아 테스트한다.
<<Shell..(myenv)C:Users\user\work\mysite>>
> py manage.py shell
(InteractiveConsole)
>> from django.test.utils import setup_test_environment
>> setup_test_environment()
>> from django.test import Client
>> cient = Client()
>> response = client.get('/')
Not Fount: /
>> response.status_code
404
>> from django.urls import reverse
>> response = client.get(reverse('polls:index'))
>> response.status_code
200
>> response.content
b'\n <ul>\n \n <li><a href=/polls/1/>What':s new?</a></li>\n \n </ul>\n'
>> response.context['latest_question_list']
<QuerySet [<Question: What's a new?>]>
이처럼 쉘에서 작성한 코드를 에디터를 통해 작성하여 원하는 값이 나오는지 확인하는 것이 뷰 테스트 코드 작성이다.
에디터를 통해 테스트 코드를 작성하기 전에 리스트 뷰 수정이 있다.
---> 뷰 개선시키기
Shell
복사
•
뷰 개선시키기
<<polls/views.py>> // 기존의 IndexView를 수정
from django.utils import timezone
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
return Question.objects.filter(
// 미래의 날짜가 안나오도록 filter를 넣어 조건을 입력한다.
pub_date__lte=timezone.now()
// '__lte' (= 'less than equal') 장고에서 제공하는 필터조건
// 큰 값은 '__gte'를 사용하고 in, contain등 많은 조건이 있다.
).order_by('-pub_date')[:5]
Shell
복사
•
뷰 테스트 코드 작성하기
<<polls/tests.py>>
from django.urls import reverse
// url을 하드코딩 하지 않도록 reverse를 import 해준다.
class QuestionModelTests(TestCase):
# ...
def create_question(question_text, days):
// 테스트 데이터를 위해 만든 함수
time = timezone.now() + datetime.timedelta(days=days)
return Question.objects.create(question_text=question_text, pub_date=time)
// 함수를 호출할 때마다, 데이터가 하나 만들어진다.
// 데이터는 각 테스트 형식마다 재설정되므로 필요할 때마다 함수를 호출하여 생성해야 한다.
// 아래 여러개의 테스트 함수들은 상황만 다르고 패턴은 똑같다.
class QuestionIndexViewTests(TestCase):
def test_no_questions(self): <데이터가 없는 경우>
response = self.client.get(reverse('polls:index'))
// 상황을 만들어서 테스트 client가 요청을 하고 response를 받아 원하는 값이 나오는지에 대해 확인하게된다.
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No polls are available.")
self.assertQuerysetEqual(response.context['latest_question_list'], [])
// 데이터가 없는 경우를 호출하여 상태코드(status_code), 응답에 포함된 내용(response),
context가 비어있는 지를 확인한다.
// 같은 값인지[~Equal], 포함되었는지[~Contains], 쿼리셋인지[~QuerysetEqual] 와 같은 테스트 메서드를 사용하여 테스트한다.
// 이러한 형태들을 익혀주면된다.
def test_past_question(self): <데이터가 과거인 경우>
create_question(question_text="Past question.", days=-30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question.>']
)
// 과거 데이터를 하나 만들고 호출한 뒤, 데이터가 나오는지 확인한다.
def test_future_question(self): <데이터가 미래인 경우>
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('polls:index'))
self.assertContains(response, "No polls are available.")
self.assertQuerysetEqual(response.context['latest_question_list'], [])
// 미래 데이터를 하나 만들고, 데이터가 안나오는 경우를 확인한다.
def test_future_question_and_past_question(self):
create_question(question_text="Past question.", days=-30)
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question.>']
)
// 과거, 미래 데이터 각각 하나씩 호출한 뒤에, 과거 데이터만 나오는 모습을 기대한다.
def test_two_past_question(self):
create_question(question_text="Past question 1.", days=-30)
create_question(question_text="Past question 2.", days=-5)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question 2.>', '<Question: Past question 1.>']
)
// 과거 데이터 2개를 호출한 뒤에, 2개의 데이터가 나오는 모습을 기대한다.
Shell
복사
•
DetailView 테스트하기 (DetailView도 IndexView와 동일함)
<<polls/views.py>>
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'
def get_queryset(self):
return Question.objects.filter(pub_date__lte=timezone.now())
// 미래 데이터가 나오지 않도록 필터를 추가해준다.
<<polls/tests.py>>
class QuestionDetailViewTests(TestCase):
def test_future_question(self):
future_question = create_question(question_text='Future question.', days=5)
// 질문을 만들고
url = reverse('polls:detail', args=(future_question.id,))
response = self.client.get(url)
// 테스트 클라이언트가 호출하고 결과(response)를 받아
self.assertEqual(response.status_code, 404)
// 검증하게 된다.
def test_past_question(self):
past_question = create_question(question_text='Past Question.', days=-5)
url = reverse('polls:detail', args=(past_question.id,))
response = self.client.get(url)
self.assertContains(response, past_question.question_text)
Shell
복사
#6 CSS, Static file (Django CSS 연결, Static 파일)
•
렌더링(Rendering) : 사용자가 서버로부터 데이터를 받아 자신의 화면에 보여지게 하는 작업
◦
정적 파일(Static) : HTML, CSS, JavaScript / 렌더링엔 이 3가지가 필요함
Django에서는 Static파일과 서버를 분리할 수 있도록 해준다.
•
Static 파일 기본 위치 : polls/static/polls/style.css
◦
앱 내부에 Static 디렉터리를 만들어 저장. 다른 어플리케이션에서 이름 중복이 일어날 수 있어 앱 이름으로 네임스페이스를 명시해준다.
◦
css파일 생성, 작성 후 html 파일에 css를 로드해주면 된다.
◦
배경 이미지를 사용하고자 하면, 네임스페이스로 사용하는 앱 하위 디렉터리 내에 images 디렉터리를 만들어 이미지를 관리하면 된다. 그리고 css에서 저장한 이미지 파일을 불러와 사용하면 된다.
<<polls/static/polls/style.css>>
li a {
color: green;
}
// Css 파일 작성
body {
background: white url("images/background.gif") no-repeat;
}
// images 디렉터리에 저장한 이미지 파일을 불러와 배경에 띄운다.
<<polls/templates/polls/index.html>>
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}">
// 작성한 Css 파일을 템플릿에서 불러온다.
Shell
복사
#7 Customize admin (Django customize the admin)
•
Admin Customizing
<<polls/admin.py>>
from django.contrib import admin
from .models import Question, Choice
class QuestionAdmin(admin.ModelAdmin):
// 어드민을 커스터마이징 하기 위해선 어드민을 상속받는 클래스를 하나 선언한다.
fields = ['pub_date', 'question_text']
// 어드민 내 우리가 필요한 값을 직접 선언하여 커스터마이징 한다.
admin.site.register(Question, QuestionAdmin)
// register 두 번째 인자로 커스터마이징 한 클래스를 넘겨주면된다.
admin.site.register(Choice)
필드가 많아 관리가 필요할 때, fieldsets로 묶어 제목을 주는 것도 가능하다.
->class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, //제목 없음 {'fields': ['question_text']}),
('Date information', {'fields': ['pub_date']}),
]
Shell
복사
일반적으로 질문(Question)을 등록할 때, 선택지(Choice)도 같이 등록할 수 있게끔 하는 게 좋을 것이다. 하지만 이전까지의 결과물은 따로따로 설정하게끔 되어있다.
•
그렇기에 Question을 외래키로 갖는 Choice도 현 위치에서 같이 등록할 수 있도록 해준다. 다음과 같이 수정해보자.
<<polls/admin.py>>
from django.contrib import admin
from .models import Question, Choice
class ChoiceInline(admin.StackedInline):
// Inline을 상속받아 클래스를 하나 만들어준다.
model = Choice
extra = 3
// 보여질 모델의 종류와, 인라인으로 보여질 갯수를 명시해준다.
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
# ...
]
inlines = [ChoiceInline]
// 위에서 만들어준 ChoiceInline 클래스를 inlines로 등록해주면 된다.
// 이로써 Question 어드민에서 Choice를 직접 관리할 수 있게 된다.
admin.site.register(Question, QuestionAdmin)
admin.site.register(Choice)
// Choice를 Question과 같이 등록하기위해 Inline으로 넣어줄 것이기 때문에 굳이 필요하지 않으면 삭제해준다.
------------------------------
여기서 extra의 개수가 늘어날 때, Choice 항목들이 화면에 너무 많이 차지하게 된다면,
StackedInline을 TabularInline으로 변경해줌으로써 테이블 형식으로 심플하게 변경 가능하다.
# ...
class ChoiceInline(admin.TabularInline):
# ...
Shell
복사
Admin 사이트에 들어가보면 현재 Question을 눌러 Question 리스트를 보았을 때,
분명 우리는 발행일도 설정했을텐데 Question밖에 없을 것이다.
•
이 때, list_display를 사용하여 리스트 항목들을 추가시켜줄 수 있다.
<<polls/admin.py>>
class QuestionAdmin(admin.ModelAdmin):
# ...
list_display('question_text', 'pub_date', 'was_published_recently')
// 질문, 발행일, 최근발행일 데이터여부 등 추가가 가능하다
// 'was_published_recently'와 같은 필요에 의해 커스터마이징 필드도 추가가능.
<<127.0.0.1:8000/admin/polls/question/>>
<위 코드의 결과로 사이트 표현 예시>
ㅁ Question Text DATE PUBLISHED WAS PUBLISHED RECENTLY
ㅁ What's new? Nov.4,2021,5:59p.m. False
Shell
복사
•
우리는 다음과 같이 해당 메소드에 몇 가지 속성을 부여함으로써 편리성을 향상시킬 수 있다.
<<polls/models.py>>
class Question(models.Model):
# ...
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
was_published_recently.admin_order_field = 'pub_date'
// admin_order_field는 필트 타이틀 클릭 시 정렬을 위한 기준을 제시해준다.
was_published_recently.boolean = True
// boolean은 True/False라는 문자에서 아이콘의 모습으로 보여준다.
was_published_recently.short_description = 'Published recently?'
// description은 타이틀을 변경시켜주는 속성이다.
<<polls/admin.py>>
class QuestionAdmin(admin.ModelAdmin):
# ...
list_filter = ['pub_date']
// 사이트 오른쪽 한쪽에 발행일 기준으로 필터 리스트를 생성할 수 있다.
search_fields = ['question_text']
// 검색창을 위 쪽에 만들어줄 수 있다.
// 편리한 기능들을 찾아 선언 후 사용하면된다.
# ...
Shell
복사
•
어드민 템플릿 변경하기
◦
어드민 모습을 원하는대로 변경 가능 / 어드민 사이트 제목 변경 등...
◦
앱 관련 템플릿은 앱 내부에 있지만, 어드민 관련 템플릿은 최상위에 위치해야한다.
최상위 위치에 templates 디렉터리 생성 → 그 안의 admin 하위 디렉터리 생성
◦
Django에게 우리의 커스터마이징 템플릿 위치를 알려주어야 한다.
아래처럼 setting 파일에 우리가 방금 만든 templates 위치를 명시해준다.
<<mysite/settings.py>>
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], // 위치를 명시한다.
'APP_DIRS': True,
'OPTIONS' : {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
Shell
복사
어드민 템플릿 위치는 장고 소스코드 안에 있다.
장고 소스코드 위치를 정확히 모르는 경우엔 다음 명령어를 실행시켜준다.
...\> py -c "import django; print(django.__path__)"
그리고 어드민 템플릿 위치를 찾아 이동해준다.
ex) user\myenv\Lib\site-packages\django\contrib\admin\templates\admin\
admin 하위 폴더 진입시, base_site라는 이름의 사이트 파일을 찾는다.
base_site 파일을 이전에 최상위 폴더에 만든 templates\admin폴더에 복사하면된다. 복사를 하고난 뒤 마음껏 원하는대로 커스터마이징 하면된다.
<base_site.html> (복사)삽입 —> <templates/admin>
<<templates/admin/base_site.html>>
{% extends "admin/base.html" %}
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a></h1>
// 이처럼 바꿔주어 어드민 사이트 최상단 제목을 Polls Administration으로 바꿀 수 있다.
{% endblock %}
{% block nav-global %}{% endblock %}
// 이렇게 장고 디렉터리에서 소스파일을 우리의 프로젝트 디렉터리로 가지고 오는
커스터마이징을 하기 위한 작업을 마쳤다.
// 필요한 템플릿을 가져와 우리의 입맛에 맞게 커스터마이징 하면된다.
새로운 기능들은 하나하나 찾아가보며 학습하여 적용함으로써 실력을 쌓는다.
Shell
복사