This guide would have saved me a load of time (at least 100 hours) when I was learning.
This small mistake wastes a load of time when debugging Django apps, particularly when you are interacting with third party servers and APIs (often AWS).
This mini-guide will unlock a simple debugging technique for you: using network responses in your dev tools.
But, many people don't do this. Instead forcing themselves to debug their requests and responses with the trial-and-error of print statements and guessing.
So, I'll show you an example with AWS, using a modified version of my guide Build a simple file uploader with Django (and Alpine.js) ποΈπ .
TLDR: Use the network tab in your dev tools to debug your Django app requests. It's much more precise than print statements and guessing.
Error - 400 Forbidden
Here's a modified version of the code for my tutorial on how to upload files really easily to S3 on AWS, using Django + AlpineJS.
With the modified code below, we get an error when we try to upload a file:
When we start to upload, we get the error of 400 forbidden. That's an imprecise error message.
The mistake here would be to start by adding print statements or searching online for "Django S3 upload 400 forbidden".
Here's the modified code for reference:
views.py
import json
import os
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.shortcuts import render
from django.http import JsonResponse
from .services import generate_presigned_post
@method_decorator(csrf_exempt, name='dispatch')
def uploader(request):
if request.method == 'GET':
return render(request, 'upload.html')
elif request.method == 'POST':
body = json.loads(request.body)
file = body.get('file')
if not file:
return JsonResponse({'error': 'Missing file in request body'}, status=400)
presigned_data = generate_presigned_post(
bucket_name=os.getenv('BUCKET_NAME'), filename=file['name']
)
return JsonResponse(presigned_data)
services.py
import os
import boto3
def generate_presigned_post(bucket_name, filename, expiration=600):
"""
Docs: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/generate_presigned_post.html#generate-presigned-post
"""
s3_client = boto3.client(
's3',
aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY')+'deliberate_mistake'
)
return s3_client.generate_presigned_post(
Bucket=bucket_name, Key=filename,
ExpiresIn=expiration,
)
Instead, here's what you should do instead of inserting print statements or guessing:
Solution: Look at your network response
- Go to your dev tools for whatever browser you're using
- Go to the network tab.
- Click on the request that you made to upload
- Look at the preview.
- Look the response
This gives us a much more precise error message: The request signature does not match the signature you required.
Now we can identify the error: we were loading an incorrect access key here from our environment variables here.
Corrected code:
import os
import boto3
def generate_presigned_post(bucket_name, filename, expiration=600):
"""
Docs: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/generate_presigned_post.html#generate-presigned-post
"""
s3_client = boto3.client(
's3',
aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY') # mistake was here
)
return s3_client.generate_presigned_post(
Bucket=bucket_name, Key=filename,
ExpiresIn=expiration,
)
Error - 403 Bad Request
Now let's try a slightly different situation. Here's our code containing a different (hidden) mistake.
import os
import boto3
def generate_presigned_post(bucket_name, filename, expiration=600):
"""
Docs: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/generate_presigned_post.html#generate-presigned-post
"""
s3_client = boto3.client(
's3',
aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY')
)
return s3_client.generate_presigned_post(
Bucket=bucket_name, Key=filename,
ExpiresIn=expiration,
)
We upload again.
This time, the error message is Bad request
. Now it's a 400 error rather than a 403.
So that suggests something is wrong with the data we're sending rather than the authentication. But still, it's unclear.
Solution: Look at your network response
As before,
- Go to your dev tools (for whatever browser you're using)
- Go to the network tab
- Click on the request that you made to upload
- Look at the preview
- Look the response
-> Clicking on the preview shows us The region US West is wrong. Expecting EU Central 1.
:
Great. So, now we add region eu-central-1
:
import os
import boto3
def generate_presigned_post(bucket_name, filename, expiration=600):
"""
Docs: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/generate_presigned_post.html#generate-presigned-post
"""
s3_client = boto3.client(
's3',
aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY'),
region_name='eu-central-1' # Or whatever your region is. You can also set this in your environment variables using the AWS CLI.
)
return s3_client.generate_presigned_post(
Bucket=bucket_name, Key=filename,
ExpiresIn=expiration,
)
Upload successful β
Congrats - simple debugging technique unlocked β
Here are some related guides about how to upload files simply with Django:
- For uploading files with Django and HTMX, see my other guide: 3 steps to upload files properly with Django (and HTMX) π.
- For uploading images and storing them in your Django database, see my other guide: How to upload images easily with Django (and save the links to your database) π€οΈ
P.S Want to build Django frontend faster?
Probably like you, I want to get my Django frontend out fast as possible (preferably instantly).
So, I'm building Photon Designer
Photon Designer lets me produce Django frontend visually and extremely quickly - like a painter sweeping his brush across the page ποΈ.
If you found this guide helpful, you can check out Photon Designer .