
- View & Template
- Form & Customizing
views.py파일에 직접httpresponse를 이용해 뷰를 구성할 수 있지만 보통 HTML 파일을 별도로 생성해서 구현하는 것이 대중적이다. templates/[app_name] 폴더에 html 파일을 만든 뒤views.py에서render함수를 이용해 반환을 해주면 된다. 넘겨진 값을 사용하려면{{ item }}중괄호 두개를 사용한다.
# polls/views.py
def index(request):
# Question 모델의 목록들을 pub_date를 기준으로 내림차순으로 5개 까지 추출
latest_question_list = Question.objects.all().order_by('-pub_date')[:5]
context = {'first_question': latest_question_list[0]}
# render 함수를 이용해 html 파일 연결
return render(request, 'polls/index.html', context)
<!-- templates/polls/index.html -->
<ul>
<!-- 전달받은 인자를 사용하기 위해서는 {{}} 사용 -->
<li>{{first_question}}</li>
</ul>
- 하나의 값이 아닌 반복문 등을 이용해 여러개의 값을 가지고 오려면 템플릿 내에서
{% %}태그를 활용하면 된다.{% for item in items %}로 시작해서 반복문이 끝나는 곳에{% endfor %}를 입력하면 된다.- 조건문을 활용 하기 위해서는
{% 조건식 %}과{% else %},{% endif %}를 사용하면 된다.
{% if question %}
<ul>
{% for question in questions%}
<li>{{question}}</li>
{% endfor %}
</ul>
{% else %}
<p>no questions</p>
{% endif %}
- 상세 페이지를 만들기 위해서는 url에 특정 상세 페이지를 나타내는 인자가 필요하다. 따라서
polls/urls.py에 인자를 받는 path를 추가하고 ,views.py에detail함수를 추가하고detail.html뷰를 생성하면 된다.
urls.py에 인자를 받는 url 설정 ( '<int:question_id>/')urlpatterns = [
path('', views.index, name='index'),
# url 뒤에 인자값으로 정수값 question_id를 받고 이를 views.detail로 연결
path('<int:question_id>/', views.detail, name='detail'),
path('some_url', views.some_url),
]
views.py에 detail 추가def detail(request, question_id):
# Question 객체들 중 pk(primary key)가 입력받은 question_id인 것 찾기
question = Question.objects.get(pk=question_id)
# render 로 뷰 생성 (question의 값들을 인자로 넘겨줌)
return render(request, 'polls/detail.html', {'question': question})
templates/polls/detail.html 추가<!-- 넘겨받은 인자의 필드 출력 -->
<h1>{{question.question_text}}</h1>
<ul>
<!-- 넘겨받은 question으로 연결된 choice 출력, 템플릿에서는 함수뒤에 () 붙이지 않음!! -->
{% for choice in question.choice_set.all %}
<li>{{choice.choice_text}}</li>
{% endfor %}
</ul>
- 링크를 추가하려면
<a>태그를 이용하면 된다.<a>태그 안에urls.py에서 지정한app_name과 url 설정의name을 이용해서 경로를 지정하고, 넘겨줘야 할 인자가 있다면{% url '경로' 인자 %}방식으로 사용하면 된다.
urls.py에서 app_name을 추가하고 detail의 name 확인하기# app_name을 polls 로 지정
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
# 링크로 만드려고 하는 detail의 name 값 detail을 기억
path('<int:question_id>/', views.detail, name='detail'),
path('some_url', views.some_url),
]
templates/polls/index.html에 <a> 태그 추가 {% if questions %}
<ul>
{% for question in questions%}
<!-- urls.py 에서 지정한 detail의 name 속성을 이용해서 경로 지정, 인자로 question의 id값을 넘겨줌-->
<li><a href="{% url 'polls:detail' question.id %}">{{question.question_text}}</a></li>
{% endfor %}
</ul>
{% else %}
<p>no questions</p>
{% endif %}
요청한 페이지가 없음을 나타내는 404 에러는
try-except구문을 사용하거나django.shortcuts라이브러리의get_object_or_404메서드를 사용할 수 있다.
try-exceptdef 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})
get_object_or_404def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
- 폼은 사용자에게 데이터를 입력받는 태그를 말한다. 입력받은 데이터를
urls.py에서 설정한 url에 따라views.py에서 처리한다.
<!-- form 태그 작성, action 속성에 {% url 'url이름' %}를 이용하여 경로를 설정 -->
<form action={% url 'polls:vote' question.id %} method='post'>
<!-- csrf token 공격 방지 -->
{% csrf_token %}
<h1>{{question.question_text}}</h1>
<!-- input 데이터가 없을 때 error_message 출력 -->
{% if error_message %}
<p><strong>{{error_message}}</strong></p>
{% endif %}
<!-- question에 따른 choice_set을 radio 형태로 출력 -->
{% 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>
urls.py 에 vote url 추가# polls/urls.py
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
# name을 vote로 해서 경로 선언
path('<int:question_id>/vote/', views.vote, name='vote'),
]
views.py에 vote 추가def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
# POST로 받은 choice를 selected_choice에 넣음
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
# 만약 POST에서 넘어온 값이 없다면 error_message를 포함하여 다시 render
except (KeyError, Choice.DoesNotExist):
return render(request, 'polls/detail.html', {'question': question, 'error_message': '선택이 없습니다'})
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:index'))
views.py에서 실행되게 하지 말고 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'])
except (KeyError, Choice.DoesNotExist):
return render(request, 'polls/detail.html', {'question': question, 'error_message': '선택이 없습니다'})
# django.db.models의 F 를 이용하여 기존의 votes 개수를 메모리가 아닌 db에서 가져온다.
selected_choice.votes = F('votes') + 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:index'))
urls.py에 result 경로 작성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'),
#result 경로 추가
path('<int:question_id>/result/', views.result, name='result'),
]
<h1>{{question.question_text}}</h1><br>
{% for choice in question.choice_set.all %}
<label>
{{choice.choice_text}} -- {{choice.votes}}
</label>
<br>
{% endfor %}
views.py에 result 를 추가하고 vote의 redirect를 result로 변경# result 추가
def result(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/result.html', {'question': question})
# 마지막 리다이렉트 부분을 polls:result로 변경, result는 question_id가 필요하므로 인자로 같이 전해주게 설정
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': '선택이 없습니다'})
selected_choice.votes = F('votes') + 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:result', args=(question_id,)))
*args : 파이썬 함수의 파라미터로 여러개의 값이 들어올 때 사용하는 파라미터
def example(*args):
sum = 0
for arg in args:
sum += arg
return sum
**kwargs : 파이썬 함수의 파라미터로 여러 개의 키워드 인자를 받을 수 있도록 기능을 제공 (입력이 key-value 와 같은 형태일 때)
def example(**kwargs):
for key, value in kwargs.items():
print(f"{key} = {value}")
Django에서는
admin.py파일에admin.site.register를 통해 admin 페이지를 활용해 모델들에 대한 CRUD 작업을 처리할 수 있었다. 이 작업을 더 사용자 맞춤형으로 만들기 위해admin.py파일을 수정해 보자.
from django.contrib import admin
from .models import *
# Register your models here.
admin.site.register(Choice)
# Choice도 Question 탭에서 같이 수정하기 위해 Inline 설정
class ChoiceInline(admin.TabularInline):
model = Choice
# 추가옵션 개수
extra = 3
# 커스터마이징 된 QuestionAdmin 페이지를 구성
class QuestionAdmin(admin.ModelAdmin):
# Question의 필드를 설정, 순서를 바꾸면 바꾼 순서대로 표시됨.
fieldsets = [
('질문 섹션', {'fields': ['question_text']}),
# 생성일 숨김(collapse)
('생성일', {'fields': ['pub_date'], 'classes': ['collapse']}),
]
# 더 깔끔하게 질문과 날짜를 출력
list_display = ['question_text', 'pub_date', 'was_published_recently']
# auto_now_add=True 때문에 오류가 생길 수 있음 따라서 pub_date 필드를 수정 불가능하게 설정
readonly_fields = ['pub_date']
# Question에 따른 Choice 변경할 때 필요한 inlines를 같이 작성
inlines = [ChoiceInline]
# 생성일을 기준으로 필터 추가
list_filter = ['pub_date']
# Question과 Choice에 대한 검색 기능 추가
search_fields = ['question_text', 'choice__choice_text']
# Question과 함께 커스터마이징한 QuestionAdmin도 같이 등록
admin.site.register(Question, QuestionAdmin)
- Question 페이지의 레이블을 변경하고 싶다면
models.py에서Question모델의 필드와 메서드에verbose를 설정하거나@admin.display()를 달아줘야 한다.
# models.py의 Question 클래스
# 각 필드의 속성으로 verbose_name을 지정해서 레이블 명을 바꿀 수 있다.
question_text = models.CharField(max_length=200, verbose_name='질문')
pub_date = models.DateTimeField(auto_now_add=True, verbose_name='생성일')
# 메서드 위에 admin.display() 애너테이션 작성
@admin.display(boolean=True, description='최근생성(하루기준)')
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

django의 문법이 익숙치 않아 특수기호 여러 개가 중첩될 때 헷갈릴 때가 많았다.