pwshub.com

How to Write an Installable Django App

In the Django framework, a project refers to the collection of configuration files and code for a particular website. Django groups business logic into what it calls apps, which are the modules of the Django framework. There’s plenty of documentation on how to structure your projects and the apps within them, but when it comes time to package an installable Django app, information is harder to find.

In this tutorial, you’ll learn how to take an app out of a Django project and package it so that it’s installable. Once you’ve packaged your app, you can share it on PyPI so that others can fetch it through pip.

In this tutorial, you’ll learn:

  • What the differences are between writing stand-alone apps and writing apps inside of projects
  • How to create a pyproject.toml file for publishing your Django app
  • How to bootstrap Django outside of a Django project so you can test your app
  • How to test across multiple versions of Python and Django using nox
  • How to publish your installable Django app to PyPI using Twine

This tutorial includes a working package to help guide you through the process of making an installable Django app. You can download the source code by clicking the link below:

Prerequisites

This tutorial requires some familiarity with Django, pip, PyPI, pyenv—or an equivalent virtual environment tool—and nox. To learn more about these, you can check out the following resources:

  • Django Tutorials
  • Using Python’s pip to Manage Your Projects’ Dependencies
  • How to Publish an Open-Source Python Package to PyPI
  • Managing Multiple Python Versions With pyenv

Starting a Sample Django App in a Project

Even if you set out to make your Django app available as a package, you’ll likely start inside a project. In the sample code, you’ll find a 000_before directory that shows the code before the app is moved onto its own, demonstrating the process of transitioning from a Django project to an installable Django app.

You can also download the finished app at the PyPI realpython-django-receipts package page, or install the package by running python -m pip install realpython-django-receipts.

The sample app is a short representation of the line items on a receipt. In the 000_before folder, you’ll find a directory named sample_project that contains a working Django project:

sample_project/
│
├── receipts/
│   ├── fixtures/
│   │   └── receipts.json
│   │
│   ├── migrations/
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   │
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
│
├── sample_project/
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
│
├── db.sqlite3
├── manage.py
├── resetdb.sh
└── runserver.sh

This tutorial was written using Django 5.0.7 and it was tested with Python 3.8 through 3.12. All of the steps outlined in this tutorial should be compatible with earlier versions of Django going back to Django 1.8. However, some modifications will be necessary if you’re using Python 2. For simplicity, the examples in this tutorial assume at least Python 3.8 across the code base.

Creating the Django Project From Scratch

The sample project and receipts app were created using the Django admin command and some small edits. To start, run the following code inside of a clean virtual environment:

This creates the sample_project project directory structure and a receipts app subdirectory with template files that you’ll use to create your installable Django app.

Next, the sample_project/settings.py file needs a few modifications:

  • Add "127.0.0.1" to the ALLOWED_HOSTS setting so you can test locally.
  • Add "receipts" to the INSTALLED_APPS list.

You’ll also need to register the receipts app’s URLs in the sample_project/urls.py file. To do so, add path("receipts/", include("receipts.urls")) to the url_patterns list. Note that you’ll need to add the include function as an import from django.urls.

Exploring the Receipts Sample App

The app consists of two ORM model classes: Item and Receipt. The Item class contains database field declarations for a description and a cost. The cost is contained in a DecimalField. It’s never a good idea to use floating-point numbers to represent money. Instead, you should always use fixed-point numbers when dealing with currencies.

The Receipt class is a collection point for Item objects. This is achieved with a ForeignKey on Item that points to Receipt. Receipt also includes total() for getting the total cost of Item objects contained in the Receipt:

Python receipts/models.py

The model objects give you content for the database. A short Django view returns a JSON dictionary with all the Receipt objects and their Item objects in the database:

In this example, the receipt_json() view iterates over all the Receipt objects, creating a pair of the Receipt objects and a list of the Item objects contained within. All of this is put in a dictionary and returned through Django’s JsonResponse().

To make the models available in the Django admin interface, you use an admin.py file to register the models as shown below:

This code creates a Django ModelAdmin for each of the Receipt and Item classes and registers them with the Django admin.

Finally, a urls.py file registers a single view in the app against a URL:

You can now include receipts/urls.py in your project’s url.py file to make the receipt view available on your website.

With everything in place, you can run python manage.py makemigrations receipts to add the necessary tables to the database. Then, you can go ahead and use the Django admin to add data. Note that you’ll need to create a superuser first, and you can do this with python manage.py createsuperuser. Once you have some data, you can visit receipts/receipt_json/ to view the results:

In this example, you use curl to visit the receipt_json view, which results in a JSON response containing the Receipt objects and their corresponding Item objects. You use json.tool to format the output.

Testing the App in the Project

Django augments the Python unittest package with its own testing capabilities, enabling you to preload fixtures into the database and run your tests. The receipts app defines a tests.py file and a fixture for testing. This test is by no means comprehensive, but it’s a good enough proof of concept:

The fixture creates two Receipt objects and four corresponding Item objects. Click on the collapsible section below for a closer look at the code for the fixture.

A Django test fixture is a serialization of the objects in the database. The following JSON code creates Receipt and Item objects for testing:

The above fixture is referenced in the ReceiptTestCase class and is loaded automatically by the Django test harness.

You can test the receipts app with the Django manage.py command:

Running python manage.py test runs the single test defined in receipts/tests.py and displays the results.

Making Your Installable Django App

Your goal is to share the receipts app without a project and make it reusable by others. You could zip up the receipts/ directory and hand it out, but that’s somewhat limiting. Instead, you want to separate the app into a package so it’s installable.

The biggest challenge in creating an installable Django app is that Django expects a project. An app without a project is just a directory containing code. Without a project, Django doesn’t know how to do anything with your code, including running tests.

Moving Your Django App Out of the Project

It’s a good idea to keep a sample project around so you can run the Django dev server and play with a live version of your app. You won’t include this sample project in the app package, but it can still live in your repository.

Additionally, it’s now common practice when packaging code to put it in a src directory. Following this idea, you can get started with packaging your installable Django app and create a src directory as a sibling of the sample project. Then, you can move the receipts directory into it:

The directory structure now looks something like this:

django-receipts/
│
├── sample_project/
│   ├── sample_project/
│   │   ├── __init__.py
│   │   ├── asgi.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   │
│   ├── db.sqlite3
│   ├── manage.py
│   ├── resetdb.sh
│   └── runserver.sh
│
├── LICENSE
├── README.rst
└── src
    └── receipts
        ├── __init__.py
        ├── admin.py
        ├── apps.py
        ├── fixtures
        │   └── receipts.json
        ├── migrations
        │   ├── 0001_initial.py
        │   └── __init__.py
        ├── models.py
        ├── tests.py
        ├── urls.py
        └── views.py

To package your app, you need to pull it out of the project. Moving it is the first step. It’s a good idea to keep the original project for testing purposes, but you shouldn’t include it in the resulting package.

Bootstrapping Django Outside of a Project

Now that your app is outside of the sample Django project, you need to tell Django how to find it. If you want to test your app, run a Django shell that can find your app, or run your migrations. First, you’ll need to configure Django and make it available.

Django’s settings.configure() and django.setup() are key to interacting with your app outside of a project. More information on these calls is available in the Django documentation.

You’re likely to need this configuration of Django in several places, so it makes sense to define it in a function. Create a file called boot_django.py that contains the following code:

In this code block, Line 8 defines the location of the src directory, and line 9 adds it to Python’s path so that the interpreter can load it as a module.

Lines 12 and 27 set up the Django environment. The settings.configure() call takes a list of arguments that are equivalent to the variables defined in a settings.py file. Anything you would need in your settings.py to make your app run gets passed into settings.configure().

The above code is a fairly stripped-down configuration. The receipts app doesn’t do anything with sessions or templates, so INSTALLED_APPS only needs "receipts", and you can skip over any middleware definitions. The USE_TZ=True value is necessary because the Receipt model contains a created timestamp. Otherwise, you would run into problems loading the test fixture.

Running Management Commands With Your Installable Django App

Now that you have boot_django.py, you can run any Django management command with a very short script:

Django allows you to programmatically call management commands through call_command(). You can now run any management command by importing and calling boot_django() followed by call_command().

Your app is now outside the project, allowing you to do all sorts of Django-y things to it. Here are four utility scripts you can define:

  1. load_tests.py to test your app
  2. makemigrations.py to create migration files
  3. migrate.py to perform table migrations
  4. djangoshell.py to spawn a Django shell that’s aware of your app

Testing Your Installable Django App

The load_tests.py file could be as simple as the makemigrations.py script, but then it would only be able to run all the tests at once. With a few additional lines, you can pass command-line arguments to the test runner, which allows you to run selective tests:

Django’s DiscoverRunner is a test discovery class compatible with Python’s unittest. It’s responsible for setting up the test environment, building the suite of tests, setting up the databases, running the tests, and then tearing it all down. Starting on line 10, get_suite() takes a list of test labels and directly calls the DiscoverRunner on them.

This script is similar to what the Django management command test does. The name-main block passes any command-line arguments to get_suite(), and if there are none, then it passes in the test suite for the app, receipts.tests. You can now call load_tests.py with a test label argument and run a single test.

Line 18 is a special case to help when testing with certain external tools. For example, tox expects a TestSuite object even though line 13 is where the testing actually happens. This isn’t necessary with nox, the multi-version testing tool you’ll learn more about in a later section. You can also check out a potential substitute for DiscoverRunner in the collapsible section below.

One of the installable Django apps that I’ve written is django-awl. It’s a loose collection of utilities that I’ve accumulated over my years writing Django projects. Included in the package is an alternative to DiscoverRunner called WRunner.

The key advantage to using WRunner is that it supports wildcard matching of test labels. Passing in a label that begins with an equals sign (=) will match any test suite or method name that contains that label as a substring. For example, the label =rec would match and run the test ReceiptTest.test_receipt() in receipt/tests.py.

Defining Your Installable Package With pyproject.toml

To put your installable Django app on PyPI, you need to first put it in a package. PyPI expects a wheel or source distribution. A wheel gets built using build. To do this, you need to create a pyproject.toml at the same directory level as your src directory.

Before digging into that, you’ll want to make sure you have some documentation. You can include a project description in pyproject.toml, which is automatically displayed on the PyPI project page. Make sure to write a README fileREADME.rst with information about your package.

PyPI supports the reStructuredText format by default, but it can also handle Markdown with extra parameters:

Config File pyproject.toml

This pyproject.toml file describes the package that you’ll build. The Python packaging ecosystem has changed rapidly in the last few years, and work is still ongoing. By using setuptools 40.9.0 or greater, the amount of configuration is simplified and you’ll typically only need the pyproject.toml file.

Lines 1 to 3 declare which build back end to use, enforcing an up-to-date version of setuptools. The [project] section of pyproject.toml includes the metadata about your project.

Lines 6 to 10 specify the project’s name, version, a short description, and the associated README.rst and LICENSE files.

Line 27 indicates a minimum version of acceptable Python, while lines 28 to 30 contain the project’s dependencies. Any installers, such as python -m pip install, will know to also install the dependencies you’ve declared. Keep in mind that you always want to tie your installable Django app to its minimum supported version of Django.

If your code has dependencies that are only required to build the system or run tests, then you can add a [project.optional-dependencies] section, as shown in lines 35 to 40. Any labels declared within this section can be optionally installed. The configuration shown here creates a dependency group called dev, which can be installed with python -m pip install realpython-django-receipts[dev].

You’re almost ready to build the package for your installable Django app. The easiest way to test it is with your sample project—another good reason to keep the sample project around. The python -m pip install command supports locally defined packages, which can be used to make sure your app still works with a project.

You can install a locally editable version of a package to test it using the -e option to pip. To ensure everything is working as intended, it’s best to start with a brand-new virtual environment. Add a new requirements.txt file containing the following:

Python Requirements requirements.txt

The -e tells pip that this is a local editable installation. You’re now ready to install:

The dependencies list in pyproject.toml tells pip that it needs Django. In the background, Django needs asgiref, pytz, and sqlparse. All the dependencies are taken care of and you should now be able to run your sample_project Django dev server. Congratulations—your app is now packaged and referenced from within the sample project!

Testing Multiple Versions With nox

Django and Python are both constantly advancing. If you’re going to share your installable Django app with the world, then you’ll need to test it in multiple environments. The third-party nox library allows you to write short Python programs that create multiple virtual environments for all combinations of supported configurations.

You install nox with pip -m install nox, which adds a nox command to your environment. When you run nox, it looks for a file named noxfile.py, which defines your testing configuration. Your noxfile.py code is responsible for declaring all combinations of Python and the dependencies that you want to test:

The above file contains two groupings of configurations, each of which gets its own function. The @nox.session() decorator defines which versions of Python to use with each configuration. You use session.install() inside a configuration function to install one or more packages, and session.run() to define the actual test.

Line 6 says that test420() should use a version of Django between 4.2 and 4.3. Line 7 calls the load_tests.py script. The external=True argument silences any warnings caused by load_tests.py not being within the virtual environment.

When you run nox, it evaluates noxfile.py and creates a virtual environment for each configuration combination. The test420() function results in five virtual environments, one for each version of Python from 3.8 through 3.12. The nox command also allows you to run a subset of environments in case you need to hunt down a problem in one particular configuration. For more information, see the command-line usage section of the nox documentation.

Publishing to PyPI

Finally, it’s time to share your installable Django app on PyPI. There are multiple tools for uploading a package, but in this tutorial you’ll use Twine. The following code builds the packages and invokes Twine:

The first command builds the source and binary distributions of your package, and the call to twine uploads it to PyPI. Of course, you’ll need a PyPI account for it to work. If you have a .pypirc file in your home directory, then you can preset your username so the only thing you’re prompted for is your password:

You can use a small shell script to grep the version number from the code. Then, call git tag to tag the repo with the version number, remove the old dist/ directories, and call the above commands.

Conclusion

Django apps rely on the Django project structure, so packaging them separately requires extra steps. You’ve seen how to make an installable Django app by extracting it from a project, packaging it, and sharing it on PyPI.

In this tutorial, you’ve learned how to:

  • Use the Django framework outside of a project
  • Call Django management commands on an app that’s independent of a project
  • Write a script that invokes Django tests with an optional single test label
  • Build a pyproject.toml file to define your package
  • Write a noxfile.py script to test multiple configurations
  • Use Twine to upload your installable Django app

You’re all set to share your next app with the world. Happy coding!

Further Reading

Django, packaging, and testing are all very deep topics. There’s lots of information out there. To dig in deeper, check out the following resources:

  • Django Documentation
  • Get Started With Django: Build a Portfolio App
  • Django Tutorials
  • Managing Multiple Python Versions With pyenv
  • Using Python’s pip to Manage Your Projects’ Dependencies
  • Getting Started With Testing in Python
  • Poetry
  • Flit

PyPI has loads of installable Django apps that are worth trying out. Here are some of the most popular:

Copy

Copied!

Happy Pythoning!

Source: realpython.com

Related stories
3 weeks ago - The first step to getting started with Python is to install it on your machine. In this tutorial, you'll learn how to check which version of Python, if any, you have on your Windows, Mac, or Linux computer and the best way to install the...
1 month ago - Thanks to the popularity of various Large-Language Models like ChatGPT, prompt engineering has become a key skill for developers (and non-developers) to have. It's important if you want to be able to tap into the full potential of these...
1 month ago - In this tutorial, you'll learn about functional programming in Python. You'll see what functional programming is, how it's supported in Python, and how you can use it in your Python code.
1 month ago - In this tutorial, you'll learn how to create and use full-featured classes in your Python code. Classes provide a great way to solve complex programming problems by approaching them through models that represent real-world objects.
1 month ago - Introduction This is the blog post version of my talk at GopherCon 2024. Range over function types is a new language feature in the Go...
Other stories
2 hours ago - Ubuntu 24.10 ‘Oracular Oriole’ is released on October 13th, and as you’d expect from a new version of Ubuntu, it’s packed with new features. As a short-term release, Ubuntu 24.10 gets 9 months of ongoing updates, security patches, and...
3 hours ago - Did you know that CSS can play a significant role in web accessibility? While CSS primarily handles the visual presentation of a webpage, when you use it properly it can enhance the user’s experience and improve accessibility. In this...
4 hours ago - Design thinking workshops are your key to turning big problems into clear solutions. In this blog, I share how to run them efficiently and keep your team aligned. The post How to run a design thinking workshop appeared first on LogRocket...
5 hours ago - New memory-optimized X8g instances offer up to 3 TiB DDR5 memory, 192 vCPUs, and 50 Gbps network bandwidth, designed for memory-intensive workloads like databases, analytics, and caching with unparalleled price/performance and efficiency.
5 hours ago - Gain indispensable data engineering expertise through a hands-on specialization by DeepLearning.AI and AWS. This professional certificate covers ingestion, storage, querying, modeling, and more.