Polls on steroids 01
This tutorial is an attempt to add some extra content to the original Django polls tutorial, explore additional features of the framework and even make the app look and respond better with some front-end tools on future chapters. It will be assumed that you’ve completed Django's official tutorial and you are already quite comfortable with Python. I’ll be using Python 3.3 and Django 1.6 under a Debian based OS.
I’ll keep the code for this tutorial in this GitHub repository.
The master branch contains the code of the original Django tutorial in almost the exact same state as if you’ve just completed it. The other branches are named with the format ‘XXPOS’, where XX is a two digit number indicating what part of the POS tutorial the code belongs to (so by the time you finish this first chapter, your code will look like the one contained on the 01POS branch). Notice that if you’re going to work with my GitHub repository, you’ll also get the sqlite3.db file including my example models.
I left all the apps that come installed by default, including the authentication system. In case you want to make use of this repository and the already created superuser, its username and password are admin and admin.
Editing the TIME_ZONE settings
It would be a good idea to edit the settings.py file of the project and change these settings to match your own. Sooner or later you’ll have to check Django’s documentation about time zones, and you might also want to check pytz, but for now you might just want to check this Wikipedia article. You should put the complete timezone name (TZ column), the country code won’t work (at least didn’t work for me). Try to synchronize the database now (python3 manage.py syncdb), if you changed the timezone for an invalid one, you’ll get an error message and the database won’t sync.
Preventing access to future polls on every view
Let’s start with a quite obvious improvement. Notice that you can still access unpublished polls through their pk number on the vote and results views if you manipulate the url. Let’s fix that by filtering our QuerySets (lines 2 and 21):
Code (click to collapse/expand)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Excluding polls with less than 2 choices
I went a little bit further than the original tutorial suggestion of eliminating polls with no choices, since I don’t think polls with less than 2 choices make much sense, but feel free to change this number to 1 or 0 if you want. We’ll need a little bit of aggregation for our querysets, you can see I’m using the annotate and the Count functions.
Code
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 |
|
Pre filtering the QuerySets
You’ve probably already noticed there’s quite a lot of repeated code here. Since we’re applying the same filtering to the QuerySet of every view, my approach to solve this was adding an auxiliary function named filter_polls. Our function returns a QuerySet based on the Poll model, so we can also remove the “model = Poll” line from the DetailView based views. Here’s the current full code (except for the imports) for polls/views.py:
Code
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 |
|
Adding some extra tests
And now I think it would be a perfect time to write some additional tests. Notice that if you run them right now, you’ll get a couple of assertion errors, but that’s perfectly fine since our previous tests didn’t take into account the number of choices with which the test polls were created, so we’ll also have to modify them. We can start by modifying the create_poll function so that it creates a number of choices for our polls determined by an extra parameter. The tests for the detail, vote and results views are exactly the same, but I think that trying to reduce the amount of code by keeping only one block of tests and iterating over the set of views would introduce tight coupling and go against the unit testing principles. Here’s the full (long and cumbersome) code of my tests:
Code
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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
|
Extra: Generating a random number of votes for a poll
It would be a good idea to add a decent amount of polls, so that this app starts to look like something real and useful. If you’re using my repository, you’ll notice I’ve added a couple of polls and choices, and also some completely random and unbiased results. You can easily add more or delete them at will with Django’s admin. Let’s combine our previous Python knowledge with the manage.py shell and Django’s db API to do something useful. There’s a sports cars related poll in my database, I’ll use this as an example. Fire up the manage.py shell and let’s do some database API hacking.
>>> from polls.models import Poll, Choice >>> from random import randrange # We’ll this to generate the random values >>> p = Poll.objects.filter(question__contains='car')[0] >>> p # Checking we actually get the poll we wanted... <Poll: What's your favorite sports car manufacturer?> >>> choices = p.choice_set.all() >>> [i.votes for i in choices] # Checking the initial values [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] >>> for i in choices: ... i.votes = randrange(2000, 6000) ... i.save() # Let us not forget to update the db for every choice! ... >>> p = Poll.objects.filter(question__contains='car')[0] >>> choices = p.choice_set.all() # Making sure that the values come from the db >>> [i.votes for i in choices] # Now let’s check if they’ve changed [5399, 3015, 3972, 4218, 5492, 3324, 2949, 3199, 2710, 2256, 5780, 5270, 5508]
Reassigning p and choices as we’ve done here should be enough to check that the database has been updated, but you can (and probably should) use other ways to make sure that you’ve actually achieved this. You can do this through the manage.py shell (make sure you’re checking updated values in the database instead of the objects referenced in your current namespace!), checking it with your RDBMS, or (probably the quickest option) by logging into Django’s admin. If you want to create an outrageous amount of polls, you can go even further and create your own poll-o-matic! You could do this by creating a document with each poll question followed by a list of choices and process it with a Python script, also generating random values for the votes with some code similar to the one used in the previous example.
Comentarios
Publicar un comentario