Skip to content
Discord Get Started

Django

Django connects to DB9 through the standard PostgreSQL backend using psycopg2. No special adapter or driver is needed — Django’s ORM, migrations, and management commands work as expected.

  • A DB9 database (create one)
  • Python 3.10+
  • Django 4.2+
Terminal
db9 create --name django-app

Get the connection string:

Terminal
db9 db status django-app

Set the connection string as an environment variable:

.env.local
DATABASE_URL="postgresql://django-app.admin:YOUR_PASSWORD@pg.db9.io:5433/postgres?sslmode=require"

Install dependencies:

Terminal
pip install django psycopg2-binary dj-database-url python-dotenv
settings.py
import dj_database_url
from dotenv import load_dotenv
load_dotenv(".env.local")
DATABASES = {
"default": dj_database_url.config(
default="postgresql://django-app.admin:YOUR_PASSWORD@pg.db9.io:5433/postgres?sslmode=require",
conn_max_age=600,
)
}
blog/models.py
from django.db import models
class User(models.Model):
email = models.EmailField(unique=True)
name = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "users"
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=500)
content = models.TextField(blank=True)
published = models.BooleanField(default=False)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "posts"
def __str__(self):
return self.title

Generate and apply migrations:

Terminal
python manage.py makemigrations blog
python manage.py migrate

Verify the tables exist:

Terminal
python manage.py dbshell
\dt
blog/views.py
from django.http import JsonResponse
from .models import User, Post
def user_list(request):
users = User.objects.all().values("id", "name", "email", "created_at")
return JsonResponse(list(users), safe=False)
def create_user(request):
if request.method == "POST":
import json
data = json.loads(request.body)
user = User.objects.create(
email=data["email"],
name=data["name"],
)
return JsonResponse({"id": user.id, "name": user.name}, status=201)
return JsonResponse({"error": "POST required"}, status=405)
def post_list(request):
posts = Post.objects.select_related("author").values(
"id", "title", "content", "author__name", "created_at"
)
return JsonResponse(list(posts), safe=False)
blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("users/", views.user_list, name="user-list"),
path("users/create/", views.create_user, name="create-user"),
path("posts/", views.post_list, name="post-list"),
]
myproject/urls.py
from django.urls import path, include
urlpatterns = [
path("api/", include("blog.urls")),
]

For a full API layer, add Django REST Framework:

Terminal
pip install djangorestframework
blog/serializers.py
from rest_framework import serializers
from .models import User, Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ["id", "title", "content", "published", "author", "created_at"]
class UserSerializer(serializers.ModelSerializer):
posts = PostSerializer(many=True, read_only=True)
class Meta:
model = User
fields = ["id", "email", "name", "posts", "created_at"]
blog/views_api.py
from rest_framework import viewsets
from .models import User, Post
from .serializers import UserSerializer, PostSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.prefetch_related("posts").all()
serializer_class = UserSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.select_related("author").all()
serializer_class = PostSerializer
blog/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views_api import UserViewSet, PostViewSet
router = DefaultRouter()
router.register("users", UserViewSet)
router.register("posts", PostViewSet)
urlpatterns = [
path("api/", include(router.urls)),
]
  • Server-side only: Django runs on the server by default, so all DB queries go through the backend. Never log or render the connection string.
  • Connection pooling: Set CONN_MAX_AGE to reuse connections across requests. For high-traffic apps, consider django-db-connection-pool or PgBouncer.
  • Port 5433: DB9 uses port 5433, not the default PostgreSQL port 5432. Double-check your DATABASES config.
  • TLS required: Always include sslmode=require in the connection string or set it in OPTIONS for DB9’s hosted service.
  • WSGI server: Use Gunicorn or uWSGI in production. Django’s development server (runserver) is not designed for production traffic.

DB9 uses port 5433, not 5432. Update your DATABASES config or DATABASE_URL to use the correct port.

OperationalError: FATAL: authentication failed

Section titled “OperationalError: FATAL: authentication failed”

Verify the username format. DB9 expects {database-name}.admin as the username (e.g., django-app.admin).

Migrations fail with relation already exists

Section titled “Migrations fail with relation already exists”

If you created tables manually before running migrations, use python manage.py migrate --fake to mark existing migrations as applied. Then run future migrations normally.

Ensure sslmode=require is set. If using the direct config, add it under OPTIONS:

Python
"OPTIONS": {
"sslmode": "require",
}

Install the binary package: pip install psycopg2-binary. For production, compile from source with pip install psycopg2 (requires libpq-dev).