The objective of this iteration is to add an end date to polls, improve the navigation and page layout, and add some useful methods so that views can query a poll using methods instead of testing attributes directly.
Another objective is to externalize data for security and portability.
You will also automate running of tests.
Tasks
- Create an Iteration 2 Plan in your ku-polls wiki and write a plan as you did for Iteration 1.
- Add links to “Iteration 2 Plan” in
- Home page in Wiki
- README.md
- Review and update these wiki documents:
- Project Plan - in particular, add details of Iteration 2 major work & goal or milestone
- Requirements - add any new requirements that are significant
- Create an
Iteration 2
task board and define tasks. - Create an
iteration2
branch for your work.- First, be sure you have merged all work from
iteration1
intomaster
and pushed both branches to Github. - Create
iteration2
as a branch frommaster
(not fromiteration1
).
- First, be sure you have merged all work from
- After you update the model (add
end_date
) and revise your polls questions, create a new data file named data/polls-v2.json.# in your "ku-polls" repository, enter: python manage.py dumpdata --indent=2 -o data/polls-v2.json polls
Include
data/
anddata/polls-v2.json
in your git repository.
New Features
- In the
Question
class, add anend_date
attribute that is the ending date for voting.end_date
can benull
. If it is null, then voting is allowed anytime afterpub_date
.
- Add a default for the
pub_date
. The default is the current date and time.- This doesn’t work:
pub_date = DateTimeField( ..., default=timezone.now())
- The expression is evaluated when the code is first loaded, not when a new Question is created.
- Use a function name without parens instead of the function value so that it is evaluated when the object is created.
- Don’t use
auto_now
. It doesn’t do what you might expect.
- This doesn’t work:
-
Items 1 & 2 change the database schema, so you need to make a migration and apply it (“migrate”).
- In the Question class, add two methods:
is_published
returns True if the current date-time is on or after question’s publication date. Use local date/time, not UTC!can_vote
returns True if voting is allowed for this question. That means, the current date/time is between thepub_date
andend_date
. Ifend_date
is null then can vote anytime afterpub_date
.
- Modify your views code to use the
is_published
andcan_vote
methods instead of testing date values. The view code should not directly testquestion.pub_date >= something
(poor encapsulation).- Does your view code look cleaner and easier to read after this change?
- Write unit tests for
is_published
. Test these cases:- question with future pub date (should not be shown in the UI)
- question with the default pub date (now)
- question with pub date in the past
- Write at least 3 unit tests for
can_vote
.- Design 3 tests for different cases. The tests should not be redundant.
- Use a descriptive name for each test and write a one sentence docstring to describe what you are testing, e.g.
def test_cannot_vote_after_end_date(self): """Cannot vote if the end_date is in the past.""" ...
- If someone navigates directly to a poll detail page when voting is not allowed, redirect them to the polls index and show an error message on the index page.
A visitor can do this by entering the poll URL in his browser, such ashttp://localhost:8000/polls/k/
(k = poll number)- Use the “Django Messages Framework” to set and show the error message. It’s much simpler than passing an
error_message
via the context. (That’s cludgy and I wonder why the tutorial did that.) - Messages Framework document describes how.
- Use the “Django Messages Framework” to set and show the error message. It’s much simpler than passing an
Navigation Improvements
Navigating the app’s pages is clumsy. Sometimes a visitor has to use the browser Back button, too! Let’s improve navigation.
- If someone goes to the base URL of the web site, such as
http://localhost:8000/
, redirect them to the polls index page.- After the redirect, the visitor’s web browser should show the actual URL (
http://localhost:8000/polls/
) not the base URL/
. - Many ways to do this. Have a look at Django’s
RedirectView
class andRedirectView.as_view()
method.
- After the redirect, the visitor’s web browser should show the actual URL (
- Improve navigation between pages.
- On the polls detail page, add a “Back to List of Polls” link so visitor can go back to the index.
- On the voting results page, also add a “Back to List of Polls” link (same text message as above).
- You can use any intuitive text or icon you like instead of “Back to List of Polls”; but be consistent.
- Remove the “Vote again” link. A visitor should get only one vote per poll!
- Add links so that a visitor to view polls results without voting.
- On polls index page, add a “Results” link for each question.
- On the poll “detail” page, also add a link to “Results”.
Other Enhancements
- Improve appearance of the voting results page.
- Make the choice text and votes line up in columns (use a table or CSS)
- Remove the ugly bullets!
- Remove the word “votes” after the count (it’s just clutter)
- Externalize configuration data in
settings.py
.- Add
python-decouple
to your project. Use it to externalize values of these variables insettings.py
:SECRET_KEY DEBUG ALLOWED_HOSTS TIME_ZONE
- Put the values of those variable in a
.env
file in your project root directory. - Include safe defaults in
settings.py
for every externalized setting, in case a variable is not set in.env
.# what is a safe default for DEBUG? DEBUG = config("DEBUG", cast=bool, default=???)
- See Externalize Configuration for more info.
- Add
- Do not commit
.env
to Git, but do create and commit asample.env
file so that someone knows what they need to set.- include helpful comments in
sample.env
# Copy this file to .env and edit the values # Create a secret key using (todo: how to create a secret key?) SECRET_KEY = secret-key-value-without-quotes # Set DEBUG to True for development, False for actual use DEBUG = False # ALLOWED_HOSTS is a comma-separated list of hosts that can access the app. # You can use wildcard chars (*) and IP addresses. Use * for any host. ALLOWED_HOSTS = localhost, 127.0.0.1, ::1, testserver # Your timezone TIME_ZONE = Asia/Bangkok
- include helpful comments in
- Add correctly-formatted docstring comments to the models and views.
- Write class docstring and method/function docstring.
- First line should be a complete sentence.
- Use
flake8
to verify docstrings.
Testing (This isn’t required in 2024 – we’ll add it later)
- Create a Github Action to automatically run your unit tests.
- You only need to run using one version of Python (the Github template uses many versions)
- You may need to include
testserver
in ALLOWED_HOSTS for the tests to run.
-
Add a github “badge” to README.md that shows the status of tests.
- All tests should pass. They should pass both on Github and when TA runs your code in a virtual env.
Evaluaton
- Project Artifacts
- “Iteration 2 Plan” is accurate and complete: GOAL, MILESTONE, FEATURES/WORK
- Iteration 2 Task Board has tasks covering the work. Most or all are “Done”.
- Requirements document contains project requirements, not iteration requirements.
- README has link to Iteration 2 Plan
- Configuration
sample.env
works (either copy to.env
or source into the environment)mysite/settings.py
externalize and has defaults for:SECRET_KEY = config("SECRET_KEY", ...)
DEBUG = config("DEBUG", default=?, cast=bool)
ALLOWED_HOSTS = config("ALLOWED_HOSTS", default="localhost,...", cast=Csv())
TIME_ZONE = config("TIME_ZONE", default="Asia/Bangkok")
python-decouple
is in “requirements.txt”
- Running
- migrations run without error
- loaddata works
python manage.py loaddata data/polls-v2.json
-
Code
is_published
is correctcan_vote
is correct- both methods have docstring describing what they do and what they return
- views do not directly test value of
question.pub_date
-
Tests
- all unit tests pass
- at least 3 tests for
is_published
- at least 3 tests for
can_vote
-
Run the App - Functionality
- ”/” redirected to “/polls/”
- “/polls/” page shows poll status: open or closed
- poll detail page has “Back to Index” or similar
- can vote
- polls results page has “Back to Index” or similar
- link to view poll results without voting
- cannot vote on a closed poll, even using URL bar or HTTP client app