Introduction to Backend Development with Django¶
Welcome to the Djangol
Presented By:
- Frinze Lapuz, Developer Experience Software Engineer at Atlassian
Prerequisites to this Workshop¶
You need the following installed:
- Browser
- VsCode (You can choose other IDEs, but for the interest of time - get something with Devcontainers support)
- Docker
The starting repo for this workshop: https://github.com/CodersforLearning/django-workshop
What you will be building as part of this workshop?¶
Imagine yourself building a project called "FeedForward" - it's a feedback management system.
A user works on a project. A user can give feedback to a project. Only the people in the project or author of the feedback can see it.
When a feedback is submitted - it will send an email to the project members - sentiment analysis will be performed on the feedback. where the value is between -1 and 1.
Below is the schematics
erDiagram
user {
uuid user_id PK
string username
string email
string password
datetime created_at
}
project {
uuid project_id PK
string name
datetime created_at
list(user) members FK
}
feedback {
uuid feedback_id PK
uuid project_id FK
uuid user_id FK
string content
datetime created_at
float(nullable) sentiment_score
}
user ||--o{ feedback : creates
project ||--o{ feedback : has
user |{--o{ project : works_on
What are APIs and REST-APIs?¶
Application Programming Interface
Analogy
Who interacts with the user interface? - the user Who interacts with the application programming interface ? - the application program (eg. the browser)
Representational State Transfer Application Programming Interface
-
backend architectural pattern that follows the GET/POST/PUT/PATCH/DELETE
-
Can be represented in Swagger/ Open API specification
Swagger/ Open API specification
In terms of using RESTful APIs, there are some naming and implementation conventions used to accurately label the endpoint with what it does.
CRUD to HTTP Verb Matching for JSON standard communications with REST-APIs
CRUD stands for Create, Read, Update, and Delete. RESTful APIs use HTTP verbs to specify the CRUD operation an endpoint is performing.
HTTP Verb | CRUD Operation |
---|---|
POST | Create/Update |
GET | Read |
PUT | Update/Replace |
PATCH | Update/Modify |
DELETE | Delete |
What is Django?¶
Django
- Python web framework for creating server-side application
Follows MVC:
- Model - database
- View – Interface (API or User Interface)
- Controller – URLs + routes
See Documentation
What is Django REST Framework (DRF)?¶
- library for creating REST-API
- just makes it easier develop REST-API
In:
- Authentication + Permission
- Generic API Views
- Serialisers (payload validation and format)
See Documentation
Interactive Workshop Time!!!¶
Firstly, open your IDE (VSCode) and open the terminal.
- Clone the repo:
git clone https://github.com/CodersforLearning/django-workshop-winter-2024.git
- Go to the directory:
cd django-workshop-winter-2024
- Open in dev container
What does the setup script do?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Initial files¶
manage.py
- the entrypoint of the Django applicationfeedforward
- the main Django applicationfeedforward/settings.py
- the configuration file of the Django applicationfeedforward/urls.py
- the URL routes of the Django applicationfeedforward/wsgi.py
andfeedforward/asgi.py
- used as the script to run production django application
Let's start the Django application¶
- Run
cd feedforward
- Perform the initial migration:
python manage.py migrate
. Notice that when you run this command, it will create adb.sqlite3
file. - Let's take a look at this file - just click it in the file explorer.
- Run the Django application:
python manage.py runserver
- Check out the Django application: http://localhost:8000
- Check out the Django admin: http://localhost:8000/admin
- Create a superuser:
python manage.py createsuperuser
. Login and look around the Django admin.
Additional info
Django ships default "django apps" defined settings.py
file. You can see the list of apps in the INSTALLED_APPS
variable.
Django apps are plugins that can be used to extend the functionality of the Django application. It's the core method of developing with this backend framework.
For the db.sqlite3
file, it's the default database that Django uses. You can change this to other databases like MySQL, PostgreSQL, etc.
Let's create our first Django App: Project¶
Run this command python manage.py startapp project
.
What did this command do?¶
Initial files that it created:
project/admin.py
- the admin interfaceproject/apps.py
- the configurationproject/models.py
- the database schemaproject/tests.py
- the test casesproject/views.py
- the views
Some files you want to create later are:
project/serializers.py
- the serializersproject/urls.py
- the URL routesproject/permissions.py
- the permissions
Installing the new "app" created¶
Go to settings.py
and add the new app to the INSTALLED_APPS
variable.
1 2 3 4 5 6 7 8 9 10 11 |
|
Creation of a Model for Project¶
Quick Reference: ERD
erDiagram
user {
uuid user_id PK
string username
string email
string password
datetime created_at
}
project {
uuid project_id PK
string name
datetime created_at
list(user) members FK
}
feedback {
uuid feedback_id PK
uuid project_id FK
uuid user_id FK
string content
datetime created_at
float(nullable) sentiment_score
}
user ||--o{ feedback : creates
project ||--o{ feedback : has
user |{--o{ project : works_on
Ready to Copy Paste
1 2 3 4 5 6 7 8 9 10 11 12 |
|
After updating the model, run:
python manage.py makemigrations
python manage.py migrate
This will create a new migration file and apply the change to your database.
Oops we forgot Content - Let's add that field¶
Suppose we want to add a description or content to our project. Just update the model like this:
1 2 3 4 5 6 7 8 9 |
|
After updating the model, run:
python manage.py makemigrations
python manage.py migrate
This will create a new migration file and apply the change to your database.
More Explanations about Migrations¶
Migrations are Django's way of propagating changes you make to your models (adding a field, deleting a model, etc.) into your database schema. Think of them as version control for your database structure.
Step 1: Initial State (Just models.py, no migrations, no DB)
1 2 3 4 5 6 7 8 |
|
Step 2: Developer runs python manage.py makemigrations
1 2 3 4 5 6 7 8 9 |
|
Step 3: Developer runs python manage.py migrate
(creates DB schema locally)
1 2 3 4 5 6 7 8 9 |
|
Step 4: Developer deploys code and migrations to production
1 2 3 4 5 6 7 8 9 |
|
Step 5: Migrator runs python manage.py migrate
in production (creates DB schema in production)
1 2 3 4 5 6 7 8 9 |
|
Step 6: Developer adds content
to models.py
1 2 3 4 5 6 7 8 9 10 |
|
Step 7: Developer runs python manage.py makemigrations
(creates new migration)
1 2 3 4 5 6 7 8 9 10 |
|
Step 8: Developer runs python manage.py migrate
(adds new field to local DB)
1 2 3 4 5 6 7 8 9 10 |
|
Step 9: Developer deploys code and migrations to production
1 2 3 4 5 6 7 8 9 10 |
|
Step 10: Migrator runs python manage.py migrate
in production (adds new field to production DB)
1 2 3 4 5 6 7 8 9 10 |
|
Legend:
- Steps are shown left-to-right for DEV and PROD.
- The "migrator" in production can be a script, a CI/CD job, or a command run in a Docker container.
When you have created that, check out db.sqlite3
and you'll see that there's a new table called project_project
.
Creation of the Admin Interface for Project¶
Ready to Copy Paste - Basic Version
1 2 3 4 |
|
Ready to Copy Paste - Cooler Version
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Now visit the Admin page and you'll see the Project model there.
Creation of a Model for ProjectFeedback¶
Quick Reference: ERD
erDiagram
user {
uuid user_id PK
string username
string email
string password
datetime created_at
}
project {
uuid project_id PK
string name
datetime created_at
list(user) members FK
}
feedback {
uuid feedback_id PK
uuid project_id FK
uuid user_id FK
string content
datetime created_at
float(nullable) sentiment_score
}
user ||--o{ feedback : creates
project ||--o{ feedback : has
user |{--o{ project : works_on
Ready to Copy Paste
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
After creating this, run python manage.py makemigrations
and python manage.py migrate
again.
When you have created that, check out db.sqlite3
and you'll see that there's a new table called project_projectfeedback
.
Creation of the Admin Interface for Project Feedback¶
Ready to Copy Paste
1 2 3 4 5 6 7 8 9 |
|
Now visit the admin interface and you'll see the ProjectFeedback model there.
Creation Views: the Interface in API¶
Step 0: Initialise Rest Framework (This is done once for a django source code)¶
We've already installed rest_framework as part of the packages, but we need to initialise it.
Putting Rest Framework in settings.py
1 2 3 4 |
|
After you put that there, we need some initialisation of "HTML templates" for the "Browsable API".
Ready to Copy Paste (Copy-pastable): urls.py
1 2 3 4 5 6 7 |
|
Diff View - Don't Copy. Copy the other one. This is only for explanation
1 2 3 4 5 6 7 8 |
|
Step 1: Project API (Basic)¶
project/serializers.py
¶
Serialisers are a way to convert Python models to JSON, XML or any other format you wish.
Ready to Copy Paste: Basic ProjectSerializer (project/serializers.py
)
1 2 3 4 5 6 7 8 |
|
project/views.py
¶
Views are the logic that handles the requests and responses.
Do not proceed further until told!
Views have different ways to be created - each with their own pros and cons. Just look and try to understand the code for now or listen at the instructor, then wait for further instructions before moving on.
Functional View for Project (project/views.py
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
Class-based View for Project (Recommended) (project/views.py
)
1 2 3 4 5 6 7 8 |
|
We recommend using class-based views (ViewSet) for most use cases, as they are more concise and easier to extend if you are always using the same pattern of accessing the database. Function-based views are better if you need to do something more custom (except if you just want to extend the existing pattern).
project/urls.py
¶
Ready to Copy Paste: project/urls.py
for Project
1 2 3 4 5 6 7 8 9 10 |
|
urls.py
(root url)¶
Ready to Copy Paste (Copy-pastable): urls.py
for Project
1 2 3 4 5 6 7 8 |
|
Diff View - Don't Copy. Copy the other one. This is only for explanation
1 2 3 4 5 6 7 8 |
|
Try it!
At this stage, you can use the http://localhost:8000/api/projects/ endpoint to list, create, retrieve, update, and delete projects.
Take a moment to play around with the endpoints.
Step 2: Enhance ProjectSerializer with UserSerializer¶
Now let's show project members as nested user objects. So we can express user information in the response.
project/serializers.py
(with UserSerializer)¶
Ready to Copy Paste: ProjectSerializer with UserSerializer (project/serializers.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Now, when you GET a project, the members field will show full user details instead of just IDs.
Step 3: Add ProjectFeedback API (Manual Nested Route Version)¶
Let's add feedback functionality to projects.
project/urls.py
(add feedbacks endpoint, manual nested route)¶
Ready to Copy Paste: project/urls.py
(add feedbacks endpoint, manual nested route)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Diff View - Don't Copy. Copy the other one. This is only for explanation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
What changed?
- Imported
ProjectFeedbackViewSet
. - Added two new
path()
entries for feedback endpoints, nested under each project.
project/views.py
(ProjectFeedbackViewSet: filter by project_id)¶
Ready to Copy Paste:
Ready to Copy Paste: ProjectFeedbackViewSet (project/views.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Diff View: Don't Copy. Copy the other one. This is only for explanation
Diff View:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Extra Exercises¶
The below is an extra exercise for you to try. It will really help you extend your knowledge further, and I can assure that you will use these in your projects.
This has just been left as extra exercise because otherwise this workshop will be too long, unless of course we have extra time.
Filters and Search in Django REST Framework¶
Filtering and searching are essential features for any API, allowing clients to retrieve only the data they need. Django REST Framework (DRF) provides powerful tools for both filtering and searching your API endpoints.
See the official DRF filtering documentation for more details.
1. Enabling Filtering and Search¶
First, make sure you have django-filter
installed (already included in the setup script for this workshop):
1 |
|
Add it to your INSTALLED_APPS
in settings.py
:
1 2 3 4 5 |
|
Then, set the default filter backend in your settings.py
:
1 2 3 4 5 6 7 |
|
2. Filtering and Searching Projects¶
Suppose you want to allow users to filter projects by name or members, and search by project name or content.
In your project/views.py
:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Now you can try queries like:
GET /api/projects/?name=FeedForward
(filter by name)GET /api/projects/?members=1
(filter by member user ID)GET /api/projects/?search=feedback
(search projects by name or content)GET /api/projects/?ordering=name
(order by name)
3. Filtering and Searching Feedbacks¶
You can do the same for feedbacks. In your ProjectFeedbackViewSet
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Now you can try queries like:
GET /api/projects/<project_id>/feedback/?user=2
(filter feedbacks by user ID)GET /api/projects/<project_id>/feedback/?search=great
(search feedbacks by content)GET /api/projects/<project_id>/feedback/?ordering=sentiment_score
(order feedbacks by sentiment)
4. Try It Out!¶
- Try filtering projects by name or members.
- Try searching for projects or feedbacks using keywords.
- Try ordering results by different fields.
Challenge: - Add more filter fields (e.g., filter feedbacks by sentiment score range). - Add search on related fields (e.g., search feedbacks by project name).
For more advanced filtering, you can create custom filter classes using django-filter
. See the DRF filtering docs for inspiration!
Automated Testing¶
Automated testing is a key part of building robust Django APIs. Django REST Framework provides tools to help you write tests for your endpoints.
Below is a simple example using APITestCase
to test the Project API. This test will:
- Create a user and log in
- Create a project via the API
- Assert that the project appears in the GET endpoint
Create a new file called project/tests.py
(or add to it if it already exists):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
To run the test, use:
1 |
|
You should see output indicating that your test ran and passed.
Testing with Multiple Users¶
Now, let's add a second test to see what happens when another user tries to access the projects. This is useful for testing permissions or visibility.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
You can now run the tests again:
1 |
|
Feel free to extend these tests to cover more scenarios, such as updating or deleting projects, or enforcing permissions so only project members can view certain projects.
JWT Authentication - Integration to Modern Frontend Frameworks¶
The only key bit to know is
install pip install djangorestframework-simplejwt
in settings.py
1 2 3 4 5 6 7 8 |
|
in urls.py
1 2 3 4 5 6 7 8 9 10 11 |
|
If you login via /api/token
, you will send something like this
1 2 3 4 |
|
you're going to get something like this back
1 2 3 4 |
|
Now once your frontend has received this token, you'll need to store it there. There's multiple ways to store it: - Local Storage (for long-term use) - Cookies (for cross-domain usage) - Session Storage (for one-session use case)
Now if your endpoint requires authentication, you need to add the Authorization
header with the token.
For example curl -X GET http://localhost:8000/api/projects/ -H "Authorization: Bearer eyJhbEXAMPLEOFJWTOKEN..."
Permissions¶
See docs
By default, if you set permission_classes = [permissions.IsAuthenticated]
, any authenticated user can edit any project. But what if you want only project members to be able to edit (update/delete) a project, while all authenticated users can still view (GET) projects?
This is where object-level permissions come in. You can create a custom permission class that checks if the user is a member of the project for unsafe methods (PUT, PATCH, DELETE), but allows all authenticated users to read (GET).
Step 1: Create a Custom Permission Class¶
Create a new file project/permissions.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Step 2: Use the Permission in Your ViewSet¶
In project/views.py
, import and use your new permission class:
1 2 3 4 5 6 |
|
Step 3: How It Works¶
- Any authenticated user can view (GET) any project.
- Only members of a project can update or delete it.
- If a non-member tries to edit, they get a 403 Forbidden error.
Note: DRF automatically checks object-level permissions for detail views (like retrieve, update, destroy). For list views, you may want to filter the queryset so users only see projects they are allowed to see.
For more details, see the DRF permissions documentation.
Sending Emails¶
Run docker-compose up
to start the mail server.
Your mail server will have this UI at http://localhost:8025/. It communicates via SMTP on port 1025.
Add these in your settings.py
1 2 3 4 5 6 7 8 |
|
Where are these values coming from?
If you check the docker-compose.yml
, you will understand that we are just configuring the values to send via SMTP to the mail server.
In actual production services, you will change this to your actual SMTP server like AWS SES, Sendgrid, Mailgun, etc.
project/utils.py
: Send email to all project members¶
Ready to Copy Paste: project/utils.py
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
project/views.py
: Email project members when feedback is created¶
Ready to Copy Paste: Email on feedback creation (project/views.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Now, whenever feedback is submitted for a project, all project members will receive an email notification. You can check the mail server UI at http://localhost:8025/ to see the sent emails.
Sentiment Analysis (Natural Language Processing / Machine Learning)¶
Sentiment analysis is the process of determining the sentiment or opinion expressed in a piece of text. It's a common task in natural language processing (NLP) and can be useful for understanding customer feedback, social media sentiment, and more.
For example:
- "I love this project" -> Positive
- "I hate this project" -> Negative
- "This project is ok" -> Neutral
Not really too related with Django, but this is where other fields such as AI connect with.
In this section, we just use a pre-trained model in transformers
library.
1 |
|
Step 1: Create a Sentiment Analysis Utility¶
Create a new file called project/sentiment.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Step 2: Update Feedback Creation to Use Sentiment Analysis¶
In your project/views.py
, update the perform_create
method of ProjectFeedbackViewSet
to calculate and save the sentiment score:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Step 3: Try It Out¶
Now, whenever a new feedback is created, the sentiment score will be automatically calculated and saved in the database. You can see the score in the admin interface or via the API.
Tip: The first time you run this, the model will be downloaded, so it may take a minute. After that, it will be cached.
Tip: If you want to avoid the initial delay (cold start) in production, you can use Django management commands to pre-download the model or run initialization code ahead of time. For example, you can run python manage.py shell
and execute the following to trigger the download:
1 2 |
|
You can also automate this as part of your deployment scripts to ensure the model is ready before your app starts serving requests.