Phần 4 – Quản lý static files. Customizing Django’s admin site
- Note lại từ tài liệu học django 3.0 https://docs.djangoproject.com/en/3.0
Introducing automated testing
Automated testing là gì ?
- Kiểm tra code
- Kiểm tra chi tiết nhỏ
- Kiểm tra hoạt động tổng thể của phần mềm
- Thực hiện việc kiểm tra một cách tự động
Tại sao cần tests ?
Nếu việc tạo ứng dụng thăm dò ý kiến(app polls đã viết ở phần trước) là phần cuối cùng của lập trình Django sẽ làm thì không cần viết tests. Nhưng tính năng của ứng dụng không bao giờ dừng thay đổi.
Nghịch lý được chứng minh là đúng: viết test sẽ mất thêm thời gian, nhưng tests sẽ giúp tiết kiệm thời gian.
Đến 1 thời điểm nào đó, ứng dụng sẽ phức tạp tới mức mà chỉ một thay đổi nhỏ có thể gây hậu quả không ngờ tới. Việc kiểm tra thực tế sẽ gây lãng phí thời gian không kiểm soát.
Nên tests tự động sẽ đặc biệt đúng vì nó thực hiện thay developer và giúp xác định code gây ra hành vi không mong muốn.
Thật khó khăn khi cứ phải tách mình ra khỏi quá trình lập trình năng suất và sáng tạo. Và phải dành thời gian cho công việc viết test 1 cách cực kỳ vô duyên và nhàm chán, nhất là khi đã biết rằng code đang hoạt động rất tốt. Cực hình hơn nữa là trên thực tế spec của các chức năng thay đổi chóng mặt dẫn đến tests cũng phải viết lại (trên thế giới chỉ 5% cty có đội ngũ đủ khả năng viết tests trước khi code tính năng, nguyên nhân bởi vì design của ứng dụng phải cực kỳ hoàn thiện và linh hoạt, design phải cực kỳ hợp lý, chính xác, đầy đủ, chi tiết không bị thay đổi vô tội vạ).
Tuy nhiên, về tổng thể thì viết tests sẽ hoàn thiện hơn là test thủ công hoặc có gắng để tìm ra nguyên nhân của một vấn đề phát sinh.
Giữa những điều tốt hãy chọn điều tố nhất – Giữa những điều tối tệ hãy chọn điều ít tồi tệ nhất :v.
Các test không chỉ xác định vấn đề, mà còn ngăn chặn chúng
Nếu không có test, hành vi dự định của ứng dụng trơ nên lờ mờ, ngay cả khi code do chính bạn viết ra, không tin hãy đọc lại code của mình một vài năm trước, tin tôi đi đó là một điều kinh khủng, bạn sẽ cố chọc sâu vào code của chính mình chỉ để tìm hiểu xem nó đang làm gì. Tests thay đổi điều này, nó làm “trong sáng ” những đoạn code ngay từ bên trong bản chất, và khi phát hiện ra lỗi có thể tập trung vào đó, ngay cả khi bạn không thấy có lỗi.
Test làm code hấn dẫn hơn
Bạn phải refactor code cho đến khi pass được test, đem so sánh với đoạn code khi chưa kết hợp với test là một cách giúp bạn code rõ ràng hơn.
Code mà không có test sẽ “đồng nghĩa” với việc phá vỡ thiết kế. Dù code bạn có code hay đến mấy thì các developer khác sẽ chỉ nghiêm túc tiếp cận khi họ thấy được tests của ứng dụng.
Tests giúp làm việc teamwork
ứng dụng phức tạp sẽ được duy trì bởi team các developer, vì vậy phải viết test tốt mới lập trình django được.
Chiến lược testing cơ bản
Một số lập trình viên tuân theo quy trình Test-driven development, viết test trước cả khi viết code, tuy có vẻ trái ngược với trực giác, nhưng thực tế nó tương tự những gì hầu hết mọi người thường làm đó laf: mô tả vấn đề, và viết code để giải quyết vấn đề. của phần mềm.
Phổ biến hơn là người mới tiếp cận với testing sẽ viết 1 đống code trước khi quyết định rằng có nên có một số tests, Có lẽ viết test càng sớm cang tốt. Cho dù đã code cả nghìn dòng, hiệu quả nhất là hãy ngay lập tức bắt đầu viết test đầu tiên khi thực hiện 1 thay đổi mới, hoặc một tính năng mới, hoặc sửa lỗi.
Đoạn trên này đọc thấy hay vãi nên đọc cho ngấm
Writing our first test
Xác định 1 bug: method Question.was_published_recently()
đúng nếu xác định Question’s pub_date là ở quá khứ, tuy nhiên nếu là ở tương lai (case này chắc chắn không thể xảy ra).
Hệ thống test tự động kiểm tra các file có tên bắt đầu = test:
tests.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token keyword">import</span> datetime <span class="token keyword">from</span> django<span class="token punctuation">.</span>test <span class="token keyword">import</span> TestCase <span class="token keyword">from</span> django<span class="token punctuation">.</span>utils <span class="token keyword">import</span> timezone <span class="token keyword">from</span> <span class="token punctuation">.</span>models <span class="token keyword">import</span> Question <span class="token keyword">class</span> <span class="token class-name">QuestionModelTests</span><span class="token punctuation">(</span>TestCase<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">def</span> <span class="token function">test_was_published_recently_with_future_question</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" was_published_recently() returns False for questions whose pub_date is in the future. """</span> time <span class="token operator">=</span> timezone<span class="token punctuation">.</span>now<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> datetime<span class="token punctuation">.</span>timedelta<span class="token punctuation">(</span>days<span class="token operator">=</span><span class="token number">30</span><span class="token punctuation">)</span> future_question <span class="token operator">=</span> Question<span class="token punctuation">(</span>pub_date<span class="token operator">=</span>time<span class="token punctuation">)</span> self<span class="token punctuation">.</span>assertIs<span class="token punctuation">(</span>future_question<span class="token punctuation">.</span>was_published_recently<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">False</span><span class="token punctuation">)</span> |
Chạy test: python manage.py test polls
Điều gì đã xảy ra ?
- Thằng manage.py test polls đã xem xét các tests trong app
polls
- Nó tìm thấy subclass của
django.test.TestCase
- Nó đã tạo 1 csdl đặc biệt cho mục đích test
- Một Question instance đã được tạo với trường pub_date ở tương lai.
- Sử dụng assertIs() để phát hiện
was_published_recently()
trả vềTrue
, mặc dù ta mong muốn làFalse
Fix Bug sửa lại polls/models.py
:
1 2 3 4 | <span class="token keyword">def</span> <span class="token function">was_published_recently</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> now <span class="token operator">=</span> timezone<span class="token punctuation">.</span>now<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">return</span> now <span class="token operator">-</span> datetime<span class="token punctuation">.</span>timedelta<span class="token punctuation">(</span>days<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator"><=</span> self<span class="token punctuation">.</span>pub_date <span class="token operator"><=</span> now |
=> Chạy test: python manage.py test polls
Full test case
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token keyword">def</span> <span class="token function">test_was_published_recently_with_old_question</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" was_published_recently() returns False for questions whose pub_date is older than 1 day. """</span> time <span class="token operator">=</span> timezone<span class="token punctuation">.</span>now<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> datetime<span class="token punctuation">.</span>timedelta<span class="token punctuation">(</span>days<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">,</span> seconds<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">)</span> old_question <span class="token operator">=</span> Question<span class="token punctuation">(</span>pub_date<span class="token operator">=</span>time<span class="token punctuation">)</span> self<span class="token punctuation">.</span>assertIs<span class="token punctuation">(</span>old_question<span class="token punctuation">.</span>was_published_recently<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">False</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">test_was_published_recently_with_recent_question</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" was_published_recently() returns True for questions whose pub_date is within the last day. """</span> time <span class="token operator">=</span> timezone<span class="token punctuation">.</span>now<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> datetime<span class="token punctuation">.</span>timedelta<span class="token punctuation">(</span>hours<span class="token operator">=</span><span class="token number">23</span><span class="token punctuation">,</span> minutes<span class="token operator">=</span><span class="token number">59</span><span class="token punctuation">,</span> seconds<span class="token operator">=</span><span class="token number">59</span><span class="token punctuation">)</span> recent_question <span class="token operator">=</span> Question<span class="token punctuation">(</span>pub_date<span class="token operator">=</span>time<span class="token punctuation">)</span> self<span class="token punctuation">.</span>assertIs<span class="token punctuation">(</span>recent_question<span class="token punctuation">.</span>was_published_recently<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">True</span><span class="token punctuation">)</span> |
Tiếp tục test cả view
Một thay đổi: View đang publish toàn bộ quention nghĩa là bao gồm cả những bản ghi ở tương lai. Và khác với ví dụ bên trên ta sẽ thay đổi view trước khi thay đổi test.
The Django test client django cung cấp test Client giúp mô phỏng hành vi người dùng với code ở múc view level. có thể sử dụng tests.py hoặc:
shell
: python manage.py shell
:
1 2 3 | <span class="token operator">>></span><span class="token operator">></span> from django.test.utils <span class="token function">import</span> setup_test_environment <span class="token operator">>></span><span class="token operator">></span> setup_test_environment<span class="token punctuation">(</span><span class="token punctuation">)</span> |
Phương pháp này sẽ chạy với csdl hiện có (không tạo csdl test). Kết quả phụ thuộc vào giờ hệ trống trên máy, nếu TIME_ZONE trong settings.py là đúng.
1 2 3 4 | <span class="token operator">>></span><span class="token operator">></span> from django.test <span class="token function">import</span> Client <span class="token operator">>></span><span class="token operator">></span> <span class="token comment"># create an instance of the client for our use</span> <span class="token operator">>></span><span class="token operator">></span> client <span class="token operator">=</span> Client<span class="token punctuation">(</span><span class="token punctuation">)</span> |
Thay đổi quy vấn view polls/views.py
:
1 2 3 4 5 6 7 8 9 | <span class="token keyword">def</span> <span class="token function">get_queryset</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Return the last five published questions (not including those set to be published in the future). """</span> <span class="token keyword">return</span> Question<span class="token punctuation">.</span>objects<span class="token punctuation">.</span><span class="token builtin">filter</span><span class="token punctuation">(</span> pub_date__lte<span class="token operator">=</span>timezone<span class="token punctuation">.</span>now<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">.</span>order_by<span class="token punctuation">(</span><span class="token string">'-pub_date'</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token number">5</span><span class="token punctuation">]</span> |
Bây giờ viết test cho cái view này, thêm vào polls/tests.py:
1 2 | <span class="token keyword">from</span> django<span class="token punctuation">.</span>urls <span class="token keyword">import</span> reverse |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | <span class="token keyword">def</span> <span class="token function">create_question</span><span class="token punctuation">(</span>question_text<span class="token punctuation">,</span> days<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Create a question with the given `question_text` and published the given number of `days` offset to now (negative for questions published in the past, positive for questions that have yet to be published). """</span> time <span class="token operator">=</span> timezone<span class="token punctuation">.</span>now<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> datetime<span class="token punctuation">.</span>timedelta<span class="token punctuation">(</span>days<span class="token operator">=</span>days<span class="token punctuation">)</span> <span class="token keyword">return</span> Question<span class="token punctuation">.</span>objects<span class="token punctuation">.</span>create<span class="token punctuation">(</span>question_text<span class="token operator">=</span>question_text<span class="token punctuation">,</span> pub_date<span class="token operator">=</span>time<span class="token punctuation">)</span> <span class="token keyword">class</span> <span class="token class-name">QuestionIndexViewTests</span><span class="token punctuation">(</span>TestCase<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">def</span> <span class="token function">test_no_questions</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" If no questions exist, an appropriate message is displayed. """</span> response <span class="token operator">=</span> self<span class="token punctuation">.</span>client<span class="token punctuation">.</span>get<span class="token punctuation">(</span>reverse<span class="token punctuation">(</span><span class="token string">'polls:index'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> self<span class="token punctuation">.</span>assertEqual<span class="token punctuation">(</span>response<span class="token punctuation">.</span>status_code<span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">)</span> self<span class="token punctuation">.</span>assertContains<span class="token punctuation">(</span>response<span class="token punctuation">,</span> <span class="token string">"No polls are available."</span><span class="token punctuation">)</span> self<span class="token punctuation">.</span>assertQuerysetEqual<span class="token punctuation">(</span>response<span class="token punctuation">.</span>context<span class="token punctuation">[</span><span class="token string">'latest_question_list'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">test_past_question</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Questions with a pub_date in the past are displayed on the index page. """</span> create_question<span class="token punctuation">(</span>question_text<span class="token operator">=</span><span class="token string">"Past question."</span><span class="token punctuation">,</span> days<span class="token operator">=</span><span class="token operator">-</span><span class="token number">30</span><span class="token punctuation">)</span> response <span class="token operator">=</span> self<span class="token punctuation">.</span>client<span class="token punctuation">.</span>get<span class="token punctuation">(</span>reverse<span class="token punctuation">(</span><span class="token string">'polls:index'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> self<span class="token punctuation">.</span>assertQuerysetEqual<span class="token punctuation">(</span> response<span class="token punctuation">.</span>context<span class="token punctuation">[</span><span class="token string">'latest_question_list'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'<Question: Past question.>'</span><span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">test_future_question</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Questions with a pub_date in the future aren't displayed on the index page. """</span> create_question<span class="token punctuation">(</span>question_text<span class="token operator">=</span><span class="token string">"Future question."</span><span class="token punctuation">,</span> days<span class="token operator">=</span><span class="token number">30</span><span class="token punctuation">)</span> response <span class="token operator">=</span> self<span class="token punctuation">.</span>client<span class="token punctuation">.</span>get<span class="token punctuation">(</span>reverse<span class="token punctuation">(</span><span class="token string">'polls:index'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> self<span class="token punctuation">.</span>assertContains<span class="token punctuation">(</span>response<span class="token punctuation">,</span> <span class="token string">"No polls are available."</span><span class="token punctuation">)</span> self<span class="token punctuation">.</span>assertQuerysetEqual<span class="token punctuation">(</span>response<span class="token punctuation">.</span>context<span class="token punctuation">[</span><span class="token string">'latest_question_list'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">test_future_question_and_past_question</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Even if both past and future questions exist, only past questions are displayed. """</span> create_question<span class="token punctuation">(</span>question_text<span class="token operator">=</span><span class="token string">"Past question."</span><span class="token punctuation">,</span> days<span class="token operator">=</span><span class="token operator">-</span><span class="token number">30</span><span class="token punctuation">)</span> create_question<span class="token punctuation">(</span>question_text<span class="token operator">=</span><span class="token string">"Future question."</span><span class="token punctuation">,</span> days<span class="token operator">=</span><span class="token number">30</span><span class="token punctuation">)</span> response <span class="token operator">=</span> self<span class="token punctuation">.</span>client<span class="token punctuation">.</span>get<span class="token punctuation">(</span>reverse<span class="token punctuation">(</span><span class="token string">'polls:index'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> self<span class="token punctuation">.</span>assertQuerysetEqual<span class="token punctuation">(</span> response<span class="token punctuation">.</span>context<span class="token punctuation">[</span><span class="token string">'latest_question_list'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'<Question: Past question.>'</span><span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">test_two_past_questions</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" The questions index page may display multiple questions. """</span> create_question<span class="token punctuation">(</span>question_text<span class="token operator">=</span><span class="token string">"Past question 1."</span><span class="token punctuation">,</span> days<span class="token operator">=</span><span class="token operator">-</span><span class="token number">30</span><span class="token punctuation">)</span> create_question<span class="token punctuation">(</span>question_text<span class="token operator">=</span><span class="token string">"Past question 2."</span><span class="token punctuation">,</span> days<span class="token operator">=</span><span class="token operator">-</span><span class="token number">5</span><span class="token punctuation">)</span> response <span class="token operator">=</span> self<span class="token punctuation">.</span>client<span class="token punctuation">.</span>get<span class="token punctuation">(</span>reverse<span class="token punctuation">(</span><span class="token string">'polls:index'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> self<span class="token punctuation">.</span>assertQuerysetEqual<span class="token punctuation">(</span> response<span class="token punctuation">.</span>context<span class="token punctuation">[</span><span class="token string">'latest_question_list'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'<Question: Past question 2.>'</span><span class="token punctuation">,</span> <span class="token string">'<Question: Past question 1.>'</span><span class="token punctuation">]</span> <span class="token punctuation">)</span> |
test DetailView
Người dùng vẫn có thể đoán url để vào trang detail của những question không hợp lệ, vậy nên cần hạn chế theo cách tương tự.
polls/views.py
:
1 2 3 4 5 6 7 8 | <span class="token keyword">class</span> <span class="token class-name">DetailView</span><span class="token punctuation">(</span>generic<span class="token punctuation">.</span>DetailView<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token keyword">def</span> <span class="token function">get_queryset</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Excludes any questions that aren't published yet. """</span> <span class="token keyword">return</span> Question<span class="token punctuation">.</span>objects<span class="token punctuation">.</span><span class="token builtin">filter</span><span class="token punctuation">(</span>pub_date__lte<span class="token operator">=</span>timezone<span class="token punctuation">.</span>now<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> |
polls/tests.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <span class="token keyword">class</span> <span class="token class-name">QuestionDetailViewTests</span><span class="token punctuation">(</span>TestCase<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">def</span> <span class="token function">test_future_question</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" The detail view of a question with a pub_date in the future returns a 404 not found. """</span> future_question <span class="token operator">=</span> create_question<span class="token punctuation">(</span>question_text<span class="token operator">=</span><span class="token string">'Future question.'</span><span class="token punctuation">,</span> days<span class="token operator">=</span><span class="token number">5</span><span class="token punctuation">)</span> url <span class="token operator">=</span> reverse<span class="token punctuation">(</span><span class="token string">'polls:detail'</span><span class="token punctuation">,</span> args<span class="token operator">=</span><span class="token punctuation">(</span>future_question<span class="token punctuation">.</span><span class="token builtin">id</span><span class="token punctuation">,</span><span class="token punctuation">)</span><span class="token punctuation">)</span> response <span class="token operator">=</span> self<span class="token punctuation">.</span>client<span class="token punctuation">.</span>get<span class="token punctuation">(</span>url<span class="token punctuation">)</span> self<span class="token punctuation">.</span>assertEqual<span class="token punctuation">(</span>response<span class="token punctuation">.</span>status_code<span class="token punctuation">,</span> <span class="token number">404</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">test_past_question</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" The detail view of a question with a pub_date in the past displays the question's text. """</span> past_question <span class="token operator">=</span> create_question<span class="token punctuation">(</span>question_text<span class="token operator">=</span><span class="token string">'Past Question.'</span><span class="token punctuation">,</span> days<span class="token operator">=</span><span class="token operator">-</span><span class="token number">5</span><span class="token punctuation">)</span> url <span class="token operator">=</span> reverse<span class="token punctuation">(</span><span class="token string">'polls:detail'</span><span class="token punctuation">,</span> args<span class="token operator">=</span><span class="token punctuation">(</span>past_question<span class="token punctuation">.</span><span class="token builtin">id</span><span class="token punctuation">,</span><span class="token punctuation">)</span><span class="token punctuation">)</span> response <span class="token operator">=</span> self<span class="token punctuation">.</span>client<span class="token punctuation">.</span>get<span class="token punctuation">(</span>url<span class="token punctuation">)</span> self<span class="token punctuation">.</span>assertContains<span class="token punctuation">(</span>response<span class="token punctuation">,</span> past_question<span class="token punctuation">.</span>question_text<span class="token punctuation">)</span> |
Thêm ý về tests
- https://docs.djangoproject.com/en/3.0/intro/tutorial05/#ideas-for-more-tests
- https://docs.djangoproject.com/en/3.0/topics/testing/
Introducing
Phần này nói về cách django quản lý “static files” như js, css, image …
Customize app’s
Tương tự polls/templates/
, tạo thư mục polls/static/
để lưu static files.
Các khai báo settings.py liên quan đến static files gọi chung là Django’s STATICFILES_FINDERS, một trong đó là AppDirectoriesFinder
sẽ tìm trong thư mục con static
của mỗi một INSTALLED_APPS
được khai báo.
Nên trước hết tạo file polls/static/polls/style.css
:
1 2 3 4 | <span class="token selector">li a {</span> <span class="token property-line"> <span class="token property">color</span><span class="token punctuation">:</span> green;</span> <span class="token selector">}</span> |
Đem sử dụng tại polls/templates/polls/index.html
: (đặt code ở trên cùng file)
1 2 3 4 | {% load static %} <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>stylesheet<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text/css<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{% static <span class="token punctuation">'</span>polls/style.css<span class="token punctuation">'</span> %}<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> |
Adding background-image
Thêm file polls/static/polls/images/background.gif
.
Thêm vào file polls/static/polls/style.css
:
1 2 |
Introducing
customizing Django’s automatically-generated admin site
Customize the admin form
polls/admin.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token keyword">from</span> django<span class="token punctuation">.</span>contrib <span class="token keyword">import</span> admin <span class="token keyword">from</span> <span class="token punctuation">.</span>models <span class="token keyword">import</span> Question <span class="token keyword">class</span> <span class="token class-name">QuestionAdmin</span><span class="token punctuation">(</span>admin<span class="token punctuation">.</span>ModelAdmin<span class="token punctuation">)</span><span class="token punctuation">:</span> fieldsets <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token punctuation">(</span><span class="token boolean">None</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token string">'fields'</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'question_text'</span><span class="token punctuation">]</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token string">'Date information'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token string">'fields'</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'pub_date'</span><span class="token punctuation">]</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span> admin<span class="token punctuation">.</span>site<span class="token punctuation">.</span>register<span class="token punctuation">(</span>Question<span class="token punctuation">,</span> QuestionAdmin<span class="token punctuation">)</span> |
Thêm related objects
Có thể thêm khai báo đối tượng Choice
1 2 3 4 5 6 | <span class="token keyword">from</span> django<span class="token punctuation">.</span>contrib <span class="token keyword">import</span> admin <span class="token keyword">from</span> <span class="token punctuation">.</span>models <span class="token keyword">import</span> Choice<span class="token punctuation">,</span> Question <span class="token comment"># ...</span> admin<span class="token punctuation">.</span>site<span class="token punctuation">.</span>register<span class="token punctuation">(</span>Choice<span class="token punctuation">)</span> |
Kiểm tra lại sẽ thấy trang admin có thêm phần quản trị cho Choice, tuy nhiên không hiệu quả nếu phải tạo question trước rồi quay sang tạo choice, nên sẽ bỏ register Choice, sửa lại như sau: polls/admin.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | from django.contrib import admin from .models import Choice, Question class ChoiceInline(admin.StackedInline): model = Choice extra = 3 class QuestionAdmin(admin.ModelAdmin): fieldsets = [ (None, {'fields': ['question_text']}), ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), ] inlines = [ChoiceInline] admin.site.register(Question, QuestionAdmin) |
=> thêm classs ChoiceInline
., extra
là số object Choice được build sẵn trên màn hình quản trị question.
Vì vấn đề hiển thị, hãy sửa lại StackedInline
thành TabularInline
:
1 2 | <span class="token keyword">class</span> <span class="token class-name">ChoiceInline</span><span class="token punctuation">(</span>admin<span class="token punctuation">.</span>TabularInline<span class="token punctuation">)</span><span class="token punctuation">:</span> |
Customize the admin change list
Khai báo option là list_display
1 2 3 4 | <span class="token keyword">class</span> <span class="token class-name">QuestionAdmin</span><span class="token punctuation">(</span>admin<span class="token punctuation">.</span>ModelAdmin<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token comment"># ...</span> list_display <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token string">'question_text'</span><span class="token punctuation">,</span> <span class="token string">'pub_date'</span><span class="token punctuation">,</span> <span class="token string">'was_published_recently'</span><span class="token punctuation">)</span> |
Sửa lại cột WAS PUBLISHED RECENTLY
:
=> Tại: polls/models.py
:
1 2 3 4 5 6 7 8 9 | <span class="token keyword">class</span> <span class="token class-name">Question</span><span class="token punctuation">(</span>models<span class="token punctuation">.</span>Model<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token comment"># ...</span> <span class="token keyword">def</span> <span class="token function">was_published_recently</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span> now <span class="token operator">=</span> timezone<span class="token punctuation">.</span>now<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">return</span> now <span class="token operator">-</span> datetime<span class="token punctuation">.</span>timedelta<span class="token punctuation">(</span>days<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator"><=</span> self<span class="token punctuation">.</span>pub_date <span class="token operator"><=</span> now was_published_recently<span class="token punctuation">.</span>admin_order_field <span class="token operator">=</span> <span class="token string">'pub_date'</span> was_published_recently<span class="token punctuation">.</span>boolean <span class="token operator">=</span> <span class="token boolean">True</span> was_published_recently<span class="token punctuation">.</span>short_description <span class="token operator">=</span> <span class="token string">'Published recently?'</span> |
Thêm filter, search_fields tại admin.py
, class QuestionAdmin
:
1 2 3 | list_filter <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'pub_date'</span><span class="token punctuation">]</span> search_fields <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'question_text'</span><span class="token punctuation">]</span> |
Customize the admin look and feel
Customizing your project’s templates
Template có thể đặt ở bất cứ đâu, tuy nhiên tốt hơn là tạo thư mục templates đặt ở ngoài cùng project.
=> Thêm đường dẫn templates tại mysite/settings.py
, DIRS
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | TEMPLATES <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token string">'BACKEND'</span><span class="token punctuation">:</span> <span class="token string">'django.template.backends.django.DjangoTemplates'</span><span class="token punctuation">,</span> <span class="token string">'DIRS'</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>os<span class="token punctuation">.</span>path<span class="token punctuation">.</span>join<span class="token punctuation">(</span>BASE_DIR<span class="token punctuation">,</span> <span class="token string">'templates'</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token string">'APP_DIRS'</span><span class="token punctuation">:</span> <span class="token boolean">True</span><span class="token punctuation">,</span> <span class="token string">'OPTIONS'</span><span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token string">'context_processors'</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token string">'django.template.context_processors.debug'</span><span class="token punctuation">,</span> <span class="token string">'django.template.context_processors.request'</span><span class="token punctuation">,</span> <span class="token string">'django.contrib.auth.context_processors.auth'</span><span class="token punctuation">,</span> <span class="token string">'django.contrib.messages.context_processors.messages'</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span> |
DIR là list of filesystem directories. => Templates có thể đặt gộp chung lại, tuy nhiên nhưng template chỉ liên quan đến mỗi app cụ thể nên để riêng trong thư mục con template của app đó.
Với template cho admin, django sẽ tìm tới package django/contrib/admin/templates/admin/base_site.html
sẽ thấy:
1 2 3 4 | {% block branding %} <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h1</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>site-name<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{% url <span class="token punctuation">'</span>admin:index<span class="token punctuation">'</span> %}<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Polls Administration<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</span><span class="token punctuation">></span></span> {% endblock %} |
=> đây là phần header trang admin
Customizing your application’s templates
Để customize trang admin index, copy template có sẵn từ package admin của django sang thư mục custom template, tương tự base_site.html, copy code sang admin/index.html , biến app_list
chính là các apps được hiển thị của project
Tương tự app admin có sẵn trong package, ta cũng có thể biến app của mình thành 1 package của riêng mình
Customize static path
Để sử dụng folder static/
ngoài cùng:
1 2 3 4 5 | STATICFILES_DIRS <span class="token operator">=</span> <span class="token punctuation">[</span> os<span class="token punctuation">.</span>path<span class="token punctuation">.</span>join<span class="token punctuation">(</span>BASE_DIR<span class="token punctuation">,</span> <span class="token string">'static'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span> |