Tutorial

Lets look at how to write the Getting started sample and use the key features.

Initialise nanodjango

First we import nanodjango and create a Django instance:

from nanodjango import Django

app = Django()

The Django instance, along with the nanodjango command, performs the magic needed to make everything work in one file.

We can pass Django settings into the constructor as named keywords. We don’t do it above as nanodjango has sensible defaults, but you can pass in any standard Django setting, plus some extra ones that nanodjango uses, like ADMIN_URL:

app = Django(
    ADMIN_URL="secret_admin/",
    SECRET_KEY=os.environ["DJANGO_SECRET_KEY"],
    ALLOWED_HOSTS=["localhost", "my.example.com"],
    DEBUG=False,
)

For a full list of special nanodjango settings, see Settings.

Create a view

In nanodjango a view is a function which is decorated with @app.route:

@app.route("/")
def count(request):
    ...

The @app.route("/") decorator serves this view at the url /, and the view function is passed the request as it would in a normal Django project. You can decorate both normal and async views in the same way.

From there we can do anything we would in a normal Django view - eg add more decorators, process the request, use Django forms. We then either return a standard Django HttpResponse, or nanodjango also lets you return a plain string for convenience.

For full details on app.route, including how to specify regular expression paths and include other urlconfs, see Views.

Create an API endpoint

We use Django Ninja to provide a simple syntax for defining APIs - nanodjango provides a convenient @app.api decorator which initialises and registers a NinjaAPI instance:

@app.api.get("/hello")
def hello(request):
    return {"message": "Hello!"}

For more details on working with Django Ninja in nanodjango, see APIs.

Create a model

Django models work exactly the same way as in Django, except you define them in the same file, and they must be defined after app = Django():

from django.db import models

app = Django()

class CountLog(models.Model):
    timestamp = models.DateTimeField(auto_now_add=True)

We can now run the script, which will create migrations for the app and apply them:

nanodjango run counter.py

You could also create migrations manually without running:

nanodjango manage counter.py makemigrations counter

For full details on how to use Django management commands with nanodjango, see Command usage.

Use the model

Once the model is defined, you can use it in a view as you would any normal Django model and view:

@app.route("/")
def count(request):
    CountLog.objects.create()
    return f"<p>Number of page loads: {CountLog.objects.count()}</p>"

This just creates an object at every request and reports on how many objects there are, but you could use it with a ModelForm just like a normal Django model and view.

A more complicated example could look like this:

@app.admin
class Author(models.Model):
    name = models.CharField(max_length=100)
    birth_date = models.DateField(blank=True, null=True)

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ["name", "birth_date"]

@app.route("add/")
def add_author(request):
    form = AuthorForm(request.POST or None)
    if form.is_valid():
        form.save()
        return "Author added"
    return render(request, "form.html", {'form': form})

Use the admin site

To add a model to the admin site, decorate your models with the app.admin decorator:

@app.admin
class CountLog(models.Model):
    ...

This decorator also lets you configure your ModelAdmin by passing class attributes:

@app.admin(
    list_display=["id", "timestamp"],
    readonly_fields=["timestamp"],
)
class CountLog(models.Model):
    ...

Using the decorator anywhere in your script will automatically enable the admin site. You can customise the url with ADMIN_SITE, or use the setting to force the admin site to be active even if you’re not using the decorator anywhere.:

app = Django(ADMIN_URL="admin/")

Deploy to production

Nanodjango has a built-in command to run your script in production mode, with debug turned off, using whitenoise, gunicorn or uvicorn, and sensible defaults:

nanodjango serve counter.py

If you want more control, you can also pass the Django instance to a WSGI or ASGI server directly:

gunicorn -w 4 counter:app
uwsgi --wsgi-file counter.py --callable app --processes 4
uvicorn counter:app

Convert to a full Django project

When you reach the point where you have several views or models, you may want to think about converting your app into a full Django project.

You can do this with:

nanodjango convert counter.py /path/to/site --name=myproject

This will create a Django project at /path/to/site/myproject, and unpack your single file into a full app at /path/to/site/myproject/counter. Your sqlite database, migrations, templates and static files will be copied across, if you have them, and in many cases it should run straight away:

cd /path/to/site
./manage.py runserver 0:8000

For full details on how to use nanodjango’s convert command, see Converting to a full Django project.