noise

Building a full-stack app with Python & Django

Jul 20, 2024
  • python
  • django
  • web
  • fullstack

After years of working in the JavaScript ecosystem, I got an itch of trying something new. I set a new goal for myself to build a web app using a different language. Since, I already had an understanding of Python, I decided to go with the language, hoping to repeat the process of trying my hands on different languages in future.

Initially the plan was to raw dog it using FastAPI for routing, Jinja for html templates, Postgres for database but decided to prioritise familiarity in Python web development first and chose Django instead.

So here's a beginner level guide on how I went about it.

Project Setup

1mkdir python-app && cd python-app

To ensure a consistent development environment and manage project dependencies, we'll leverage pipenv, a Python virtual environment tool. If you don't have pipenv installed, refer to the installation instructions here

Let's create our Python environment using pipenv with the following command:

1pipenv install

Running this command will generate two files, Pipfile and Pipfile.lock, similar to how a Node.js project uses package.json and package-lock.json. These files manage project dependencies and ensure everyone uses the exact same package versions, preventing unexpected behavior from mismatched dependencies.

Now that our environment is created, let's activate it. This ensures that any commands you run use the specific dependencies listed in our Pipfile instead of relying on globally installed packages.

1pipenv shell

You shall see following output:

1. /Users/prvnbist/.local/share/virtualenvs/tutorial-WmUVFWeu/bin/activate

Installing django

Django is a high-level Python web framework famous for its "batteries-included" approach, offering features like user authentication and admin panels to jumpstart development.

We can use pipenv to manage packages in our project.

1pipenv install django

Django provides handy built-in commands to streamline project setup. Let's use one of them to create a project.

1django-admin startproject project .

The command generated following files at the root of the project.

1manage.py (Django's command-line utility for administrative tasks)
2web
3 __init__.py (An empty file that tells Python that this directory should be considered a Python package)
4 asgi.py (An entry-point for ASGI-compatible web servers to serve your project)
5 settings.py (Settings/configuration for this Django project)
6 urls.py (The URL declarations for this Django project)
7 wsgi.py (An entry-point for WSGI-compatible web servers to serve your projec)

To launch the development server and access your project in a web browser, run the following command:

1python manage.py runserver

The following information will be displayed in the command prompt:

1Watching for file changes with StatReloader
2Performing system checks...
3
4System check identified no issues (0 silenced).
5
6You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
7Run 'python manage.py migrate' to apply them.
8July 19, 2024 - 08:26:41
9Django version 5.0.7, using settings 'project.settings'
10Starting development server at http://127.0.0.1:8000/
11Quit the server with CONTROL-C.

Open http://127.0.0.1:8000 in your browser to view the default Django welcome page. This is the starting point for your web application.

Default Django Homepage

App Setup

Each django project can house multiple apps, essentially functioning as mini web applications (think websites, blogs, etc.). To craft a new app within our project, let's utilize the following command:

1python manage.py app

Taking a peek at the project structure now, you'll find:

1app
2 migrations/
3 __init__.py
4 __init__.py
5 admin.py
6 apps.py
7 models.py
8 tests.py
9 views.py

To integrate our newly created app with the project, we need to tell Django about it. Open the project/settings.py file and locate the INSTALLED_APPS setting. This is a list that defines all the apps recognized by your Django project. Add the name of your app (which should match the app folder name) to the end of this list.

1INSTALLED_APPS = [
2 ...,
3 'app'
4]

VS Code Setup (Optional)

While manually activating the environment and running the server works, VS Code offers a smoother experience. Setting up a launch configuration automates these steps. With a click from the Run and Debug panel or simply pressing F5, your environment will activate, and the server will start, streamlining your development workflow.

To automate environment activation and server startup, let's leverage VS Code's launch configurations. Open the Command Palette (Cmd/Ctrl + Shift + P) and search for "Python: Select Interpreter". Choose the recommended option from the list that'll open another panel from where select django and it'll create the launch.json for you.

VS Code Command Palette

In case, the file is not created, copy the following snippet and create the .vscode/launch.json file at the root of your project.

1{
2 "version": "0.2.0",
3 "configurations": [
4 {
5 "name": "Python Debugger: Django",
6 "type": "debugpy",
7 "request": "launch",
8 "args": ["runserver" ],
9 "django": true,
10 "autoStartBrowser": false,
11 "program": "${workspaceFolder}/manage.py"
12 }
13 ]
14}

Go to the Run and Debug Panel or press Ctrl/CMD + Shift + D and run the python debugger and or just press F5 and you should see the terminal opening and running the development server.

Creating views

In web development, a view often acts like a traffic controller. It handles incoming requests for specific URLs (routes) and determines how to respond. Let's create a view handler within our app/views.py file. This code will define how the application responds to a particular URL:

1from django.http import HttpResponse
2
3def index(request):
4 return HttpResponse('Hello World')

Next, we need to connect it to a specific URL in the browser. This mapping is done in a file called app/urls.py (which we'll create since Django doesn't include it by default). Essentially, we're telling Django: "when someone visits the homepage URL (/), use this view handler to determine the response."

1from django.urls import path
2
3from . import views
4
5urlpatterns = [
6 path("", views.index, name="index"),
7]

Now let's move in to our project and map the urls there as well in project/urls.py file.

1from django.contrib import admin
2from django.urls import include, path
3
4urlpatterns = [
5 path('', include('app.urls')),
6 path('admin/', admin.site.urls),
7]

The development server should have automatically restarted. Head over to your browser and visit http://127.0.0.1:8000. You should be greeted by the message: "Hello World!"

Hello World

Creating templates

Django provides a robust templating system for crafting the user interface. This separates the presentation layer (HTML) from the application logic (Python code). Let's create a dedicated folder named templates within your app directory. Inside this folder, create an index.html file. This file will house the HTML structure that users will interact with.

1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Python App</title>
7 </head>
8 <body>Hello World</body>
9</html>

Now, let's revisit our view function in app/views.py. Here, we'll leverage Django's templating system to return the index.html template we just created. By returning this template, the view essentially instructs Django on which HTML content to display in the user's browser for the homepage (/).

1from django.shortcuts import render
2
3def index(request):
4 return render(request, "index.html")

Head back to the browser and hit the page refresh, you should now see your template rendering on the homepage.

As your application grows, you'll likely have multiple routes with unique templates. To maintain consistency and avoid code duplication, Django offers a way to define base templates. Let's create a base.html file within your app/templates folder. This base template will act as a foundation, containing the common layout elements that will be shared across various pages in your application.

1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Python App</title>
7 </head>
8 <body>
9 <h1>Python App</h1>
10 {% block content %} {% endblock %}
11 </body>
12</html>

The block tag in base.html defines a reusable section of the template. This section can be overridden (replaced) with custom content in child templates, like index.html. Think of it as a placeholder that child templates can fill with their unique content, while still inheriting the overall layout from the base template.

1{% extends "base.html" %}
2{% block content %}
3 <h2>Homepage</h2>
4{% endblock %}

Base templates (e.g., base.html) define reusable layouts with block sections for customization. Child templates (e.g., index.html) use extends to inherit the base layout and override specific blocks with unique content.

Save the changes and refresh the browser, and you should now see the <h1>(Python App) tag followed by the <h2>(Homepage) tag within the inherited layout!

New Homepage

Creating models

In Django, models act as blueprints for your data. Each model maps to a database table, defining the structure and organization of your information. Let's create these models within the app/models.py file of our application.

1from django.db import models
2from django.utils import timezone
3
4class Exercise(models.Model):
5 id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
6 title = models.TextField(null=False, blank=False)
7 date = models.DateField(null=False, blank=False)
8 created_at = models.DateTimeField(default=timezone.now)

Think of each Django model as a Python class representing a real-world entity (like a blog post or a product). Within this class, you define fields that map to database table columns. For example, an id field might be an auto-incrementing integer or a uuid, while a title field could be a string with a maximum length. These fields essentially determine the data you can store for each instance of your model.

Our Exercise model inherits from Django's built-in models.Model class, providing a foundation for all database-backed models in your application. Each field within the model definition corresponds to a column in the database table. For instance, we've created an id field of type UUIDField. This leverages Django's database field types to ensure the id column stores Universally Unique Identifiers (UUIDs). These randomly generated IDs guarantee uniqueness across your database.

1pipenv install uuid

As our next step, we'll register our model in the app/admin.py file.

1from django.contrib import admin
2from app import models
3
4admin.site.register(models.Exercise)

Let's head over to the admin panel that comes pre-configured with django framework on the /admin path but before we do that we've to perform migrations otherwise you'll see the following error.

Admin Panel Error Screen

To access the admin panel, we need to address a common step: running migrations. Migrations synchronize your models with the database, ensuring everything is set up correctly. Let's run the necessary commands to fix this.

1You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
2Run 'python manage.py migrate' to apply them.

Running migrations

Django migrations are like automatic updates for your database. Whenever you modify your models (adding fields, changing data types), migrations ensure the database reflects those changes. While Django creates migrations for you, it's important to know when to run them to keep everything in sync.

1python manage.py migrate

Now if you try to access /admin, you should be able to see a login screen but how do we login?

To grant access to Django's admin panel, we'll need to create a superuser account. A superuser has full administrative privileges. We can achieve this using the following command.

1python manage.py createsuperuser

Creating super user

Head back to the admin panel (http://127.0.0.1:8000/admin) and log in with your superuser credentials. Remember to restart the development server (Ctrl/Cmd + C followed by the server start command) for the changes, including migrations, to take effect.

After login, you'll be redirected to this page.

Django Admin View

Our Exercise model isn't quite ready for the admin panel yet. Django needs to apply migrations to synchronize your models with the database. Let's run the necessary commands to create and apply these migrations, ensuring the Exercise model is properly reflected in the admin panel.

1python manage.py makemigrations

Running the command will generate a migration file within your application's migrations folder. This file acts as a blueprint for applying the model changes to your database. Let's proceed with running the migrations to bring everything in sync.

1python manage.py migrate

Refresh the admin panel (http://127.0.0.1:8000/admin) and navigate to the Exercises model. With migrations applied, you should now see a functional interface for managing your exercise data!

Exercise Model View

Now that the Exercises model is set up in the admin panel, feel free to add a few sample exercises! In the next steps, we'll connect to the database and dynamically display these exercises within our web templates, bringing your Django application to life!

Render exercises on the home page

To display our exercises in the template, let's modify the index handler within app/views.py. We'll introduce code to retrieve exercise data from the database.

1from django.shortcuts import render
2
3from app import models
4
5def index(request):
6 exercises = (
7 models.Exercise.objects.all().order_by("created_at")
8 )
9 return render(request, "index.html", context={'exercises': exercises})

Django provides a fantastic tool called an Object-Relational Mapper (ORM). Think of it as a translator between the world of Python objects (like our Exercise model) and the relational database. This ORM allows us to interact with the database using familiar Python code.

In the updated index handler, we're leveraging the ORM by calling the Exercise model directly. This fetches all exercise objects from the database and conveniently orders them by their creation date (created_at) using Django's built-in functionality.

Now that we have the exercises retrieved from the database, let's pass them along to our index.html template. We'll use context parameter to achieve this. context is essentially a dictionary that allows us to send data from our Python code (views) to our HTML templates. In this case, we'll include the exercises list as part of the context, making it accessible within the template.

1{% extends "base.html" %} {% block content %}
2 <h2>Exercises</h2>
3 <ul>
4 {% for exercise in exercises %}
5 <li>{{exercise.title}}</li>
6 {% endfor %}
7 </ul>
8{% endblock %}

With the index view handler updated to fetch exercises and the context passing data to the template, save your changes. Refresh the browser, and voila! You should now see your exercises displayed on the homepage!

Exercise List

Congratulations! We've successfully built and launched a foundational Django application. This includes route handlers for defining application logic, templates for crafting the user interface, models for structuring your data, and database integration for persistent storage.

Get in touch

Have a project for me, or just want to say Hi🙋🏽‍♂️? Feel free to email me at prvnbist@gmail.com.