How to package a nanodjango app

You may want to add a nanodjango site to a bigger project, or you may want to turn a nanodjango app into a standalone project on PyPI (such as privipod, a nanodjango-based secret sharing tool).

To do this, you may want to put your nanodjango script inside a package - for example:

```
myproject/

__init__.py __main__.py app.py

```

where app.py is your nanodjango script, and you want to launch it from __main__.py - but using the name of your package, myproject.

Nanodjango was always designed and intended to be run as a single file, where it picks up the app name from the file name. However, needs must, so there is limited support for what you’re trying to do.

However, be warned: here be dragons.

Running with a different name

When you instantiate Django, pass it the APP_NAME of your containing package:

app = Django(APP_NAME="myproject")

This is going to fix your paths and module lookups so that your nanodjango script will think it’s actually called “myproject”, and it’ll look for its static files, templates, migrations etc in your myproject/ dir - ie myproject/static/ etc.

Potential issues

However, I warned you there are dragons:

  • This approach has not been widely used, and is likely break in exciting ways if you push it too far.

  • This also means it’ll expect your myproject/templates/ etc are intended for your nanodjango app;

  • … which means anything under myproject/static/ and myproject/public/ will be public to anyone who can access the webserver;

  • … and it’ll overwrite anything in myproject/static-collected/ if you’re running in production mode;

  • … and it’ll create any migrations it needs to in myproject/migrations/.

  • It will be treating your myproject/ dir as your project root - so your default sqlite database will also end up in your Python installation’s site-packages, which means it’s not exactly safe.

So, non-exhaustive list of things you will want to do:

  • Specify a different database path

  • Always ship with complete migrations (or never ship migrations) - you could end up with clashes if your users’ migration history doesn’t match yours

  • Be careful to avoid directory name clashes - perhaps specify custom paths for static, public, templates, static-collected, migrations etc.

How to actually get it to run

Your __main__.py could look something like this:

from .app import app

if __name__ == "__main__":
    app.run()

But chance are you’re going to want to control the Django settings before you instantiate it. The easiest pattern for this is probably to set some global variables somewhere before you import your app; for example, you could have this setup:

You can see this is a convoluted and messy pattern, but it works. There are other approaches, but this is probably the simplest way to crowbar an existing script into a package.