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)2web3 __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 StatReloader2Performing system checks...34System check identified no issues (0 silenced).56You 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:419Django 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.
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:
1app2 migrations/3 __init__.py4 __init__.py5 admin.py6 apps.py7 models.py8 tests.py9 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.
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 HttpResponse23def 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 path23from . import views45urlpatterns = [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 admin2from django.urls import include, path34urlpatterns = [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!"
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 render23def 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!
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 models2from django.utils import timezone34class 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 admin2from app import models34admin.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.
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
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.
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!
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 render23from app import models45def 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!
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.