Okay, now that the holidays are over. Let's get back to it, shall we? My hope is to get these out on a more regular schedule. However, like during the holiday season, sometimes other obligations (family, work) take priority.
I'm just going to jump right in to creating the QR app and designing/testing our basic "starter" model.
Create the app
Creating the app is simple enough.
$ django-admin startapp qr
Then open the config/settings.py file and add 'qr', to the end of the "INSTALLED_APPS" variable.

Now that we have the "application" created, we can start thinking about content.
Location location location
We need to figure out where we're going to store the QR code images when they're generated. In a larger project we might consider storing the images on a CDN (content delivery network) for better distribution over geographically different locations. But this isn't a big project. This is a small project where I don't expect anyone to really use so I'm not going to worry about load times, yet.
What I will do, is create a 'media' folder at the base of the project and set the 'MEDIA_ROOT' variable to the new folder in the settings.py file.

Note: I also had to add import os to the top of my settings file.
Also, quick tip: keep a separate terminal open with python manage.py runserver running. Whenever you save a change to a file it'll refresh the local web server and report any errors (like using the os module without importing it).
Create the model
This will be a simple model to start. All we want is a single line text input which we'll use to generate the QR code. We can add more QR code customization later.
So what fields do I need?
- a field to store the input text.
- a field to store the location of the generated image
- and a field for the creation date.
I'm also adding in a couple of functions (stolen from the Django tutorial) to help later on.
Here is what my
qr/models.pyfile looks like so far.
import datetime
from django.db import models
from django.utils import timezone
class QRCode(models.Model):
input_text = models.CharField(max_length=200)
qr_code_img = models.ImageField(upload_to='qr_codes/')
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.input_text
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <=self.pub_date <= now
I also needed to install the pillow module using pip install pillow to use the ImageField field.
The Segno Package
Now that we have a model, I'd like to do something with it. As in, create some QR codes. To do that, I've decided to go with the Segno package. And... I'll be honest, I did about 10 seconds of searching and just picked something. Segno seems easy enough to use and looks like it has the features that I'd want in a QR code generating web app.
Installing Segno was straight forward:
(.venv)$ pip install segno
Creating a QR code using Segno is also very easy.
import segno
qrcode = segno.make("test")
qrcode.save("test.png")
This creates a "test" QR code and saves it as "test.png". Easy right?
You can find more info on the Segno package from the PyPi website.
Testing the model
Now let's test out the model using the Segno package. I'm taking another chapter out of the Django tutorial and using the Django shell to do our testing. But first, let's migrate our changes.
(.venv)$ ./manage.py makemigrations
Migrations for 'qr':
qr/migrations/0001_initial.py
+ Create model QRCode
(.venv)$ ./manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, qr, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying qr.0001_initial... OK
Applying sessions.0001_initial... OK
Now, let's enter into the shell to play around with our new model.
(.venv)$ ./manage.py shell
7 objects imported automatically (use -v 2 for details).
Python 3.14.2 (main, Dec 5 2025, 00:00:00) [GCC 15.2.1 20251111 (Red Hat 15.2.1-4)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> import segno # import segno
>>> import os
>>> from django.utils import timezone # import django timezone
>>> QRCode.objects.all() # show that nothing has been done, yet.
<QuerySet []>
Everything is setup, let's create a new QRCode object with some test input text. Then we'll use the new object's input_text to create the QR code and use the object's ID for the naming.
>>> qrcode = QRCode(input_text="This is a test", pub_date=timezone.now())
>>> qrcode.save()
>>> qrcode.id
1
>>> qrcode.qr_code_img = f"{qrcode.id}.png"
>>> qrcode.save()
>>> from config.settings import MEDIA_ROOT
>>> qr_code_path = os.path.join(MEDIA_ROOT, 'qr_codes', f"{qrcode.id}.png")
>>> qr = segno.make_qr(qrcode.input_text)
>>> qr.save(qr_code_path, scale=4)
And now we should have a PNG file saved at media/qr_codes/1.png.

To be honest, this last part took a lot longer than expected. I kept running into a FileNotFoundError when attempting to create the QR code image. This was resolved by creating the qr_code_path variable with the full path to the "1.png" file location, which involved needing to import the MEDIA_ROOT variable from config.settings.
Next up, I'll work on creating the templates for the input form and the QR code results page, then I'll work on creating the views later.
To some of you more experienced Django developers, some tips and tricks are always appreciated in the comments. Especially if it makes something that I've done in the post easier to accomplish.
Top comments (0)