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.
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?
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.
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.
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.