Project

General

Profile

Feature #878

Basic Django Web Application

Added by Daniel Curtis almost 4 years ago. Updated over 3 years ago.

Status:
Closed
Priority:
Normal
Assignee:
Target version:
Start date:
12/04/2016
Due date:
% Done:

100%

Spent time:

Description

This is a condensed version of the Django tutorial found here

Setup The Environment

  • Setup a python virtual environment to develop in
    • For Arch Linux use issue #876
    • For FreeBSD use issue #874

Install Django

  • Switch to the pydev user and activate the virtualenv:
    su - pydev
    source ~/venv/new_app/bin/activate
    
  • And install Django:
    pip install Django
    

New Django App

  • Create a new Django project:
    django-admin startproject www_example_com
    cd www_example_com
    
  • Edit the project settings.py file:
    vi www_example_com/settings.py
    
    • And add the host IP address of the local development machine:
      ALLOWED_HOSTS = ['192.168.1.90']
      
  • Test the new app by running the development test web server:
    ./manage.py runserver 0.0.0.0:8000
    

    NOTE: Press CTRL+C to stop the test web server
  • Now create a new simple poll web application:
    ./manage.py startapp polls
    

Initial Framework

Create View

  • Create the initial view:
    vi polls/views.py
    
    • And add the following:
      from django.shortcuts import render
      from django.http import HttpResponse
      
      def index(request):
          return HttpResponse("Hello, world. You're at the polls index.")
      
  • Now map the view to a URL by creating:
    vi polls/urls.py
    
    • And add the following:
      from django.conf.urls import url
      
      from . import views
      
      app_name = 'polls'
      urlpatterns = [
          url(r'^$', views.index, name='index'),
      ]
      
  • Next, point the root URLconf at the polls.urls module:
    vi www_example_com/urls.py
    
    • And add the URL handler the polls/ part of the web application:
      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),
      ]
      
  • Rerun the development test server:
    ./manage.py runserver 0.0.0.0:8000
    

Connect to a Database

  • Begin by initializing the database:
    ./manage.py migrate
    

Create Model

  • Create the data model file:
    vi polls/models.py
    
    • And add the following to create two data models, Question and Choice:
      import datetime
      from django.db import models
      from django.utils import timezone
      
      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):
              now = timezone.now()
              return now - datetime.timedelta(days=1) <= self.pub_date <= now
      
      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
      
  • Edit the www_example_com/settings.py file:
    vi www_example_com/settings.py
    
    • And add the polls.apps.PollsConfig dotted path to the INSTALLED_APPS setting to include the app in our project:
      INSTALLED_APPS = [
          'polls.apps.PollsConfig',
          'django.contrib.admin',
          'django.contrib.auth',
          'django.contrib.contenttypes',
          'django.contrib.sessions',
          'django.contrib.messages',
          'django.contrib.staticfiles',
      ]
      
  • Now create the polls data model migrations to update the database structure:
    ./manage.py makemigrations polls
    
  • Then apply the migration:
    ./manage.py migrate
    

Adding Questions

  • Start the management shell:
    ./manage.py shell
    
    • And run the following to create a question:
      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()
      

Adding Choices

  • Start the management shell:
    ./manage.py shell
    
    • And run the following to create two choices:
      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)
      

Admin Interface

  • Create an admin user:
    ./manage.py createsuperuser
    
  • Edit the admin file for the poll app:
    vi polls/admin.py
    
    • And add the following to make the poll app modifiable in the admin interface
      from django.contrib import admin
      from .models import Question
      
      admin.site.register(Question)
      
  • Rerun the development test server:
    ./manage.py runserver 0.0.0.0:8000
    

Extending a View With Arguments

  • Edit the polls/views.py file:
    vi polls/views.py
    
    • Add a few more views to polls/views.py. These views are slightly different, because they take an argument:
      # 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)
      
  • Then edit the polls/urls.py file:
    vi polls/urls.py
    
    • And add the new views into the polls.urls module by adding the following url() calls:
      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'),
      ]
      

Dynamic Views

  • Edit the views of the polls app:
    vi polls/views.py
    
    • And refactor the index() view to display the latest 5 poll questions in the system, separated by commas, according to publication date:
      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
      
  • Create a directory for templates in the polls app, then another directory in the templates directory for polls:
    mkdir polls/templates
    mkdir polls/templates/polls
    
  • Then create a template for the index of the polls app:
    vi polls/templates/polls/index.html
    
    • And add the following:
      {% 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 %}
      

404 Error Page

  • Edit the polls app views:
    vi polls/views.py
    
    • And refactor the detail() view to display a 404 error for non-existent objects:
      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})
      
  • And create a template for the detail page in the polls app:
    vi polls/templates/polls/detail.html
    
    • And add the following:
      <h1>{{ question.question_text }}</h1>
      <ul>
      {% for choice in question.choice_set.all %}
          <li>{{ choice.choice_text }}</li>
      {% endfor %}
      </ul>
      

Create a Form

  • Edit the polls app detail template:
    vi polls/templates/polls/detail.html
    
    • And refactor the template to add an HTML <form> element:
      <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>
      
  • Edit the polls app views handler:
    vi polls/views.py
    
    • And refactor the vote() and results() functions to create the real versions:
      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,)))
      
  • Create the polls app template for the results:
    vi polls/templates/polls/results.html
    
    • And add the following:
      <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>
      

Refactoring

  • Edit the polls app URL handler:
    vi polls/urls.py
    
    • And refactor the second and third URL regexes that handle the Detail and Results views:
      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'),
      ]
      
  • Then edit the polls app views handler:
    vi polls/views.py
    
  • Refactor the old index(), detail(), and results() views and use Django’s generic views instead:
    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'
    

Static Files

CSS

  • Create a CSS file for the polls app:
    vi polls/static/polls/style.css
    
    • And add the following:
      li a {
          color: green;
      }
      
  • Next, edit the polls app index.html template:
    vi polls/templates/polls/index.html
    
    • An add the following at the top:
      {% load static %}
      
      <link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />
      

Background Image

  • Create an images subdirectory in the polls/static/polls/ directory:
    mkdir polls/static/polls/image
    

    NOTE: Inside this directory, put an image called background.gif.
  • Then, edit the stylesheet created earlier:
    vi polls/static/polls/style.css
    
    • And add the following:
      body {
          background: white url("image/background.gif") no-repeat right bottom;
      }
      
  • Set the STATIC_ROOT setting to the directory from which you’d like to serve these files:
    STATIC_URL = '/static/'
    STATIC_ROOT = os.path.join(BASE_DIR, 'static')
    
  • Run the collectstatic management command:
    ./manage.py collectstatic
    

Resources

History

#1 Updated by Daniel Curtis almost 4 years ago

  • Description updated (diff)
  • Status changed from New to In Progress
  • % Done changed from 0 to 20

#2 Updated by Daniel Curtis almost 4 years ago

  • Description updated (diff)

#3 Updated by Daniel Curtis almost 4 years ago

  • % Done changed from 20 to 40
  • Description updated (diff)
  • Start date changed from 12/03/2016 to 12/04/2016

#4 Updated by Daniel Curtis almost 4 years ago

  • Description updated (diff)

#5 Updated by Daniel Curtis almost 4 years ago

  • Description updated (diff)
  • % Done changed from 40 to 50

#6 Updated by Daniel Curtis almost 4 years ago

  • Description updated (diff)

#7 Updated by Daniel Curtis almost 4 years ago

  • Description updated (diff)
  • % Done changed from 50 to 60

#8 Updated by Daniel Curtis almost 4 years ago

  • Description updated (diff)
  • % Done changed from 60 to 100

#9 Updated by Daniel Curtis almost 4 years ago

  • Status changed from In Progress to Resolved
  • Description updated (diff)

#10 Updated by Daniel Curtis almost 4 years ago

  • Description updated (diff)

#11 Updated by Daniel Curtis almost 4 years ago

  • Description updated (diff)

#12 Updated by Daniel Curtis almost 4 years ago

  • Description updated (diff)

#13 Updated by Daniel Curtis over 3 years ago

  • Status changed from Resolved to Closed

Also available in: Atom PDF