article

Thursday, June 30, 2022

Python Django Simple Blog with Summernote WYSIWYG Editor

Python Django Simple Blog with Summernote WYSIWYG Editor

Install django-summernote.

pip install django-summernote

django_summernote to INSTALLED_APP in settings.py.

INSTALLED_APPS += ('django_summernote', ) 

django_summernote.urls to urls.py.

from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [ 
    path('admin/', admin.site.urls), 
    path('summernote/', include('django_summernote.urls')),
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 
Media settings
X_FRAME_OPTIONS = 'SAMEORIGIN'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')


To explore more configuration read the official documentation of the package, https://github.com/summernote/django-summernote


blogsite/settings.py
 
//blogsite/settings.py
    
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp', #add myapp
]

INSTALLED_APPS += ('django_summernote', ) 

X_FRAME_OPTIONS = 'SAMEORIGIN'
myapp/views.py
 
//myapp/views.py
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.contrib import messages
from myapp.models import Category, Post

category_list = Category.objects.exclude(status = 2).all()
context = {
    'page_title' : 'Simple Blog Site',
    'category_list' : category_list,
    'category_list_limited' : category_list[:3]
}
# Create your views here.
#Logout
def logoutuser(request):
    logout(request)
    return redirect('/')

def home(request):
    context['page_title'] = 'Home'
    posts = Post.objects.filter(status = 1).all()
    context['posts'] = posts
    return render(request, 'home.html',context)

def view_post(request,pk=None):
    context['page_title'] = ""
    if pk is None:
        messages.error(request,"Unabale to view Post")
        return redirect('home-page')
    else:
        post = Post.objects.filter(id = pk).first()
        context['page_title'] = post.title
        context['post'] = post
    return render(request, 'view_post.html',context)

def post_by_category(request,pk=None):
    if pk is None:
        messages.error(request,"Unabale to view Post")
        return redirect('home-page')
    else:
        category = Category.objects.filter(id=pk).first()
        context['page_title'] = category.name
        context['category'] = category
        posts = Post.objects.filter(category = category).all()
        context['posts'] = posts
    return render(request, 'by_categories.html',context)

def categories(request):
    categories = Category.objects.filter(status = 1).all()
    context['page_title'] = "Category Management"
    context['categories'] = categories
    return render(request, 'categories.html',context)
blogsite/urls.py
 
//blogsite/urls.py
from django.contrib import admin
from django.urls import include, path
  
from django.conf import settings
from django.conf.urls.static import static
  
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('myapp.urls')),
    path('summernote/', include('django_summernote.urls')),
] + static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)
myspp/urls.py
 
//myspp/urls.py
from . import views
from django.contrib import admin
from django.urls import path, re_path
from django.contrib.auth import views as auth_views
from django.views.generic.base import RedirectView

urlpatterns = [
    path('redirect-admin', RedirectView.as_view(url="/admin"),name="redirect-admin"),
    path('', views.home, name="home-page"),
    path('logout',views.logoutuser,name='logout'),
    path(r'view_post/<int:pk>',views.view_post,name='view-post'),
    path(r'<int:pk>',views.post_by_category,name='category-post'),
    path('categories',views.categories,name='category-page'),
]
Make Migrations
Run the commands below to make migrations:
python manage.py makemigrations
python manage.py migrate
C:\django\blogsite>python manage.py makemigrations
C:\django\blogsite>python manage.py migrate
myapp/models.py
 
//myapp/models.py
from django.db import models
from unicodedata import category
from django.db import models
from django.contrib.auth.models import User

from django.utils import timezone

class Category(models.Model):
    name = models.CharField(max_length=250)
    description = models.TextField(blank=True, null=True)
    status = models.IntegerField(default = 1)
    date_added = models.DateTimeField(default=timezone.now)
    date_updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

class Post(models.Model):
    category=models.ForeignKey(Category,on_delete=models.CASCADE)
    title = models.TextField()
    author = models.ForeignKey(User,on_delete=models.CASCADE)
    blog_post = models.TextField()
    banner = models.ImageField(blank=True, null = True, upload_to= 'images/')
    status = models.IntegerField(default = 0)
    date_added = models.DateTimeField(default=timezone.now)
    date_updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title + " - " + self.category.name
Bootstrap 5
https://getbootstrap.com/docs/5.0/getting-started/introduction/
https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css
myapp/templates/home.html
//myapp/templates/home.html
{% extends "base.html" %} 

{% block pageContent %}
<section class="text-center">
    <h4 class="mb-5"><strong>All Posts</strong></h4>
    <div class="row">
        {% for post in posts %}
        <div class="col-lg-4 col-md-6 mb-4">
            <div class="card shadow border">
                <div class="bg-image hover-overlay ripple" data-mdb-ripple-color="light">
                    <img src="{% if post.banner %}{{ post.banner.url }}{% else %}{{ MEDIA_URL}}/media/default/python-django.png{% endif %}" class="img-fluid post-banner bg-gradient bg-dark" />
                    <a href="#!">
                        <div class="mask" style="background-color: rgba(251, 251, 251, 0.15);"></div>
                    </a>
                </div>
                <div class="card-body">
                    <h5 class="card-title">{{ post.title }}</h5>
                    <div class="card-text truncate-3">{{ post.blog_post|safe }} </div>
                    <a href="{% url 'view-post' post.id %}" class="btn btn-primary">Read</a>
                </div>
            </div>
        </div>
        {% endfor %}


    </div>
    {% if not posts %}
    <center>No Bogs has been posted yet</center>
    {% endif %}
</section>
</div>  
{% endblock pageContent %}
myapp/templates/base.html
//myapp/templates/base.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<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"> 
    {% if page_title %}
    <title>{{ page_title }} | Blog Site</title>
    {% else %}
    <title>Blog Site</title>
    {% endif %}
    <link rel="stylesheet" href="{% static 'blogApp/assets/bootstrap/css/bootstrap.min.css' %}">
</head>
<body>
    {% block TopNavigation %} {% include "TopNavigation.html" %} {% endblock TopNavigation %}
    <main class="my-5">
        <div class="container" style="padding-top: 50px;">
            {% block pageContent %} {% endblock pageContent %}
        </div>
    </main>
    {% block ScriptBlock %} {% endblock ScriptBlock %}
    <footer class="bg-light text-lg-start">
        <div class="text-center p-3" style="background-color: rgba(0, 0, 0, 0.2);">
            © {% now 'Y' %} Copyright:
            <a class="text-dark" href="#" target="_blank">Cairocoders</a>
        </div>
    </footer>
</body>
</html>
myapp/templates/TopNavigation.html
//myapp/templates/TopNavigation.html
{% load static %}
<header>
    <nav class="navbar navbar-expand-lg navbar-light bg-white fixed-top">
        <div class="container">
            <a class="navbar-brand" target="_blank" href="#">Cairocoders</a>
            <button class="navbar-toggler" type="button" data-mdb-toggle="collapse" data-mdb-target="#navbarExample01" aria-controls="navbarExample01" aria-expanded="false" aria-label="Toggle navigation">
            <i class="fas fa-bars"></i>
            </button>
            <div class="collapse navbar-collapse" id="navbarExample01">
                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                    <li class="nav-item active">
                        <a class="nav-link" aria-current="page" href="{% url 'home-page' %}">Home</a>
                    </li>
                    {% for category in category_list_limited %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'category-post' category.pk %}">{{ category.name }}</a>
                    </li>
                    {% endfor %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'category-page' %}">All Categories</a>
                    </li>
                </ul>

                <ul class="navbar-nav d-flex flex-row">
                    {% if user.id %}
                    <li class="nav-item me-3 me-lg-0">
                        Hello, {{ user.first_name }} {{user.last_name}}
                    </li>
                    <li class="nav-item me-3 me-lg-0">
                        <a class="nav-link" href="{% url 'logout' %}">Logout</a>
                    </li>
                    {% endif %}
                </ul>
            </div>
        </div>
    </nav>
</header>
myapp/templates/view_post.html
//myapp/templates/view_post.html
{% extends "base.html" %} 

{% block pageContent %}
<section class="text-center">
    <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
        <div class="row justify-content-center">
            <div class=" col-lg-12 col-md-12 col-sm-12 col-xs-12 card card-default rounded-0 shadow">
                <div class="card-body">
                    <center>
                        <img src="{% if post.banner %}{{ post.banner.url }}{% else %}{{ MEDIA_URL}}/media/default/python-django.png{% endif %}" alt="" class="img-fluid bg-gradient" id="view-post-banner">
                    </center>
                    <h4 class="fw-bolder mt-4 text-start">{{ post.title }}</h4>
                    <hr>
                    <div class="lh-1 text-start">
                        <span class="me-5"><small>Author: <b>{{ post.author }}</b></small></span>
                        <span class="me-5"><small>Category: <b>{{ post.category }}</b></small></span>
                        <span><small><i class="fa fa-calendar-day"></i> Published: <b>{{ post.date_added|date:"F d, Y h:i A" }}</b></small></span>

                    </div>
                    <div class="clear-fix py-3"></div>
                    <div>{{ post.blog_post|safe }}</div>
                </div>
            </div>
        </div>
    </div>
</section>
</div>
{% endblock pageContent %}
myapp/templates/by_categories.html
//myapp/templates/by_categories.html
{% extends "base.html" %} 

{% block pageContent %}
<section class="text-center">
    <h4 class="mb-5"><strong>'{{ category }}' Posts</strong></h4>

    <div class="row">
        {% for post in posts %}
        <div class="col-lg-4 col-md-6 mb-4">
            <div class="card shadow border">
                <div class="bg-image hover-overlay ripple" data-mdb-ripple-color="light">
                    <img src="{% if post.banner %}{{ post.banner.url }}{% else %}{{ MEDIA_URL}}/media/default/python-django.png{% endif %}" class="img-fluid post-banner bg-gradient bg-dark" />
                    <a href="#!">
                        <div class="mask" style="background-color: rgba(251, 251, 251, 0.15);"></div>
                    </a>
                </div>
                <div class="card-body">
                    <h5 class="card-title">{{ post.title }}</h5>
                    <div class="card-text truncate-3">{{ post.blog_post|safe }} </div>
                    <a href="{% url 'view-post' post.id %}" class="btn btn-primary">Read</a>
                </div>
            </div>
        </div>
        {% endfor %}


    </div>
    {% if not posts %}
    <center>No Bogs has been posted yet</center>
    {% endif %}

</section>
</div>
{% endblock pageContent %}
myapp/templates/categories.html
//myapp/templates/categories.html
{% extends 'base.html' %} {% block pageContent %}
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
    <div class="card card-default rounded-0 shadow ">
        <div class="card-header">
            <div class="d-flex w-100 align-items-center justify-content-between">
                <h4 class="card-title fw-bold">Category Management</h4>

            </div>
        </div>
        <div class="card-body">
            <div class="container-fluid">
                <div id="list" class="list-group">
                    {% for category in categories %}
                    <a href="{% url 'category-post' category.id %}" class="list-group-item list-group-item-action">
                        <h4><b>{{ category.name }}</b></h4>
                        <hr>
                        <p>{{ category.description }}</p>
                    </a>
                    {% endfor %}
                </div>
                {% if not categories %}
                <center>No Category Listed Yet</center>
                {% endif %}
            </div>
        </div>
    </div>
</div>
{% endblock pageContent %} 
myapp/admin.py
 
//myapp/admin.py
import django
from django.contrib import admin
from myapp.models import Category, Post
from django_summernote.admin import SummernoteModelAdmin

# Register your models here.
admin.site.register(Category)
#admin.site.register(Post)

class PostAdmin(SummernoteModelAdmin):
    summernote_fields = ('blog_post',)
  
admin.site.register(Post, PostAdmin)
Run : C:\django\blogsite>python manage.py runserver

Related Post