Create a blog application using Django and PostgreSQL

Django is an open-source, high-level Python web framework that abstracts away much of the difficulty of web development and allows you to focus on writing your application. Django includes support for common web development tasks such as user authentication, templates, views, models, and security.

IBM starter kits are a great way to get you started on IBM Cloud as they contain all the necessary files you need to start coding and deploy your applications.

Learning objectives

In this tutorial, you will learn how to:

  • Create a Django application running on the IBM Cloud.
  • Create a PostgreSQL database on the IBM Cloud.
  • Connect your PostgreSQL database to you Django application.
  • Deploy your application using Cloud Foundry or IBM Cloud Kubernetes Service.
  • Create a simple application to create blogs.
  • Create models, views, forms, URLs, and HTML/CSS files.
  • Deploy the blog application on the IBM Cloud using Git (continuous delivery) or Cloud Foundry through the terminal.

Prerequisites

To complete this tutorial, you need:

Estimated time

You can complete this tutorial in about an hour.

Steps

Step 1. Create a PostgreSQL database

Start with provisioning the PostgreSQL database, since it takes about 5 to 10 minutes.

  1. Log into your IBM Cloud account.
  2. Open the Databases for PostgreSQL service.
  3. Select your preferred region from the Select a region list.

    Screen capture of the Select a region section within the Databases for PostgreSQL service creation page

  4. Type in a unique name for your database service in the Service name field.

  5. Select a resource group from the list.
  6. You can let the remaining fields remain as the default values.
  7. Click Create to start provisioning your database service.

    Screen capture of the Configure your resource section within the Databases for PostgreSQL service creation page

Step 2. Create a Python Django starter application

  1. Go to the Python Django App starter kit
  2. Click Get started.
  3. Click on the Create tab.
  4. Enter a unique name for your application within the App name field. Note that the name of your application will be part of the application URL.
  5. Choose a resource group.
  6. Click the Create button. This will create your application on IBM Cloud and then allow you to deploy it.

    Screen capture of the Create tab within the Python Django App starter kit page

  7. Once the application is created, you will be redirected to the App details page for your application.

  8. Click the Deploy your app button to enable the continuous delivery pipeline for your application.

    Screen capture of the App details page

Step 3. Enable continuous delivery

You can deploy your application using any of the three deployment targets displayed on the screen: IBM Cloud Kubernetes Service, Red Hat OpenShift, or Cloud Foundry. This tutorial covers deployment through Cloud Foundry and the IBM Cloud Kubernetes Service.

Step 3a. Deploy through Cloud Foundry

This step shows how to set up the continuous delivery pipeline that will deploy your application into Cloud Foundry on IBM Cloud.

  1. In the Deployment target section, select Cloud Foundry.
  2. Click the New button to create an IBM Cloud API key.
  3. Select the default value of the API key and click Create. The API key allows the deployment to access your resources.
  4. Select the preferred Region, Organization, and Space for your deployment.
  5. Ensure the memory allocation per instance value is set to a minimum of 128 MB.
  6. Click Next to continue.

    Screen capture of the Deployment target section of the App details page with Cloud Foundry highlighted

  7. Configure the DevOps toolchain by selecting the region it should be created in. It is preferable to choose the same region as the one you chose earlier in this step (within task 4).

  8. Click Create. This will return you to the application overview page and start the deployment process.

    Screen capture of the Configure the DevOps toolchain page

  9. The Deployment Automation section now contains the details of your delivery pipeline. The Status field of the pipeline will eventually change from In progress to Success after it is successfully deployed. Deployment takes a few minutes to complete.

    Screen capture of the Deployment Automation section of the App details page with Status field displaying `Success`

  10. Click Status to view the progress of the pipeline deployment.

    Screen capture of the Delivery Pipeline page, which shows that both the Build and Deploy stages passed

  11. Within the Delivery Pipeline status page, click View logs and history to view the progress.

  12. Back on the App details page, click on the App URL (within the Details section) to view the starter kit code that deployed.

    Screen capture of the Deployment Automation section of the App details page with Status field displaying `Success`

Step 3b. Deploy through a Kubernetes cluster

This step shows how to set up the continuous delivery pipeline that will deploy your application using the IBM Cloud Kubernetes Service.

  1. In the Deployment target section, select Kubernetes Service.
  2. Click the New button to create an IBM Cloud API key.
  3. Select the default value of the API key and click Create. The API key allows the deployment to access your resources.
  4. Select the appropriate Cluster registry region, Cluster registry namespace, Cluster region, Cluster resource group, and Cluster namespace.
  5. In the Cluster name field, select the cluster you identified for the Prerequisites section.
  6. Click Next to continue.

    Screen capture of the Create a new API key with full access box

  7. Configure the DevOps toolchain by selecting the region it should be created in. It is preferable to choose the same region as the one you chose earlier in this step (within task 4).

  8. Click Create. This will return you to the application overview page and start the deployment process.

    Screen capture of the Configure the DevOps toolchain page

  9. The Deployment Automation section now contains the details of your delivery pipeline. The Status field of the pipeline will eventually change from In progress to Success after it is successfully deployed. Deployment takes about 5 minutes to complete. (There is an additional stage in comparison to deployment through Cloud Foundry.)

    Screen capture of the App details page, where the Deployment Automation section shows the Status field displaying `In progress`

  10. Click Status to view the progress of the pipeline deployment.

    Screen capture of the Delivery Pipeline page, which shows that the Build stage passed, the Containerize stage passed with warnings, and the Deploy stage passed

  11. Within the Delivery Pipeline status page, click View logs and history to view the progress.

  12. On the App details page, within the Details section, click on the App URL to view the starter kit code that deployed.

    Screen capture of the App details page, where the Deployment Automation section shows the Status field displaying `Warning` and the App URL field is highlighted

Step 4. Connect services to your application

  1. You can now connect your PostgreSQL database to your application.
  2. On the App details page, click Connect existing services.

    Screen capture of the App details page, where the Deployment Automation section shows the Status field displaying `Warning` and the __Connect existing services__ button is highlighted

  3. Select the database service you created.

    Screen capture that shows a check mark next to a selected database

  4. Click Next to finish the process. Your service will be connected to the application in a few minutes.

Step 5. Create your blog application

The deployed application also creates a GitLab project.

  1. Back on the App details page, click on the Source link to open the Gitlab project in a new tab.
  2. Click Clone to clone the project.

    Screen capture of the Gitlab project with a Clone button highlighted

  3. Go to the directory where you cloned your project and create a virtual environment (also known as vitualenv). It is preferable to run your project in a virtual environment as that creates an isolation for your Python/Django project and allows you to run different projects on different versions without affecting the others.

    #this will install the necessary files to create a virtualenv
    pip install virtualenv
    
    #name your virtual environment, I am calling it venv
    virtualenv venv
    
    #activate your virtualenv
    source venv/bin/activate
    
    #Once your work on this project is over, deactivate your virtualenv
    deactivate
    
  4. Open the code in your preferred integrated development environment (IDE). I am using PyCharm. You will see the venv and all the other files created for you.

  5. In the pip file, add the following code:

    # To install Django and psycopg2 for the postgresql
    
    Django = "==2.2.8"
    psycopg2-binary = "==2.8.5"
    

    Note: We are using psycopg2-binary because it does not require configuration of psycopg2.

  6. Save the file. Open your terminal and run the following commands:

    # this creates a pipfile.lock with all the dependeicies installed.
    pipenv install
    
  7. Once the pipfile.lock is successfully created, go back to your IDE.

    Screen capture of the IDE status

  8. In the .gitignore file, add your virtualenv files.

    .env
    .venv
    env/
    venv/
    ENV/
    env.bak/
    venv.bak/
    
  9. You will notice that you have 2 apps already created. app is where you put all your source code and pythondjangoapp is for the settings and base URLs.

  10. Go to pythondjangoapp > settings > base.py. After the WSGI_APPLICATION, you can add your database credentials to connect your application to the database. Put in the name, user, password, host, and port with the details from the service you created on the cloud.

    Screen capture of the `base.py` file within the PyCharm IDE

  11. Save the file and make migrations using the following commands. This will create the default tables that come with PostgreSQL.

    python manage.py makemigrations
    python manage.py migrate
    

    Screen capture of the running migrations that occur as a result of the previous commands

  12. To locally run the application, you can use the following command:

    python manage.py runserver
    
  13. In the app folder, you now create your database table in the models.py file as follows:

    # -*- coding: utf-8 -*-
    from django.db import models
    from django.contrib.auth.models import User
    from datetime import datetime, date
    
    class BlogPost(models.Model):
       author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='author')
       title = models.CharField(max_length=200, unique=True)
       post = models.TextField()
       created_on = models.DateTimeField(auto_now_add=True)
       updated_on = models.DateTimeField(auto_now=True)
       status = models.BooleanField(default=False)
    
  14. Create a forms.py file under the app folder and add the following code:

    from django import forms
    from .models import BlogPost
    
    class BlogForm(forms.ModelForm):
       title = forms.CharField(required=True, max_length=200, widget=forms.TextInput(
           attrs={'placeholder': 'Enter the title', 'class': 'form-control'}
       ))
       post = forms.CharField(required=True, max_length=5000, widget=forms.Textarea(
           attrs={'placeholder':'Write your post here', 'rows': 10,'cols': 75, 'style': 'border: 1px solid; border-radius: 8px;'})
       )
    
       class Meta:
           model = BlogPost
           fields = ('title', 'post', 'status')
           exclude = ('created_on', 'updated_on', 'author')
    
  15. In the views.py file, add the following code:

    from __future__ import unicode_literals
    
    from django.http import JsonResponse
    from django.views.generic import TemplateView
    from django.http import Http404
    from django.shortcuts import render, redirect, get_object_or_404
    from .models import BlogPost
    from .forms import BlogForm
    from django.utils import timezone
    
    class Home(TemplateView):
       template_name = 'index.html'
    
       def get(self, request, *args):
           published = BlogPost.objects.filter(status=True)
           drafts = BlogPost.objects.filter(status=False)
           data = BlogPost.objects.all()
           for d in data:
               print("post:", d.title)
           args = {'data': data, 'pub': published, 'drafts': drafts}
           return render(request, self.template_name, args)
    
    class EditPost(TemplateView):
       template_name = 'edit_post.html'
    
       def get(self, request, *args, **kwargs):
           id = int(kwargs['id'])
           post = get_object_or_404(BlogPost, id=id)
           print("post:", post)
           form = BlogForm(instance=post)
           data = BlogPost.objects.all()
           for d in data:
               print("post:", d.title)
           args = {'id': id, 'form': form, 'post': post, 'data': data}
           return render(request, self.template_name, args)
    
       def post(self, request, **kwargs):
           post = get_object_or_404(BlogPost, id=int(kwargs['id']))
           if request.method == "POST":
               form = BlogForm(request.POST, instance=post)
               if request.POST.get("submitbtn"):
                   if form.is_valid():
                       print("validating form")
                       post = form.save(commit=False)
                       post.author = request.user
                       post.updated_on = timezone.now()
                       post.status = True
                       post.save()
                       print("form saved")
                       return redirect('home')
                   else:
                       print("invalid form")
                       print("errors:", form.errors)
    
    class CreatePost(TemplateView):
       template_name = 'new_post.html'
    
       def get(self, request, *args):
           form = BlogForm()
           posts = BlogPost.objects.filter(status=False, author_id=request.user.id)
           print("user:", request.user)
           print("user:", request.user.id)
           args = {'form': form, 'posts': posts}
           return render(request, self.template_name, args)
    
       def post(self, request):
           print("inside post")
           if request.method == "POST":
               form = BlogForm(request.POST)
               if request.POST.get("submitbtn"):
                   print("Submit clicked")
                   if form.is_valid():
                       print("validating form")
                       post = form.save(commit=False)
                       post.author = request.user
                       post.created_on = timezone.now()
                       post.updated_on = timezone.now()
                       post.status = True
                       post.save()
                       print("form saved")
                       return redirect('home')
                   else:
                       print("invalid form")
                       print("errors:", form.errors)
               elif request.POST.get("savebtn"):
                   print("Submit clicked")
                   if form.is_valid():
                       print("validating form")
                       post = form.save(commit=False)
                       post.author = request.user
                       post.created_on = timezone.now()
                       post.updated_on = timezone.now()
                       post.status = False
                       post.save()
                       print("form saved")
                       return redirect('home')
                   else:
                       print("invalid form")
                       print("errors:", form.errors)
    
    def health(request):
       state = {"status": "UP"}
       return JsonResponse(state)
    
    def handler404(request):
       return render(request, '404.html', status=404)
    
    def handler500(request):
       return render(request, '500.html', status=500)
    
  16. In urls.py file, add the following code:

    from django.urls import path
    
    from . import views
    from django.contrib import admin
    from .views import CreatePost, Home, EditPost
    
    urlpatterns = [
                  path('home/', Home.as_view(), name='home'),
                  path('', Home.as_view(), name='home'),
                  path('new_post/', CreatePost.as_view(), name='new_post'),
                  path('edit_post/<int:id>/', EditPost.as_view(), name='edit_post'),
                  path('admin/', admin.site.urls),
                  path('health', views.health, name='health'),
                  path('404', views.handler404, name='404'),
                  path('500', views.handler500, name='500'),
                  ]
    
  17. Under the templates folder, create two additional HTML files. Name them new_post.html and edit_post.html.

  18. Replace the index.html file with the following code:

    <!DOCTYPE html>
    {% load static %}
    <html lang="en">
    <head>
       <meta charset="UTF-8">
       <title>Home</title>
       <script src='https://kit.fontawesome.com/a076d05399.js'></script>
       <link href="{% static 'css/default.css' %}" rel="stylesheet" type="text/css" media="all"/>
    
    </head>
    <body>
    <div class="header">
     <h1>Blogs</h1>
    </div>
    
    <div align="center">
    <a href="{% url 'new_post' %}"> Create a new post <i class="fa fa-plus-circle" aria-hidden="true"></i></a>
    </div>
    
    <div class="content">
    <H1>List of posts</H1>
       <br><br>
    
    {% for p in pub %}
       <a href="{% url 'edit_post' p.id %}"><h3> <i class="fa fa-pen"></i>  {{ p.title }}</h3> </a>
       <p class="card-text text-muted h6">{{ p.author }} | {{ p.updated_on}} </p>
       <p class="card-text">{{p.post|slice:":200" }}</p>
       <br>
    {% endfor %}
    </div>
    
    <div class="content">
    <H1>List of Drafts</H1>
       <br><br>
    {% for d in drafts %}
    <a href="{% url 'edit_post' d.id %}"><h3> <i class="fa fa-pen"></i>  {{ d.title }}</h3> </a>
       <p class="card-text text-muted h6">{{ d.author }} | {{ d.updated_on}} </p>
       <p class="card-text">{{d.post|slice:":200" }}</p>
       <br>
       {% endfor %}
    </div>
    
    </body>
    </html>
    
  19. In the new_post.html file, add the following code:

    <!DOCTYPE html>
    {% load static %}
    <html lang="en" class="no-js">
    <head>
       {% block title %}
       <title>Blog </title>
       {% endblock title %}
       <meta content="width=device-width, initial-scale=1.0" name="viewport">
    
       <!-- Place your favicon.ico and apple-touch-icon.png in the template root directory -->
       <link href="favicon.ico" rel="shortcut icon">
       <link rel="shortcut icon" href="" type="image/ico">
       <link href="{% static 'css/default.css' %}" rel="stylesheet" type="text/css" media="all"/>
    
       <style>
       input[type=text], select {
         width: 40%;
         padding: 12px 20px;
         margin: 8px 0;
         display: inline-block;
         border: 1px solid #ccc;
         border-radius: 8px;
         box-sizing: border-box;
       }
       </style>
    </head>
    <body>
    <div class="header">
     <h1>Blogs</h1>
    </div>
    
    <div align="center">
    <H2>Create new post</H2>
    <br><br>
    </div>
    
    <div align="center">
    <form method="POST">
       {% csrf_token %}
       <div class="u-form-group">
       <label>Title: </label>
       {{ form.title }}
       <br><br>
       <label>Post:</label>{{ form.post }}<br/>
    
    <button class="button button2" id="submitbtn" type="submit" name="submitbtn" value="submitbtn">Create new post</button>
    <button class="button button2" id="savebtn" type="submit" name="savebtn" value="savebtn">Save as Draft</button>
    </div>
    </form>
    
    </div>
    
    </body>
    </html>
    
  20. In the edit_post.html file, add the following code:

    <!DOCTYPE html>
    {% load static %}
    <html lang="en" class="no-js">
    <head>
       {% block title %}
       <title>Blog </title>
       {% endblock title %}
       <meta content="width=device-width, initial-scale=1.0" name="viewport">
    
       <!-- Place your favicon.ico and apple-touch-icon.png in the template root directory -->
       <link href="favicon.ico" rel="shortcut icon">
       <link rel="shortcut icon" href="" type="image/ico">
       <link href="{% static 'css/default.css' %}" rel="stylesheet" type="text/css" media="all"/>
    
       <style>
       input[type=text], select {
         width: 40%;
         padding: 12px 20px;
         margin: 8px 0;
         display: inline-block;
         border: 1px solid #ccc;
         border-radius: 8px;
         box-sizing: border-box;
       }
       </style>
    </head>
    <body>
    <div class="header">
     <h1>Blogs</h1>
    </div>
    
    <div align="center">
    <H2>Edit Blog</H2>
    <br><br>
    </div>
    
    <div align="center">
    <form method="POST">
       {% csrf_token %}
       <div>
       <label>Title: </label>
       {{ form.title }}
       <br><br>
       <label>Post:</label>{{ form.post }}<br/>
    
    <button class="button button2" id="submitbtn" type="submit" name="submitbtn" value="submitbtn">Submit</button>
       </div>
    </form>
    </div>
    
    </body>
    </html>
    
  21. Go to Static folder> css folder> default.css and add the following CSS code. You will also have to add this file to the main directory, "staticfiles" > css > default.css.

    /* Style the body */
       body {
         font-family: Arial;
         margin: 0;
       }
    
       /* Header/Logo Title */
       .header {
         text-align: center;
         background: #00cdcd;
         color: white;
         font-size: 30px;
       }
    
       /* Page Content */
       .content {padding:20px;}
    
    .button {
     background-color: #00cdcd; /* Green */
     border-radius: 8px;
     color: black;
     padding: 15px 32px;
     text-align: center;
     text-decoration: none;
     display: inline-block;
     font-size: 18px;
     font-weight: bold;
     margin: 4px 2px;
     cursor: pointer;
     -webkit-transition-duration: 0.4s; /* Safari */
     transition-duration: 0.4s;
    }
    
    .button2:hover {
     box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
    }
    
    #new-container {
       margin: 0 auto;
       width: 70%;
    }
    
    .link-section {
       margin: auto;
       padding: 1em;
       width: 80%;
    }
    
    .ibm-cloud {
       display: block;
       margin-left: auto;
       margin-right: auto;
       width: 50%;
    }
    
    .ibm-cloud img {
       display:block;
       margin:auto;
    }
    
    .link-item a {
       color: #6e98df;
       text-decoration: underline;
       font-size: 1em;
    }
    
    .left-link-items {
       float: left;
       padding: 1em;
       margin-left: 20%;
    }
    
    .right-link-items {
       float: right;
       padding: 1em;
       margin-right: 20%;
    }
    
    .right-arrow{
       padding-right: 0.5em;
    }
    .title {
       color: black;
       text-align: center;
    }
    
    .title-404 {
       color: black;
       text-align: center;
       font-size: 4em;
    }
    
    .subtitle {
       color: black;
       text-align: center;
    }
    
    .ibm-404 {
       margin: auto;
       width: 35%;
    }
    
    .ibm-404 a {
       color: #6e98df;
       text-decoration: underline;
    }
    
  22. Save the files. You must make migrations again, since you added a model table in the models.py file.

    python manage.py makemigrations
    python manage.py migrate
    

    Screen capture of the running migrations that occur as a result of the previous commands

  23. Create a super user for the Django admin.

    python manage.py createsuperuser
    

    Screen capture of the `Superuser` creation as a result of the previous commands

Step 6. Deploy your blog application

You are now ready to deploy your application to IBM Cloud. Since you cloned the project from GitLab and created your delivery pipeline, the deployment process now becomes very simple.

  1. On your terminal, commit your files and push them to GitLab.

    #To find the files that you have made changes to
    git status
    
    #To add the files to the project
    git add *
    
    #Commit the files that you made changes to
    git commit -m “enter your message here”
    
    #push your code
    git push
    

    Back on the App details page within IBM Cloud, in the Deployment Automation section, you will notice that the Status field changes to In progress. Your application will deploy within the next few minutes.

    Screen capture of the App details page, where the App URL field is highlighted, the Services section shows Databases for PostgreSQL, and the Deployment Automation section shows the highlighted Status field displaying `In progress`

  2. Alternatively, if you followed Step 3a. Deploy through Cloud Foundry, you can also use your terminal to deploy the application with Cloud Foundry with the following commands:

    #Login to IBM Cloud
    ibmcloud login
    
    #Deploy your application to Cloud Foundry
    ibmcloud dev deploy -t buildpack
    

    Screen capture of the results of the previous commands

    Similarly, if you followed Step 3b. Deploy through a Kubernetes cluster, you can use the same commands in your terminal to deploy the application using Kubernetes. These commands automatically start the deployment process on the cloud since you created an automated delivery pipeline earlier in Step 3b.

  3. Your application is now up and running on the URL (the one located in the App URL field on the App details page within IBM Cloud). Visit <Your URL>/admin/ and log in with your superuser credentials. This step is necessary since you do not have a login page. By logging in through the admin account, you are able to use the username as the author name in this application.

    Screen capture of the Django administration login page

  4. The Home page of the application opens, where you can view all of the created posts and a list of draft posts that are yet to be published.

    Screen capture of the Home page of the blog application, which displays a List of posts section and List of Drafts section

  5. Click Create a new post.

  6. Enter a title in the Title field and add content within the Post field.
  7. You can either click Create new post to publish your content or select Save as Draft.

    Screen capture of the Create new post page

Summary

Congratulations! You successfully created a blog application connected to a relational database, PostgreSQL, that is hosted on the IBM Cloud. You also learned how to edit the application code, perform create, retrieve, update, and delete operations, and automatically deploy changes.

To continue learning how to deploy applications to a public cloud, visit the following resources: