Django – View và Template

4.1/5 - (74 votes)

Trong phần này chúng ta sẽ tìm hiểu về View và Template trong Django.

Trong Django thì một View là một hàm/phương thức làm một công việc cụ thể nào đó, một View  thường đi kèm với một Template (chúng ta sẽ tìm hiểu ở dưới). Ví dụ, một ứng dụng Blog có một số View sau:

  • Home – hiển thị các bài viết mới nhất.
  • Entry – hiển thị bài viết nào đó.
  • Archive – Lưu trữ các bài viết theo năm/tháng.
  • Comment – xử lý việc đăng bình luận của độc giả.

Trong ứng dụng “Poll” mà chúng ta đã viết từ đầu series tới bây giờ, chúng ta sẽ xây dựng các View sau:

  • Index – Hiển thị các câu hỏi mới.
  • Detail – Hiển thị một câu hỏi nhất định nào đó và đưa các câu trả lời để người dùng chọn.
  • Result – Hiển thị kết quả bầu chọn của người dùng.
  • Vote  – Xử lý việc trả lời của người dùng.

Trong Django, một trang web được tạo ra bởi các hàm View, Django sẽ chọn View nào tùy thuộc vào URL mà chúng ta đã thiết lập. Có thể bạn đã từng thấy những đường dẫn URL nhìn rất “gớm” như “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B” do website tự tạo ra, Django cho phép chúng ta tạo những đường dẫn dễ nhìn hơn, chẳng hạn như đường dẫn bài viết mà bạn đang đọc 🙂

Để từ một đường dẫn URL đến một View thì Django sử dụng khái niệm URLConf, đây là một module Python của Django làm nhiệm vụ phân tích đường dẫn và chuyển đến một hàm View nhất định.

Tạo View

Chúng ta sẽ viết thêm một số hàm View mới.

def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

Chúng ta viết thêm 3 view mới là detail(), results()vote().

Tiếp theo chúng ta tạo thêm các URL trỏ đến từng view này.

from django.conf.urls import url

from . import views

urlpatterns = [
    # ex: /polls/
    url(r'^$', views.index, name='index'),
    # ex: /polls/5/
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    # ex: /polls/5/results/
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

Bạn có thể gõ trên thanh địa chỉ trình duyệt đến localhost:8000/polls/34/, Django sẽ gọi đến hàm detail() và hiển thị chuỗi text tương ứng, bạn có thể thử với đường dẫn polls/34/results/polls/34/vote/ nữa.

Khi bạn gõ địa chỉ lên thanh URL của trình duyệt, Django sẽ đọc biến urlpatterns trong file mysite.urls vì mặc định file này được trỏ tới trong biến ROOT_URLCONF trong file mysite/settings.py, các đối tượng url sẽ được đọc dần dần từ trên xuống dưới cho đến khi có một đường dẫn vừa khít với URL mà bạn nhập vào. Khi tìm thấy localhost:8000/polls vừa khít với '^polls/', Django sẽ cắt đoạn phía sau URL ra (vd như /polls/34/) và gửi đến file polls/urls.py để tiếp tục quá trình tìm kiếm, tại đây đoạn tiếp theo vừa khít với chuỗi Regex r'^(?P<question_id>[0-9]+)/$', mà chuỗi Regex này trỏ đến phương thức views.detail() nên Django sẽ gọi phương thức này với tham số như sau:

detail(request=<HttpRequest object>, question_id='34')

Chuỗi Regex (?P<question_id>[0-9]+) cho Django biết có một chuỗi con trong đoạn URL có dạng một con số (có 1 hoặc nhiều chữ số) và biến được truyền vào phương thức detail() có tên là question_id. Với sức mạnh của Regex thì bạn có thể tạo mẫu URL như thế nào cũng được, tất nhiên là tốt nhất nên làm cho nó ngắn gọn và dễ đọc.

Template

Các hàm View trả về một trong 2 thứ: hoặc là trả về một đối tượng HttpReponse chứa nội dung HTML để hiển thị lên trình duyệt, hoặc là một lỗi exception 404. Bạn muốn trả về thứ gì là tùy bạn thiết kế, có thể là đọc dữ liệu từ CSDL và dùng template để hiển thị dữ liệu, tạo file PDF, hiển thị nội dung XML, tạo file ZIP… Django không quan tâm bạn làm gì mà chỉ cần cái đối tượng HttpResponse hoặc exception cuối cùng mà bạn tạo ra thôi.

Chúng ta sẽ sửa lại hàm index() một tí.

from django.http import HttpResponse

from .models import Question

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)

Đoạn code trên rất đơn giản, chỉ là lấy ra 5 đối tượng Question được thêm vào gần đây nhất, sau đó lấy thuộc tính question_text đưa vào biến output rồi trả về thôi.

Bạn có thể vào địa chỉ localhost:8000/polls/ để xem kết quả và thấy là nếu chỉ in text ra như thế này thì giao diện quá xấu, mà nếu muốn thay đổi giao diện thì bạn phải điều chỉnh lại trong hàm index() như thêm các thẻ h1, h2, thêm các mã CSS… như thế rất bất tiện, thế nên Django cung cấp cho chúng ta hệ thống Template, chúng ta có thể viết phần giao diện ở Template và dùng nó để hiển thị dữ liệu của các View cho đẹp mắt hơn, vậy tóm lại mục đích chính của View chính là kết nối giữa Template và Model.

Đầu tiên chúng ta tạo một thư mục có tên là templates trong thư mục polls, Django sẽ tự động tìm các file template trong thư mục này. Ở đây bạn tạo thêm một thư mục khác nữa tên là polls, trong thư mục này bạn tạo một file tên index.html.

{% if latest_question_list %}
    <ul>
       {% for question in latest_question_list %}
       <li>
             <a href="/polls/{{question.id}}/">
                {{question.question_text}} 
             </a>
       <li>
       {% endfor %}
    </ul>
{% else %}
   <p>No polls are available.</p>
{% endif %}

Trong file index.html chúng ta viết như trên.

Trình duyệt chỉ hiểu code HTML chứ không hiểu code Python, để có thể sử dụng code Python thì Django cung cấp cho chúng ta các thẻ template, thẻ template bắt đầu và kết thúc bằng cặp kí tự {% %} hoặc {{ }}, các câu lệnh Python nằm trong cặp dấu {% %}, còn các biến thì nằm trong cặp {{ }}.

from django.shortcuts import render
from django.http import HttpResponse
from django.template import loader

from .models import Question 

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {
        'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))

Chúng ta cũng sửa lại đoạn code trong hàm index() để sử dụng template.

from django.template import loader

Để có thể dùng template thì trước hết chúng ta import module loader trong gói django.template.

template = loader.get_template('polls/index.html')

Để dùng template thì chúng ta dùng phương thức get_template(), mặc định Django đã biết là sẽ tìm template trong thư mục polls/templates/ nên chúng ta chỉ cần đưa vào đường dẫn đến polls/index.html là đủ.

return HttpResponse(template.render(context, request))

Sau đó chúng ta gọi đến phương thức template.render() để tạo nội dung HTML có sử dụng template. Tham số đầu tiên là nội dung HTML trả về, tham số thứ 2 là đối tượng request được gửi đến.

from django.shortcuts import render

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {
        'latest_question_list': latest_question_list,
    }
    return render(request, 'polls/index.html', context)

Ngoài ra bạn cũng có thể dùng hàm render() để trả về một đối tượng HttpResponse một cách trực tiếp luôn, với tham số thứ nhất là đối tượng request, tham số thứ 2 là đường dẫn đến file template, tham số thứ 3 là nội dung HTML trả về.

Giải phóng lỗi 404

from django.shortcuts import render
from django.http import HttpResponse, Http404
from django.template import loader

from .models import Question

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})

Chúng ta sẽ giải phóng lỗi exception 404 nếu đường dẫn ID sai trong hàm detail().

from django.http import Http404

Đầu tiên chúng ta import module Http404

except Question.DoesNotExist:
        raise Http404("Question does not exist")

Phương thức get() sẽ trả về một exception DoesNotExist nếu không tìm thấy bản ghi nào trong list, tại đây chúng ta giải phóng một đối tượng Http404. Còn nếu không có gì thì chúng ta trả về nội dung HTML như thường.

<h1>{{ question.question_text }}</h1>
<ul>
    {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_set }}</li>
    {% endfor %}
</ul>

Trên đây là đoạn code template cho hàm view detail().

Bạn có thể thử nhập đường dẫn không có thật chẳng hạn như localhost:8000/polls/123456 để thấy Django trả về trang 404.

URL động

Bây giờ có một vấn đề như thế này, trong các file template như index.html, các đường link chúng ta tham chiếu đến trong các thẻ <a> là do chúng ta tự viết, chẳng hạn như href="/polls/..." ở dưới đây:

<li>
    <a href="/polls/{{ question.id }}/">
        {{ question.question_text }}
    </a>
</li>

Giả sử trong trang web của chúng ta bắt buộc phải có 100 thẻ <a> như thế, việc code từng đường dẫn là cực kỳ mệt mỏi, và khi muốn thay đổi chẳng hạn như chúng ta muốn đổi URL từ localhost:8000/polls/1/ thành localhost:8000/polls/details/1/ thì chúng ta phải đổi bằng tay hết 100 thẻ <a> đó, thế thì chỉ có chết 🙂

Do đó chúng ta có thể tham chiếu đến các đối tượng url đã định nghĩa trong file urls.py của ứng dụng. Khi chúng ta tạo các đối tượng url trong file urls.py, tham số thứ 3 là name, tham số này do chúng ta tự đặt, và Django cho phép chúng ta tham chiếu đến chúng trong các file template.

<a href="{% url 'detail' question.id %}">
    {{ question.question_text }}
</a>

Bằng cách này chúng ta có thể tham chiếu đến đối tượng url trong file urls.py, và khi nào cần thay đổi URL mới thì chúng ta chỉ cần thay đổi trong file urls.py là được:

...
url(r'^details/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

Đặt namespace cho URL

Khi dùng đến URL động thì lại phát sinh một vấn đề nữa, mặc định thì Django tự động tìm các file template bên trong thư mục template, vậy thì giả sử khi chúng ta có thêm nhiều ứng dụng khác ngoài polls, chẳng hạn như một ứng dụng blog, trong đó cũng có hàm view detail(), và hàm view này cũng sử dụng một template tên là detail.html, vậy thì khi đó Django sẽ gắn template của ứng dụng polls vào view detail() của ứng dụng blog,  như thế sẽ báo lỗi vì ứng dụng blog sẽ không có các biến giống như polls.

Để giải quyết vấn đề này, chúng ta sẽ đặt namespace cho các biến url, nếu bạn chưa biết namespace là gì thì cứ nghĩ rằng chúng cũng giống như một cách gộp nhóm những thứ giống nhau lại với nhau thôi, nếu muốn tìm hiểu thêm thì bạn có thể search trên mạng, mình không đi sâu ở đây.

from django.conf.urls import url
from . import views

app_name = "polls"
urlpatterns = [
    # ex: /polls/
    url(r'^$', views.index, name='index'),
    # ex: /polls/detils/5/
    #url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    url(r'^details/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    # ex: /polls/5/results/ 
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote')
]

Để đặt tên namespace cho các đối tượng url thì chúng ta chỉ cần đặt giá trị cho biến app_name trong file urls.py là được.

<a href="{% url 'polls:detail' question.id %}">
    {{ question.question_text }}
</a>

Trong file template, vd như index.html thì chúng ta chỉ cần viết đủ tên <namespace>:<tên_biến_url> là xong, vd url có name là detail thì viết đầy đủ thành polls:detail.

4.8 8 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.

7 Comments
Inline Feedbacks
View all comments
Do Quyet
Do Quyet
7 năm trước

em chào admin.trước tiên em cảm ơn ad vì những hướng dẫn vô cùng dễ hiểu này,tuy nhiên em vẫn còn 1 số thắc mắc như là: ” làm sao để đưa vote sang 1 bên như ad áp dụng ở trang phocode.com ” mong ad giải đáp thắc mắc giúp em.cảm ơn ad

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

ôi.em tưởng bác dùng Django chứ :V

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

bác có thể giúp em cái thiết kế csdl được không.đây là lần đầu tiếp xúc với lập trình nên cái này em hơi bối dối. ?

Van Phuong
Van Phuong
3 năm trước

Xin chào admin ạ. Ad cho em hỏi là mình có thể gắn một url độc lập trong href của thẻ a trong file .html mà bỏ qua chuyển hướng về dạng http://localhost/… được không ạ. Ví dụ em muốn gắn link sang một website khác ví dụ <a href=” ‘https://google.com/’ + {{post.link}}’></a> chẳng hạn ạ? Em xin cảm ơn.