Your First Blogging app
Create a django project and create an named home with following commands
Mkdir blogapp && cd blogapp
Python3 -m venv .venv
# In windows,
.venv/Scripts/activate or .venv\scripts\activate
# In MacOS/linux,
source .venv/bin/activate
pip install django django-tinymce pillow
django-admin startproject blogproject .
django-admin startapp home
Open settings.py and add ‘home’ & ‘tinymce’ to installed_apps list In blogproject/urls.py add this line
"""1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path,include #import include
urlpatterns = [
path('admin/', admin.site.urls),
path('',include('home.urls')) #add this line
]
Create a urls.py file inside home folder
from django.urls import path
from .views import home,detail
urlpatterns = [
path('',home,name="home"),
path('blog/<int:id>/',detail,name="detail"),
]
Goto models.py and create a class named Blog:
from django.db import models
from django.contrib.auth.models import User
from tinymce.models import HTMLField
from django.urls import reverse
class Blog(models.Model):
image = models.ImageField(upload_to="blog_images/")
title= models.CharField(max_length=30)
description = HTMLField()
author = models.ForeignKey(User,on_delete=models.CASCADE)
def __str__(self):
return f"{self.title} - {self.author}"
def get_absolute_url(self):
return reverse('detail',args=[self.id])
Goto admin.py and create a BlogAdmin to assign the request user as the admin & hide the author field in admin panel.
from django.contrib import admin
from .models import Blog
@admin.register(Blog)
class BlogAdmin(admin.ModelAdmin):
exclude = ('author',)
def save_model(self, request, obj, form, change):
if not obj.pk:
obj.author = request.user
super().save_model(request, obj, form, change)
Now lets setup Staticfiles like CSS,JS and HTML. And also media files (User uploaded static files)
Goto settings.py and add these new configs along with static_url
STATIC_URL = 'static/'
STATIC_ROOT = 'staticfiles/'
STATICFILES_DIRS = [ BASE_DIR / 'static',]
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
Goto blogproject/urls.py and make it
from django.contrib import admin
from django.urls import path,include
from django.conf import settings #import settings
from django.conf.urls.static import static #import static function
urlpatterns = [
path('admin/', admin.site.urls),
path('',include('home.urls'))
]
if settings.DEBUG:
#handle static files
urlpatterns += static(settings.STATIC_URL,document_root=settings.STATIC_ROOT)
#handle media files
urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
Goto views.py
from django.shortcuts import render,get_object_or_404
from .models import Blog
from django.core import paginator
# Create your views here.
def home(req):
blogs = Blog.objects.all()
blogs = paginator.Paginator(blogs,9)
page_number = req.GET.get('page')
blogs = blogs.get_page(page_number)
return render(req,'index.html',{'blogs':blogs})
def detail(req,id):
Blog = get_object_or_404(Blog,id=id)
return render(req,detail.html',{'blog':blog})
Create templates folder and index.html inside it.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Blog — Home</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
/* Small custom styles */
.hero {
background: linear-gradient(90deg, #0d6efd22 0%, #6c757d11 100%);
}
.post-excerpt { color: #6c757d; }
.tag { font-size: .85rem; margin: .15rem; }
.card-img-top { object-fit: cover; height: 180px; }
</style>
</head>
<body>
<!-- Navbar -->
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
<div class="container">
<a class="navbar-brand fw-bold" href="#">MyBlog</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navMenu">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navMenu">
<ul class="navbar-nav ms-auto align-items-lg-center">
<li class="nav-item"><a class="nav-link" href="#">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#">Contact</a></li>
</ul>
</div>
</div>
</nav>
<!-- Hero -->
<header class="hero py-5 mb-4">
<div class="container text-center">
<h1 class="display-5 fw-bold">Thoughts, Tutorials & Stories</h1>
<p class="lead text-muted mb-3">A clean, responsive Bootstrap blog layout to showcase your articles.</p>
<div>
<a class="btn btn-primary me-2" href="#">Get Started</a>
<a class="btn btn-outline-secondary" href="#">Browse Categories</a>
</div>
</div>
</header>
<!-- Main content -->
<main class="container mb-5">
<div class="row g-4">
<!-- Posts -->
<div class="col-lg-8">
<!-- Post Card -->
{% for blog in blogs %}
<article class="card mb-4 shadow-sm">
<img src="{{ blog.image.url }}" class="card-img-top" alt="Post image">
<div class="card-body">
<h3 class="card-title">{{ blog.title }}</h3>
<p class="text-muted small mb-1">By {{ blog.author }}</p>
<p class="post-excerpt mb-3">{{ blog.description|truncatechars_html:'70'|safe }}</p>
<a class="btn btn-sm btn-primary" href="{% url "detail" blog.id %}">Read more</a>
</div>
</article>
{% endfor %}
<!-- Pagination -->
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if blogs.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ blogs.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-label="Previous">
<span aria-hidden="true">«</span>
</span>
</li>
{% endif %}
{% for num in blogs.paginator.page_range %}
{% if blogs.number == num %}
<li class="page-item active"><span class="page-link">{{ num }}</span></li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
{% endif %}
{% endfor %}
{% if blogs.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ blogs.next_page_number }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-label="Next">
<span aria-hidden="true">»</span>
</span>
</li>
{% endif %}
</nav>
</div>
<!-- Sidebar -->
<aside class="col-lg-4">
<!-- Search -->
<div class="card mb-4 shadow-sm">
<div class="card-body">
<form class="d-flex" role="search" action="#" method="get">
<input class="form-control me-2" type="search" placeholder="Search posts" aria-label="Search">
<button class="btn btn-primary" type="submit">Search</button>
</form>
</div>
</div>
<!-- About -->
<div class="card mb-4 shadow-sm">
<div class="card-body">
<h6 class="card-title">About the author</h6>
<p class="card-text text-muted">Short bio or description that introduces the blog and the author. Link to about page for more.</p>
<a href="#" class="btn btn-sm btn-outline-primary">Learn more</a>
</div>
</div>
<!-- Categories -->
<div class="card mb-4 shadow-sm">
<div class="card-body">
<h6 class="card-title">Categories</h6>
<ul class="list-unstyled mb-0">
<li><a href="#" class="text-decoration-none">Design</a> <span class="text-muted small">· 12</span></li>
<li><a href="#" class="text-decoration-none">Development</a> <span class="text-muted small">· 24</span></li>
<li><a href="#" class="text-decoration-none">Tutorials</a> <span class="text-muted small">· 9</span></li>
<li><a href="#" class="text-decoration-none">Opinion</a> <span class="text-muted small">· 6</span></li>
</ul>
</div>
</div>
<!-- Tags -->
<div class="card mb-4 shadow-sm">
<div class="card-body">
<h6 class="card-title">Tags</h6>
<div>
<a href="#" class="btn btn-outline-secondary btn-sm tag">bootstrap</a>
<a href="#" class="btn btn-outline-secondary btn-sm tag">css</a>
<a href="#" class="btn btn-outline-secondary btn-sm tag">javascript</a>
<a href="#" class="btn btn-outline-secondary btn-sm tag">html</a>
<a href="#" class="btn btn-outline-secondary btn-sm tag">webdev</a>
</div>
</div>
</div>
<!-- Recent posts -->
<div class="card shadow-sm">
<div class="card-body">
<h6 class="card-title">Recent posts</h6>
<ul class="list-unstyled">
<li class="mb-2">
<a class="text-decoration-none" href="#">Design systems for small teams</a>
<div class="text-muted small">May 10, 2025</div>
</li>
<li class="mb-2">
<a class="text-decoration-none" href="#">Optimizing images for fast delivery</a>
<div class="text-muted small">Apr 26, 2025</div>
</li>
<li>
<a class="text-decoration-none" href="#">Accessibility basics</a>
<div class="text-muted small">Mar 08, 2025</div>
</li>
</ul>
</div>
</div>
</aside>
</div>
</main>
<!-- Footer -->
<footer class="bg-light py-4 border-top">
<div class="container text-center small text-muted">
© 2025 MyBlog · Built with Bootstrap
</div>
</footer>
<!-- Bootstrap JS bundle (includes Popper) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Detail.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ blog.title }} — Post</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.card-img-top { object-fit: cover; height: 360px; }
.post-meta { color: #6c757d; font-size: .95rem; }
.post-body { white-space: pre-wrap; }
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
<div class="container">
<a class="navbar-brand fw-bold" href="{% url 'home' %}">MyBlog</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navMenu">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navMenu">
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link" href="{% url 'home' %}">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#">Contact</a></li>
</ul>
</div>
</div>
</nav>
<main class="container my-5">
<article class="card shadow-sm">
{% if blog.image %}
<img src="{{ blog.image.url }}" class="card-img-top" alt="{{ blog.title }}">
{% endif %}
<div class="card-body">
<h1 class="card-title display-6 mb-2">{{ blog.title }}</h1>
<div class="d-flex align-items-center mb-3">
<div class="me-3">
{% if blog.author.profile.image %}
<img src="{{ blog.author.profile.image.url }}" alt="{{ blog.author }}" class="rounded-circle" style="width:48px;height:48px;object-fit:cover;">
{% endif %}
</div>
<div>
<div class="post-meta">By <strong>{{ blog.author }}</strong></div>
{% if blog.published_date %}
<div class="post-meta small">{{ blog.published_date|date:"F j, Y" }}</div>
{% endif %}
</div>
</div>
<div class="post-body mb-4">
{{ blog.description|safe }}
</div>
<a href="{% url 'home' %}" class="btn btn-outline-secondary">← Back to posts</a>
</div>
</article>
</main>
<footer class="bg-light py-4 border-top">
<div class="container text-center small text-muted">
© {{ now.year }} MyBlog · Built with Bootstrap
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>