How to Build a Web Application from Scratch Using Python and Django
Django is a full stack web framework, a collection of Python libraries that allows users to build a quality web application efficiently. Django provides every mean for backend such as “Django admin”, which is a built-in backend that let users to administer their web applications without letting them having to code much. In this article, we are going to implement a blog that gives your blog some functionalities, such as post. In other words, we will build the models like “Post”, which provide the basic operations such as, list, detail, edit, and delete.
If you need more help, you can always check out the following websites: The official source code repository, Django tutorial
Install Django and set up environments
Python installation
If you haven’t installed Django, you can simply install Django using Python package manager, “pip”.
$ shell> pip install “django>=1.10” |
You should install Django 1.10 version or above.
Set up an environment for web development
python3 -m venv myenv $ source myenv/bin/activate $ pip install django==1.8 |
Create Your First Django Project
Let’s create a project at your current directory that you set up for the project by using these commands.
$ dev/firstProject> python manage.py migrate # apply models to DB $ dev/firstProject> python manage.py createsuperuser # create superuser account $ dev/firstProject> python manage.py runserver # activate development server |
- The Python command could be python, python3 or python3.5 – it depends on your python3 interpreter and machines.
- It is recommended to use Chrome or Firefox as your web browser as your web browser in web development since they provide a quality development tools
Migrations
In Django, you can create a table that is mapped to its model at Database after the creation of Model class. Migration is the process of applying the modification or creation of Python Model class to DB. This is executed through the ORM (Object-Relational Mapping) service that is provided by Django.
There are two major steps, preparation for Migration, and applying it to DB, to create tables from Django Model class. Specifically, you can follow these steps:
- Add Django App you want to INSTALLED_APPS lists at settings.py
- Execute the command below to create or modify table schemas from Model class:
$ ./manage.py makemigrations |
This creates a subfolder called “migrations” within Django App, and make python migration fils to create and modify tables.
- This command is used to apply migrations to DB.
$ ./manage.py migrate |
Create the First app, “Blog”
The structure of the project we are creating is following:
firstProject ├───manage.py # a administrative tool for your web application └───mysite settings.py # a setting file for settings of your web application urls.py # routing, a pattern list for `urlresolver` wsgi.py __init__.py |
1.Add the Basic Settings
Create a file settings.py under mysite/
Set the time zone that you want by modifying TIME_ZONE at settings.py
TIME_ZONE = ‘Asia/Seoul’ |
In order to add the static file path, let’s add the following codes:
STATIC_URL = ‘/static/’ STATIC_ROOT = os.path.join(BASE_DIR, ‘static’) |
Open “mysite/settings.py”, and add the app that we just created to INSTALLED_APPS
INSTALLED_APPS = ( ‘django.contrib.admin’, ‘django.contrib.auth’, ‘django.contrib.contenttypes’, ‘django.contrib.sessions’, ‘django.contrib.messages’, ‘django.contrib.staticfiles’, ‘blog’, ) |
- Set up a Database
By default, sqlite3 is installed, and
DATABASES = { ‘Default’: {‘ENGINE’: ‘django.db.backends.sqlite3’, ‘NAME’: os.path.join(BASE_DIR, ‘db.sqlite3’),}} |
In order to create DB, Django uses manage.py
$ python manage.py migrate |
Now, we can run the server to see whether it actually works
$ python manage.py runserver |
- Create a Model
In Django, a model is a layer that provides data service. It is supposed to be defined at “models.py” that is created by default in each Django App. You can define more than one model class at “models.py” module, and each model class corresponds to each table at database.
class Post(models.Model): title = models.CharField(max_length=100) content = models.TextField() # tags = models.CharField(max_length=100, blank=True) # lnglat = models.CharField(max_length=50, blank=True, help_text=’test’) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) |
def __str__(self): return self.title |
def get_absolute_url(self): return reverse(‘blog:post_detail’, args=[self.pk]) |
There are many field types in Django, and the following chart is a brief summary of primary field types.
Field Type | Info |
CharField | A string field, for small- to large-sized strings. |
IntegerField | An integer. Values from -2147483648 to 2147483647 are safe in all databases supported by Django. |
DecimalField | A fixed-precision decimal number, represented in Python by a Decimal instance. |
TextField | A large text field. |
DateTimeField | A date and time, represented in Python by a datetime.datetime instance. |
BooleanField | A true/false field. |
BinaryField | A field to store raw binary data. |
FileField | A file-upload field. |
ImageField | Inherits all attributes and methods from FileField, but also validates that the uploaded object is a valid image. |
UUIDField | A field for storing universally unique identifiers. |
If you want to understand the model types in the above, You can refer model field type reference here.
Model fields can have many different options or arguments depending their field types. For example, CharField can have “max_length”, which is the option that means the max of character string. Generally, field options are assigned as arguments at constructors. The following chart is a summary of the options that can be applied in all field types.
Field Option | Info |
null (Field.null) | If null=True, store an empty value to DB as NULL. In DB, Null is permitted.
e.g.) models.IntegerField(null=True) |
blank (Field.blank) | If blank=False, the field is Required field, and if blank is True, it’s Optional Field.
e.g.) models.DateTimeField(blank=True) |
primary_key (Field.primary_key) | It shows that the field is Primary Key.
e.g.) models.CharField(max_length=10, primary_key=True) |
unique (Field.unique) | It shows that the field is unique at a table. It also creates Unique Index to its column.
e.g.) models.integerField(unique=True) |
default (Field.default) | It assigns a default value of a field.
e.g.) models.CharField(max_length=3, default=”AA”) |
To use the model created, make migration files, and execute migrations to database.
$ python manage.py makemigrations blog $ python manage.py migrate blog |
Django Framework provides ForeignKey, ManyToManyField, and OneToOneField classes to express relationships between tables or fields. In particular, ForeignKey is commonly used to express relationships of models, tables, Many-To-One, and One-To-Many. If you want to get more information about this, please look up this link: https://docs.djangoproject.com/es/1.11/ref/models/fields/#module-django.db.models.fields.related
- Create Django Administrator
We need to add these lines at “blog/admin.py” to be accessed from administrative panel
from django.contrib import admin from .models import Post |
admin.site.registerfrom django.contrib import admin from .models import PostPost) |
$ python manage.py createsuperuser $ python manage.py runserver |
Create an id to log in the panel.
To test the above, try to add a post at Posts
- Upload the project to Git repository
Initialize a repository and commit it.
If you need more help, see this tutorial:
https://git-scm.com/book/en/v2/Getting-Started-Getting-Help
- Add URL patterns
Django uses regular expressions for URL. Bring blog to mysite by following these codes
from django.conf.urls import include, url from django.contrib import admin urlpatterns = [ url(r’^admin/’, include(admin.site.urls)), url(r”, include(‘blog.urls’)), ] |
Add “blog/url.py”
from django.conf.urls import url from . import views urlpatterns = [ url(r’^$’, views.post_list, name=‘post_list’), ] |
- Create Views
In Django, a view is like “Controller” of other MVC Framework. It is a place that holds a logic of an application. It gets some information from the model we created, and it pass the information to templates. Views are supposed to be defined at views.py of Django App, and each function defines each view. Each view, a callable object, takes HTTP as an input parameter, and returns HTTP Response.
Open “blog/views.py”, and create “post_list”.
from django.shortcuts import render def post_list(request): return render(request, ‘blog/post_list.html’, {}) |
- Create HTML Templates
Whereas View in Django has the role of the Controller has the role of the Controller in other MVC Framework, Django’s Template works as View in other MVC Framework. Templates are used to build dynamic web pages applying data that is delivered from View to the templates. Django development guidelines recommends to follow this directory structure such as, “/home/templates/home/index.html”. The reason why the directory structure is encouraged is that there might be name collision in the process of importing templates. For example, if there is “layout.html” at App1, and App2 has a template that has the same file name, and if App2’s view calls “create.html”, it uses “create.html” of App1. This is because when it finds its templates, it doesn’t really find templates within its app, but it finds template folders in the entire App in order.
Create “post_list.html” under “blog/templates/blog”. Be careful with the directory structure.
- Add Django ORM and QuerySets
In Django, QuerySets(https://docs.djangoproject.com/en/2.0/ref/models/querysets/) is used to align or process data.
Access shell using this command at console.
$ python manage.py shell |
After import models, we can use QuerySets.
>>> from blog.models import Post >>> Post.objects.all() [<Post: Hello World>, <Post: Koala>] |
>>> from django.contrib.auth.models import User >>> User.objects.all() [<User: edward>] >>> me = User.objects.get(username=’edward’) |
>>> Post.objects.create(author=me, title=’Goodbye my friend’, text=’bye’) <Post: Goodbye my friend> >>> Post.objects.all() [<Post: Hello World>, <Post: Koala>, <Post: Goodbye my friend>] |
You can do filter QuerySets as below shows.
>>> Post.objects.filter(author=me) [<Post: Hello World>, <Post: Koala>, <Post: Goodbye my friend>] >>> Post.objects.filter(title__contains=’Koala’) [<Post: Koala>] |
>>> from django.utils import timezone >>> Post.objects.filter(published_date__lte=timezone.now()) [] >>> post = Post.objects.get(title__contains=”Goodbye”) >>> post.publish() >>> Post.objects.filter(published_date__lte=timezone.now()) [<Post: Goodbye my friend>] |
It is possible to align and call it all at once by chaining.
>>> Post.objects.order_by(‘created_date’) [<Post: Hello World>, <Post: Koala>, <Post: Goodbye my friend>] >>> Post.objects.order_by(‘-created_date’) [<Post: Goodbye my friend>, <Post: Koala>, <Post: Hello World>] >>> Post.objects.filter(published_date__lte=timezone.now()).order_by(‘published_date’) |
- Add Dynamic Data in Templates
Modify “blog/views.py”.
from django.shortcuts import render from django.utils import timezone from .models import Post |
def post_list(request): posts = Post.objects.filter(published_date__lte=timezone.now()).order_by(‘published_date’) return render(request, ‘blog/post_list.html’, {‘post’: posts}) |
Modify “blog/templates/blog/post_list.html”.
<div> <h1><a href=“/”>First Blog</a></h1> </div> {% for post in posts %} <div> <p>published: {{ post.published_date }}</p> <h1><a href=“”>{{ post.title }}</a></h1> <p>{{ post.text|linebreaks }}</p> </div> {% endfor %} |
- Add CSS
Put static files to “blog/static”.
Write out “blog/static/css/blog.css”.
h1 a { color: #FCA205; } |
Apply it to templates
{% load staticfiles %} <!doctype html> <html lang=“ko”> <head> <meta charset=“utf-8”> <title>First Blog </title> <link rel=“stylesheet” href=“{% static ‘css/blog.css’ %}”> </head> <body> <div> <h1><a href=“/”>First Blog</a></h1> </div> {% for post in posts %} <div> <p>published: {{ post.published_date }}</p> <h1><a href=“”>{{ post.title }}</a></h1> <p>{{ post.text|linebreaks }}</p> </div> {% endfor %} </body> </html> |
- Template Extension
When you develop a website, you will notice that many pages have the common HTML codes. It is very inefficient to use the repeated code at web pages. Django provides a functionality called, “Template Extension” or “Template Inheritance”, which is like Inheritance in Object Oriented Programming to solve the problem.
Write out “base.html” as follows.
{% load staticfiles %} <!doctype html> <html lang=“ko”> <head> <meta charset=“utf-8”> <title>First Blog </title> <link rel=“stylesheet” href=“//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css”> <link rel=“stylesheet” href=“//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css”> <link rel=“stylesheet” href=“{% static ‘css/blog.css’ %}”> </head> <body> <div class=“page-header”> <h1><a href=“/”>First Blog</a></h1> </div> <div class=“content container”> <div class=“row”> <div class=“col-md-8”> {% block content %} {% endblock %} </div> </div> </div> </body> </html> |
Now import the base file, and put the contents at the content block.
- Create a Page to Add Post Details
Now we are going to make a page to see blog posts.
Modify the link of “blog/templates/blog/post_list.html”.
<h1><a href=“{% url ‘post_detail’ pk=post.pk %}”>{{ post.title }}</a></h1> |
Add “post_detail” at “blog/urls.py”, which means we are going to store the contents at “pk” variable. Pk means primary key.
from django.conf.urls import url from . import views urlpatterns = [ url(r’^$’, views.post_list, name=‘post_list’), url(r’^post/(?P<pk>[0-9]+)/$’, views.post_detail, name=‘post_detail’), ] |
Add post_detail at “blog/views.py”. If there is no key, 404 Not Found page is needed. In this case, the function, “get_object_or_404”, can be used.
from django.shortcuts import render, get_object_or_404 # … def post_detail(request, pk): post = get_object_or_404(Post, pk=pk) return render(request, ‘blog/post_detail.html’, {‘post’: post}) |
Now create “blog/templates/blog/post_detail.html”.
{% extends ‘blog/base.html’ %} {% block content %} <div class=“post”> {% if post.published_date %} <div class=“date”> {{ post.published_date }} </div> {% endif %} <h1>{{ post.title }}</h1> <p>{{ post.text|linebreaks }}</p> </div> {% endblock %} |
- Create a Form to View Posts
Django provide a functionality that can simply create forms.
Create “blog/forms.py”
from django import forms from .models import Post class PostForm(forms.ModelForm): class Meta: model = Post fields = (‘title’, ‘text’,) |
Add a link that can access to the form at “blog/templates/blog/base.html”
<a href=“{% url ‘post_new’ %}” class=“top-menu”><span class=“glyphicon glyphicon-plus”></span></a> |
Add this at “blog/url.py”
url(r’^post/new/$’, views.post_new, name=‘post_new’), |
Add form and a view, “post_new”, at “blog/views.py”.
from django.shortcuts import redirect from .forms import PostForm #… def post_new(request): if request.method == “POST”: form = PostForm(request.POST) if form.is_valid(): post = form.save(commit=False) post.author = request.user post.published_date = timezone.now() post.save() return redirect(‘blog.views.post_detail’, pk=post.pk) else: form = PostForm() return render(request, ‘blog/post_edit.html’, {‘form‘: form}) |
- Create a Page to Edit Posts
Add “blog/templates/blog/post_edit.html”
{% extends ‘blog/base.html’ %} {% block content %} <h1>New post</h1> <form method=“POST” class=“post-form”>{% csrf_token %} {{ form.as_p }} <button type=“submit” class=“save btn btn-default”>Save</button> </form> {% endblock %} |
Add this following link at “blog/templates/blog/post_detail.html”
<a class=“btn btn-default” href=“{% url ‘post_edit’ pk=post.pk %}”><span class=“glyphicon glyphicon-pencil”></span></a> |
Add the following code at “blog/urls.py”
url(r’^post/(?P<pk>[0-9]+)/edit/$’, views.post_edit, name=‘post_edit’), |
Add a view, “post_edit” at “blog/views.py”
def post_edit(request, pk): post = get_object_or_404(Post, pk=pk) if request.method == “POST”: form = PostForm(request.POST, instance=post) if form.is_valid(): post = form.save(commit=False) post.author = request.user post.published_date = timezone.now() post.save() return redirect(‘blog.views.post_detail’, pk=pk) else: form = PostForm(instance=post) return render(request, ‘blog/post_edit.html’, {‘form‘: form}) |
Initialize the contents in advance by adding an instance at “PostForm()”.
What’s Next?
If you want to learn more about web development or need any further resources, you can try the following references.
The official tutorial of Django
Hello Web App: Learn How to Build a Web App
Django Tutorial: The Local library Website