Django and PostgreSQL composite types

PostgreSQL has this nifty feature called composite types that you can use to create your own types from the built-in PostgreSQL types. It’s a bit like hstore, only structured, which makes it great for structured data that you might reuse multiple times in a model, like addresses.

Unfortunately to date, they were pretty much a pain to use in Django. There were some older implementations for versions of Django before 1.7, but they tended to do things like create surprise new objects in the namespace, not be migrateable, and require connection to the DB at any time (i.e. during your build).

Anyway, after reading a bunch of their implementations and then the Django source code I wrote django-postgres-composite-types.

Install with:

pip install django-postgres-composite-types

Then you can define a composite type declaratively:

from django.db import models
from postgres_composite_type import CompositeType


class Address(CompositeType):
    """An address."""

    address_1 = models.CharField(max_length=255)
    address_2 = models.CharField(max_length=255)

    suburb = models.CharField(max_length=50)
    state = models.CharField(max_length=50)

    postcode = models.CharField(max_length=10)
    country = models.CharField(max_length=50)

    class Meta:
        db_type = 'x_address'  # Required

And use it in a model:

class Person(models.Model):
    """A person."""

    address = Address.Field()

The field should provide all of the things you need, including formfield etc and you can even inherit this field to extend it in your own way:

class AddressField(Address.Field):
    def __init__(self, in_australia=True, **kwargs):
        self.in_australia = in_australia

        super().__init__(**kwargs)

Finally to set up the DB there is a migration operation that will create the type that you can add:

import address
from django.db import migrations


class Migration(migrations.Migration):

    operations = [
        # Registers the type
        address.Address.Operation(),
        migrations.AddField(
            model_name='person',
            name='address',
            field=address.Address.Field(blank=True, null=True),
        ),
    ]

It’s not smart enough to add it itself (can you do that?). Nor would it be smart enough to write the operations to alter a type. That would be a pretty cool trick. But it’s useful functionality all the same, especially when the alternative is creating lots of 1:1 models that are hard to work with and hard to garbage collect.

It’s still pretty early days, so the APIs are subject to change. PRs accepted of course.

Author: Danielle

Danielle is an Australian software engineer, computer scientist and feminist. She doesn’t really work on GNOME any more (sadly). Opinions and writing are solely her own and so not represent her employer, the GNOME Foundation, or anyone else but herself.

4 thoughts on “Django and PostgreSQL composite types”

  1. Looks promising! Can we use these types as primary keys for Django models (thus providing a useful workaround for the lack of composite pks in the Djanho ORM)?

    I presume indexes work as efficiently as regular composite indexes as well?

  2. If that’s possible in Postgres then it’s possible though untested here. There’s a bunch of caveats of things we’ve just not implemented yet: such as building indexes automatically.

  3. Hi Danielle,

    I’ve written up some similar code, and details at http://schinckel.net/2014/09/24/using-postgres-composite-types-in-django/

    Since then, I’ve come across some significant issues, especially related to different databases (and needed to have signal handlers based on connection_created and so on). From a quick perusal of your code, it looks like you have handled similar issues.

    You should think about submitting a PR to django with this stuff.

  4. Yep, I looked at your implementation before I started, and borrowed some ideas from it. I ran into a few issues with things I wanted to do in yours as it requires connection to the database at times when I don’t have it (i.e. during Docker build) and it didn’t behave cleanly with migrations, which was problematic. A PR to Django would be nice, once we’ve shaken the bees out. Alternatively Django needs to open up the ability to generate new types of migration operations.

Comments are closed.

Leave a Reply

Your email address will not be published. Required fields are marked *