
https://docs.djangoproject.com/ko/5.1/intro/tutorial03/
1. View 개요
view는 사용자가 웹사이트에 접속했을 때 보여줄 내용을 결정하는 코드입니다.
- 사용자가 웹사이트에서 특정 정보를 조회하거나 데이터를 제출하면 view는 그 요청에 대한 로직을 처리
- 템플릿과 데이터를 연결해서 사용자가 볼 수 있는 화면을 만들어주는 역할
좀 더 구체적인 예시로 설명 드리면,
# views.py
from django.http import HttpResponse
def home_view(request):
return HttpResponse("Welcome to the home page!")
사용자가 특정 URL에 접속하면 Django는 적절한 view를 호출합니다. 위 코드에서 'home_view'는 사용자가 요청한 페이지에 대해 어떤 응답을 줄지 로직을 처리하는 view입니다.
HttpResponse로 간단히 환영 문구를 사용자에게 보여줍니다.
view는 템플릿 HTML 파일에도 연결이 되는데 여기서 템플릿은 사용자가 보는 웹 페이지입니다.
템플릿에 데이터를 전달해서 사용자에게 보여줄 화면을 구성합니다.
# views.py
from django.shortcuts import render
def home_view(request):
# 데이터를 템플릿에 전달
return render(request, 'home.html', {'message': 'Hello, welcome to the home page!'})
1.1 render 함수
'home_view'에서 render 함수로 사용자가 요청한 웹 페이지 HTML 템플릿과 데이터를 응답해주는데요,
render함수는 HTML 페이지를 사용자에게 보여주는 기능을 담당합니다.
render함수는 내부적으로 아래와 같이 동작합니다.
# django/shortcuts.py의 render 함수 내부 동작 방식
def render(request, template_name, context=None, content_type=None, status=None, using=None):
"""
1. 템플릿 로더로 template_name에 해당하는 템플릿을 찾음
2. 컨텍스트 데이터와 함께 템플릿을 렌더링
3. HttpResponse 객체로 변환해서 반환
"""
content = loader.render_to_string(template_name, context, request, using=using)
return HttpResponse(content, content_type, status)
render함수 인자 구성은 아래와 같습니다.
render(request, 'home.html', {'message': 'Hello,'})
1) request(필수 전달 값): 사용자가 웹사이트에 접속할 때 보내는 모든 요청 정보 (어느 브라우저에 요청했는지, 데이터 전달 방식은 'POST'인지 'GET'인지 등등..)
2) template name(필수 전달 값): 보여줄 HTML 템플릿 파일명 'home.html'
🤔여기서 파일명 전달으로만 어떻게 식별하지 의문이 드실 수 있을 것 같습니다.
Django가 템플릿 파일들을 찾을 수 있도록 'settings.py'에 템플릿 경로 지정을 해줘야합니다.
이 설정이 있어야 render()함수에서 'home.html' 템플릿 이름을 사용할 수 있습니다.
- 프로젝트 레벨 템플릿
# settings.py
TEMPLATES = [
{
'DIRS': [
BASE_DIR / 'templates', # 프로젝트 전체의 템플릿
],
'APP_DIRS': True, # 각 앱의 templates 폴더를 자동으로 검색
},
]
# 디렉토리 구조
my_project/
├── templates/ # 프로젝트 레벨 템플릿
│ ├── base.html # 공통 템플릿
│ ├── blog/ # blog 앱 템플릿
│ │ └── post.html
│ ├── shop/ # shop 앱 템플릿
│ │ └── product.html
│ └── users/ # users 앱 템플릿
│ └── profile.html
- 앱별 템플릿 디렉토리
# 디렉토리 구조
my_project/
├── blog/
│ └── templates/
│ └── post.html
├── shop/
│ └── templates/
│ └── product.html
└── users/
└── templates/
└── profile.html
# settings.py
TEMPLATES = [
{
'DIRS': [
BASE_DIR / 'blog' / 'templates',
BASE_DIR / 'shop' / 'templates',
BASE_DIR / 'users' / 'templates',
],
'APP_DIRS': True, # 각 앱의 templates 폴더를 자동으로 검색
},
]
일반적으로는 프로젝트 레벨 템플릿 디렉토리를 사용하는 것이 권장된다고 합니다.
3) context(선택적): {'message': "Hello"} : HTML 파일에 전달할 데이터로 예시 코드에서는 message라는 키에 담긴 'Hello' 값이 해당 템플릿에 전달됩니다. context는 항상 딕셔너리 형태여야합니다.
💡django에서는 템플릿 컨텍스트 프로세서라는 기능이 있어서 context 딕셔너리 외에도 자동으로 템플릿에 추가되는 데이터들이 있습니다.
예를 들면
- request 객체: {% if request.user.is_authenticated %}
- 현재 사용자 정보: {% if user.is_superuser %} 등
context에 수동으로 추가하지 않아도 기본 데이터를 템플릿에서 사용할 수 있습니다.
템플릿 엔진을 커스터마이징해서 동작 방식을 변경할 수 있지만, 비효율적인 작업이므로 동적으로 데이터를 표시해야된다면 context를 명시적으로 전달하는 것이 필수적입니다.
1.2 View 추가하기
view 개요 내용이 조금 길어졌지만...😅 이제 튜토리얼 스탭으로 돌아가서 view를 추가해보겠습니다.
추가한 view들은 인수를 받아서 모양이 조금 다릅니다.
# polls.views.py
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id)
except Question.DoesNotExist:
raise Http404("Question does not exist")
return render(request, "polls/detail.html", {"question": question})
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 모듈로 연결 해야합니다.
# 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/results/
path("<int:question_id>/results/", views.results, name="results"),
# ex: /polls/5/vote/
path("<int:question_id>/vote/", views.vote, name="vote"),
]
detail 뷰함수를 호출하는 url 요청을 해보면, 요래 결과가 나옵니다.

/polls/34/ 요청 👉🏼 detail(request=<HttpRequest object>, question_id=1) 뷰함수 호출
question_id=1 인수 부분은 URL 패턴에서 동적인 값을 처리하는 방식인 <int:question_id>에서 왔습니다.
👁️ URL에서 데이터를 "캡쳐"
URL 패턴에서 꺽쇠 괄호 <> 를 사용하면 URL의 특정 부분을 동적으로 가져와서 뷰 함수에 전달할 수가 있습니다.
path('<int:question_id>/', views.detail) URL 패턴이 있다고 하면,
URL이 /polls/1/로 들어올 때, 1이 question_id라는 이름으로 detail() 함수에 전달됩니다.
해당 패턴은 ':'를 기준으로 변환기, 패턴이름 두 부분으로 나뉘는데요,
<int:question_id> 의 경우 변환기로 값 타입을 정의하고 (int) , 뷰함수에서 사용할 변수이름을 정의합니다 (question_id)
1.3 View 실질적인 기능 추가하기
Django view는 브라우저 요청이 들어오면 아래 둘 중 하나를 반드시 반환해야 합니다.
1) 요청된 페이지 내용이 담긴 HttpResponse 객체를 반환
- 요청한 페이지의 내용(HTML, JSON, 텍스트 등)을 담고 있는 응답 객체
2) Http404 같은 예외
- 요청한 페이지를 찾을 수 없거나 에러가 발생하면 적절한 예외를 발생 시킴
응답을 생성하기 위해 어떤 데이터를 가져오고 어떤 내용을 반환할지는 개발자 설계에 따라 달라집니다.
데이터베이스 레코드를 읽을 수도 있고, 파이썬에서 서드파티로 제공되는 템플릿 시스템을 사용할 수도 있습니다.
아래 예제는 데이터베이스에서 발행일(pub_date)기준으로 최근 5개의 질문을 가져오는 뷰함수입니다.
output 문자열을 포함하는 HttpResponse 객체를 반환하네요.
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)
#출력 예시 “질문1, 질문2, 질문3, 질문4, 질문5”
그런데 이렇게 뷰에서 페이지 디자인이 하드코딩 되어 있으면 페이지 구조가 변경되면 다시 해당 뷰함수의 파이썬 코드를 편집해야하게 되는데요.. 👽
이런 문제점을 장고 템플릿이 해결 해주는 도구입니다.
1.1 render 함수에서 다뤘던 템플릿 디렉토리 설정 부분을 다시 읽어 보시면 이젠 이해가 좀 더 잘 되실거예요.
템플릿에 아래 코드를 입력하고 (❗️아래는 코드 일부이므로 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
from django.http import HttpResponse
from django.template import loader
from .models import Question
def index(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))
이 코드는 polls/index.html 템플릿을 불러온 후에 context 데이터를 전달합니다.
근데 뭔가 loader, HttpResponse 불러오는 방식이 복잡하게 느껴지기도 하는데요.
이럴때 코드를 쉽게 표현하는 방식이 render() 함수입니다.
# polls.views.py
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)
훠얼씬 깔끔해졌습니다요✨
그럼 이제 예외 상황의 에러를 일으켜봅시다.
details 뷰함수에 태클을 한번 걸어볼텐데요.
해당 데이터가 전달 되는 detail 템플릿에는 예제 동작을 위해 question을 정의합니다.
<!-- polls/templates/polls/detail.html -->
{{ question }}
요청된 질문 ID가 존재하지 않는 Http404 예외 에러를 발생시킵니다.
# polls/views.py
from django.http import Http404
from django.shortcuts import render
from .models import Question
# ...
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id)
except Question.DoesNotExist:
raise Http404("Question does not exist")
return render(request, "polls/detail.html", {"question": question})
Http404 예외 발생에도 쉽게 표현할 수 있는 객체가 있는데요. get_object_or_404입니다.
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(모델이름, 키워드 인수(검색조건))
get_object_or_404() 함수는 장고 모델을 첫번째 인자로 받고 몇개의 키워드 인수를 모델 관리자의 get() 함수에 넘깁니다.
모델 관리자의 get() 함수에 넘긴다는 말은 지정한 모델에서, 주어진 조건으로, 데이터를 검색한다라는 의미입니다.
그리고 리스트가 비어있는 경우에 Http404 예외를 발생시킵니다.
SQL 조건 검색과 비슷한 맥락으로 이해하셔도 될 것 같네요.
SELECT * FROM Question
WHERE id = 1 AND YEAR(pub_date) = 2024;
1.4 템플릿 시스템 사용해보기
위 예제에서 details.html에 전달된 question context 변수를 템플릿에 적용해봅시다.
<!-- 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>
템플릿 시스템은 변수 속성에 접근하기 위해 dot-lookup 문법을 사용합니다.
{{ question.question_text }} 구문을 보면 장고는 먼저 question 객체를 찾고, 연결된 선택자 question_text에 접근합니다.
{% for choice in question.choice_set.all %} 반복구문의 question.choice_set.all 에서는 question 객체를 찾고 choice_set에 접근하고 all로 모든 정보를 가져옵니다.
이때 모든 정보가 반환된 Choice 객체 반복자는 {% for %} 구문을 사용하기 적합하겠네요.
이외에 장고 템플릿 지침서에 대해 아래 링크에서 참고 해주세요.
https://docs.djangoproject.com/ko/5.1/topics/templates/
1.5 템플릿에서 하드코딩된 URL 제거하기
템플릿에서 URL 링크를 아래와 같이 적으면 부분적인 하드코딩 정의가 됩니다.
이렇게 되면 URL 패턴이 바뀔때 모든 템플릿을 수정해야되는 상황이 발생해버립니다.
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
URL 모듈 정의에 URL 명칭을 붙여주면 특정 경로에 대한 의존성을 제거할 수 있습니다.
URL을 직접 쓰지 않고 이름으로 참조하는 방식으로 됩니다.
# polls/ursl.py
path("<int:question_id>/", views.detail, name="detail"),
이제 템플릿에는 name으로 정의한 매개변수가 {% url 'detail' %} 형태로 정의됩니다.
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
추가적으로 장고 프로젝트에 여러 앱이 있는 상황에서 URL이 겹치면 충돌이 나는데요,
myproject/
polls/ # 투표 앱
urls.py
views.py
blog/ # 블로그 앱
urls.py
views.py
# polls/urls.py
path('detail/<int:id>/', views.detail, name='detail')
# blog/urls.py
path('detail/<int:id>/', views.detail, name='detail')
두 앱 모두 detail 이라는 이름의 URL을 가지고 있어서 충돌이나겠죠.
이럴때 네임스페이스를 추가해주면 충돌을 예방할 수 있습니다.
# polls/urls.py
app_name = 'polls' # 네임스페이스 추가
urlpatterns = [
path('detail/<int:question_id>/', views.detail, name='detail'),
]
# blog/urls.py
app_name = 'blog' # 네임스페이스 추가
urlpatterns = [
path('detail/<int:post_id>/', views.detail, name='detail'),
]
템플릿에 적용하면 :로 구분하여 정의하면 됩니다.
<!-- polls 앱의 detail 뷰 링크 -->
<a href="{% url 'polls:detail' question.id %}">투표 상세보기</a>
<!-- blog 앱의 detail 뷰 링크 -->
<a href="{% url 'blog:detail' post.id %}">블로그 글 보기</a>
part 4 에서 계속...
'Programming👩🏻💻 > Web framework' 카테고리의 다른 글
[Django] 공식문서로 익혀보기 part4.2 - 함수/클래스뷰, 제네릭뷰 (1) | 2024.11.30 |
---|---|
[Django] 공식문서로 익혀보기 part4.1 - 폼(Form) 작성하기 (1) | 2024.11.24 |
[Django] 공식문서로 익혀보기 part2 - 데이터베이스 설치 (6) | 2024.11.15 |
[Django] 공식문서로 익혀보기 part1 - 첫 시작, 프로젝트와 앱 만들기 (5) | 2024.11.15 |
Django - URL 패턴 name 속성 (0) | 2024.08.09 |