At first glance, and especially if you search for these topics in your favorite search engine, Django and system design seem to belong to two very different universes. But in reality, I believe they don’t.
Django is a very popular and powerful stable Python-based web framework. It is very convenient for developing complex applications. Usually, when we talk about system design interviews at FAANG/MAANG, we tend to focus on the high-level aspects of a system. We often ignore the “how” part. The problem is that while there are online resources available for both Django (which are mostly from the coding perspective) and system design (which are often high-level and completely ignore the coding perspective, and if there is some coding, it is typically not focused on Python and almost never on Django, which is Python-based). Hence, the dilemma.
Let’s examine the two in a bit more detail and understand why we need to look at both in the same context. We will also explain why the topics that we’ll cover in this blog are an important missing puzzle piece in the jigsaw of complex web applications. Let’s start with Django.
Django is a high-level Python web framework that enables the rapid development of secure and maintainable websites. Built by experienced developers, Django takes care of much of the hassle of web development, allowing developers to focus on writing apps without reinventing the wheel.
It follows the “Don’t Repeat Yourself” (DRY) principle, which means it aims to reduce redundancy in code, making it more efficient and easier to maintain. With its extensive documentation, active community, and versatility, Django has been used to build a wide range of websites, from content management systems to social networks and news sites.
Additionally, Django provides security features out of the box, helping developers avoid common security mistakes. Overall, it’s a powerful tool for creating robust web applications!
Django is a free and open-source web application framework written in Python. It is used for rapid web development and clean, pragmatic design. It is built by experienced developers to make repetitive tasks easier, so you can focus on writing apps instead of reinventing the wheel. This course teaches Django for beginning and intermediate level learners. The course includes a hands-on learning experience with the help of interactive widgets. At the end of the course, you will have created a project in Django that can be used in your portfolio.
Designing robust and scalable software systems is crucial for creating high-performance, reliable applications. Key considerations include choosing an appropriate system architecture (such as microservices or monolithic), implementing horizontal scaling, using caching and asynchronous processing, and ensuring secure authentication and authorization. By following best practices and considering factors like data storage, message queues, and API design, developers can build systems that handle increased load while maintaining performance and reliability.
While for small applications it might not seem to be that essential, system design is crucial for any reasonably complex Django application. The reason for that is that it focuses on the set of design principles that ensure the designed web application is well-structured, efficient, and scalable.
To put things into perspective, when we build a Django project, we’re not just writing code; we’re essentially creating a complex digital ecosystem with various components (such as views, models, templates, and databases). Proper system design helps us address a number of critical aspects such as scalability, modularity and maintainability, security, database design, API design, and performance optimization.
While there are a considerable number of resources focusing on the coding aspects of Django, as well as a number of others purely focusing on system design, if you check things out, there are not many, if at all, on system design, particularly for Django.
In this blog, we’ll address this problem by illustrating system design for a Django website from the ground up as well as point you to valuable online resources that you might want to use to gain a better understanding of both Django and system design.
To better understand system design, we need to take a problem and then follow it through till it reaches its logical conclusion. To ensure we don’t get distracted, let’s take a very simple problem.
Let’s design a Django blog website.
Let’s start by coming up with the key features and the requirements for the application. So, to keep things simple, here’s a possible list of requirements that we can expect from probably any blog website:
To start, we need to implement user registration, login, and logout functionality. We also need to allow users to create accounts and manage their profiles.
We need to be able to do CRUD operations on our blog posts. Users should be able to read existing posts, write new posts, edit existing ones, and delete posts.
We must enable users to leave comments on blog posts. Additionally, comments must be associated with specific posts and should be displayed on the post detail page.
We should allow the users to use a rich text editor (e.g., TinyMCE or CKEditor) for composing blog posts, allowing them to format text, add images, and embed media.
Not only should the blog be interesting, but we must ensure that it looks good on different devices (desktop, tablet, mobile). That would imply the use of various CSS frameworks (e.g., Bootstrap) to create a responsive layout.
To ensure we have a proper structure, we would need to implement pagination for listing blog posts. This would imply that we shall display a limited number of posts per page and provide navigation to other pages.
Blogs typically categorize posts into different topics or genres. We would need to allow users to filter posts by category or tag.
What would be a blog without a search function? We would need to add a search bar to find specific blog posts. We can implement search using Django’s built-in features or third-party libraries.
There has to be an admin so we can utilize the Django admin site for managing blog content. Admins can create, edit, and delete posts, categories, and tags.
Finally, it would make little sense to have a professional website if it does not ensure proper access control. We would have to have capabilities limiting certain actions (e.g., editing or deleting posts) to authorized users.
Now that we have tried to have a basic idea of the user requirements, we must use techniques involving creating elements such as user personas, use cases, and user stories for our Django blog project. This will help us understand our target audience, their needs, and the functionality our application should provide.
The number of blog user personas is extraordinarily large. So, for the sake of this blog, let’s take a sample of user personas to help us get started with the project:
Emma is a book lover who enjoys reading blogs about literature, book reviews, and author interviews. She wants a simple and intuitive blog where she can discover new book recommendations and engage with other readers. Emma expects a clean design, easy navigation, and a pleasant reading experience.
Alex is a software developer interested in tech-related content. They visit tech blogs to learn about the latest programming languages, frameworks, and best practices. Alex appreciates well-organized categories, code snippets, and tutorials.
Maya dreams of becoming a blogger herself. She wants to create and publish her own content on various topics. Maya needs a user-friendly platform that allows her to write, edit, and manage her blog posts easily.
Now that we have an idea of what kind of users might be interested in using our platform, let’s write some use cases.
Emma wants to leave comments on blog posts.
She registers an account, logs in, and gains access to comment functionality.
Maya wants to share her thoughts on a recent book she read.
She logs in, navigates to the “Create New Post” page, writes her content, and publishes it.
Alex notices a typo in one of their tech-related posts.
They log in, find the post, edit the content, and save the changes.
Emma is interested in fantasy book reviews.
She clicks the “Fantasy” category to view relevant posts.
Alex wants to find articles related to Python web frameworks.
They use the search bar and enter “Django” to see relevant posts.
With an idea of the use cases, let’s create a couple of example user stories that can be tested subsequently.
User story 1:
As a casual reader (Emma), I want to read book reviews and leave comments on interesting posts.
Acceptance criteria: Emma can register, log in, read posts, and comment on them.
User story 2:
As an aspiring blogger (Maya), I want to create and publish my own blog posts.
Acceptance criteria: Maya can log in, write new posts, edit existing ones, and publish them.
User story 3:
As a tech enthusiast (Alex), I want to find tech-related content easily.
Acceptance criteria: Alex can browse posts by category, search for specific topics, and view code snippets.
Wireframes are essential for visualizing the layout and structure of our Django blog application. Wireframes don’t need to be overly detailed; they’re meant to convey the overall structure. They serve as a blueprint for the site’s design.
With that in mind, let’s create a few example wireframes for the main pages of our blog:
The homepage should display a list of blog posts. Each post should show the title, a brief excerpt, and the publication date. We also need to include pagination controls to navigate through multiple pages of posts.
When a user clicks a post title, they should be taken to the individual post page. We need to show the full content of the post along with comments. We will also include a comment form at the bottom for users to leave their thoughts.
Here, we need to create wireframes for the registration and login forms, which will include fields for username, email, password, and confirmation.
After logging in, users should have a profile page. This should display user information (e.g., username, profile picture) and allow users to edit their profile details.
When creating a new post or editing an existing one, users would need a form. This will include fields for the post title, content (rich text editor), and category selection.
Let’s also take a look at a wireframe model for when the user has logged in and is dealing with the posts and the comments.
Let’s define the data models for our Django blog application using Django’s Object-Relational Mapping (ORM). These models will represent the core entities in our system.
First, let’s understand how we can create database tables and relationships in Django with examples.
In a Django-based project, creating database tables and defining relationships between them is essential. Let’s break it down:
Each Django model corresponds to a database table. The way we define models is by means of writing a models.py
file within our app. To create a new table, all we have to do is simply define a new model class. For example:
from django.db import modelsclass Author(models.Model):name = models.CharField(max_length=200)
Subsequently, when we run the makemigrations
command, Django will automatically generate the necessary SQL statements to create the new database table.
Any relational database is incomplete without relationships. Django supports all three main types of relationships between models. Let’s see how each of them can be designed in Django:
A record in one table relates to a single record in another table.
Example: A User
model can have a one-to-one relationship with a Profile
model. Each user has only one profile, and each profile is associated with only one user. Let’s define a one-to-one relationship using OneToOneField.
class User(models.Model):name = models.CharField(max_length=50)class Profile(models.Model):user = models.OneToOneField(User, on_delete=models.PROTECT, primary_key=True)language = models.CharField(max_length=50)email = models.EmailField(max_length=70, blank=True, unique=True)
One of the most common relationships in any relational database is the one-to-many or the many-to-one relationship. This connects two tables, establishing a many-to-one relationship.
Example: A Department
model can have multiple employees. Each employee belongs to a single department. We can easily define a foreign key using ForeignKey.
class Employee(models.Model):name = models.CharField(max_length=100)department = models.ForeignKey(Department, on_delete=models.CASCADE)
The third type of relationship represents a many-to-many association between two models.
Example: A Book
model can be associated with multiple Authors
, and each author can be associated with multiple books. Likewise, to define a many-to-many relationship, we can simply use the ManyToManyField.
class Author(models.Model):name = models.CharField(max_length=200)class Book(models.Model):title = models.CharField(max_length=200)authors = models.ManyToManyField(Author)
After defining the models, we need to learn to always execute a set of commands to create the database tables given as follows:
python manage.py makemigrationspython manage.py migrate
We shall next examine a basic set of models that can be expanded or modified based on the project’s requirements.
This model represents our user profiles.
Fields: user
(OneToOneField
to the built-in User
model), bio
, profile_picture
, etc.
This represents different blog post categories (e.g., Technology, Literature, Travel).
Fields: name
, slug
(for SEO-friendly URLs), description
.
This model represents individual blog posts.
Fields: title
, content
(Rich Text Field), author
( ForeignKey
to User Profile
), category
( ForeignKey
to Category
), created_at
, updated_at
.
The comment model represents user comments on blog posts.
Fields: post
(ForeignKey
to BlogPost
), author
( ForeignKey
to UserProfile
), text
, created_at
.
The tag model represents tags associated with blog posts (e.g., #Python, #BookReview).
Fields: name
, slug
.
Here’s how we can define these models in our models.py
file:
from django.db import modelsfrom django.contrib.auth.models import Userclass UserProfile(models.Model):user = models.OneToOneField(User, on_delete=models.CASCADE)bio = models.TextField(blank=True)profile_picture = models.ImageField(upload_to='profile_pics/', blank=True)class Category(models.Model):name = models.CharField(max_length=100)slug = models.SlugField(unique=True)description = models.TextField()class BlogPost(models.Model):title = models.CharField(max_length=200)content = models.TextField() # You can use a rich text field hereauthor = models.ForeignKey(UserProfile, on_delete=models.CASCADE)category = models.ForeignKey(Category, on_delete=models.CASCADE)created_at = models.DateTimeField(auto_now_add=True)updated_at = models.DateTimeField(auto_now=True)class Comment(models.Model):post = models.ForeignKey(BlogPost, on_delete=models.CASCADE)author = models.ForeignKey(UserProfile, on_delete=models.CASCADE)text = models.TextField()created_at = models.DateTimeField(auto_now_add=True)class Tag(models.Model):name = models.CharField(max_length=50)slug = models.SlugField(unique=True)# If needed, we can add more fields like description, popularity, etc.
Remember to run python manage.py makemigrations
and python manage.py migrate
after defining models to create the corresponding database tables.
Let’s take a look at some commonly missed best practices focused on data modeling.
Before creating database tables, we must define our domain model and understand the entities, their attributes, and the relationships between them. It helps to sketch out a class diagram or entity-relationship diagram (ERD) to visualize our model.
It is recommended to choose meaningful names for our models. A well-named model not only makes the code more readable but also saves time when things become complex. For example, instead of naming a model Data
, consider Product
or Customer
.
It goes without saying that the selection of the most appropriate field types for our model attributes can be considered very important. Here are some examples:
CharField
for short text (e.g., names, titles).
TextField
for longer text (e.g., descriptions, blog content).
IntegerField
, FloatField
, or DecimalField
for numeric values.
DateField
or DateTimeField
for dates and times.
BooleanField
for boolean values (True/False).
Use choices
for fields with predefined options (e.g., status, category).
By default, Django creates an auto-incrementing integer field called id
as the primary key for each model. We can customize the primary key by setting primary_key=True
on a specific field.
It is customary to use relationships between models. Here’s a sampling of the typical relationships:
One-to-one: Use OneToOneField
.
Many-to-one (foreign key): Use ForeignKey
.
Many-to-many: Use ManyToManyField
.
Consider using related_name
to specify reverse relationships.
It pays to add indexes to fields that are frequently used for filtering or sorting. Queries can be optimized by using either select_related
or prefetch_related
.
Abstract base classes can be used to share common fields and methods across multiple models. Inheritance (single or multi-table) allows us to reuse existing models.
Database constraints can be defined for particular columns by using keywords such as unique
, null
, and default
values. We can also use on_delete
to handle cascading deletes or protect related records.
We should regularly create and apply migrations to keep our database schema in sync with our models by running makemigrations
and migrate
commands.
Write tests for your models to ensure correctness. Use fixtures or factories to create test data.
In any Django project, creating views to handle requests is a crucial step. Views are responsible for processing incoming HTTP requests and returning appropriate responses. Let’s briefly explore both function-based views (FBVs) and class-based views (CBVs):
To create a simple FBV, simply define a Python function that takes an HttpRequest
object as an argument and returns an HttpResponse
.
from django.http import HttpResponsedef home_view(request):return HttpResponse("Welcome to our blog!")
Django requires creating a file where we can map the URL pattern to our FBV. The file name is titled urls.py
. Here’s an example file.
from django.urls import pathfrom .views import home_viewurlpatterns = [path('', home_view, name='home'),]
CBVs provide more flexibility and reusability. Here’s an example of a simple CBV:
from django.views import Viewfrom django.http import HttpResponseclass AboutView(View):def get(self, request):return HttpResponse("About us page")
We can also create a URL mapping (urls.py
) file for mapping the URL pattern to the CBV:
from django.urls import pathfrom .views import AboutViewurlpatterns = [path('about/', AboutView.as_view(), name='about'),]
FBVs are simpler and easier to understand. They are ideal for straightforward views. It is recommended to use them when we don’t need class-based features like mixins or inheritance.
CBVs are clearly more powerful and extensible as they allow mixins and inheritance. They should be used whenever there's a need for more complex views or if we want to reuse common functionality.
We have gone over a lot of material, but there’s still a lot left. To complete the application, we would still need to use Django forms for efficient data input. The process would include validation and processing user-submitted data, handling form submissions and display feedback, form validation, and user-related operations. However, going over all of these would be beyond the scope of this blog.
In this blog, we have demonstrated how Django and system design might be interlinked to develop powerful applications. To learn more about Django and system design, we encourage you to check out some of the courses on the Educative platform.
System Design Interview preparation in 2024
Free Resources