[DEV] 4주차. 장고 활용한 API서버 만들기(2)
1. Views
- models : DB에 정보를 저장하고 읽어옴
- view : 읽어온 모델의 정보들 활용
polls/view.py
from django.http import HttpResponse
from .models import *
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)
polls/urls.py
에서 http://127.0.0.1:8000/polls/ 를 접속했을 때views.index
를 표시하도록 했기 때문에 위와 같이 나옴
2. Templates
-
HTML로 데이터를 잘 표시해주는 도구
polls/templates/polls
폴더 생성- 그 안에
index.html
생성
<ul>
<li>text</li>
</ul>
polls/views.py
from django.http import HttpResponse
from .models import *
from django.shortcuts import render
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 render(request, 'polls/index.html')
모델의 값 출력하기
polls/views.py
- render import
- context 변수 값을 템플릿에 넘겨줌
from django.http import HttpResponse
from .models import *
from django.shortcuts import render
def index(request):
latest_question_list = Question.objects.order_by("-pub_date")[:5]
context = {'first_question': latest_question_list[0]}
return render(request, 'polls/index.html', context)
templates/polls/index.html
- 변수는
{{중괄호}}
두개! - first_question 변수에 들어있는 값 출력
- Question 모델의
__str__
값
- Question 모델의
- 변수는
<ul>
<li> {{first_question}} </li>
</ul>
반복문
- django shell 에서
from polls.models import *
latest_question_list = Question.objects.order_by('-pub_date')[:5]
for question in latest_question_list:
print(question.question_list)
휴가 계획이 있나요?
커피 vs 녹차
가장 좋아하는 디저트는?
휴가를 어디서 보내고 싶으세요?
-
templates 으로
- indexing
questions.0
- 제어문
from django.http import HttpResponse
from .models import *
from django.shortcuts import render
def index(request):
latest_question_list = Question.objects.order_by("-pub_date")[:5]
context = {'questions': latest_question_list}
return render(request, 'polls/index.html', context)
<ul>
{% for question in questions %}
<li> {{question}} </li>
{% endfor %}
</ul>
조건문
- question이 없다면 ‘no questions’ 출력
{% if questions %}
<ul>
{% for question in questions %}
<li> {{question}} </li>
{% endfor %}
</ul>
{% else %}
<p>no questions</p>
{% endif %}
3. 상세페이지
- question id로 질문 내용 출력하기
polls/urls.py
- url에 숫자 입력받는 방법
<int: >
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
]
polls/views.py
from django.http import HttpResponse
from .models import *
from django.shortcuts import render
def detail(request, question_id):
question = Question.objects.get(pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
templates/polls/detail.html
question.choice_set.all
뒤에 괄호 넣지 않음
<h1></h1>
<ul>
{% for choice in question.choice_set.all %}
<li> {{choice.choice_text}} </li>
{% endfor %}
</ul>
4. 상세 페이지로 링크 추가
<a>
태그,url
태그 이용!
url에 앱 이름 설정
polls/urls.py
app_name = 'polls'
추가- 꼭 앱의 이름으로 해야하는 것이 아닌, 임의로 지정하는 값임!
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'),
]
templates/polls/index.html
{% if questions %}
<ul>
{% for question in questions %}
<li><a href="{% url 'polls:detail' question.id%}">{{question.question_text}}</li>
{% endfor %}
</ul>
{% else %}
<p>no questions</p>
{% endif %}
- url 태그로 앱 이름이
polls
이고, path 이름이detail
인 url을 불러옴 - 전달하는 인자는
question.id
이고, 출력하는 값은question.question_text
/polls 기본 화면
링크를 타고 들어간 화면 - question.id
: 5가 전달됨
detail
view에서 detail.html 불러옴
detail.html에 의해 question.choice_set.all
이 출력됨
5. 404 에러 처리
polls/views.py
def detail(request, question_id):
question = Question.objects.get(pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
questino = Question.objects.get(pk=100000)
- pk가 100000인 데이터가 없을 경우 Error를 발생시킴
- 에러가 발생했을 경우 서버는 500 에러 코드를 내림
- 이를 설명할 수 있는 404 에러로 바꿀 것
try-except
구문 활용
from django.http import Http404
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id)
except Question.DeosNotExist:
raise Http404('Question does not exist')
return render(request, 'polls/detail.html', {'question': question})
shortcuts 활용
- 훨씬 간결한 코드!
from django.shortcuts import render, get_object_or_404
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
6. 폼 Forms
선택 옵션 Radio 버튼으로
templates/polls/detail.html
<form action='#' method="post">
<h1>{{question.question_text}}</h1>
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice " value="">
<label for="choice{{ forloop.counter }}">
{{choice.choice_text}}
</label>
<br>
{% endfor %}
<input type="submit" value="Vote">
</form>
forloop.counter
- 반복문을 돌면서 1부터 1씩 증가하는 값
token
vote
버튼을 누르면403 Error - CSRF verification failed.
발생- 제출할 토큰이 없는데 제출한 경우
- token
- 서버에서 그려준 폼에서만 값을 제출할 수 있도록 방어하는 역할
templates/polls/detail.html
- form 태그 밑에
{% csrf_token %}
추가
- form 태그 밑에
<form action='#' method="post">
{% csrf_token %}
<h1>{{question.question_text}}</h1>
{% 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 %}
<input type="submit" value="Vote">
</form>
제출 내용 받기
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>/vote/', views.vote, name='vote'),
]
polls/views.py
from django.http import HttpResponse, HttpResponseRedirect
from .models import *
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
def index(request):
latest_question_list = Question.objects.order_by("-pub_date")[:5]
context = {'questions': latest_question_list}
return render(request, 'polls/index.html', context)
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
selected_choice = question.choice_set.get(pk=request.POST['choice'])
# choice는 detail.html 에서 지정한 name // value 값이 들어옴
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:index'))
templates/polls/detail.html
- form 태그의
action
옵션에 뷰 지정
<form action={% url 'polls:vote' question.id %} method="post">
{% csrf_token %}
<h1>{{question.question_text}}</h1>
{% 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 %}
<input type="submit" value="Vote">
</form>
네
를 누르고 admin 페이지에서 vote 값을 확인해보면 1 증가한 것을 볼 수 있음
아무것도 안누르고 제출했을 때 오류 방어
polls/views.py
from django.http import HttpResponse, HttpResponseRedirect
from .models import *
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
def index(request):
latest_question_list = Question.objects.order_by("-pub_date")[:5]
context = {'questions': latest_question_list}
return render(request, 'polls/index.html', context)
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': 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']) # detail.html 에서 지정한 name // value 값이 들어옴
except (KeyError, Choice.DoesNotExist):
return render(request, 'polls/detail.html', {'question': question, 'error_message': '선택이 없습니다'})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:index'))
- error message 표시
templates/polls/detail.html
에 if문 추가
<form action={% url 'polls:vote' question.id %} method="post">
{% csrf_token %}
<h1>{{question.question_text}}</h1>
{% 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 %}
<input type="submit" value="Vote">
</form>
급 render 함수에 대한 궁금증
django.shortcuts 의 render 라이브러리에 대해 설명해줄래?
django.shortcuts
는 Django 웹 프레임워크에서 자주 사용되는 뷰 함수를 작성할 때 도움을 주는 유틸리티 라이브러리입니다. 이 라이브러리는 간단한 HTTP 응답 생성 및 다양한 렌더링 작업을 수행하는 데 사용됩니다. 가장 일반적으로 사용되는 함수 중 하나는 render
함수입니다.
render
함수는 HTML 템플릿을 사용하여 클라이언트에게 렌더링된 페이지를 제공하는 데 사용됩니다. 주로 뷰 함수 내에서 사용되며, 다음과 같은 인자를 받습니다:
- request (필수): 요청 객체. 이 객체는 웹 브라우저로부터 받은 요청에 대한 정보를 포함하고 있습니다.
- 템플릿 이름 (필수): 렌더링할 HTML 템플릿 파일의 경로 및 이름을 지정합니다. 예를 들어, “myapp/mytemplate.html”와 같이 템플릿 파일의 경로를 문자열로 제공합니다.
- context (선택적): 템플릿에 전달할 데이터를 포함하는 딕셔너리. 이 데이터는 템플릿에서 변수로 사용됩니다.
- content_type (선택적): 렌더링된 응답의 콘텐츠 유형을 나타내는 문자열. 기본값은 “text/html”입니다.
- status (선택적): HTTP 응답 상태 코드를 나타내는 정수. 기본값은 200 (OK)입니다.
- using (선택적): 사용할 템플릿 엔진을 지정하는 문자열. 기본적으로 Django는 설정에서 설정한 템플릿 엔진을 사용합니다.
render
함수는 템플릿을 렌더링하고, context
로 전달된 데이터를 템플릿 변수와 함께 사용하여 최종 HTML 응답을 생성합니다. 이후 이 응답은 클라이언트에게 반환됩니다. 이렇게 함으로써 Django 개발자는 웹 애플리케이션의 다양한 페이지를 생성하고 데이터를 동적으로 표시하는 작업을 쉽게 수행할 수 있습니다.
html에서 보여지는 부분을 그려주는 함수라고 이해하면 될까?
네, render
함수는 HTML에서 보여지는 부분을 그려주는 함수로 이해할 수 있습니다. 이 함수는 Django 뷰에서 사용되며, HTML 템플릿을 렌더링하여 클라이언트에게 전달되는 웹 페이지의 내용을 생성합니다.
일반적으로 Django 웹 애플리케이션에서 다음과 같은 작업을 수행합니다:
- 사용자의 요청을 처리하는 뷰 함수를 작성합니다.
- 해당 뷰 함수에서
render
함수를 사용하여 HTML 템플릿을 렌더링합니다. - 템플릿에 데이터를 전달하여 동적으로 생성된 페이지를 만듭니다.
- 이 페이지를 HTTP 응답으로 반환하여 클라이언트에게 보여집니다.
따라서 render
함수는 HTML 페이지의 내용을 그리고 사용자에게 보여주는 역할을 합니다. 이를 통해 Django 애플리케이션은 사용자 인터페이스를 동적으로 구성하고, 데이터베이스와 상호작용하여 웹 페이지를 생성할 수 있습니다.
7. 에러 방어하기
1) 중간에 선택 옵션이 없어지는 경우
- ‘휴가 계획이 있나요?’ 질문에 ‘네’, ‘아니오’ 옵션이 있음
- 사용자가 선택하려는 와중에 ‘네’ 옵션이 사라짐
polls/views.py
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'])
except (KeyError, Choice.DoesNotExist):
return render(request, 'polls/detail.html', {'question': question, 'error_message': '선택이 없습니다'})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:index'))
except
구문에Choice.DoesNotExist
에러를 포함함으로써 에러 처리
2) 서버가 정확한 값을 전달해주지 않는 경우
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']) # detail.html 에서 지정한 name // value 값이 들어옴
except (KeyError, Choice.DoesNotExist):
return render(request, 'polls/detail.html', {'question': question, 'error_message': f"선택이 없습니다. id={request.POST['choice']}"})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:index'))
return render(request, 'polls/detail.html', {'question': question, 'error_message': f"선택이 없습니다. id={request.POST['choice']}"})
구문으로 id 값을 같이 출력해주어 에러 원인 확인
3) 같은 DB를 사용하는 두 서버에서 동시에 버튼을 누를 경우
- vote 값이 1만 증가할 것
- 1 증가하는 연산을 서버에서 하는 것이 아니라 DB에서 하도록!
F
함수 사용F
: 값을 DB에서 읽어서 사용해라
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']) # detail.html 에서 지정한 name // value 값이 들어옴
except (KeyError, Choice.DoesNotExist):
return render(request, 'polls/detail.html', {'question': question, 'error_message': f"선택이 없습니다. id={request.POST['choice']}"})
else:
selected_choice.votes = F('votes') + 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:index'))
8. 결과 조회 페이지
1) polls/views.py
에 view 추가
2) polls/urls.py
에 path 추가
3) polls/templates/polls
에 템플릿 추가
4) polls/views.py
에서 이 페이지로 이동하는 코드 추가
1)
def result(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/result.html', {'question': question})
2)
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/vote/', views.vote, name='vote'),
path('<int:question_id>/result/', views.result, name='result'),
]
3)
<h1>{{question.question_text}}</h1><br>
{% for choice in question.choice_set.all %}
<label>
{{choice.choice_text}} -- {{choice.votes}}
</label>
<br>
{% endfor %}
4)
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']) # detail.html 에서 지정한 name // value 값이 들어옴
except (KeyError, Choice.DoesNotExist):
return render(request, 'polls/detail.html', {'question': question, 'error_message': f"선택이 없습니다. id={request.POST['choice']}"})
else:
selected_choice.votes = F('votes') + 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:result', args=(question_id,)))
- vote view에서
vote
버튼을 누르면 result view로 이동하도록! - 주의 인자를 전달하는
args=()
옵션을 추가할 때 꼭 뒤에,
를 넣어주어야 함!
- vote를 누르고 status code = 302
- 302 : Redirect status code
9. admin 페이지 편집
polls/admin.py
편집 페이지 커스터마이징
from django.contrib import admin
from .models import *
admin.site.register(Choice)
class ChoiceInline(admin.TabularInline):
# stackedInline : 수직
# TabularInline : 수평
model = Choice
extra = 3 # 추가로 등록할 옵션의 개수
class QuestionAdmin(admin.ModelAdmin):
# 섹션 나누기, 순서 바꾸기
fieldsets = [
('질문 섹션', {'fields': ['question_text']}),
('생성일', {'fields': ['pub_date'], 'classes': ['collapse']}), # 숨김처리
]
# 읽기 전용 설정
readonly_fields = ['pub_date']
# inline 적용
inlines = [ChoiceInline]
admin.site.register(Question, QuestionAdmin)
목록 페이지 커스터마이징
from django.contrib import admin
from .models import *
# admin.site.register(Choice)
class ChoiceInline(admin.TabularInline):
model = Choice
extra = 3
class QuestionAdmin(admin.ModelAdmin):
# 섹션 나누기, 순서 바꾸기
fieldsets = [
('질문 섹션', {'fields': ['question_text']}),
('생성일', {'fields': ['pub_date'], 'classes': ['collapse']}),
]
# 목록 편집
list_display = ('question_text', 'pub_date', 'was_published_recently')
readonly_fields = ['pub_date']
inlines = [ChoiceInline]
# filter 생성
list_filter = ['pub_date']
# 리스트로 표현할 수 없는 값 검색
search_fields = ['question_text', 'choice__choice_text']
admin.site.register(Question, QuestionAdmin)
옵션으로도 검색 가능