My dear reader, how are you? السلام عليكم
Strength and growth come only through continuous effort and struggle – Napoleon Hill
This is the first part of StackDuplica web-Application tutorial series. This application has been adopted from the book Building Django 2.0 Web Applications. We will create a question-answer platform for users to be able to ask questions and get responses.
Few useful links to practically follow the project:
- StackDuplica GitHub repository — DirectMe
- All other tutorials on StackDuplica — DirectMe
- Clone the application from GitHub using
git clone https://github.com/ArsalanShahid116/StackDuplica
Setting-up Django Project
StackDuplica$ virtualenv stackenv
StackDuplica$ source stackenv/bin/activate
(stackenv)StackDuplica$ pip3 install django
(stackenv)StackDuplica$ pip3 freeze > requirements.txt
# Create a Django project
(stackenv)StackDuplica$ django-admin startproject config
# Change project name
(stackenv)StackDuplica$ mv config StackApp
(stackenv)StackDuplica/StackApp$ django-admin startapp qanda
# list the qanda application in the list of installed apps in settings.py of project as shown below
INSTALLED_APPS = [
'qanda', # add this
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
# Configure Django project to use PostgreSQL.
# open StackApp/config/settings.py and update the following DATABASE variable as follows
DATABASES = {
'default':
{
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mymdb',
'USER': 'mymdb',
'PASSWORD': 'development',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
(stackenv)StackDuplica/StackApp$ pip3 install psycopg2
(stackenv)StackDuplica/StackApp$ python3 manage.py makemigrations
(stackenv)StackDuplica/StackApp$ python3 manage.py migrate
(stackenv)StackDuplica/StackApp$ python3 manage.py runserver
Check if development server is working by entering the following URL in browser http://127.0.0.1:8000/. It should be giving you something as shown in Figure 1. If it does, you are doing great. cheers!
A detail on individual Django project files is given in one of my earlier posts at DirectMe

Fig 1: Django Welcome Page
Defining StackDuplica models and adding application wide templates and style sheets
We will start by first defining our model functions. Since it would be a question-answer application, we may need two models, i.e., one for questions and the other for answers. Each question can have one or more answers from users so we will use a question model as a ForeignKey. The models are as below:
# open qanda/models.py and add the following program
from django.conf import settings
from django.db import models
from django.urls.base import reverse
class Question(models.Model):
title = models.CharField(max_length=140)
question = models.TextField()
user = models.ForeignKey(to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('cueeneh:question_detail', kwargs={'pk': self.id})
def can_accept_answers(self, user):
return user == self.user
class Answer(models.Model):
answer = models.TextField()
user = models.ForeignKey(to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
question = models.ForeignKey(to=Question,
on_delete=models.CASCADE)
accepted = models.BooleanField(default=False)
class Meta:
ordering = ('-created', )
We will now create database migrations as shown below
(stackenv)StackDuplica/StackApp$ python3 manage.py makemigrations qanda (stackenv)StackDuplica/StackApp$ python3 manage.py migrate qanda
We will now add a path to our system level templates and static files in settings.py as shown below
# open config/settings.py and add the application-wide templates directory path as highlighted below
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'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',
],
},
},
]
# open config/settings.py and add the application-wide static files directory path
STATISFILES_DIR = [os.path.join(BASE_DIR, 'static'),]
We will now add a system-wide base template
# open templates/base.html and add the following program
{% load static %}
<!DOCTYPE html>
<html lang="en" >
<head >
<meta charset="UTF-8" >
<title >{% block title %}StackDuplica{% endblock %}</title >
<link
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb"
crossorigin="anonymous" >
<link
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
rel="stylesheet"
integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN"
crossorigin="anonymous" >
<link rel="stylesheet" href="{% static "base.css" %}" >
</head >
<body >
<nav class="navbar navbar-expand-lg bg-light" >
<div class="container" >
<a class="navbar-brand" href="/" >StackDuplica</a >
<ul class="navbar-nav" >
</ul >
</div >
</nav >
<div class="container" >
{% block body %}{% endblock %}
</div >
</body >
</html >
Let us now add a simple system-wide style sheet for our application as shown below
# open static/base.css and add the following program
nav.navbar {
margin-bottom: 1em;
}
.question-preview {
margin-bottom: 1em;
}
.question .meta
{
border-bottom: lightgrey solid .01em;
margin-bottom: 2em;
padding-bottom: 1em;
}
.question .body
{
padding-bottom: 1em;
}
.answer {
border-bottom: lightgrey dotted .01em;
margin-bottom: 1em;
padding-bottom: .5em;
}
Adding Forms, Views, and templates for Asking Questions
First add the Question form as below
# create qanda/forms.py and add the following program
from django import forms
from django.contrib.auth import get_user_model
from .models import Question
class QuestionForm(forms.ModelForm):
user = forms.ModelChoiceField(
widget=forms.HiddenInput,
queryset=get_user_model().objects.all(),
disabled=True,
)
class Meta:
model = Question
fields = ['title', 'question', 'user', ]
Now add a view to allow user posting the questions using Django class-based gerenic CreateView
# open qanda/views.py and add the following program
from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http.response import HttpResponseRedirect, HttpResponseBadRequest
from django.urls.base import reverse
from django.views.generic import (
CreateView,
)
from .forms import QuestionForm
from .models import Question
class AskQuestionView(LoginRequiredMixin, CreateView):
form_class = QuestionForm
template_name = 'qanda/ask.html'
def get_initial(self):
return {
'user': self.request.user.id
}
def form_valid(self, form):
action = self.request.POST.get('action')
if action == 'SAVE':
# save and redirect as usual.
return super().form_valid(form)
elif action == 'PREVIEW':
preview = Question(
question=form.cleaned_data['question'],
title=form.cleaned_data['title'])
ctx = self.get_context_data(preview=preview)
return self.render_to_response(context=ctx)
return HttpResponseBadRequest()
Let us now add a template for users to ask questions that extends the base template.
{% extends "base.html" %}
{% load markdownify %}
{% load crispy_forms_tags %}
{% block title %} Ask a question {% endblock %}
{% block body %}
<div class="col-md-12" >
<h1 >Ask a question</h1 >
{% if preview %}
<div class="card question-preview" >
<div class="card-header" >
Question Preview
</div >
<div class="card-body" >
<h1 class="card-title" >{{ preview.title }}</h1>
{{ preview.question | markdownify }}
</div >
</div >
{% endif %}
<form method="post" >
{{ form | crispy }}
{% csrf_token %}
<button class="btn btn-primary" type="submit" name="action"
value="PREVIEW" >
Preview
</button >
<button class="btn btn-primary" type="submit" name="action"
value="SAVE" >
Ask!
</button >
</form >
</div >
{% endblock %}
If we take a look at the template then we can see that we have loaded two packages just below extending the base template. One is markdownify and the second is crispy_forms. The first one is to add some text editing features like bold, italic and etc. And the second one is to add a style to standard Django forms.
We will now install and configure both of these packages as shown below:
# Installing and configuring markdownify
(stackenv)StackDuplica$ pip3 install django-markdownify
# open qanda/settings.py and add the following
INSTALLED_APPS = [
'qanda',
'markdownify', # add this
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MARKDOWNIFY_STRIP = False
MARKDOWNIFY_WHITELIST_TAGS = [
'a',
'abbr',
'acronym',
'b',
'blockquote',
'em',
'i',
'li',
'ol',
'p',
'strong',
'ul'
]
# installing and configuring crispy forms
(stackenv)StackDuplica$ pip3 install django-crispy-forms
# open config/settings.py and add the following
INSTALLED_APPS = [
'qanda',
'markdownify',
'crispy_forms', # add this
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
CRISPY_TEMPLATE_PACK = 'bootstrap4'
Finally add the routes for AskQuestions as following
# create qanda/urls.py and add the following program
from django.urls.conf import path
from qanda import views
app_name = 'qanda'
urlpatterns = [
path('ask', views.AskQuestionView.as_view(), name='ask'),
]
# create config/urls.py and add the following program
from django.contrib import admin
from django.urls import path, include
import qanda.urls
urlpatterns = [
path('admin/', admin.site.urls),
path('', include(qanda.urls, namespace='qanda')),
]
If you run the development server at this point in time you should be able to see the added functionalities at http://127.0.0.1:8000/ask
In the next tutorial, we will add an answer feature to the StackDuplica.
I hope you find this tutorial useful. If you find any errors or feel any need for improvement, let me know in your comments below.
Signing off for today. Stay tuned and I will see you next week! Happy learning.





