Feature #878
Updated by Daniel Curtis about 8 years ago
{{>toc}}
This is a condensed version of the Django tutorial found "here":https://docs.djangoproject.com/en/1.10/intro/tutorial01/
h1. Setup The Environment
* Setup a python virtual environment to develop in
#* For Arch Linux use issue #876
#* For FreeBSD use issue #874
h1. Install Django
* Switch to the pydev user and activate the virtualenv:
<pre>
su - pydev
source ~/venv/new_app/bin/activate
</pre>
* And install Django:
<pre>
pip install Django
</pre>
h1. New Django App
* Create a new Django project:
<pre>
django-admin startproject www_example_com
cd www_example_com
</pre>
* Edit the project settings.py file:
<pre>
vi www_example_com/settings.py
</pre>
#* And add the host IP address of the local development machine:
<pre>
ALLOWED_HOSTS = ['192.168.1.90']
</pre>
* Test the new app by running the development test web server:
<pre>
./manage.py runserver 0.0.0.0:8000
</pre>
*NOTE*: Press @CTRL+C@ to stop the test web server
* Now create a new simple poll web application:
<pre>
./manage.py startapp polls
</pre>
h2. Initial Framework
h3. Create View
* Create the initial view:
<pre>
vi polls/views.py
</pre>
#* And add the following:
<pre>
from django.shortcuts import render
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
</pre>
* Now map the view to a URL by creating:
<pre>
vi polls/urls.py
</pre>
#* And add the following:
<pre>
from django.conf.urls import url
from . import views
app_name = 'polls'
urlpatterns = [
url(r'^$', views.index, name='index'),
]
</pre>
* Next, point the root URLconf at the polls.urls module:
<pre>
vi www_example_com/urls.py
</pre>
#* And add the URL handler the @polls/@ part of the web application:
<pre>
from django.conf.urls import include, url
from django.contrib import admin
app_name = 'polls'
urlpatterns = [
url(r'^polls/', include('polls.urls')),
url(r'^admin/', admin.site.urls),
]
</pre>
* Rerun the development test server:
<pre>
./manage.py runserver 0.0.0.0:8000
</pre>
#* And open a web browser and go to http://192.168.1.90/polls
*NOTE*: Press @CTRL+C@ to stop the test web server
h2. Connect to a Database
* Begin by initializing the database:
<pre>
./manage.py migrate
</pre>
h3. Create Model
* Create the data model file:
<pre>
vi polls/models.py
</pre>
#* And add the following to create two data models, *Question* and *Choice*:
<pre>
from django.db import models
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
</pre>
* Edit the @www_example_com/settings.py@ file:
<pre>
vi www_example_com/settings.py
</pre>
#* And add the *@polls.apps.PollsConfig@* dotted path to the INSTALLED_APPS setting to include the app in our project:
<pre>
INSTALLED_APPS = [
'polls.apps.PollsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
</pre>
* Now create the polls data model migrations to update the database structure:
<pre>
./manage.py makemigrations polls
</pre>
* Then apply the migration:
<pre>
./manage.py migrate
</pre>
h3. Adding Questions
* Start the management shell:
<pre>
./manage.py shell
</pre>
#* And run the following to create a question:
<pre>
import django
from polls.models import Question, Choice
from django.utils import timezone
q = Question(question_text="What's new?", pub_date=timezone.now())
q.save()
</pre>
h3. Adding Choices
* Start the management shell:
<pre>
./manage.py shell
</pre>
#* And run the following to create two choices:
<pre>
import django
from polls.models import Question, Choice
from django.utils import timezone
q = Question.objects.get(pk=1)
q.choice_set.create(choice_text='Not much', votes=0)
q.choice_set.create(choice_text='Same Stuff, Different Day', votes=0)
</pre>
h2. Admin Interface
* Create an admin user:
<pre>
./manage.py createsuperuser
</pre>
* Edit the admin file for the poll app:
<pre>
vi polls/admin.py
</pre>
#* And add the following to make the poll app modifiable in the admin interface
<pre>
from django.contrib import admin
from .models import Question
admin.site.register(Question)
</pre>
* Rerun the development test server:
<pre>
./manage.py runserver 0.0.0.0:8000
</pre>
#* And open a web browser and go to http://192.168.1.90/admin
*NOTE*: Press @CTRL+C@ to stop the test web server
h2. Extending a View With Arguments
* Edit the polls/views.py file:
<pre>
vi polls/views.py
</pre>
#* Add a few more views to polls/views.py. These views are slightly different, because they take an argument:
<pre>
# Add after the index function
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)
</pre>
* Then edit the polls/urls.py file:
<pre>
vi polls/urls.py
</pre>
#* And add the new views into the polls.urls module by adding the following url() calls:
<pre>
from django.conf.urls import url
from . import views
app_name = 'polls'
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
</pre>
h2. Dynamic Views
* Edit the views of the polls app:
<pre>
vi polls/views.py
</pre>
#* And refactor the index() view to display the latest 5 poll questions in the system, separated by commas, according to publication date:
<pre>
from django.http import HttpResponse
from django.shortcuts import render
from .models import Question
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)
# Leave the rest of the views (detail, results, vote) unchanged
</pre>
* Create a directory for templates in the polls app, then another directory in the templates directory for polls:
<pre>
mkdir polls/templates
mkdir polls/templates/polls
</pre>
* Then create a template for the index of the polls app:
<pre>
vi polls/templates/polls/index.html
</pre>
#* And add the following:
<pre>
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="{% url 'polls:detail' question.id %}/">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
</pre>
h2. 404 Error Page
* Edit the polls app views:
<pre>
vi polls/views.py
</pre>
#* And refactor the detail() view to display a 404 error for non-existent objects:
<pre>
from django.shortcuts import get_object_or_404, render
from .models import Question
# ...
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
</pre>
* And create a template for the detail page in the polls app:
<pre>
vi polls/templates/polls/detail.html
</pre>
#* And add the following:
<pre>
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
</pre>
h1. Create a Form
* Edit the polls app detail template:
<pre>
vi polls/templates/polls/detail.html
</pre>
#* And refactor the template to add an HTML @<form>@ element:
<pre>
<h1>{{ question.question_text }}</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>
</pre>
* Edit the polls app views handler:
<pre>
vi polls/views.py
</pre>
#* And refactor the @vote()@ and @results()@ functions to create the real versions:
<pre>
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
from django.urls import reverse
from .models import Choice, Question
# ...
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
# Redisplay the question voting form.
return render(request, 'polls/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
</pre>
* Create the polls app template for the results:
<pre>
vi polls/templates/polls/results.html
</pre>
#* And add the following:
<pre>
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
</pre>
h1. Refactoring
* Edit the polls app URL handler:
<pre>
vi polls/urls.py
</pre>
#* And refactor the second and third URL regexes that handle the Detail and Results views:
<pre>
from django.conf.urls import url
from . import views
app_name = 'polls'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'),
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
</pre>
* Then edit the polls app views handler:
<pre>
vi polls/views.py
</pre>
* Refactor the old @index()@, @detail()@, and @results()@ views and use Django’s generic views instead:
<pre>
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.views import generic
from django.utils import timezone
from .models import Choice, Question
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
"""
Return the last five published questions (not including those set to be
published in the future).
"""
return Question.objects.filter(
pub_date__lte=timezone.now()
).order_by('-pub_date')[:5]
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'
def get_queryset(self):
"""
Excludes any questions that aren't published yet.
"""
return Question.objects.filter(pub_date__lte=timezone.now())
class ResultsView(generic.DetailView):
model = Question
template_name = 'polls/results.html'
</pre>
h1. Static Files
h2. CSS
* Create a CSS file for the polls app:
<pre>
vi polls/static/polls/style.css
</pre>
#* And add the following:
<pre>
li a {
color: green;
}
</pre>
* Next, edit the polls app index.html template:
<pre>
vi polls/templates/polls/index.html
</pre>
#* An add the following at the top:
<pre>
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />
</pre>
h2. Background Image
* Create an images subdirectory in the polls/static/polls/ directory:
<pre>
mkdir polls/static/polls/image
</pre>
*NOTE*: Inside this directory, put an image called background.gif.
* Then, edit the stylesheet created earlier:
<pre>
vi polls/static/polls/style.css
</pre>
#* And add the following:
<pre>
body {
background: white url("images/background.gif") no-repeat right bottom;
}
</pre>
* Set the STATIC_ROOT setting to the directory from which you’d like to serve these files:
<pre>
STATIC_ROOT = "/var/www/example.com/static/"
</pre>
* Run the collectstatic management command:
<pre>
./manage.py collectstatic
</pre>
h1. Resources
* https://docs.djangoproject.com/en/1.10/intro/tutorial01/