The Django Sea: Making the blog

Ishan Rawat
The Techie Trio
Published in
15 min readJun 10, 2020

This article is contributed and reviewed by Prakhar Gupta, Akanksha Verma, and Ishan Rawat.

This is the second part of our dive into the sea of Django. If you are a new diver and need some knowledge of what’s happening, quickly go to the first part of this blog. (Here is the link)

So, before diving deeper to continue our journey, we would love to share with you the meaning of the name of the sea we are in. The name of this sea is Django, a Romany term meaning ‘I awake’.

Well, why so? This still remains a mystery to our oceanographers.

Anyways, in our further dives, we shall go through the CRUD(create, read, update and delete) basics for the Django framework by making a simple ‘Blog web app’: where a user can create a blog, update or delete that blog and in addition, can see all the blogs that have been posted (even by other authors).

This tutorial will be covered in these four steps:

  1. Making a Blog app
  2. Making a User app for User Registration
  3. Create a Post in the Blog app
  4. Reading, Deleting, and Updating the post created

Splash! Splash!💦💦

Making Blog App

Application and Routes-

We create an app named blog within our project with the command:

python manage.py startapp blog

The newly created blog directory should look somewhat like:

Let’s talk a bit about two important inbuilt modules(files containing python code) on which we are going to work very soon:

  • models.py — It contains models, each of which maps to a separate table in the database(we’ll tell about the database later on).
  • views.py — Contains views which are Python functions that take a Web request and return a Web response.

If you remember, we had urls.py in our main project. Similarly, we need to create a urls.py module in this blog directory also.

Why? To map URLs of blog views(wait and watch!).

Finally, we come to the most prestigious part: our coding phase!!

Django has many inbuilt functions/objects in different predefined modules, so we shall be importing them in our files. You’ll get used to it soon :)

Now type the following code in urls.py(of the blog which we have just created) in order to import views :

from django.urls import path
from . import views
urlpatterns = [
path(‘’, views.home, name = ‘blog-home’),
path(‘about/’, views.about, name = ‘blog-about’),
]

And now in your project’s(SocialNetwork) urls.py include recently created urls.py(of Blog app), and map it to a route (here we mapped it to ‘ ’).

from django.urls import path, includeurlpatterns = [
path(‘admin/’, admin.site.urls),
path(‘’, include(‘blog.urls’))
]

Guess why we did so?

Now, whenever we type localhost:8000/, we’ll be redirected to urls.py of the blog app.

But, if this style pesters you, you can also fill in space with blog/ for example and then localhost:8000/blog/ shall fulfill the same purpose.

After this, we need to create our views in the views.py module for the routes we set up just now, and for practice, we return an Httpresponse using HttpResponse object, although we are about to create much more than this.

from django.shortcuts import render
from django.http import HttpResponse
def home(request):
return HttpResponse(‘<h1>Blog Home</h1>’)
def about(request):
return HttpResponse(‘<h1>Blog About</h1>’)

Templates -

Only “Blog Home” or “Blog About” looks too vague to us (and hopefully you too).

If I could make customized web pages, the emptiness could be filled. Well, if that is your request, we offer you another kind of file called templates. As you would have guessed, templates are simply web pages which would be rendered as a web response returned from views.py file.

To create the templates, we shall create a new folder inside the blog directory named ‘templates’ and inside that directory, we shall create another directory named ‘blog’.

What the hell !!
Sorry, but dear, this is the Django convention which searches for templates in a manner as:

<app_folder> 
templates
<appname>
<template_name>.html

So all our HTML files shall reside inside the blog folder (inside templates folder).

And for now, we make two templates ‘blog.html’ and ‘about.html’ with just boilerplate code.

<!DOCTYPE html>
<head>
<title>Document</title>
</head>
<body>
</body>
</html>

This still looks very plain(though you can make it awesome! We won’t, we are lazy 😛).

We will only keep them updating with the features we incorporate.

But mind you, the final design would also be pretty shitty! (lazy lazy lazy….)

Coming back to our main work, the directory structure should now look like this.

blog 
templates
blog
blog.html
about.html

After making templates the only step left to render them is to update view.py

So:

from django.shortcuts import renderdef home(request):
return render(request, ‘blog/blog.html’)
def about(request):
return render(request, ‘blog/about.html’)

Now, this should get rendered on running.

Did it? It didn’t 😆.

Okay, okay sorry for this but the only step left (finally, this time finally) is to add our application in settings.py module to make Django recognize.

Let see if you are able to find that file or not…

When you would have found it(after a great struggle), add ‘blog.apps.BlogConfig’ within the INSTALLED APPS list (remember to separate it with a comma).

Another thing we hope you would have found out on your own is the naming convention for the above(we are joking, this wasn’t obvious):

‘<appname>.apps.<appname>Config’

Setting Database and creating SuperUser-

Do you remember that once upon a time we opened the admin page? Yes, that admin page. We are going to talk about it here.

Now, the most immediate thought that comes in mind on seeing the admin page is that there would be a user whose credentials are asked on-page. Well, yes we are going to create that.

We create an admin who is the superuser in Django and further other users with lesser powers are created by this admin(you’ll even find a registration page in the later part of our tutorial).

The admin page

Another note, remember these commands,

python manage.py makemigrations
python manage.py migrate

Yeah! They’ll be explained now. These commands help set up the database for our app. The makemigrations command builds the database and migrate applies changes to it.

Another thing to clarify on the deeds done in the past: when you opened the home page for the first time(to see the rocket), it could have been done only by the startserver command but for opening the admin page all the three commands(the two above and the startserver) were required since admin page requires the establishment of the database while the home page does not.

Now, we hope you get the use of these two commands. They are run when changes are to be applied related to the database or its establishment is required. Else, the startserver command is sufficient for most of the cases.

Damn! Why do we get off the main topic so many times?

So coming back, let’s create our superuser to access the admin page.

python manage.py createsuperuser

And now, enter the credentials asked(put the password as heavy as you can just for fun :P)

Oh! The superuser got created so easily.

With the admin powers now, we can create, delete, or modify the other users. (Though there are none now :P)

Models And Databases -

If we’ve reached this far, another mysterious aspect of the Django sea needs to be disclosed. With secret powers hidden inside its depths, it allows us to use any database of our choice, with something called ORM(Object Relational mapping) which actually maps our line of code in models.py to set up the database(eg. Make a table in SQL database).

Now, are we going to build our models! And for that, we are using SQLite which is the default DBMS for Django(needs no additional installation/configuration). Other DBMS require extra python packages to establish a Django database connection, and we, for sure are very lazy to do any such gimmick.

from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.urls import reverse
# Create your models here.class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User,on_delete=models.CASCADE)
def __str__(self):
return self.title

Reminder — Don’t forget to make migrations and migrate, else the changes won’t reflect in the database.

Making User app

Now, we come to the deeper parts of the sea. Don’t get lost in the dark!

Registration-

After making the superuser of the project we also need to make some normal users who are people who come and write blogs on our website.

And for that, we need to have an explicit user registration.

Well, did we talk about something user-related? Ah, we then need to create another app called users!

This step would look familiar to you:

python manage.py startapp users

The same old tale of setting routes in the app’s urls.py gets a twist here.

This time, we shall directly import the user views in the project’s urls.py and use the register function(defined below in views.py).

If this tale has a twist, let us also reverse our approach this time.

We shall first write views.py and then map the url(s) in urls.py.

Now, let’s think a bit about what we need in the views: A registration form that creates new users with all the required checks to pass them through.

But that looks a bit lengthy.

Thanks to the Django sea! Hidden inside its large caves, exists a creature called the UserCreationForm which does the job for us.

So luckily, this is all we need to do:

from django.shortcuts import render,redirect
from django.contrib.auth.forms import UserCreationForm
def register(request):
if request.method == ‘POST’:
form = UserCreationForm(request.POST)
if form.is_valid():
form.save()
return redirect(‘./../blog/blog.html’)
else:
form = UserCreationForm()
return render(request, ‘users/register.html’, {‘form’: form})

This part needs some explanation: suppose we open the registration page, we send a GET request to the server and the else statement gets triggered. We get in return a web response which on being rendered opens the registration page. On filling the form the information would be sent through a POST request to the page itself and the if statement would get triggered. The form would be validated first by form.is_valid() and then form.save() shall save the user credentials.

Finally, after all this, the user shall be redirected to the home page.

And with this, the logic of the registration page seems finished!

But the registration page of which we are talking about doesn’t exist yet.

Let’s create one!

It will lie in the template directory as explained before-

users
templates
users
register.html

Again, we are very lazy to do any sort of designing in it and hence expect that from you. The only thing we are gonna do is to explain only a crucial part on which the form is based. The rest is just plain HTML!

<form method= “POST”>
{% csrf_token %}
{{ form }}
<button type= “submit”>Submit</button>
</form>

This is the form part of the registration page. The curly braces put on form and csrf_token is a way of accessing variables in an HTML file.

The form is a variable declared in another python file and passed on to this template.

The csrf token is a predefined variable in Django.

The form seems reasonable, but, is this token another mystery of the Django?

Pretty much, yeah!

CSRF stands for Cross-Site Request Forgery(cool name!) and Django has built-in protection against CSRF attacks(these attacks target state-changing requests). In order to protect us from these attacks, it’s mandatory to include csrf_token within any form in Django.

Anyways…

Reminder- Add route in main urls.py for the register template-

from users import views as user_viewsurlpatterns = [
path(‘register/’,user_views.register,name=’register’),
]

We hope that these lines look familiar to you :)

Login And Logout System -

So, we’ve created a user. Now, we will be needing a login and logout system also (so many errands for a user!).

But the good part here is that we, for the first time have the chance to introduce you to another pearl of the Django sea: the class-based views.

Though we will be talking about them in the next heading, we mentioned them here since we are using them now.

So, initially, let us set up routes for login and logout pages, so add(don’t override already present code) this code in your Project’s urls.py:

from django.contrib.auth import views as auth_views
urlpatterns = [
path(‘login/’, auth_views.LoginView.as_view(template_name= ‘users/login.html’), name=’login’),
path(‘logout/’, auth_views.LogoutView.as_view(template_name= ‘users/logout.html’), name=’logout’)
]

LoginView (for user login) and LogoutView (for logout) are two of those class-based views (though again, there’s isn’t much to talk about them here)

Django does not give you templates for them, you need to create them on our own(you know our quality of templates :P).

The login.html template is designed in a similar way as the register template as below.

<!DOCTYPE html>
<html lang=”en”>
<head>
<title>Document</title>
</head>
<body>
<form method=”POST”>
{% csrf_token %}
<fieldset >
<legend>
Login
</legend>
{{form}}
</fieldset>
<button type=”submit”>Login</button>
</form>
<a href=”{% url ‘register’ %}”>Sign up</a>
</body>
</html>

Also, add a redirect URL in settings.py:

LOGIN_REDIRECT_URL = ‘blog-home’

In logout.html, you just need to provide a link for logging in again

<!DOCTYPE html>
<html lang=”en”>
<head>
<title>Document</title>
</head>
<body>
You have Been Logged out
<a href=”{% url ‘login’ %}”>Login Again</a>
</body>
</html>

Add following code block in <body> of blog.html so that login and logout features can be accessed from the home page-

{% if user.is_authenticated %}
<a href=”{%url ‘logout’%}”>Logout</a>
{% else %}
<a href=”{%url ‘login’%}”>Login</a>
<a href=”{%url ‘register’%}”>Sign up</a>
{% endif %}

CRUD And LISTING The Posts-

Before we tell you anything, we must congratulate you that you are indeed an excellent diver who has come with us till this depth.

But, we must warn you that this is the final and the deadliest part of our dive. Be on track with us and do not get lost somewhere…

So, this is the section where we try to explain the class-based views a bit better by implementing CRUD features in our project.

Hence, the class-based views that we shall use are-

  • ListView — To list all posts on the home page i.e. blog.html.
  • DetailView — To see each post in detail.
  • CreateView — To allow users to create posts that show up in blog.html.
  • UpdateView — Update post made using CreateView.
  • DeleteView — Delete a post made by a user.

Also, earlier the views which we used were function-based views(declared with “def” keyword) as we have defined functions.

Note on Syntax information: class <name of inheriting class>(<name of base class>).

List Posts:

Here we make an instance of a class PostListView that inherits class ListView. ListView is a predefined class-based view in Django. We hope that the syntax information supplied by us is of use now.

from django.views.generic import ListView
from .models import Post
class PostListView(ListView):
model = Post #specify model to use
template_name = ‘blog/blog.html’ #specify template
context_object_name = ‘posts’ #specify object name
ordering = [‘-date_posted’]
#to order posts(from newest to oldest)

A short note on template convention- If you remember, function-based views looked up templates in a particular convention.

Similarly, class-based views follow a naming convention to look up for templates.

<model-name>_<viewtype>.html

E.g.- post_detail.html, post_list.html, etc.

But we can override this convention by explicitly specifying the template_name as done above.

Update blog.html to catch all posts and display them by adding following code in the <body> of the HTML document:

{%for post in posts%} 
<hr>
<h1>{{post.title}}</h1>
<p>{{post.content}}</p>
<h3>
{{post.author}}
</h3>
<h4>
{{post.date_posted}}
</h4>
{%endfor%}

The line context_object_name in views.py might be a bit itchy to you.

If you observe blog.html, we have used the same name(‘posts’) to refer to the object. Though there is a default name to refer to the object which is ‘object’ itself, to make our code more understandable, we overrode the name and gave the name ‘posts’.

To end up with listing posts, let’s update routes by adding (and not overriding) in our urls.py(in blog directory):

from django.urls import path
from . import views
from .views import PostListView
urlpatterns = [
path(‘’,PostListView.as_view(),name= ‘blog-home’),
path(‘about/’,views.about,name= ‘blog-about’)
]

Another note(yes, we love giving notes ??): This time, there is no argument to specify the template name in as_view() function since we had already specified the template in the class definition.

Detail Post-

After listing posts, we need to show the details of a specific post when clicked. This part will be handled by Detail view.
An interesting thing here is that we shall use a special routing scheme by utilizing the primary key. A URL as localhost:8000/post/1 shall indicate a blog with id =1. Well, this makes sense: there can be any number of blogs, so we need a unique identifier for each and the primary key is just the perfect thing for this.

Coming back to code stuff, edit your urls.py after importing Detail view as shown:

from .views import PostDetailViewurlpatterns = [path(‘post/<int:pk>/’, PostDetailView.as_view(), name=’post-detail’),]

Note: pk stands for the primary key.

In views.py :

from django.views.generic import ListView, DetailView
from .models import Post
class PostDetailView(DetailView):
model = Post

Another template needs to be created! Following the naming convention, we name it post_detail.html (in blog/templates/blog folder).

<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8">
<title>Blog Home!</title>
</head>
<body>
{%if user.is_authenticated %}
<a href=”{%url ‘logout’%}”>Logout</a>
<br>
<h1>{{object.title}}</h1>
<p>{{object.content}}</p>
<h3>
{{object.author}}
</h3>
<h3>
{{object.date_posted}}
</h3>
<hr>
<a href= “{% url ‘post-update’ object.id %}”>Update This Post</a>
<hr>
<a href= “{% url ‘post-delete’ object.id %}”>Delete This Post</a>
{% else %}
<a href=”{%url ‘login’%}”>Login</a>
{% endif %}
<br>
</body>
</html>

The only thing left is to update blog.html to allow navigation to post_detail.html, as done here:

<a href= “{% url ‘post_detail’ post.id %}”> 
#post
</a>

Yipee!! We can now read the post!

But, there are none at this moment.😔

Create Post-

We welcome you, young diver, to the most famous part of the Django sea. Only a few have the guts to see it!

Here, we’ll use the legendary CreateView class to create posts.
Let’s navigate to our favorite module, views.py, and add up the PostCreateView, as shown:

from django.views.generic import CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
#fields we need in our PostCreationForm.
fields = [‘title’, ‘content’]
#form_valid saves the form automatically, it is overridden here:)
def form_valid(self, form):
#specify author of post as current user.
form.instance.author = self.request.user
#return the filled form.
return super().form_valid(form)

As explained earlier PostCreateView inherited CreateView, but in addition as we want the user to be logged in to create a post. For that, we add another argument, LoginRequiredMixin, which handles this.

Also, update urls.py(of blog app) by importing Createview and adding the following to urlpatterns:

from .views import PostCreateViewpath(‘post/new’, PostCreateView.as_view(), name = ‘post-create’)

The only thing left is: creating a template named post_form.html.(we know, our UI is ridiculous 😂).

<!DOCTYPE html>
<html lang=”en”>
<head>
<title>Document</title>
</head>
<body>
<form method=”POST”>
{% csrf_token %}
<fieldset >
<legend>
New Post
</legend>
{{form}}
</fieldset>
<button type=”submit”>Post It</button>
</form>
</body>
</html>

And yes, we are done with our CREATE posts function.

Actually not! 😜

After creating a post, what would happen? Common sense suggests that the user should be redirected somewhere.

Umm….let us redirect the helpless user to the post detail view(that is detailed post page).

Well, that’s not as easy as you think.

In Django if we need to provide url of a model object (each post is a model object, we guess), we would be needing a get_absolute_url method( in ‘post’ model, obviously) which would exist in models.py module of the blog app:

from django.urls import reverse
def get_absolute_url(self):
return reverse(‘post-detail’, kwargs={‘pk’ : self.pk})

You might be wondering about why we have used reverse in place of redirect, because there’s a bit difference between them. Reverse function is used to find URL of a given resource and is specific to Django whereas Redirect is not specific to any framework, it simply for a given URL, the user should be instructed to visit a specific URL.

Update Post-

Change is the law of nature. And so is the law of the UPDATE view:

Navigate to your views.py module and update…

from django.views.generic import ListView, DetailView, UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
fields = [‘title’, ‘content’]
def form_valid(self,form):
form.instance.author =self.request.user
return super().form_valid(form)

def test_func(self):
post =self.get_object()
if self.request.user == post.author:
return True
return False

Another new mixin here!

LoginRequiredMixin is the same old species we found recently. Coming to the newly found species: UserPassesTestMixin.It helps in performing some tests required by us as per our needs. How? It searches for a method test_func in views.py and performs the “tests” specified by us in the method before running the view (to update post). Here, the only verification we require in the name of the test is: to verify that the author of the post is the same as the user updating the post.

The same old story with routes…

from .views import PostUpdateViewpath(‘post/<int:pk>/update/’, PostUpdateView.as_view(), name=’post-update’),

Delete Posts-

As the end our diving seems nearer, we introduce you to the last site of the Django sea we can offer: DeleteView.

Since you are a mature diver now, you may interpret the meaning easily (it’s plain English!)

The tale of routes goes the same again…

from .views import PostDeleteViewpath(‘post/<int:pk>/delete/’, PostDeleteView.as_view(), name=’post-delete’),

The only file which always has something new for us, views.py:

from django.views.generic import DeleteViewclass PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Post
success_url=’/’ # After deletion, redirect here.
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
return False

It also requires the same mixins as that of update views.

The end of this section is marked with a deliberate show of our designing skills with the template “post_confirm_delete.html” in the same old place of its brother templates.

<!DOCTYPE html>
<html lang=”en”>
<head>
<title>Document</title>
</head>
<body>
<form method=”POST”>
{% csrf_token %}
<fieldset >
<legend>
Delete Post
</legend>
</fieldset>
BE SURE TO DELETE {{object.title}}
<hr>
<button type=”submit” >Yes Delete</button>
</form>
<hr>
<a href=”{% url ‘post-detail’ object.id %}”>Cancel</a>
</body>
</html>

CRUD…….done!!

Alas! We come at the end of this diving trip. It was a huge pleasure to show you the deeply hidden glories of the Django sea.

As you might be familiar with us now, our laziness continues and we hence do not entertain you with any sort of farewell speech…

P.S: Another thing to note is, we are never lazy in giving P.S.(s), so this time we inform you that we have a repo which has this readymade project for your help.

--

--