Non-primary key database sequence

AutoField has to have a primary key, but I have a column we want to increment. It’s a kind of account ID that we want to generate, but we don’t want to have them as the primary key as it’s a bit inflexible if we want to change the scheme at some point in the future, and at the moment all our models have UUIDs for PKs, which seems unnecessary to me but it’s what we have.

As we’re running Postgres we should be able to just have an extra identity field and I don’t mind create a new model Field class to do this… I am just wondering if this is going to break something somewhere? Presumably there’s some reason AutoFields have to be the primary key but I couldn’t surface much information up on it or if having a custom field class would bypass these issues.

Does anyone have an idea? Or if there’s a better solution to this problem I’m not thinking of?

We had a similar situation - what we had done was create the SERIAL column directly in the database using SQL, and defined the field in the model as an IntegerField. (If we had to do it again, we’d probably define the field as a custom migration.)

1 Like

We are also looking to do something similar. We created a custom migration script that does the creates a column short_url_ref in our model. However, what I don’t understand is that you said you defined the field in the model as an IntegerField - is it like a place holder or do you run migration again:

python manage.py makemigrations

This is my migration script, is there anything additional you are doing from your side. Because I am seeing that if I do makemigrations then django asks me to provide a default value etc.

def add_short_url_ref_field(apps, schema_editor):
    connection = schema_editor.connection
    with connection.cursor() as cursor:
            cursor.execute(
                'PRAGMA table_info({});'.format(connection.ops.quote_name(model._meta.db_table))
            )
            columns = cursor.fetchall()
            column_exists = any(column[1] == 'short_url_ref' for column in columns)
            if not column_exists:
                cursor.execute(
                    'ALTER TABLE {} ADD COLUMN short_url_ref SERIAL;'
                    .format(connection.ops.quote_name(model._meta.db_table))
                )

class Migration(migrations.Migration):
    dependencies = [
        ('', ''),
    ]

    operations = [
        migrations.RunPython(add_short_url_ref_field, reverse_code=remove_short_url_ref_field),
    ]

Your help would be appreciated here!

There is another possibility mentioned here: Django 4.2, is a 2nd autofield-like field on a model possible? - #4 by jaddison

Yes, we ran makemigrations, primarily to ensure that Django knows that the models match the database. But since we’ve already created the field in the model and the column in the database, we ran migrate --fake for it. That makes the definition of a default pretty much irrelevant.

Having said that, if we were to find ourselves in a similar situation now, we’d probably take a look at the approach in the thread that Tom references above.

When you ran make migrations didn’t Django complain about IntegerField() being non-nullable? When I run makemigrations I get a prompt asking to define the field as nullable - which in turn creates another migration script that adds the field. If it’s not too much to ask it would be really helpful if you could share me the steps to that you took?

Honestly, I don’t recall. Regardless, it didn’t matter because the migrate was run with the --fake parameter, which meant that nothing in the migration was going to be performed anyway.

I developed django-sequencefield that address exactly that case:

It’s now easy to add as many field as you want taking their values from a postgresql sequence. The sequence is created through a migration. The sequence can be shared through multiple tables which is also pretty useful in some cases