html
을 사용해서 데이터를 잘 보이게 정리해 주는 도구가 템플릿(Templates)
이다. 1) HTML 템플릿
생성
뷰(View)
를 템플릿(Template)
으로 연결해 주기 위해 먼저 템플릿(Template)
을 만들어 주어야 한다.templates/polls
라는 폴더를 추가한 후 index.html
이라는 기본 화면을 만들어 주었다.Django
서버에 접속했을 때 보여 줄 화면을 html을 통해 작업한다.<ul>
<li>{{first question}}</li> <!--넘어오는 파라미터를 표출해 주기 위해서는 {{파라미터의 KEY}}로 작성해 준다.-->
<ul>
2) 뷰(View)
에서 템플릿 연결
views.py
로 간다. 이때 models.py
에 있는 데이터를 화면에서 표출해 줄 것이므로 from .models import *
를 해 준다.템플릿(template)
으로 그려 주기 위해서는 from django.shortcuts import render
을 해 주고 render
메소드를 사용해 주어야 한다.render(request, '템플릿(Template_name).html', 내용)
from .models import *
from django.shortcuts import render
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5] #pub_date를 역순으로 정렬을 해서 다섯 개를 가지고 온다.
context = {'first_question': latest_question_list[0]}
return render(request, 'polls/index.html', context)
.order_by()
의 SQL
- 위의 ORDER BY문을
.SQL
로 출력하면 다음과 같다.SELECT "polls_question"."id" , "polls_question"."question_text" , "polls_question"."pub_date" FROM "polls_question" ORDER BY "polls_question"."pub_date" DESC LIMIT 5
3) 여러 개의 리스트를 표출
latest_question_list[0]
를 전체 리스트를 보낼 수 있도록 수정해 준다.from django.shortcuts import render
from django.http import HttpResponse
from .models import *
# Create your views here.
def index(request):
lastest_question_list = Question.objects.order_by('-pub_date')[:5] #pub_date를 역순으로 정렬을 해서 다섯 개를 가지고 온다.
context = {'questions': lastest_question_list}
return render(request, 'polls/index.html', context)
템플릿
에서 수정을 해 주는데 이때 {{questions[0]}}
이렇게 index를 호출해 주면 오류가 발생한다. 그 원인은 html
에서는 index 호출을 저렇게 하는 것이 아니라 {{questions.0}}
이렇게 해 주어야 한다.html
에서 for문
을 돌려야 한다. for문
과 if-else 조건문
은 {% %}
를 사용하여 작성해야 한다.{% if questions %} <!--if문 표현 방법 닫을 때는 endif를 사용한다.-->
<ul>
{% for question in questions %} <!--for문 표현 방법 닫을 때는 endfor를 사용한다.-->
<li> {{question}} </li>
{% endfor %}
</ul>
{% else %}
<p>no questions</p>
{% endif %}
1) 상세 페이지 HTML 템플릿
생성
detail.html
이라는 HTML 템플릿
을 Templates/polls
폴더에 생성한다.question.chioce_set.all
은 하나가 아니기 때문에 for문
을 써 주어야 하며 기존 Django Shell
과 Visual Studio Code
에서는 choice_set.all()
이라 쓰지만 html
에서는 그렇게 쓸 경우 오류가 발생한다. 꼭 .choice_set.all
이라고 써 주어야 한다.<h1>
{{question.question_text}}
</h1>
<ul>
{% for answer in question.choice_set.all %}
<li>
{{answer.choice_text}}
</li>
{% endfor %}
</ul>
2) 상세 페이지 urls.py
에 추가하여 url
생성
url
이 있어야 해당 페이지로 이동이 가능하기 때문에 urls.py
에 상세 페이지를 추가해 준다.from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'), #이 방식이 question_id를 넘겨 주는 방식 (views.detail에)
path('some_url', views.some_url),
]
3) 상세 페이지 views.py
에 생성 후 표출
question_id
와 같은 유일한 고유 값인 pk로 설정해 준다.def detail(request, question_id):
question = Question.objects.get(pk=question_id) #파라미터로 넘겨 준 question_id와 일치하는 question을 담는다
return render(request, 'polls/detail.html', {'question': question}) #이 question을 question으로 해서 detail.html로 보내 준다.
polls/(question_id)
를 넘겨 주면 그 question_id
에 해당하는 질문과 답변이 나오게 된다.4) 상세 페이지 링크 추가하기 (질문을 클릭하면 상세로 이동하도록)
click
이동 시에는 <a>
태그를 이용하게 된다.index.html
로 수정이 가능하다. {% if questions %}
<ul>
{% for question in questions %}
<li> <a href="/polls/{{question.id}}">{{question.question_text}} </a></li>
{% endfor %}
</ul>
{% else %}
<p>no questions</p>
{% endif %}
html 템플릿
이 바뀌게 된다.{% if questions %}
<ul>
{% for question in questions %}
<li> <a href="{% url 'polls:detail' question.id %}">{{question.question_text}} </a></li> #polls를 지정하지 않고 바로 detail과 연결
{% endfor %}
</ul>
{% else %}
<p>no questions</p>
{% endif %}
404 Page Not Found
는 사용자가 뭔가 요청을 잘못 준 것 같다는 응답을 서버에서 주는 것이다.status code: 404
인 것이다.Django Page
에서 없는 파라미터를 보내 http://127.0.0.1:8000/polls/101/
다음과 같은 창을 만들면 pk
데이터 중 101
이 존재하지 않기 때문에 status code:404
가 나와야 하는데 status code:500
으로 나게 된다.Django
가 알지 못하기 때문에.404
로 가도록 처리해 주어야 한다.views.py
화면에서 404
에러를 직접 raise
해 준다. from django.http import Http404
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})
404
에러는 자주 발생하는 경우이기 때문에 django.shortcuts
에서 get_object_or_404
서비스를 제공한다.404
를 처리할 경우 코드는 다음과 같아진다.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})
Question
과 Choice
를 이용해서 사용자들이 직접 투표할 수 있는 설문조사 폼(forms)
을 만들어 보자html
작업을 진행해야 한다.1) 투표를 위한 Form 생성
detail.html
이 수정되어야 함을 알 수 있다.<form action='#' method="post"> <!--설문 조사를 할 경우 다음 action="#" 여기에 모션이 들어가게 될 것이다.-->
<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>
vote
버튼을 눌러 투표를 하게 되면 403 Forbidden
오류가 발생한다.token
이 없는데 Form
을 제출하려고 해서 생기는 다음과 같은 오류이다. 이 경우 detail.html
에서 token
을 추가해 주면 된다.<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>
action
이 비어 있기 때문이다. 2) 투표 후 url
생성
url
을 추가해 준다.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'),
path('some_url', views.some_url),
]
3) views.py
에 연결될 url의 환경 생성
views.py
에서 question_id
를 받아 준 후 해당 question
의 choice_set
을 가지고 와 준다.choice_set
의 choice
즉, choice_id
를 통해 선택한 choice
값을 담아 둔다.choice 값
을 1을 올려 준 후 이 내용을 DB
에도 반영해야 하기 때문에 .save()
를 써 주게 된다.HttpResponseRedirect(reverse(위치))
는 지정된 위치로 되돌아가게 해 주는 기능이다. 즉, 모든 투표가 완료되면 다시 index.html
목록 화면으로 돌아가도록 구현해 주었다.from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect
from .models import *
from django.http import Http404
from django.urls import reverse
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
selected_choice = question.choice_set.get(pk=request.POST['choice'])
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:index'))
votes
컬럼이 1이 올라간 것을 볼 수 있다. 1) 아무것도 선택하지 않은 경우 혹은 선택할 항목이 삭제된 경우 (KEYERROR
)
try-except
문을 통해서 처리해 줄 수 있다.#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'))
DoesNotExist
오류가 발생하게 된다. 그렇기 때문에 Choice.DoesNotExist에 대해서도 처리를 해 준 것이다. detail.html
을 수정한다.<form action={% url 'polls:vote' question.id %} method="post">
{% csrf_token %}
<h1>
{{question.question_text}}
</h1>
{% if error_message %} <!--만약 error_message가 존재한다면 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>
ERROR
가 발생하는 경우 ERROR 문구
를 띄워 주게 된다.2) 동시 선택이 일어난 경우
selected_choice.votes += 1
을 해 주고 있다. 만약 이 경우 A 서버와 B 서버가 동시에 접속하고 현재 votes가 1이라면 둘을 합쳐서 3이 되어야 하는데 2가 된다. votes += 1
과 같은 일을 서버에서 하는 것이 아니라 DB
에서 처리하게 해 주어야 한다.F
는 DB
에서 바로 가지고 온 값이다.from django.db.models import F
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 = F('votes') + 1 #DB에서 votes를 바로 읽어서 오고 읽어서 온 값에 1을 더한다.
selected_choice.save()
return HttpResponseRedirect(reverse('polls:index'))
detail
을 만든 것과 동일한 순서를 진행한다. 다만 투표 후에 보여 주는 화면이 result.html
화면이 되는 것이다. 1) url
을 생성
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'),
path('<int:question_id>/result/', views.result, name='result'), #result 화면을 추가해 준다.
path('some_url', views.some_url),
]
2) views.py
로 result 생성 및 연결
result
를 생성한다. vote
에 투표 후 result
로 넘어가도록 연결해 주어야 한다. 이때 result
는 파라미터가 있기 때문에 그냥 호출하는 것이 아니라 args=(parameter,)
를 추가해서 호출한다. (꼭 parameter를 호출한 뒤 ,를 붙이는 걸 기억해 둔다.)from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect
from .models import *
from django.http import Http404
from django.urls import reverse
from django.db.models import F
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 = F('votes') + 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:result', args=(question.id,)))
def result(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/result.html', {'question': question})
3) result.html
을 통해 템플릿 생성
votes
투표 수를 보여 주면 될 것 같다. <h1>
{{question.question_text}}
</h1>
<br>
{% for choice in question.choice_set.all %}
<label>
{{choice.choice_text}} -- {{choice.votes}}
</label>
<br>
{% endfor %}
1) Question add 화면
커스터마이징
question_text
와 pub_date
를 입력받는 화면을 커스터마이징 하기 위해 admin.py
의 코드를 수정해 준다.admin.site.register(model_name)
을 했던 것과 동일하게 admin.site.register(model_name, customizing_class_name)
을 호출해 주어야 한다.fieldsets
을 만들어 준다. pub_date
같은 경우 auto_now_add = True(이미 자동으로 추가가 되도록 설정해 둠.)
처리를 해 주었기 때문에 그냥 데이터를 받으면 오류가 발생하여 readonly
처리해 주어야 한다. classes
를 통해 visibility
를 설정해 줄 수 있다.class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
('질문 섹션', {'fields': ['question_text']}),
('생성일', {'fields': ['pub_date'], 'classes': ['collapse']}),
]
readonly_fields = ['pub_date']
admin.site.register(Question, QuestionAdmin)
2) Choice
의 항목도 Question
에서 같이 추가
Question
의 커스터마이징 class에 inlines
를 사용해 준다.inlines
으로 들어갈 부분도 동일하게 class를 생성해 주는데 인자로 layout
을 설정할 수 있다.model
과 extra
를 설정해 줄 수 있는데 이때 extra
는 몇 개를 추가할 수 있게 할 것이냐는 설정이다.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']
inlines = [ChoiceInline]
admin.site.register(Question, QuestionAdmin)
3) 목록
을 리스트 형식으로 수정
admin.py
의 커스터마이징 class에 list_display
를 추가한다.list_display
시에는 목록에 보여 주고 싶은 model
의 column
을 설정해 준다.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]
models.py
를 수정해 주어야 한다. column
으로 정의된 경우 .CharField()나 .DateTimeField()
등 Field를 정의해 줄 때 인자로 verbose_name
을 지정해 주면 된다.@admin.display(description ='')
를 해 주면 되는데 boolean 값의 경우 boolean=True
설정으로 보기 예쁜 형태로 만들어 줄 수 있다.from django.db import models
from django.utils import timezone
import datetime
from django.contrib import admin
#model은 models.Model을 상속받아야 한다.
class Question(models.Model):
question_text = models.CharField(max_length = 200, verbose_name ='질문')
pub_date = models.DateTimeField(auto_now_add = True, verbose_name='생성일')
@admin.display(boolean=True, description='최근 생성(하루 기준)')
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
def __str__(self):
if self.was_published_recently():
new_badge = '[new]'
else:
new_badge = ''
return f'{new_badge} 제목: {self.question_text}, 날짜: {self.pub_date}'
4) 필터(검색)
기능 추가
필터(검색)
기능을 사용하기 위해서는 admin.py
의 커스터마이징 class를 수정해 주어야 한다.list_filter
를 이용하면 된다. Django
에서 제공하는 필터 기능
이다.search_fields
를 사용하면 된다. 이때 넘기는 파라미터 역시 정해 주어야 한다.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]
list_filter = ['pub_date']
search_fields = ['question_text', 'choice__choice_text'] #질문 내용뿐만 아니라 choice 항목도 같이 검색되도록 구현
admin.site.register(Question, QuestionAdmin)
1. *args와 **kwargs
*args
는 함수에서 사용되는파라미터
로 함수가 호출되고여러 개의 인자(argument)
를 입력받는 상황에서 유연성을 높여 준다.def example(*args): for arg in args: print(arg)
**kwargs
는 함수에서 사용되는파라미터
로 함수가 호출될 때여러 개의 키워드 인자(keyword argument)
-dictionary
인자를 받을 수 있는 기능을 제공한다.def example(**kwargs): for key, value in kwargs.items(): print(f"{key} = {value}")
2. CRUD(Create, Read, Update, Delete)
- 소프트웨어가 가져야 하는 가장 기본적인 데이터 처리 기능을 말한다.