In this post we will build a blog application using Django, before going thru this application, you should have basic knowledge of Django. If you want to review basics, please go thru following posts.
Let us create project
$django-admin startproject blogapp
$cd blogapp
/blogapp$ python3.6 manage.py runserver
Now our basic project is running. Let us create an app now.
$python3.6 manage.py startapp blog
urls.py is not created inside app directory, we need to create blog/urls.py manually.
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.index, name='index'),
]
To have application urls accessible from main project, we need to add this urls.py with main project
from django.contrib import admin
from django.urls import include, path # Add include here
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('todo.urls')),# Add this line
]
before doing further changes let us create sample view in blog/views.py
we will edit this file later for details but we need to create this view to avoid any error while running intermediate command.
from django.shortcuts import render, get_object_or_404, redirect
def index(request):
context = "temp"
return render(request, 'blog/index.html',{'context':context})
Changes in settings.py file
Add application in settings file
INSTALLED_APPS = [
'blog.apps.BlogConfig', # add this statement
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
Now let us define templates directory as below
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
BASE_DIR + '/templates/', # add this line
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
and finally define static directory at the end of settings.py file.
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
We need to manually create static and templates directories. Directory structure created looks as below:
├── static
│ ├── css
│ ├── img
│ └── js
├── templates
│ ├── components
│ └── blog
├── media
│ ├── img
Creating model
Let us first create todoapp database and database user
$sudo su - postgres
[sudo] password for conquistadorjd:
postgres@inspiron-3542:~$ psql
psql (10.10 (Ubuntu 10.10-0ubuntu0.18.04.1))
Type "help" for help.
postgres=# CREATE DATABASE blogapp;
CREATE DATABASE
postgres=# CREATE USER blogappuser WITH PASSWORD 'password';
CREATE ROLE
postgres=# ALTER ROLE blogappuser SET client_encoding TO 'utf8';
ALTER ROLE
postgres=# ALTER ROLE blogappuser SET default_transaction_isolation TO 'read committed';
ALTER ROLE
postgres=# ALTER ROLE blogappuser SET default_transaction_isolation TO 'read committed';
ALTER ROLE
postgres=# ALTER ROLE blogappuser SET timezone TO 'UTC';
ALTER ROLE
postgres=# GRANT ALL PRIVILEGES ON DATABASE blogapp TO blogappuser;
GRANT
postgres=# \q
postgres@inspiron-3542:~$ exit
logout
Now we need to configure this database in our application
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'blogapp',
'USER': 'blogappuser',
'PASSWORD': 'password',
'HOST': 'localhost',
'PORT': '',
}
}
Now let us create a model
from django.db import models
from django.contrib.auth.models import User
STATUS = (
(0,"Draft"),
(1,"Publish")
)
class Category(models.Model):
# created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created at")
# updated_at = models.DateTimeField(auto_now=True, verbose_name="Updated at")
title = models.CharField(max_length=255, verbose_name="Title")
# slug = models.CharField(max_length=20, unique=True)
class Meta:
verbose_name = "Category"
verbose_name_plural = "Categories"
ordering = ['title']
def __str__(self):
return self.title
class Tag(models.Model):
name = models.CharField(max_length=20, unique=True)
# slug = models.CharField(max_length=40, unique=True)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
author = models.ForeignKey(User, on_delete= models.CASCADE,related_name='blog_posts')
content = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
status = models.IntegerField(choices=STATUS, default=0)
category = models.ForeignKey(Category, on_delete = models.CASCADE,verbose_name="Category",default=None,blank=True)
updated_on = models.DateTimeField(auto_now= True)
tags = models.ManyToManyField(Tag, related_name='rel_posts',default=None,blank=True)
featured_image = models.ImageField(upload_to='img', blank=True, null=True)
class Meta:
ordering = ['-created_on']
def __str__(self):
return self.title
class Comment(models.Model):
blog_post = models.ForeignKey(Post,on_delete = models.CASCADE,verbose_name = "blog_post",related_name="comments")
comment_author = models.CharField(max_length = 50)
comment_author_email = models.EmailField()
comment_content = models.CharField(max_length = 200)
comment_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.comment_content
class Meta:
ordering = ['-comment_date']
Once this is done, we need to run following two commands
$ python3.6 manage.py makemigrations blog
$ python3.6 manage.py migrate
Let us have this model accessible from admin and create some dummy data for development and unit testing.
from django.contrib import admin
from .models import Post,Comment,Category,Tag
admin.site.register(Post)
admin.site.register(Comment)
admin.site.register(Category)
admin.site.register(Tag)
Now create a admin user using following command
python3.6 manage.py createsuperuser
Let us run the server and login to admin from http://127.0.0.1:8000/admin and create some dummy data.
We had created url.py file under blogs directory in previous steps. Now update code as below:
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.index, name='index'),
path('<str:category>/<str:slug>/', views.details, name='details'),
]
We need to update main application urls.py file to enable media upload.
from django.contrib import admin
from django.urls import path, include
from . import settings
from django.contrib.staticfiles.urls import static #add this for media
from django.contrib.staticfiles.urls import staticfiles_urlpatterns #add this for media
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')),# Add this line for new app
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) #add this for media
blog/views.py
from django.shortcuts import render, get_object_or_404, redirect
from .models import Post
def index(request):
context = Post.objects.all()
return render(request, 'blog/index.html',{'context':context})
def details(request, category, slug):
blog_post = Post.objects.get(slug=slug)
comments = blog_post.comments.all()
print('comments', comments)
return render(request, 'blog/detail.html',{'context':blog_post,'comments':comments})
index.html
{% extends "components/base.html" %}
{% block content %}
<div class="container-fluid"/>
<div class="row"/>
<div class="col-1"/></div/>
<div class="col-8"/>
{% if context %}
<div class="list-group"/>
{% for post in context %}
<a href="{{post.category}}/{{post.slug}}" class="list-group-item list-group-item-action flex-column align-items-start"/>
<div class="d-flex w-100 justify-content-between"/>
<h1 class="mb-1"/>{{post.created_on|date:"jS F Y" }} by {{post.title}} </h1/>
<small/>{{post.created_on|date:"jS F Y" }}</small/>
</div/>
<p class="mb-1"/>{{post.content|linebreaks|slice:":200"}}</p/>
<small/>more ...</small/>
</a/><br/> <br/><br/>
{% endfor %}
</div/>
{% else %}
<p/> Nothing here :) . Finally you found it</p/>
{% endif %}
</div/>
<div class="col-3"/></div/>
</div/>
</div/>
{% endblock %}
details.html
{% extends "components/base.html" %}
{% load static %}
{% block content %}
{% if context %}
<div class="container-fluid"/>
<div class="row"/>
<div class="col-1"/></div/>
<div class="col-8"/>
<h2/>{{context.title|linebreaks}}</h2/>
<small/> {{context.created_on|date:"jS F Y"}} by {{context.author}}</small/><br/>
<img src="{{context.featured_image.url}}" alt="Smiley face" class="rounded mx-auto d-block" height="250" width="250"/>
<div id="postdetails"/>{{context.content|linebreaks}}</div/>
<br/>
{% if comments %}
<h2/>Comments</h2/>
<div class="list-group"/>
{% for comment in comments %}
<!-- <a href="#" class="list-group-item list-group-item-action "/> --/>
<div class="d-flex w-100 justify-content-between"/>
<h6 class="mb-1"/>by {{comment.comment_author}}</h6/>
<small/>{{comment.comment_date|date:"jS F Y"}}</small/>
</div/>
<p class="mb-1"/>{{comment.comment_content}}</p/>
{% endfor %}
</div/>
{% endif %}
</div/>
<div class="col-3"/></div/>
</div/>
{% else %}
<p/>No tasks are available.</p/>
{% endif %}
</div/>
{% endblock %}
Here template files are very basic ones, you can update it as per your needs. Once you have core functionality ready, its up to you, how you want to display it.
If you run into any issue while working on this code, please do let me know.