Simple Django Web Application Tutorial

Published Feb 01, 2017Last updated Sep 17, 2017
Simple Django Web Application Tutorial

The source code for this project is available from:
https://github.com/rogargon/myrecommendations/tree/web2-html

Starting a Project

After installing Python and Django,
it is possible to create a new Django project from the command line.
In our case the project is called 'myrecommendations':

django-admin.py startproject myrecommendations

cd myrecommendations

mkdir templates

In myrecommendations/settings.py

  • Review your database settings, for instance for an SQLite database they should be:
DATABASES = {
    'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
  • And register the templates folder adding it to the list of 'DIRS':
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        ...
    },
]

Finally, let Django take control of the database:

python manage.py migrate

The 'migrate' command looks at INSTALLED_APPS defined in 'settings.py' and creates
all required database tables according to the database settings.

Finally, create the admin user:

python manage.py createsuperuser

Creating an Application

Now that the project is ready, it is time to define project applications.
In the case of this tutorial there is just one application, called 'myrestaurants'.

python manage.py startapp myrestaurants

Then, add 'myrestaurants' to the INSTALLED_APPS list in myrecommendations/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myrestaurants',
]

Create your Data Model

To define the 'myrestaurants' data model composed of 'Restaurant', 'Dish', 'Review' and 'RestaurantReview', add the following code to the file 'models.py' in the new application
folder:

from django.db import models
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from datetime import date

class Restaurant(models.Model):
    name = models.TextField()
    street = models.TextField(blank=True, null=True)
    number = models.IntegerField(blank=True, null=True)
    city = models.TextField(default="")
    zipCode = models.TextField(blank=True, null=True)
    stateOrProvince = models.TextField(blank=True, null=True)
    country = models.TextField(blank=True, null=True)
    telephone = models.TextField(blank=True, null=True)
    url = models.URLField(blank=True, null=True)
    user = models.ForeignKey(User, default=1)
    date = models.DateField(default=date.today)

    def __unicode__(self):
        return u"%s" % self.name

class Dish(models.Model):
    name = models.TextField()
    description = models.TextField(blank=True, null=True)
    price = models.DecimalField('Euro amount', max_digits=8, decimal_places=2, blank=True, null=True)
    user = models.ForeignKey(User, default=1)
    date = models.DateField(default=date.today)
    image = models.ImageField(upload_to="myrestaurants", blank=True, null=True)
    restaurant = models.ForeignKey(Restaurant, null=True, related_name='dishes')

    def __unicode__(self):
        return u"%s" % self.name

class Review(models.Model):
    RATING_CHOICES = ((1, 'one'), (2, 'two'), (3, 'three'), (4, 'four'), (5, 'five'))
    rating = models.PositiveSmallIntegerField('Rating (stars)', blank=False, default=3, choices=RATING_CHOICES)
    comment = models.TextField(blank=True, null=True)
    user = models.ForeignKey(User, default=1)
    date = models.DateField(default=date.today)

    class Meta:
        abstract = True

class RestaurantReview(Review):
    restaurant = models.ForeignKey(Restaurant)

Once the model is defined, it is time to update the database schema to accommodate the previous data model entities:

python manage.py makemigrations myrestaurants

python manage.py migrate

Optionally, register your model with the administrative interface (if you
have the admin application enabled under INSTALLED_APPS in
settings.py), so you get a user interface for CRUD operations for free in '<URL>/admin’.

First, in myrecommendations/settings.py, check that installed
applications includes:

'django.contrib.admin',

Finally, in admin.py in the myrestaurants directory, include:

from django.contrib import admin
import models

admin.site.register(models.Restaurant)
admin.site.register(models.Dish)
admin.site.register(models.RestaurantReview)

Now, you can run the server:

python manage.py runserver

And check that you can administrate the new models from:
http://localhost:8000/admin

Design your URLs

From the project root directory, edit myrecommendations/urls.py and add
to the list of 'urlpatterns' those for the application:

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'\^admin/', admin.site.urls),
    url(r'\^myrestaurants/', include('myrestaurants.urls', namespace='myrestaurants')),
]

In the myrestaurants application directory create urls.py with the following code:

from django.conf.urls import url
from django.utils import timezone
from django.views.generic import DetailView, ListView, UpdateView
from models import Restaurant, Dish
from forms import RestaurantForm, DishForm
from views import RestaurantCreate, DishCreate, RestaurantDetail

urlpatterns = [

# List latest 5 restaurants: /myrestaurants/
    url(r'\^\$', 
        ListView.as_view(
        	queryset=Restaurant.objects.filter(date__lte=timezone.now()).order_by('date')[:5],
        	context_object_name='latest_restaurant_list',
        	template_name='myrestaurants/restaurant_list.html'),
        name='restaurant_list'),

# Restaurant details, ex.: /myrestaurants/restaurants/1/
    url(r'\^restaurants/(?P<pk>\d+)/\$',
        RestaurantDetail.as_view(),
        name='restaurant_detail'),

# Restaurant dish details, ex: /myrestaurants/restaurants/1/dishes/1/
    url(r'\^restaurants/(?P<pkr>\d+)/dishes/(?P<pk>\d+)/\$',
        DetailView.as_view(
        	model=Dish,
        	template_name='myrestaurants/dish_detail.html'),
        name='dish_detail'),

# Create a restaurant, /myrestaurants/restaurants/create/
    url(r'\^restaurants/create/\$',
        RestaurantCreate.as_view(),
        name='restaurant_create'),

# Edit restaurant details, ex.: /myrestaurants/restaurants/1/edit/
    url(r'\^restaurants/(?P<pk>\d+)/edit/\$',
        UpdateView.as_view(
        	model = Restaurant,
        	template_name = 'myrestaurants/form.html',
        	form_class = RestaurantForm),
        name='restaurant_edit'),

# Create a restaurant dish, ex.: /myrestaurants/restaurants/1/dishes/create/
    url(r'\^restaurants/(?P<pk>\\d+)/dishes/create/\$',
    	DishCreate.as_view(),
        name='dish_create'),

# Edit restaurant dish details, ex.: /myrestaurants/restaurants/1/dishes/1/edit/
    url(r'\^restaurants/(?P<pkr>\\d+)/dishes/(?P<pk>\\d+)/edit/\$',
    	UpdateView.as_view(
    		model = Dish,
    		template_name = 'myrestaurants/form.html',
    		form_class = DishForm),
    	name='dish_edit'),

# Create a restaurant review, ex.: /myrestaurants/restaurants/1/reviews/create/
# Unlike the previous patterns, this one is implemented using a method view instead of a class view
    url(r'\^restaurants/(?P<pk>\\d+)/reviews/create/\$',
    	'myrestaurants.views.review',
    	name='review_create'),
]

Custom Class Views

Then, define the custom application class views in 'views.py' adding the following code:

from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.views.generic import DetailView
from django.views.generic.edit import CreateView
from models import RestaurantReview, Restaurant, Dish
from forms import RestaurantForm, DishForm

class RestaurantDetail(DetailView):
  model = Restaurant
  template_name = 'myrestaurants/restaurant_detail.html'

  def get_context_data(self, **kwargs):
    context = super(RestaurantDetail, self).get_context_data(**kwargs)
    context['RATING_CHOICES'] = RestaurantReview.RATING_CHOICES
    return context

class RestaurantCreate(CreateView):
  model = Restaurant
  template_name = 'myrestaurants/form.html'
  form_class = RestaurantForm

  def form_valid(self, form):
    form.instance.user = self.request.user
    return super(RestaurantCreate, self).form_valid(form)

class DishCreate(CreateView):
  model = Dish
  template_name = 'myrestaurants/form.html'
  form_class = DishForm
  
  def form_valid(self, form):
    form.instance.user = self.request.user
    form.instance.restaurant = Restaurant.objects.get(id=self.kwargs['pk'])
    return super(DishCreate, self).form_valid(form)

def review(request, pk):
  restaurant = get_object_or_404(Restaurant, pk=pk)
  review = RestaurantReview(
      rating=request.POST['rating'],
      comment=request.POST['comment'],
      user=request.user,
      restaurant=restaurant)
  review.save()
  return HttpResponseRedirect(reverse('myrestaurants:restaurant_detail', args=(restaurant.id,)))

Create your Application Templates

First, create a base.html template in myrestaurants/templates/myrestaurants

{% load staticfiles %}

<!DOCTYPE html>
<html lang="en">

<head>
<link rel="stylesheet" href="{% static "style/base.css" %}" />
<title>{% block title %}MyRestaurants by MyRecommentdations.org{% endblock %}</title>
</head>

<body>

<div id="header">
  {% block header %}
  {% if user.username %}<p>User {{ user.username }}</p>
  {% else %}<p><a href="/login/">Sign in</a></p>{% endif %}
  {% endblock %}
</div>

<div id="sidebar">
  {% block sidebar %}<ul><li><a href="/myrestaurants">Home</a></li></ul>{% endblock %}
</div>

<div id="content">
  {% block content %}
  {% if error_message %}<p><strong>{{ error_message}}</strong></p>{% endif %}
  {% endblock %}
</div>

<div id="footer">
  {% block footer %}{% endblock %}
</div>

</body>

</html>

Next create restaurant_list.html in myrestaurants/templates/myrestaurants

{% extends "myrestaurants/base.html" %}

{% block content %}
<h1>
  Restaurants 
  {% if user %}(<a href="{% url 'myrestaurants:restaurant_create' %}">add</a>){% endif %}
</h1>

<ul>
  {% for restaurant in latest_restaurant_list %}
    <li><a href="{% url 'myrestaurants:restaurant_detail' restaurant.id %}">
    {{ restaurant.name }}</a></li>
  {% empty %}<li>Sorry, no restaurants registered yet.</li>
  {% endfor %}
</ul>
{% endblock %}

And restaurant_detail.html, which includes the list of dishes and the
review form also in myrestaurants/templates/myrestaurants

{% extends "myrestaurants/base.html" %}

{% block content %}
<h1>
  {{ restaurant.name }}
  {% if user == restaurant.user %}
    (<a href="{% url 'myrestaurants:restaurant_edit' restaurant.id %}">edit</a>)
  {% endif %}
</h1>

<h2>Address:</h2>

<p>
  {{ restaurant.street }}, {{ restaurant.number }} <br/>
  {{ restaurant.zipcode }} {{ restaurant.city }} <br/>
  {{ restaurant.stateOrProvince }} ({{ restaurant.country }})
</p>

<h2>Dishes
  {% if user %}
    (<a href="{% url 'myrestaurants:dish_create' restaurant.id %}">add</a>)
  {% endif %}
</h2>

<ul>
  {% for dish in restaurant.dishes.all %}
    <li><a href="{% url 'myrestaurants:dish_detail' restaurant.id dish.id %}">
    {{ dish.name }}</a></li>
  {% empty %}<li>Sorry, no dishes for this restaurant yet.</li>
  {% endfor %}
</ul>

<h2>Reviews</h2>

<ul>
  {% for review in restaurant.restaurantreview_set.all %}
    <li>
      <p>{{ review.rating }} star{{ review.rating|pluralize }}</p>
      <p>{{ review.comment }}</p>
      <p>Created by {{ review.user }} on {{ review.date }}</p>
    </li>
  {% endfor %}
</ul>

<h3>Add Review</h3>

<form action="{% url 'myrestaurants:review_create' restaurant.id %}" method="post">
  {% csrf_token %}

  Message: <textarea name="comment" id="comment" rows="4"></textarea>
  <p>Rating:</p>
  <p>
    {% for rate in RATING_CHOICES %}
      <input type="radio" name="rating" id="rating{{ forloop.counter }}" value="{{ rate.0 }}" />
      <label for="choice{{ forloop.counter }}">{{ rate.1 }} star{{rate.0|pluralize }}</label>
      <br/>
    {% endfor %}
  </p>
  <input type="submit" value="Review" />
</form>

{% endblock %}

{% block footer %}

Created by {{ restaurant.user }} on {{ restaurant.date }}

{% endblock %}

Create Forms

Finally, there are the forms in the new file forms.py that are
automatically created from the Restaurant and Dish models to create and
edit them:

from django.forms import ModelForm
from models import Restaurant, Dish

class RestaurantForm(ModelForm):
  class Meta:
    model = Restaurant
    exclude = ('user', 'date',)

class DishForm(ModelForm):
  class Meta:
    model = Dish
    exclude = ('user', 'date', 'restaurant',)

And the template that shows them, form.html in myrestaurants/templates/myrestaurants

{% extends "myrestaurants/base.html" %}

{% block content %}

<form method="post" action="">
  {% csrf_token %}
  <table>
    {{ form.as_table }}
  </table>
  <input type="submit" value="Submit"/>
</form>

{% endblock %}

Schema Migration

Migrations are how Django stores changes to your models (and thus your
database schema).

Previously, after creating the database, we have enabled the migrations
mechanism with the command:

python manage.py makemigrations myrestaurants

The previous command computes the changes to be performed to the schema,
in this case to create it from scratch, and stores them in
myrestaurants/migrations/0001_initial.py

Then, the following command applied this changes and populates the
database schema:

python manage.py migrate

From this moment, whenever the model is updated, it is possible to
migrate the schema so the data already inserted in the database is
adapted to the new schema database.

The previous step should be then repeated. First, to compute the changes
to be done to the schema and all the instance data currently stored:

python manage.py makemigrations myrestaurants

This will generate a new migration file, like
myrestaurants/migrations/0002_...py

Then, the changes are applied to synchronize the model and the database:

python manage.py migrate

Note: if the migrations mechanism is not activated for a particular app,
when the app model is changed the database must be deleted and
recreated.

Image Field

The image field is a kind of field in the data model that allows associating
images to model entities and storing them.

First of all, it is necessary to install the Python image library
Pillow. Follow:
http://pillow.readthedocs.org/en/latest/installation.html

Then, in myrecommendations/settings.py add:

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

And in myrecommendations/urls.py, add at the end:


from django.conf import settings

if settings.DEBUG:
    urlpatterns += patterns('', 
    	url(r'\^media/(?P<path>.\*)\$', 'django.views.static.serve', 
    		{'document_root': settings.MEDIA_ROOT, }),
)

Finally, in myrestaurants/models.py it is possible now to add an ImageField
to the Dish class to store images of the dishes:

  image = models.ImageField(upload_to="myrestaurants", blank=True, null=True)

This field can be then used from the templates to display images, for instance to the
dish_detail.html template to be added in myrestaurants/templates/myrestaurants:

{% extends "myrestaurants/base.html" %}

{% block content %}

<h1>
  {{ dish.name }}
  {% if user == dish.user %}
    (<a href="{% url 'myrestaurants:dish_edit' dish.restaurant.id dish.id %}">edit</a>)
  {% endif %}
</h1>

<p>{{ dish.description }}</p>

{% if dish.image %}
  <p><img src="{{ dish.image.url }}"/></p>
{% endif %}

<p>Served by 
  <a href="{% url 'myrestaurants:restaurant_detail' dish.restaurant.id %}">
    {{ dish.restaurant.name}}
  </a>
</p>

{% endblock %}

{% block footer %}
  Created by {{ dish.user }} on {{ dish.date }}
{% endblock %}

It is also important, when editing the image field using forms, to add
the appropriate encoding to be used when uploading the image. To do that,
edit form.html and include the appropriate enctype attribute:

{% extends "myrestaurants/base.html" %}

{% block content %}

<form method="post" enctype="multipart/form-data" action="">
  {% csrf_token %}
  <table>
    {{ form.as_table }}
  </table>
  <input type="submit" value="Submit"/>
</form>

{% endblock %}

And remember, if you modify the class Dish to add the new image field image, you
will need to migrate the database to upload the relevant tables so they include
the new field:

python manage.py makemigrations myrestaurants

python manage.py migrate

Otherwise, you will need to remove the database file and start from scratch.

Discover and read more posts from Roberto García
get started
Enjoy this post?

Leave a like and comment for Roberto

13
11
11Replies
Joachim Kre
2 days ago

Félicitations Roberto, très bon tutoriel.

Roberto García
8 days ago

In case of issues, please, use as reference the code at: https://github.com/rogargon/myrecommendations/tree/web2-html

Shipoopi
8 days ago

No prob. Also, I’m having trouble with the “add dish/review/restaurant” functions. This is the error I get:

ImproperlyConfigured at /myrestaurants/restaurants/10/dishes/create/
No URL to redirect to. Either provide a url or define a get_absolute_url method on the Model.

The items get created, but the redirect after creation doesn’t work. I included the form_valid code you supplied, but it doesn’t seem to be doing anything.

I also tried defining a get_absolute_url method on the Model, and this helped…but I can’t re-direct with the parameters I want (i.e. restaurant_id after adding a new dish).

Any ideas?

I’m on Python 2.7/Django 1.11

Show more replies

Get curated posts in your inbox

Read more posts to become a better developer