📢 공지사항
///////
home

Django 1

상태
박준영
배정
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&#x27: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
복사