새로운 앱 추가
python3 manage.py startapp [app이름]
이렇게 하면 해당 앱을 위한 디렉토리가 생성된다.
- 수정해야 할 파일 : main/settings.py , main/urls.py , app/urls.py , app/views.py
main/settings.py
INSTALLED_APPS = [
'apitest.apps.ApitestConfig',
'polls.apps.PollsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
- 새로 설치한 앱의 내용을 추가해주어야 일단 설치됐다고 인식한다.
- config 형태로 추가해주는 이유는 앱을 구성하는 클래스의 구조 자체를 넘겨줘야 하기 때문
main/urls.py
from django.contrib import admin
from django.urls import include,path
urlpatterns = [
path('apitest/', include('apitest.urls')),
path('polls/', include('polls.urls')),
path('admin/', admin.site.urls),
]
- 메인 프로젝트 앱의 url에서 새로 추가한 앱의 urls.py를 include해서 가져오는 개념으로 생각해야 한다.
- URL이 들어올 경우 이 파일에서 우선적으로 판정하여 해당되는 app의 urls.py로 URL을 넘겨준다.
apps/urls.py
#apitest/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('',views.IndexView.as_view(),name='index')
]
- main/urls.py 에서 이쪽으로 URL을 넘겨주면 URL 패턴에 따라 어떤 뷰를 호출해야 하는지 찾는다.
- URL 패턴을 보고 path에 정의되는대로 넘긴다고 보면됨.
apps/views.py
from django.http import HttpResponse , HttpResponseRedirect
from django.shortcuts import get_object_or_404,render
from django.urls import reverse
from django.views import generic
# Create your views here.
class IndexView(generic.ListView):
template_name = 'apitest/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
"""Return the last five published questions."""
return HttpResponse("Hello, world. You're at the apitest index.")
- app/urls.py에서 import해서 사용하는 파일.
- urls.py가 URL 패턴에 따라 view를 판정해줬다면, 여기서는 판정된 view에 따라 어떤 class를 호출할지를 정한다.
- 호출된 클래스의 내용별로 app/templates/app/ 디렉토리에 있는 html 파일을 불러오기도 한다.
입력 URL의 처리
입력한 URL 에 대한 처리는 urls.py에서 담당.
입력한 내용에 맞춰서 url과 view를 연결해줌.
views.py는 url에 대응하여 정의되는데, 해당 view가 불러와졌을 때 어떻게 동작할지를 정의함.
여기서 template 에 있는 html파일과 연결할 수 도 있고, 단순 파이썬 코드의 동작 결과를 리턴시킬 수도 있음.
리턴시 HttpResponse를 사용하면 http 프로토콜의 response 형식을 이용하여 해당 데이터를 응답해주는것이고, render함수를 사용해 html 페이지를 띄워줄 수 도 있다.
이때 context로 해당 페이지 호출시 인자값을 넣어주는 방식.
정리하면,
- URL이 들어오면 메인 urls.py에서 그 URL이 어떤 app으로 연결되고자 하는건지를 판단하고
- 해당 app의 urls.py로 연결시켜주면 그 app/urls.py은 URL 패턴에 따라 연결될 페이지를 판정하여 views.py로 넘김
- app/views.py 는 URL 패턴에 따라 호출할 Class를 지정함
- 지정된 Class는 해당하는 웹 페이지를 templates/app/ 디렉토리에서 가져와서 화면에 띄워줌.
template에 있는 html파일은 해당 context를 이름을 지정하여 사용할 수 있다.
tempaltes/index.hml
{% for question in latest_question_list %}
<li><a href="{% url 'polls:detail' question.id %}">{{question.question_text}}</a></li>
{% endfor %}
템플릿 페이지에서 {% url 'view이름' 인자 }형식을 사용하면 url을 하드코딩 해서 넣는 대신에 views.py 에 있는 해당 view 이름에 알맞는 템플릿 페이지로 연결해준다.
app/views.py
class DetailView(generic.DetailView):
template_name = "polls/detail.html"
model = Question
urls.py 에 있는 형식대로 해당 view를 호출하여 path를 만들고, 그 다음 부터는 views.py에 정의된 대로 동작한다.
인자값은 해당 view의 request 이후 순서대로 인자가 되고, url 형식 또한 정의된 대로 polls/ 아래로 만들어진다.
-> 페이지 호출시 여러개의 인자를 넘기려면 어떻게 해야하나?
HttpResponseRedirect 함수는 HttpResponse과는 다르게, 응답값만 보내는게 아니라 해당 페이지로 Redirect까지 진행한다.
views.py 에서 reverse함수를 사용하면 위의 render 처럼 url을 하드코딩 하지 않아도 페이지를 로드할 수 있게 해주는데, reverse('polls:result',args=(question_id,)) 처럼 사용한다고 예를 들면,
urls에 정의된 대로 'int:question_id/results/'로 연결 할 때 args에 담긴 question_id를 인자로 하여 해당 페이지의 주소를 반환한다.
제네릭 뷰 - 코드 작성을 더 간편하게 하자
app_name = 'polls'
urlpatterns = [
# ex : /polls/
path('',views.IndexView.as_view(),name='index'),
# questin_id means DB's data id.
# view.<viewname> is the view's that defined in views.py
# ex : /polls/5/
path('<int:pk>/',views.DetailView.as_view(),name='detail'),
# ex : /polls/5/results/
path('<int:pk>/results/',views.ResultsView.as_view(),name='results'),
# ex : /polls/5/vote/
path('<int:question_id>/vote/',views.vote,name='vote')
]
차이점 : 일단 urls.py 의 view 이름부터 좀 더 띠껍게 바뀜.
view.index,name='index') -> views.IndexView.as_view(),name='index') 의 형태로 뒤에 as_view() 가 붙고 view의 명칭도 바꿔서 만든다.
그리고 question_id를 사용하는 부분들의 명칭을 pk로 바꾸는데, 이는 DB를 사용할때 보다 더 직관적으로 쓰게 만드려고 하는것 같다. (public_key)
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
#context = Question.objects.order_by("-pub_date")[:5]
def get_queryset(self):
return Question.objects.order_by("-pub_date")[:5]
class DetailView(generic.DetailView):
template_name = "polls/detail.html"
model = Question
class ResultsView(generic.DetailView):
template_name = "polls/results.html"
model = Question
그다음 views.py
일단 뷰를 def 형태가 아닌 class 형태로 선언해주고 , class 내에 def를 선언하여 사용한다.
render함수를 통해 리턴 페이지와 컨텍스트 인자를 명시해주는 대신, tamplate_name 과 context_object_name으로 리턴 페이지와 컨텍스트 인자의 이름을 명시하고 오브젝트 자체를 리턴시켜준다.
그리고 함수의 인자로 question_id를 받아오는 대신 model 이라는 객체에 Question 모델 자체를 담아서 사용하게 하며 , 리턴형을 명시해주지 않아도 template_name , context_object_name , model 셋을 가지고 자동으로 리턴이 진행된다.
왜 그러냐면, 제네릭뷰는 url로 들어온 기본 키 값이 pk라고 기대하기 때문에 굳이 question_id 형태를 사용할 필요가 없어지니까 Question 모델만 지정해주면 알아서 pk를 가져오는 셈.
각 클래스에 사용할 generic 뷰의 기본 형태는 ListView 와 DetailView 두가지로 정해져 있는 듯.
질문 : 템플릿 페이지에서 question. 이라는 이름으로 Question 모델을 가져와서 사용하는데, views는 그런 이름으로 사용하게 따로 설정해주지 않았다. question이라는 문자열이 정의되는 곳은 models에서 choice의 foreignKey 뿐. 어떻게 이런게 가능할까?
html 템플릿에서 css나 이미지와 같은 정적 파일들을 사용하고자 할땐 {% load static %} 를 써주자.
이는 현재 파일 내에서 static 파일을 사용하기 위해 절대경로를 생성한다는 의미이다.
이 뒤에 사용할때는 {% url %} 쓰듯이 {% static %}을 써주면 된다.
nginx와 연동해서 사용할때는 이 static을 적용시켜주기 위해 .loadstatic을 쓰는것.
모델의 관리자 옵션 편집이 필요할때는 패턴=모델 어드민 클래스를 만들어 register 의 두번째 인수로 전달하여 사용해준다. 이는 모델 내에 있는 항목들의 순서를 원하는 대로 바꿔줄 수 있게 도와주며 필드가 많으면 많을 수록 편하게 정리하는데 도움을 준다.
Choice 모델의 경우 Question 모델에 연결되어 쓰이기 때문에, Register로 추가해주는 것 보다 inline 방식을 통해 Question의 하위로 명시해서 사용하는게 더 효율적이다.
이 외에도 list_filter, list_display 등의 인자를 이용하면 관리자 페이지를 더 사용하기 편하게 개선할 수 있다.
장고의 settings.py 파일에서 DIR 옵션이 기본설정으로 비어있고 APP_DIRS 설정이 TRUE인 경우 장고는 기본 관리자 템플릿을 각 앱의 서브디렉토리에서 templates를 자동으로 찾아 대체한다.
DIR 옵션을 지정해주면 사용자 지정 템플릿을 관리자 템플릿으로 사용할 수 있게 된다.