Creating a web application using Python and the Django Web Framework.

Django is a really cool web development framework for python. It does remind me of Java web development though in that it does a lot for you that you can’t see. Working with Django was really fun and once you get the hang of it, you feel like programming more because its so intuitive. You can learn more about the framework here. This tutorial assumes you (me) knows about python. You will have to to set it up as well. I’m also using Django 2. A lot of the tutorials I watched were still in the old version but that didn’t prove to be much of an issue. My go to Pluralsight course can be found here. It is outdated however.

Screenshot of built application

For this tutorial I created a small group application. You can create groups, add users and add members. Members are users who are part of a group.

To get started I followed the tutorial on the django website. This tutorial shows you how to set up your app. You can check out the tutorial here. There are two main steps to setup your application. You create a project and then you create your app. In django you can have multiple apps in your project. You can uses these how you wish but it is generally great for modularity.

$ django-admin startproject mysite  # create your project
$ python manage.py startapp polls  # create your app
$ python manage.py runserver 8080 # run your server 

Alrite now lets get into my code. For that you just need to run the server and/or following along. After you downloaded the git repo. You can find that here.

Routing and Urls

With all web applications the first thing you want to start thinking about is urls or routing. How do you navigate your application. In django we have the urls.py file. This file stores your url paths. Check out the file here.

Every app has its own urls.py file and the main project has a global urls.py file.

Quick Note
from django.urls import path
from . import views  # views are imported form the views.py file

urlpatterns = [
    path('', views.index, name="index"),
    path('users',views.users,name="users"),
    path('edit_group/<id>/',views.edit_group,name="edit_group"),
]

As you can see we return an list called urlpatterns with a different paths using a path function. The path function takes a path, a view and a name. You can name your urls anything you want. This is a good idea as you will use this functionality later on. Note the last url in the list. It holds a placeholder called id. That is how you create urls with parameters.

Now remember you have a main project and then apps. Most of our code for this tutorial is in the groups app, but ever so often we will need to interact with the main project files to do global configurations.

All the urls in the app

There are special urls associated with special views. You notice we imported some views above called LoginView, LogoutView. I will discuss those later. We are using the view functions that we created in that module in our urls.py file. I will get to views later on just remember that is what is happening. To be honest in order to create a url path you need to create a view so lets get to views now.

Controllers and Rendering Views

Your views in python are like controllers in PHP or JAVA web frameworks. Your views interact with the request objects and send responses back to the user/website. We have one view file for our groups app called views.py. In it are functions that render the views. Check out the full views.py file here.

# views.py 

def index(request):
    groups = Groups.objects.all()
    return render(request, "groups/index.html",{
        "active" : "index",
        'groups' : groups
    })

def group_detail(request, id):
    group = Groups.objects.get(pk=id)
    return render(request,'groups/group_detail.html',   {'model':group})

Above we have two functions. Each take a request object. The section function also takes an id argument. In our urls.py file when we say views.index we mean the index url will be rendered by the index function in the views file. Or if we said views.group_detail the url will be pointing to the group_detail function. View must return something and above they are returning a function called render. Render takes a request object, a template path and some content which is a dictionary that will be accessible in the template file.

Directory Structure

Lets take a look at the folder structure of our application as it is important going forward. I will review this first step after because it can be the most confusing. For now – know that we need functions in our views.py file to point to paths in our urls.py file. These functions render template files which are html files. This is how your web page is displayed.

Overview of folder structure

Now using the picture above you can get a better understanding of how your django application is structured. Remember though that this is just the app not the main project. So every app will have this kind of layout unless you choose to configure it differently.

HTML files and Templating

In your views when you render a template the template path points to the groups/templates in your directory setup. Django checks in your apps directory for a templates folder. So the path is relative to that. We added the group folder in the template folder to avoid conflicts. But note that base.html is outside.

return render(request,'groups/group_detail.html',{'model':group})

In our views we can have basic html. But we can add special tags that do special things. For example in our group_detail.html page we can display a model attribute like below

<div class="card-header">
   {{ model.name }}
</div>

This will show the group name. We can do this for different model attributes. This is how we display our tables in different pages. For example in the index.html – which is the main page – we display the table using a for loop

<tbody>
              {% for g in groups %}
              <tr>
                  <td><a href="{{ g.get_absolute_url }}">{{ g.name }}</a></td>
                  <td>{{ g.description }}</td>
                  <td>{{ g.author }}</td>
                  <td><a href="{{ g.delete_url }}" class="btn btn-primary btn-sm">Delete</a></td>
                </tr>
              {%  endfor %}
 </tbody>

We can see the double curly brackets used to display the name, description and author. However there is another type of tags the {% %} tags. You can execute expressions within these. This is how the for in loop was created. Some more examples are shown below. We use these all over our files.

{% for g in groups %}   - for in loop

{%  endfor %}  - close for in loop

{% url 'save_group' %}  -  get the full url for save_group 

{% if user.is_authenticated %} - check if user is authenticated

{% endif %} - end if statement

{% load staticfiles %} - grants access to the static directory

{% static 'groups/bootstrap.css' %}  - get the path for assets

We can inherit views using the extend function. In this project we currently have a main view called base html. In base.html we have a section where we call block content. When we extend the base view the html file that extends the base.html file is placed within these two tags.

{% block content %}
{% endblock %}

The view that is inheriting the view also needs to have these tags between the content that needs to be shown.

Models, Databases, Migrations

Creating models and databases is easy in django. Once we create a model we can generate a migration. A migration interacts with the database to create our table based on the model we created. To create a model we go to the models.py file in our app directory. You can view the groups model below. Check out the full models file here.

from django.db import models

class Groups(models.Model):
    name = models.CharField(max_length=50,                               blank=True,help_text="Group Name")
    description = models.TextField(help_text="Add a description")
    author = models.TextField(help_text="Choose an Author")
    status = models.CharField(max_length=64)
    created_at = models.DateTimeField(auto_now_add=True)

Our model is a class that inherits the models class form django.db packages. We can create fields in our model that will reflect how the table is created. We have a few text fields and some char fields. Note our datetime field and the auto_now_add attribute.

In our members model we added some foreign fields. We need to import the User model which comes by default in Django. We use the User model to associate a group with.

from django.contrib.auth.models import User

class Members(models.Model):
    group = models.ForeignKey(Groups, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

Once our models are created we need to deal with migrations.

python manage.py makemigrations

The makemigrations command is used to create the migrations from the models we just created. When we look at our migrations folder in our application we will see a new migration file. Once our migration is created we can just run the migrations command.

python manage.py migrate

The migrate command will execute any outstanding migrations including the ones we just created. Once this is done we will have our members and groups tables.

Model Functions

Models have a lot of functionality built into them. We can see how these are used in different views.

In the index view we need to get all the groups to display in a table.

groups = Groups.objects.all()

For the user and members views we did the same thing

users = User.objects.all()
members = Members.objects.all()

Say we need to get a single object we can do the following

group = Groups.objects.get(pk=id)

And finally to delete a model what can we do

Groups.objects.get(pk=id).delete()

A lot of things are going on in the views section of our application so lets focus on the views.py file for now.

More about Views

To redirect to another page we can do the following

from django.shortcuts import render, redirect

def welcome(request):
    return redirect("index")

The value we put in the redirect function is the name we used in the urls.py file. Of course we could just place a url there and it will work fine. Remember the view function must return something if it is being used as a request.

If we didn’t want to render a template we can send some text to our page using the HttpResponse function

from django.http import HttpResponse 

def welcome(request):
    return HttpResponse("Hello World")

We can check to see if the request is a POST request

def create_group(request):
    if request.method == "POST":
      name = request.POST.get("name")

Above we can also see how to get a post attribute from the session POST parameter.

Forms and Posting

Using forms is a great way to auto generating form layouts in your template views. They handle validation for you and make it easy to save your models. To create a form we just add a forms.py file to our app directory. Check the full file here.

class GroupForm(ModelForm):
    class Meta:
        model = Groups
        exclude = ('created_at', )

class UserForm(ModelForm):
    class Meta:
        model = User
        fields = ('username','email','password')

We can then create form classes. Above we have two form classes. The forms we create inherit from the ModelForm class. The forms uses another class called Meta. This class sets up how our model form behaves. In the meta class we tell it what model is being used and we can then exclude some fields or display only some fields. The GroupForm shows exclude and the UserForm shows the fields method. Once this is done our forms are created. We can use them in our views.

In views if we wanted to save a group we do

def save_group(request):
    if request.method == "POST":
        form = GroupForm(data=request.POST)
        if form.is_valid():
            form.save()
            return redirect("index")
    else: 
        form = GroupForm()
        return render(request,'groups/save_group.html',{'form':form})

We can see how we pass in the request.POST to our form. We then use the is_valid() function and save the form. This will create a new group row in our groups table in the database. If the request is not POST we create a empty GroupForm and send it to the view. In our view we can display all the fields we added from the form object we pass to it.

<form action="{% url 'save_group' %}" method="post">
  {% csrf_token %}
  {{ form }}
        <button type="submit" class="btn btn-primary">Submit</button>
</form>

So in the view above we display the form between the curly brackets. The form object only display the inputs we need to supply the form opening and closing tags and a submit button as well. We have to remember to add the csrf_token otherwise the form will not submit. Do this on all our forms.

Authentication and Access Control

Authentication in django has some cool features. You can import views for login and logout. In your views you can add decorators to defined whether a view function needs authentication.

from django.contrib.auth.decorators import login_required

@login_required
def delete_group(request, id):
    Groups.objects.get(pk=id).delete()
    return redirect("index")

So in the above code snippet we imported the login required decorator and we added it to the delete_group function. When a user attempts to go to this view function they will be redirected if they are not logged in. The code snippet shown below was shown already but the authentication is available in views as seen below. Just call the user.is_authenticated function.

{% if user.is_authenticated %}
      <div class="container">
          <div class="alert alert-success" role="alert">
              You are logged in as <b>{{ user.username }}</b>. Welcome!
            </div>
      </div>
      {% endif %}

In urls.py file we can add our login and logout views.

from django.contrib.auth.views import LoginView, LogoutView

urlpatterns = [ path('login',LoginView.as_view(template_name="groups/login.html"),name="group_login"),
    path('logout',LogoutView.as_view(),name="group_logout"),
]

Using the LoginView.as_view function you can select a template_name where the login view will be held. For our view we use the login.html template.

More settings

In order to use the urls for your projects you need to import them into the main project urls.py file. I mentioned this earlier on. Check out the settings file here.

urlpatterns = [
    path('admin/', admin.site.urls),
    path('groups/', include('groups.urls'))
]

So you notice that the root for all our group urls have group/. So every other url will be based on that root. We use the include function to add our group app urls to the main urls.py file.

In the settings.py file for your project you need to be sure to add your apps to the list of installed apps. Below I had to add two apps – groups and crispy_forms.


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'groups',
    'crispy_forms'
]

Crispy forms is something I installed using pip. PIP is a package manager for django and can be used to add extra functionality to your web apps.

At the bottom of the settings.py file we have some constants that affects how django behaves.

CRISPY_TEMPLATE_PACK='bootstrap4'

LOGIN_REDIRECT_URL= "index"
LOGOUT_REDIRECT_URL= "index"
LOGIN_URL="group_login"

Django automatically redirects to different places when authentication is in play. Specifying these url locations will allow it to redirect to the correct locations.

The Admin Site

Django creates a admin site for you automatically. It provides you will all the admin features you can think of. You can manage any models ( tables ) you are working with.

INSTALLED_APPS = [
    'django.contrib.admin',
]

In the main settings.py file you can see the admin app as being installed. In order for you to work with it you need to do two things. First create a super user and then add the models you want access to in the admin module.

python manage.py createsuperuser

The command create super user should walk you through the steps to create a super user. Once you have a user you can use the admin site. Just navigate to http://127.0.0.1:8000/admin.. Or whatever your site is being hosted. But the /admin is the most important part.

Django Login

The admin page is auto generated for you. You can manage your models and create new users, groups, privileges. Because my app is named groups there is a bit of a conflict. But under Groups you can see our models being Groupss and Memberss. Not sure why they named like that.

Django Admin Page

Before you have access to this screen you need to tell django that you want to edit your models in the admin site. In you admin.py file you will add the following

from django.contrib import admin
from .models import Groups, Members

# Register your models here.

admin.site.register(Groups)
admin.site.register(Members)

Once you register your models you can edit them in your admin panel.

Conclusion

Django is a great framework. So good that I didn’t want to stop using it. I will do more reviews on it later..maybe. A lot is done for you so you can get started easily. I did all of this using a SQLite database but you can of course connect to real databases.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s