Blog App with Django and Bulma CSS Framework
Tags: django, bulma, blog
Published: 2023-01-25 13:22:56

Previously, I've built a blog app with Flask, you can check out my article on it [here](https://dev.to/faozziyyah/how-to-build-a-blog-app-with-flask-9h2). Then, I decided to build another one using Django and another CSS framework called Bulma. This time around, I added additional features to my blog post such as categories, post approval by admin, like and unlike post, follow and unfollow users, profile and account settings.

In this article, I'll explain how to build this blog app with Django and Bulma CSS for styling.
Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. You can learn more about it by [visiting this website](https://www.djangoproject.com/).

This article is divided into four parts:
1. Login, Logout, Signup, Profile and Account Settings
2. Add post, view all post, edit post, delete and view individual post.
3. Like and unlike post, add comments and view post categories.
4. Admin page, view other users, follow and unfollow users.

## 1. Prerequisites
The following are the basic requirements before starting this project:

- [Python](https://www.python.org/downloads/)
- [Visual Studio Code](https://code.visualstudio.com/download)
- [Git bash](https://git-scm.com/downloads) or any other terminal
- [Bulma](https://bulma.io/documentation/) - CSS framework for styling our application

## 2. Setting Up
**A. Create a Project folder:** navigate into the section where you want to have your project and type these commands to create a new directory or folder
```
// create a new directory(folder)
$ mkdir blog-app

// navigate into the folder
$ cd blog-app
```
**B. Create a virtual environment:** [virtual environment](https://www.geeksforgeeks.org/python-virtual-environment/) is a tool that helps to keep dependencies required by different projects separate by creating isolated python virtual environments for them. This is one of the most important tools that most Python developers use.

```
// navigate into the folder
$ cd blog-app

// create a virtual environment
$ python3 -m venv venv

// activate the virtual environment
$ . venv/Scripts/activate
```
**C. Install Django**: `pip install django`
**D. Start your Project**: 
```
$ django-admin startproject edublog

$ cd edublog
```
**E. Create App**: `python manage.py startproject blog`
**F. Add app to settings.py:** open up the settings.py file in your edublog folder and add the new app to INSTALLED_APPS within settings.py.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qjjj7buo0mie7pe1y6k7.png)

**G. create other folders:** Create TEMPLATES folder for our HTML files, MEDIA folder for uploading images and STATIC folder for images and CSS files.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6iw3orbbi9y1yjupx3dq.png)

**H. STATICFILES_DIRS:** add STATICFILES_DIRS to the settings.py file in order to see the effect of the styles similarly for the images.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dyswstais51xd8xry1us.png)

**I. Further Set-up:**
![Adding templates directory to settings.py](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ejvjxbskvm4qsu7zqpzc.png)

![urls.py](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r8gf0vwq14kn7bp63pzp.png)

**J. Adding broiler plates to HTML:** add these to your base.html and index.html files. This includes the set up for the bulma CSS framework.
```
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
    {% load static %}
    <link rel="stylesheet" href="{% static 'css/bulma.min.css' %}">
    <script src="https://kit.fontawesome.com/1cbee15c00.js" crossorigin="anonymous"></script>

    <title>Edublog</title>
</head>
<body>

<footer class="columns" id="footer">

        <div class="column">
            <h5>Links</h5>
                <a href="{% url 'index' %}">Home</a>
                <a href="{% url 'about' %}">About</a>
                <a href="{% url 'about' %}">Contact</a>
        </div>
        </div>

        <div class="column">

            <a class="button is-success" href="http://www.facebook.com/profile.php?id="><i class="fab fa-facebook"></i></a>
            <a class="button is-primary" href="http://www.linkedin.com/in/"><i class="fab fa-linkedin"></i></a>
            <a class="button is-link" href="http://twitter.com/"><i class="fab fa-twitter"></i></a>
            <a class="button is-danger" href="http://youtube.com/"><i class="fab fa-youtube"></i></a>
            <a class="button is-warning" href="mailto:"><i class="fas fa-envelope"></i></a>

        </div>
        
        <div class="column">
            <p>© All rights reserved {{ current_year }} Faozziyyah Daud</p>
        </div>

  </footer>
</body>
```
**K. Navbar setup:** add the following Bulma CSS navbar element to both base and index html files
```
<header>
        <!-- Fixed navbar -->
        <nav class="navbar is-link" role="navigation" aria-label="main navigation">

            <div class="navbar-brand">
                <a class="navbar-item" href="{% url 'index' %}">
                    <img src="{% static 'images/logo.png' %}" width="" height="">
                </a>
          
                <a role="button" class="navbar-burger is-active" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
                  <span aria-hidden="true"></span>
                  <span aria-hidden="true"></span>
                  <span aria-hidden="true"></span>
                </a>
            </div>
          
            <div id="navbarBasicExample" class="navbar-menu is-active">

                <div class="navbar-start">

                    <div class="navbar-item">
                        <a href="{% url 'index' %}" class="nava"><i class="fas fa-home"></i> Home </a>
                    </div>

                    <div class="navbar-item">
                        <a href="{% url 'add-post' %}" class="nava"><i class="fa fa-plus-circle" aria-hidden="true"></i> Add Post </a>
                    </div>
            
                    <div class="navbar-item has-dropdown is-hoverable">    
                        <a class="navbar-link"><i class="fa fa-info-circle" aria-hidden="true"></i> More </a>
            
                        <div class="navbar-dropdown">    
                            <a class="navbar-item" href="{% url 'about' %}"><i class="fa fa-info-circle" aria-hidden="true"></i> About </a>
                            <a href="{% url 'about' %}" class="navbar-item"> <i class="fa fa-address-card"></i> Contact </a>    
                            <hr class="navbar-divider">    
                            <a class="navbar-item"> Report an issue </a>
                        </div>

                    </div>

                </div>
          
                <div class="navbar-end"> 

                    <form class="navbar-item" method="POST" action="{% url 'search-posts' %}">
                        {% csrf_token %}
        
                        <div class="field has-addons">

                            <div class="control">
                              <input class="input" type="search" style="background-color: transparent;" placeholder="Search posts..." aria-label="Search" name="searched" />
                            </div>

                            <div class="control">
                              <button class="button is-primary" type="submit"> Search </button>
                            </div>

                        </div>
                        
                    </form>

                    <div class="navbar-item"> 

                        <div class="buttons"> 

                            {% if user.is_superuser %}

                                <a class="navbar-item button is-success is-light" href="{% url 'admin-approval' %}">
                                    <span class="icon">
                                        <i class="fa fa-user-circle"></i>
                                    </span>
                                    <span>Admin</span>
                                </a>

                            {% endif %}

                            {% if user.is_authenticated %}

                                <div class="navbar-item has-dropdown is-hoverable">    
                                    <a class="navbar-link">
                                        <img src="{{user_profile.profileimg.url}}" class="header-avatar" style="width: 32px; height: 32px; border-radius: 100%; margin-left: 15px;">
                                    </a>
                        
                                    <div class="navbar-dropdown">

                                        <a class="navbar-item button is-primary is-light" href="{% url 'settings' %}">
                                            <span class="icon">
                                                <i class="fa fa-cog" aria-hidden="true"></i>
                                            </span>
                                            <span>Account settings</span>
                                        </a>

                                        <a class="navbar-item button is-link is-light" href="profile/{{user_profile.user}}">
                                            <span class="icon">
                                                <i class="fa fa-user-circle"></i>
                                            </span>
                                            <span>Profile</span>
                                        </a>

                                        <hr class="navbar-divider">    

                                        <a class="navbar-item button is-warning is-light" href="{% url 'logout' %}">
                                            <span class="icon">
                                                <i class="fa fa-sign-out" aria-hidden="true"></i>
                                            </span>
                                            <span>Log Out</span>
                                        </a> 

                                    </div>
            
                                </div>  

                            {% else %}

                                <a class="navbar-item button is-primary is-light" href="{% url 'register' %}">
                                    <span class="icon">
                                        <i class="fa fa-sign-in" aria-hidden="true"></i>
                                    </span>
                                    <span>Sign up</span>
                                </a>
    
                                <a class="navbar-item button is-light is-light" href="{% url 'login' %}">
                                    <span class="icon">
                                        <i class="fa fa-sign-in" aria-hidden="true"></i>
                                    </span>
                                    <span>Log in</span>
                                </a> 

                            {% endif %}  

                        </div>    
                    </div>

                </div>
            </div>
        </nav>
      </header>
```
L. **Jinja template:**[jinja](https://jinja.palletsprojects.com/en/3.1.x/) is a fast, expressive, extensible templating engine. Special placeholders in the template allow writing code similar to Python syntax. Then the template is passed data to render the final document.
Add the following to the base and index html files
![The block content is the area where all contents of the webpage belongs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d929rzpf6gwu86gv528p.png)

## 2. Login and Signup
This enables a new user create an account before viewing the post page. create register.html and login.html files in your templates. You can view the source code [here](https://github.com/faozziyyah/django-blog-app/blob/main/edublog/templates/register.html).
Then create a view.py file inside the blog folder and add the following. This determines the route of the pages.
```
from django.urls import path
from . import views
urlpatterns = [
    path('', views.signin, name='login'),
    path('index', views.index, name='index'),
    path('signup', views.signup, name='register'),
    path('logout', views.logout, name='logout'),
]
```
This third step is to include the functionalities in the view.py file
```
def signin(request):
    
    if request.method == 'POST':
        username = request.POST['username']
        password = request.POST['password']

        user = auth.authenticate(username=username, password=password)

        if user is not None:
            auth.login(request, user)
            return redirect('/index')
        else:
            messages.info(request, 'Credentials Invalid')
            return redirect('login')

    else:
        return render(request, 'login.html')
```
and for the signup:
```
def signup(request):

    if request.method == 'POST':
        username = request.POST['username']
        email = request.POST['email']
        password = request.POST['password']
        password2 = request.POST['password2']

        if password == password2:
            if User.objects.filter(email=email).exists():
                messages.info(request, 'Email Taken')
                return redirect('register')
            elif User.objects.filter(username=username).exists():
                messages.info(request, 'Username Taken')
                return redirect('register')
            else:
                user = User.objects.create_user(username=username, email=email, password=password)
                user.save()

                #log user in and redirect to settings page
                user_login = auth.authenticate(username=username, password=password)
                auth.login(request, user_login)

                #create a Profile object for the new user
                user_model = User.objects.get(username=username)
                new_profile = Profile.objects.create(user=user_model, id_user=user_model.id)
                new_profile.save()
                return redirect('settings')
        else:
            messages.info(request, 'Password Not Matching')
            return redirect('register')
        
    else:
        return render(request, 'register.html')

```

## 3. Logout user
To enable a user logout successfully, add the following to the views.py file. The `@login_required(login_url='login_user')` ensures that the user is logged in before he can logout.
```
@login_required(login_url='login_user')
def logout(request):
    auth.logout(request)
    return redirect('/')
```

## 4. User Profile
This is the user's personal page where he can view all posts he created, number of followers and number of people he is following. It also contains a like to view his account settings.
**First,** create a profile.html page, check the source code [here](https://github.com/faozziyyah/django-blog-app/blob/main/edublog/templates/profile.html).
**Secondly,** add the following in the urls.py file `
    path('profile/<str:pk>', views.profile, name='profile'),`.
The `<str:pk>` gets the ID of the current user.
**Thidly,** add this to the views.py file
```
# profile page
@login_required(login_url='login_user')
def profile(request, pk):
    user_object = User.objects.get(username=pk)
    user_profile = Profile.objects.get(user=user_object)
    user_posts = Post.objects.filter(user=pk)
    user_post_length = len(user_posts)
    context = {
        'user_object': user_object,
        'user_profile': user_profile,
        'user_posts': user_posts,
        'user_post_length': user_post_length,
    }
    return render(request, 'profile.html', context)

```
**Lastly,** let's create the profile model. Add these to the models.py file. It create a profile table in our databse.
```
from django.db import models
from django.contrib.auth.models import User
import uuid
from datetime import date, datetime

# Create your models here.
class Profile(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    id_user = models.IntegerField()
    bio = models.TextField(blank=True)
    profileimg = models.ImageField(upload_to='profile_images', default='blank-profile-picture.png')
    location = models.CharField(max_length=100, blank=True)

    def __str__(self):
        return self.user.username
```

## 5. Migrations
After adding a table or making any changes to the database, we need to migrate our changes by doing the following in your terminal
```
$ python manage.py makemigrations
$ python manage.py migrate
```

## 6. Superuser
TO access our database and view the admin page of our app, we need to create a superuser.
Add the following code to your terminal and follow the prompts.
`python manage.py createsuperuser`
Then open this [link ](http://127.0.0.1:8000/admin/)in your browser and login with the credentials of the superuser you created.

## 7. User Settings
This page enables a user make changes to his existing profile
First, create settings.html file. check the code [here](https://github.com/faozziyyah/django-blog-app/blob/main/edublog/templates/settings.html).
second, add this to urls.py file, `
    path('settings', views.settings, name='settings'),`
Third, Add this to the views.py file

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o1syuot7a5h4artmdcz1.png)