├── .gitignore ├── LICENSE ├── README.md ├── manage.py ├── mysite ├── __init__.py ├── db.sqlite3 ├── settings.py ├── urls.py └── wsgi.py └── polls ├── __init__.py ├── admin.py ├── models.py ├── templates └── polls │ ├── detail.html │ ├── index.html │ └── results.html ├── tests.py ├── urls.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | .git 4 | .project -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Django Software Foundation and individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Django nor the names of its contributors may be used 15 | to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | django-poll-app 2 | =============== 3 | 4 | The solution to https://docs.djangoproject.com/en/dev/intro/tutorial01/ 5 | 6 | Getting Started 7 | --------------- 8 | 9 | ### Initial Setup ### 10 | 1. Make a new virtualenv: ``virtualenv env`` 11 | 2. Activate the virtualenv: ``source env/bin/activate`` 12 | 3. Install Django: ``pip install Django`` 13 | 4. Edit ``mysite/settings.py:36`` to match your timezone 14 | 5. Run the server: ``python manage.py runserver`` 15 | 6. Open website in browser at ``http://localhost:8000/polls`` or admin at ``http://localhost:8000/admin`` (admin:admin) 16 | 17 | ### After initial setup ### 18 | 1. Activate the virtualenv: ``source env/bin/activate`` 19 | 2. Run the server: ``python manage.py runserver`` 20 | 3. Open website in browser at ``http://localhost:8000/polls`` or admin at ``http://localhost:8000/admin`` (admin:admin) 21 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /mysite/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chive/django-poll-app/8f71c7ef2992a5b19301ad89577fdeab04bf0be9/mysite/__init__.py -------------------------------------------------------------------------------- /mysite/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chive/django-poll-app/8f71c7ef2992a5b19301ad89577fdeab04bf0be9/mysite/db.sqlite3 -------------------------------------------------------------------------------- /mysite/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Django settings for mysite project. 4 | 5 | DEBUG = True 6 | TEMPLATE_DEBUG = DEBUG 7 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 8 | 9 | 10 | ADMINS = ( 11 | # ('Your Name', 'your_email@example.com'), 12 | ) 13 | 14 | MANAGERS = ADMINS 15 | 16 | DATABASES = { 17 | 'default': { 18 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 19 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # Or path to database file if using sqlite3. 20 | # The following settings are not used with sqlite3: 21 | 'USER': '', 22 | 'PASSWORD': '', 23 | 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. 24 | 'PORT': '', # Set to empty string for default. 25 | } 26 | } 27 | 28 | # Hosts/domain names that are valid for this site; required if DEBUG is False 29 | # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts 30 | ALLOWED_HOSTS = [] 31 | 32 | # Local time zone for this installation. Choices can be found here: 33 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 34 | # although not all choices may be available on all operating systems. 35 | # In a Windows environment this must be set to your system time zone. 36 | TIME_ZONE = 'Europe/Zurich' 37 | 38 | # Language code for this installation. All choices can be found here: 39 | # http://www.i18nguy.com/unicode/language-identifiers.html 40 | LANGUAGE_CODE = 'en-us' 41 | 42 | SITE_ID = 1 43 | 44 | # If you set this to False, Django will make some optimizations so as not 45 | # to load the internationalization machinery. 46 | USE_I18N = True 47 | 48 | # If you set this to False, Django will not format dates, numbers and 49 | # calendars according to the current locale. 50 | USE_L10N = True 51 | 52 | # If you set this to False, Django will not use timezone-aware datetimes. 53 | USE_TZ = True 54 | 55 | # Absolute filesystem path to the directory that will hold user-uploaded files. 56 | # Example: "/var/www/example.com/media/" 57 | MEDIA_ROOT = '' 58 | 59 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 60 | # trailing slash. 61 | # Examples: "http://example.com/media/", "http://media.example.com/" 62 | MEDIA_URL = '' 63 | 64 | # Absolute path to the directory static files should be collected to. 65 | # Don't put anything in this directory yourself; store your static files 66 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 67 | # Example: "/var/www/example.com/static/" 68 | STATIC_ROOT = '' 69 | 70 | # URL prefix for static files. 71 | # Example: "http://example.com/static/", "http://static.example.com/" 72 | STATIC_URL = '/static/' 73 | 74 | # Additional locations of static files 75 | STATICFILES_DIRS = ( 76 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 77 | # Always use forward slashes, even on Windows. 78 | # Don't forget to use absolute paths, not relative paths. 79 | ) 80 | 81 | # List of finder classes that know how to find static files in 82 | # various locations. 83 | STATICFILES_FINDERS = ( 84 | 'django.contrib.staticfiles.finders.FileSystemFinder', 85 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 86 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 87 | ) 88 | 89 | # Make this unique, and don't share it with anybody. 90 | SECRET_KEY = '05=^qgbhg3!6-dzb6#&2j^jmh-2fgc%22!z_!w*&8iy_m$2*$*' 91 | 92 | # List of callables that know how to import templates from various sources. 93 | TEMPLATE_LOADERS = ( 94 | 'django.template.loaders.filesystem.Loader', 95 | 'django.template.loaders.app_directories.Loader', 96 | # 'django.template.loaders.eggs.Loader', 97 | ) 98 | 99 | MIDDLEWARE_CLASSES = ( 100 | 'django.middleware.common.CommonMiddleware', 101 | 'django.contrib.sessions.middleware.SessionMiddleware', 102 | 'django.middleware.csrf.CsrfViewMiddleware', 103 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 104 | 'django.contrib.messages.middleware.MessageMiddleware', 105 | # Uncomment the next line for simple clickjacking protection: 106 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware', 107 | ) 108 | 109 | ROOT_URLCONF = 'mysite.urls' 110 | 111 | # Python dotted path to the WSGI application used by Django's runserver. 112 | WSGI_APPLICATION = 'mysite.wsgi.application' 113 | 114 | TEMPLATE_DIRS = ( 115 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 116 | # Always use forward slashes, even on Windows. 117 | # Don't forget to use absolute paths, not relative paths. 118 | os.path.join(BASE_DIR, 'templates'), 119 | ) 120 | 121 | INSTALLED_APPS = ( 122 | 'django.contrib.auth', 123 | 'django.contrib.contenttypes', 124 | 'django.contrib.sessions', 125 | 'django.contrib.sites', 126 | 'django.contrib.messages', 127 | 'django.contrib.staticfiles', 128 | # Uncomment the next line to enable the admin: 129 | 'django.contrib.admin', 130 | # Uncomment the next line to enable admin documentation: 131 | # 'django.contrib.admindocs', 132 | 'polls' 133 | ) 134 | 135 | # A sample logging configuration. The only tangible logging 136 | # performed by this configuration is to send an email to 137 | # the site admins on every HTTP 500 error when DEBUG=False. 138 | # See http://docs.djangoproject.com/en/dev/topics/logging for 139 | # more details on how to customize your logging configuration. 140 | LOGGING = { 141 | 'version': 1, 142 | 'disable_existing_loggers': False, 143 | 'filters': { 144 | 'require_debug_false': { 145 | '()': 'django.utils.log.RequireDebugFalse' 146 | } 147 | }, 148 | 'handlers': { 149 | 'mail_admins': { 150 | 'level': 'ERROR', 151 | 'filters': ['require_debug_false'], 152 | 'class': 'django.utils.log.AdminEmailHandler' 153 | } 154 | }, 155 | 'loggers': { 156 | 'django.request': { 157 | 'handlers': ['mail_admins'], 158 | 'level': 'ERROR', 159 | 'propagate': True, 160 | }, 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /mysite/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from django.contrib import admin 3 | 4 | admin.autodiscover() 5 | 6 | urlpatterns = patterns('', 7 | url(r'^polls/', include('polls.urls', namespace="polls")), 8 | url(r'^admin/', include(admin.site.urls)), 9 | ) 10 | -------------------------------------------------------------------------------- /mysite/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mysite project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks 19 | # if running multiple sites in the same mod_wsgi process. To fix this, use 20 | # mod_wsgi daemon mode with each site in its own daemon process, or use 21 | # os.environ["DJANGO_SETTINGS_MODULE"] = "mysite.settings" 22 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 23 | 24 | # This application object is used by any WSGI server configured to use this 25 | # file. This includes Django's development server, if the WSGI_APPLICATION 26 | # setting points here. 27 | from django.core.wsgi import get_wsgi_application 28 | application = get_wsgi_application() 29 | 30 | # Apply WSGI middleware here. 31 | # from helloworld.wsgi import HelloWorldApplication 32 | # application = HelloWorldApplication(application) 33 | -------------------------------------------------------------------------------- /polls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chive/django-poll-app/8f71c7ef2992a5b19301ad89577fdeab04bf0be9/polls/__init__.py -------------------------------------------------------------------------------- /polls/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Choice, Poll 4 | 5 | 6 | class ChoiceInline(admin.TabularInline): 7 | model = Choice 8 | extra = 3 9 | 10 | 11 | class PollAdmin(admin.ModelAdmin): 12 | fieldsets = [ 13 | (None, { 14 | 'fields': ['question'], 15 | }), 16 | ('Date information', { 17 | 'fields': ['pub_date'], 18 | 'classes': ['collapse'], 19 | }), 20 | ] 21 | inlines = [ChoiceInline] 22 | list_display = ('question', 'pub_date', 'was_published_recently') 23 | list_filter = ['pub_date'] 24 | search_fields = ['question'] 25 | date_hierarchy = 'pub_date' 26 | 27 | admin.site.register(Poll, PollAdmin) 28 | admin.site.register(Choice) 29 | -------------------------------------------------------------------------------- /polls/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.db import models 4 | from django.utils import timezone 5 | 6 | 7 | class Poll(models.Model): 8 | question = models.CharField(max_length=200) 9 | pub_date = models.DateTimeField('date published') 10 | 11 | def __unicode__(self): # Python 3: def __str__(self): 12 | return self.question 13 | 14 | def was_published_recently(self): 15 | now = timezone.now() 16 | return now - datetime.timedelta(days=1) <= self.pub_date < now 17 | 18 | was_published_recently.admin_order_field = 'pub_date' 19 | was_published_recently.boolean = True 20 | was_published_recently.short_description = 'Published recently?' 21 | 22 | 23 | class Choice(models.Model): 24 | poll = models.ForeignKey(Poll) 25 | choice_text = models.CharField(max_length=200) 26 | votes = models.IntegerField(default=0) 27 | 28 | def __unicode__(self): # Python 3: def __str__(self): 29 | return self.choice_text -------------------------------------------------------------------------------- /polls/templates/polls/detail.html: -------------------------------------------------------------------------------- 1 |

{{ poll.question }}

2 | 3 | {% if error_message %}

{{ error_message }}

{% endif %} 4 | 5 |
6 | {% csrf_token %} 7 | {% for choice in poll.choice_set.all %} 8 | 9 |
10 | {% endfor %} 11 | 12 |
-------------------------------------------------------------------------------- /polls/templates/polls/index.html: -------------------------------------------------------------------------------- 1 | {% if latest_poll_list %} 2 | 7 | {% else %} 8 |

No polls are available.

9 | {% endif %} -------------------------------------------------------------------------------- /polls/templates/polls/results.html: -------------------------------------------------------------------------------- 1 |

{{ poll.question }}

2 | 3 | 8 | 9 | Vote again? -------------------------------------------------------------------------------- /polls/tests.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.core.urlresolvers import reverse 4 | from django.utils import timezone 5 | from django.test import TestCase 6 | 7 | from .models import Poll 8 | 9 | 10 | class PollMethodTests(TestCase): 11 | 12 | def test_was_published_recently_with_future_poll(self): 13 | """ 14 | was_published_recently() should return False for polls whose 15 | pub_date is in the future 16 | """ 17 | future_poll = Poll(pub_date=timezone.now() + datetime.timedelta(days=30)) 18 | self.assertEqual(future_poll.was_published_recently(), False) 19 | 20 | def test_was_published_recently_with_old_poll(self): 21 | """ 22 | was_published_recently() should return False for polls whose pub_date 23 | is older than 1 day 24 | """ 25 | old_poll = Poll(pub_date=timezone.now() - datetime.timedelta(days=30)) 26 | self.assertEqual(old_poll.was_published_recently(), False) 27 | 28 | def test_was_published_recently_with_recent_poll(self): 29 | """ 30 | was_published_recently() should return True for polls whose pub_date 31 | is within the last day 32 | """ 33 | recent_poll = Poll(pub_date=timezone.now() - datetime.timedelta(hours=1)) 34 | self.assertEqual(recent_poll.was_published_recently(), True) 35 | 36 | 37 | def create_poll(question, days): 38 | """ 39 | Creates a poll with the given `question` published the given number of 40 | `days` offset to now (negative for polls published in the past, 41 | positive for polls that have yet to be published). 42 | """ 43 | return Poll.objects.create( 44 | question=question, 45 | pub_date=timezone.now() + datetime.timedelta(days=days) 46 | ) 47 | 48 | 49 | class PollViewTests(TestCase): 50 | def test_index_view_with_no_polls(self): 51 | """ 52 | If no polls exist, an appropriate message should be displayed. 53 | """ 54 | response = self.client.get(reverse('polls:index')) 55 | self.assertEqual(response.status_code, 200) 56 | self.assertContains(response, "No polls are available.") 57 | self.assertQuerysetEqual(response.context['latest_poll_list'], []) 58 | 59 | def test_index_view_with_a_past_poll(self): 60 | """ 61 | Polls with a pub_date in the past should be displayed on the index page. 62 | """ 63 | create_poll(question="Past poll.", days=-30) 64 | response = self.client.get(reverse('polls:index')) 65 | self.assertQuerysetEqual( 66 | response.context['latest_poll_list'], 67 | [''] 68 | ) 69 | 70 | def test_index_view_with_a_future_poll(self): 71 | """ 72 | Polls with a pub_date in the future should not be displayed on the 73 | index page. 74 | """ 75 | create_poll(question="Future poll.", days=30) 76 | response = self.client.get(reverse('polls:index')) 77 | self.assertContains(response, "No polls are available.", status_code=200) 78 | self.assertQuerysetEqual(response.context['latest_poll_list'], []) 79 | 80 | def test_index_view_with_future_poll_and_past_poll(self): 81 | """ 82 | Even if both past and future polls exist, only past polls should be 83 | displayed. 84 | """ 85 | create_poll(question="Past poll.", days=-30) 86 | create_poll(question="Future poll.", days=30) 87 | response = self.client.get(reverse('polls:index')) 88 | self.assertQuerysetEqual( 89 | response.context['latest_poll_list'], 90 | [''] 91 | ) 92 | 93 | def test_index_view_with_two_past_polls(self): 94 | """ 95 | The polls index page may display multiple polls. 96 | """ 97 | create_poll(question="Past poll 1.", days=-30) 98 | create_poll(question="Past poll 2.", days=-5) 99 | response = self.client.get(reverse('polls:index')) 100 | self.assertQuerysetEqual( 101 | response.context['latest_poll_list'], 102 | ['', ''] 103 | ) 104 | 105 | 106 | class PollIndexDetailTests(TestCase): 107 | def test_detail_view_with_a_future_poll(self): 108 | """ 109 | The detail view of a poll with a pub_date in the future should 110 | return a 404 not found. 111 | """ 112 | future_poll = create_poll(question='Future poll.', days=5) 113 | response = self.client.get(reverse('polls:detail', args=(future_poll.id,))) 114 | self.assertEqual(response.status_code, 404) 115 | 116 | def test_detail_view_with_a_past_poll(self): 117 | """ 118 | The detail view of a poll with a pub_date in the past should display 119 | the poll's question. 120 | """ 121 | past_poll = create_poll(question='Past Poll.', days=-5) 122 | response = self.client.get(reverse('polls:detail', args=(past_poll.id,))) 123 | self.assertContains(response, past_poll.question, status_code=200) -------------------------------------------------------------------------------- /polls/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, url 2 | 3 | from . import views 4 | 5 | 6 | urlpatterns = patterns('', 7 | url(r'^$', views.IndexView.as_view(), name='index'), 8 | url(r'^(?P\d+)/$', views.DetailView.as_view(), name='detail'), 9 | url(r'^(?P\d+)/results/$', views.ResultsView.as_view(), name='results'), 10 | url(r'^(?P\d+)/vote/$', views.vote, name='vote'), 11 | ) 12 | -------------------------------------------------------------------------------- /polls/views.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.http import HttpResponseRedirect 3 | from django.shortcuts import get_object_or_404, render 4 | from django.utils import timezone 5 | from django.views import generic 6 | 7 | from .models import Choice, Poll 8 | 9 | 10 | class IndexView(generic.ListView): 11 | template_name = 'polls/index.html' 12 | context_object_name = 'latest_poll_list' 13 | 14 | def get_queryset(self): 15 | """ 16 | Return the last five published polls (not including those set to be 17 | published in the future). 18 | """ 19 | return Poll.objects.filter( 20 | pub_date__lte=timezone.now() 21 | ).order_by('-pub_date')[:5] 22 | 23 | 24 | class DetailView(generic.DetailView): 25 | model = Poll 26 | template_name = 'polls/detail.html' 27 | 28 | def get_queryset(self): 29 | """ 30 | Excludes any polls that aren't published yet. 31 | """ 32 | return Poll.objects.filter(pub_date__lte=timezone.now()) 33 | 34 | 35 | class ResultsView(generic.DetailView): 36 | model = Poll 37 | template_name = 'polls/results.html' 38 | 39 | 40 | def vote(request, poll_id): 41 | p = get_object_or_404(Poll, pk=poll_id) 42 | try: 43 | selected_choice = p.choice_set.get(pk=request.POST['choice']) 44 | except (KeyError, Choice.DoesNotExist): 45 | # Redisplay the poll voting form. 46 | return render(request, 'polls/detail.html', { 47 | 'poll': p, 48 | 'error_message': "You didn't select a choice.", 49 | }) 50 | else: 51 | selected_choice.votes += 1 52 | selected_choice.save() 53 | # Always return an HttpResponseRedirect after successfully dealing 54 | # with POST data. This prevents data from being posted twice if a 55 | # user hits the Back button. 56 | return HttpResponseRedirect(reverse('polls:results', args=(p.id,))) 57 | --------------------------------------------------------------------------------