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.