Trong phần này chúng ta sẽ viết form cho các hàm view, sau đó tìm hiểu về hệ thống view có sẵn của Django.
Tạo form
Chúng ta sẽ viết lại template detail
.
<h1>{{ question.question_text }}</h1> {% if error_messsage %} <strong>{{ error_messsage }}</strong> {% endif %} <form action="{% url 'polls:vote' question.id %}" method="post"> {% csrf_token %} {% 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> {% endfor %} <input type="submit" value="Vote" /> </form>
Chúng ta tạo một form cho phép người dùng vote các câu trả lời.
{% csrf_token %}
Bạn chỉ cần biết câu lệnh trên sẽ giúp website của chúng ta chống lại kiểu tấn công CSRF là đủ, nếu muốn bạn có thể tìm hiểu thêm trên mạng, ở đây mình không đi sâu.
<form action="{% url 'polls:vote' question.id %}" method="post"> ... </form>
Chúng ta tạo thẻ form và đưa url dẫn đến view vote()
với tham số question.id
là ID của đối tượng Choice trong CSDL,
phương thức gửi lên là phương thức POST. Nếu bạn chưa biết sự khác nhau giữa GET và POST thì cứ nhớ là nếu gửi dữ liệu lên server mà có thay đổi CSDL thì phải dùng phương thức POST.
{% 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> {% endfor %}
Chúng ta hiển thị các radio button, mỗi radio button hiển thị một câu trả lời (Choice
) cho từng Question. Các radio button sẽ được gán thuộc tính name
là choice
.
Thuộc tính forloop.counter
là số thứ tự của vòng lặp giống như khi bạn viết for(int i...)
trong C++ vậy.
Tiếp theo chúng ta sẽ viết lại hàm view vote()
xử lý từng thao tác vote.
from django.shortcuts import get_object_or_404, render from django.http import HttpResponse, Http404, HttpResponseRedirect from django.core.urlresolvers import reverse from .models import Question, Choice 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': "You didn't select a choice.", }) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
Hàm view vote()
sẽ xử lý việc vote các câu trả lời.
question = get_object_or_404(Question, pk=question_id)
Khác với phần trước, chúng ta phải dùng khối lệnh try...except
để giải phóng một lỗi 404, ở đây chúng ta có thể dùng hàm get_object_or_404()
, hàm này sẽ chạy phần try
và sẽ tự giải phóng một lỗi 404 luôn cho chúng ta nếu có.
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': "You didn't select a choice.", })
Khi một hàm view được gọi, chúng ta có thể lấy các dữ liệu được gửi kèm theo qua đối tượng request
, tùy vào phương thức được gọi và tên biến được đặt mà chúng ta lấy ra cho phù hợp, nếu không sẽ có lỗi ngoại lệ xảy ra, chẳng hạn như KeyError
là lỗi sai tên biến, có thể tên biến là choice
mà bạn gửi lên theo url là choiec
hay chce
…, lỗi DoesNotExist
là lỗi đối tượng không tồn tại…
else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
Nếu không có lỗi xảy ra thì chúng ta tăng thuộc tính votes trong đối tượng Choice
lên 1 và lưu vào CSDL. Sau đó trả về một đối tượng HttpResponseRedirect
chứ không dùng đối tượng HttpResponse
như thường để tránh các trường hợp người dùng nhấn nút back trên trình duyệt (bạn vẫn có thể dùng HttpResponse
nếu muốn), HttpResponseRedirect
nhận một đối tượng url thông qua hàm reverse()
, hàm này sẽ trả về một đường dẫn có dạng như /polls/1/results/.
Tiếp theo chúng ta sẽ viết lại hàm view result()
.
from django.shortcuts import get_object_or_404, render def results(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/results.html', {'question': question})
View results()
sẽ hiển thị số lượng vote của mỗi câu trả lời Choice
.
Hàm này khá đơn giản, chúng ta chỉ lấy về đối tượng Question
và gửi đến template results.html
ở dưới để hiển thị.
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li> {{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes | pluralize }} </li> {% endfor %} </ul> <a href="{% url 'polls:detail' question.id %}">Vote again?</a>
Đoạn code HTML trên cũng rất đơn giản, chúng ta chỉ lấy từng đối tượng Choice và in tên cùng với số lượng votes của chúng ra, ngoài ra chúng ta còn thêm một đường dẫn về trang detail
. Tham số pluralize
sẽ thêm kí tự ‘s‘ để biểu thị số nhiều trong tiếng Anh.
Các lớp View của Django
Các hàm view mà chúng ta đã viết như index()
, detail()
và result()
rất đơn giản… và rất thừa.
Thường thì các trang web được viết ra đều làm một công việc gần như là tương tự nhau, đó là lấy dữ liệu từ CSDL dựa vào lệnh request được gửi lên thông qua URL, sau đó tải template và gắn dữ liệu đó vào template rồi trả về cho người dùng. Vì tính chất lặp đi lặp lại như thế nên Django cung cấp cho chúng ta hệ thống View có sẵn.
Các View này là các lớp của Django, mục đích chính của các lớp này là giúp bạn đỡ phải viết code Python nhiều, mặc dù thực ra thì cũng có nhiều trường hợp mà việc tự viết các View sẽ tốt hơn là dùng View có sẵn, cũng giống như việc học lập trình vậy, bạn phải biết viết các thuật toán trước khi sử dụng thư viện có sẵn.
Chúng ta sẽ sửa lại ứng dụng polls
để sử dụng các lớp View này.
Đầu tiên chúng ta phải sửa lại các đường dẫn url một tí.
from django.conf.urls import url from . import views app_name = "polls" urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'), url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'), url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ]
Tên 3 hàm view được thay đổi và tên biến tham số gửi lên cũng được thay đổi từ question_id
thành pk
.
Tiếp theo chúng ta sẽ viết lại các hàm view mới.
from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from django.views import generic from .models import Choice, Question class IndexView(generic.ListView): template_name = 'polls/index.html' context_object_name = 'latest_question_list' def get_queryset(self): return Question.objects.order_by('-pub_date')[:5] class DetailView(generic.DetailView): model = Question template_name = 'polls/detail.html' class ResultsView(generic.DetailView): model = Question template_name = 'polls/results.html' 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': "You didn't select a choice.", }) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
Chúng ta sẽ sử dụng các lớp View có sẵn của Django ngoại trừ hàm vote()
là giữ nguyên.
from django.views import generic
Đầu tiên chúng ta import module generic
.
class IndexView(generic.ListView): ... class DetailView(generic.DetailView): ...
Chúng ta sẽ sử dụng 2 loại view là ListView
và DetailView,
ListView
lưu trữ danh sách các đối tượng, DetailView
lưu thông tin về một đối tượng cụ thể.
model = Question
Mỗi lớp View cần biết về mô hình dữ liệu mà nó sẽ lưu trữ thông qua thuộc tính model
. Khi đã biết loại model mà mình sẽ dùng, các view này tự động “nghĩ” rằng khóa chính trong CSDL có tên là pk
, do đó trong các đối tượng url chúng ta gửi lên tham số có tên là pk
.
template_name = 'polls/detail.html'
Mặc định các lớp View này cũng “nghĩ” rằng template sẽ được dùng có dạng <app_name>/<model_name>_detail.html
(hoặc <app_name>/<model_name>_list.html
), nếu không có file nào như vậy tồn tại thì Django sẽ tự động tạo các file này và sử dụng, chúng ta có thể “bảo” Django sử dụng template do chúng ta tự viết bằng cách gán vào thuộc tính template_name, như thế Django sẽ không tạo template cho nó nữa.
class IndexView(generic.ListView): ... context_object_name = 'latest_question_list'
Trong hàm detail()
cũ, chúng ta tự khai báo các biến question
và latest_question_list
để dùng trong template, mặc định các biến này đã có sẵn trong các lớp DetailView.
Tuy nhiên đối với hàm ListView
thì biến mặc định lại có tên là question_list
, nên để Django dùng tên do chúng ta tự đặt thì chúng ta phải gắn tên đó vào thuộc tính context_object_name
hoặc bạn phải dùng tên do Django đặt trong các file template.