Django – Form và các View có sẵn

4.9/5 - (45 votes)

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 GETPOST 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 namechoiceThuộ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()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à ListViewDetailView, 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 questionlatest_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.

4.5 4 votes
Article Rating
Subscribe
Thông báo cho tôi qua email khi
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

15 Comments
Inline Feedbacks
View all comments
hoan_ph
hoan_ph
7 năm trước

Tác gỉa cho em hỏi chút về câu lệnh:

selected_choice = question.choice_set_get(pk=request.POST['choice'])

Em làm theo hướng dẫn và đúng như trên nhưng toàn báo lỗi.
Mong tác gỉa có thể hướng dẫn thêm

hoan_ph
hoan_ph
7 năm trước
Reply to  Phở Code

1. Nếu dùng đoạn code đó thì báo: ‘Question’ object has no attribute ‘choice_set_get’
2. Nếu dùng

selected_choice = question.choice_set(pk=request.POST['choice'])

thì có lỗi:
TypeError at /polls/3/vote/
‘RelatedManager’ object is not callable

Mong tác gỉa giups đỡ. Xin cảm ơn

hoan_ph
hoan_ph
7 năm trước
Reply to  Phở Code

Mình đã check lại, ko có gì khác với các code của tác gỉa cả. Nhưng vẫn ko ok!
Help me.
cho hỏi cái

choice_set_get()

phải tự code hay thế nào nhỉ?

Ngoc Duy
Ngoc Duy
7 năm trước
Reply to  hoan_ph

ben ban co khoa hoc chi tiet ve django khong?

thanh
thanh
4 năm trước
Reply to  hoan_ph

choice_set.get(..)

hoan_ph
hoan_ph
7 năm trước

Cảm ơn tác giả mình sẽ xem lại xem sao 😉

Hien nguyen
Hien nguyen
7 năm trước
Reply to  hoan_ph

bạn sửa lại như này nhé. tại cú pháp nó thay đổi ở phiên bản Django mới ấy mà.
selected_choice = question.choice_set.get(pk=request.POST[‘choice’])

hoan_ph
hoan_ph
7 năm trước

Cảm ơn bạn nhé ;). Mình đã sửa như bạn và thành công

Huy
Huy
7 năm trước
class DetailView(generic.DetailView):
    model = Question 
    template_name = 'polls/detail.html'
 
class ResultsView(generic.DetailView):
    model = Question
    template_name = 'polls/results.html'

Bạn ơi cho mình hỏi là phần này mình không điền biến

context_object_name = 'xxx'

Vậy là bên template nó tự hiểu là biến question luôn hả ?.
Mình có thể đổi tên nó là một biến khác được ko?

Cám ơn.

Huy
Huy
7 năm trước
Reply to  Huy

Cho mình hỏi thêm là : Khi mình không khai báo biến mà trong template của detail.html và results.html lại hiểu được đối tượng question.
Mình có đọc ở trên mà ko hiểu cho lắm

SOn
SOn
2 năm trước

Bài viết rất hữu ích , tuy nhiên có 1 vài lỗi “chính tả” ..
question.choice_set_get –> phải là question.choice_set.get