36

I'm currently building a project which involves a lot of collective intelligence. Every user visiting the web site gets created a unique profile and their data is later used to calculate best matches for themselves and other users.

By default, Django creates an INT(11) id field to handle models primary keys. I'm concerned with this being overflown very quickly (i.e. ~2.4b devices visiting the page without prior cookie set up). How can I change it to be represented as BIGINT in MySQL and long() inside Django itself?

I've found I could do the following (http://docs.djangoproject.com/en/dev/ref/models/fields/#bigintegerfield):

class MyProfile(models.Model):
    id = BigIntegerField(primary_key=True)

But is there a way to make it autoincrement, like usual id fields? Additionally, can I make it unsigned so that I get more space to fill in?

Thanks!

letoosh
  • 511
  • 2
  • 6
  • 13

7 Answers7

21

Django now has a BigAutoField built in if you are using Django 1.10:

https://docs.djangoproject.com/en/1.10/ref/models/fields/#bigautofield

Garry Polley
  • 4,253
  • 1
  • 22
  • 29
  • 1
    In document said it's from 1 to ..., but it accepts -1, how can I have a positive big auto field? – user907988 Feb 24 '19 at 07:32
  • 1
    Latest version of the documentation is here: https://docs.djangoproject.com/en/2.2/ref/models/fields/#bigautofield Using the field without giving a value will result in a positive integer. – Garry Polley Feb 25 '19 at 16:11
  • There is now a setting for this feature: https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field – Garry Polley Jul 29 '22 at 16:12
18

Inspired by lfagundes but with a small but important correction:

class BigAutoField(fields.AutoField):
    def db_type(self, connection):  # pylint: disable=W0621
        if 'mysql' in connection.__class__.__module__:
            return 'bigint AUTO_INCREMENT'
        return super(BigAutoField, self).db_type(connection)

add_introspection_rules([], [r"^a\.b\.c\.BigAutoField"])

Notice instead of extending BigIntegerField, I am extending AutoField. This is an important distinction. With AutoField, Django will retrieve the AUTO INCREMENTed id from the database, whereas BigInteger will not.

One concern when changing from BigIntegerField to AutoField was the casting of the data to an int in AutoField.

Notice from Django's AutoField:

def to_python(self, value):
    if value is None:
        return value
    try:
        return int(value)
    except (TypeError, ValueError):
        msg = self.error_messages['invalid'] % str(value)
        raise exceptions.ValidationError(msg)

and

def get_prep_value(self, value):
    if value is None:
        return None
    return int(value)

It turns out this is OK, as verified in a python shell:

>>> l2 = 99999999999999999999999999999
>>> type(l2)
<type 'long'>
>>> int(l2)
99999999999999999999999999999L
>>> type(l2)
<type 'long'>
>>> type(int(l2))
<type 'long'>

In other words, casting to an int will not truncate the number, nor will it change the underlying type.

Larry
  • 664
  • 6
  • 9
  • I found I also required a BigForeignKey to make sure the fk types are also set to bigint. – shangxiao Nov 07 '14 at 10:20
  • The question of "OK" regarding the calls to int() in the declaration of the AutoField class are system dependent. If you are on a 64 bit system, then sys.maxint will return the right value and you are ok. If for any reason in 2015 you are on a 32 bit system, well, then Bigint wouldn't work in postgres anyhow. :-) – Stephan Doliov Aug 25 '15 at 18:59
14

NOTE: This answer as modified, according to Larry's code. Previous solution extended fields.BigIntegerField, but better to extend fields.AutoField

I had the same problem and solved with following code:

from django.db.models import fields
from south.modelsinspector import add_introspection_rules

class BigAutoField(fields.AutoField):
    def db_type(self, connection):
        if 'mysql' in connection.__class__.__module__:
            return 'bigint AUTO_INCREMENT'
        return super(BigAutoField, self).db_type(connection)

add_introspection_rules([], ["^MYAPP\.fields\.BigAutoField"])

Apparently this is working fine with south migrations.

lfagundes
  • 2,978
  • 5
  • 24
  • 25
  • 3
    I have down-voted this answer because I used this solution in production, and it caused a production bug. The problem is that because this field does not extend AutoField, Django will not fetch the ID from the DB after writing a new model. This is a difference in behavior from typical id auto increment fields. I will propose a different solution below. I am borrowing 99.9% of this solution, but I don't want others to make the same mistake. – Larry Jun 11 '13 at 02:47
  • Tks! I wish I could remember where I need this before :-). – lfagundes Jun 11 '13 at 11:42
  • 2
    What if using PostgreSQL? Maybe we could add another condition in this answer: `elif 'postgres' in connection.__class__.__module__: return 'bigserial'` – pawamoy Dec 03 '15 at 15:10
  • This doesn't work if you want to reference the field (ForeignKey) because of this code: https://github.com/django/django/blob/1.9.8/django/db/models/fields/related.py#L951-L964. There is an obvious workaround but it's still painful :( – mvdb Aug 01 '16 at 15:59
  • 1
    From Django v1.10 onwards, BigAutoField is included in Django itself. SEE https://code.djangoproject.com/ticket/14286 – Stijn de Witt Aug 03 '16 at 08:22
7

You could alter the table afterwards. That may be a better solution.

Matthew Schinckel
  • 35,041
  • 6
  • 86
  • 121
7

Since Django 3.2 the type of implicit primary key can be controlled with the DEFAULT_AUTO_FIELD setting (documentation). So, there is no need anymore to override primary keys in all your models.

#This setting will change all implicitly added primary keys to BigAutoField
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

Note that starting with Django 3.2 new projects are generated with DEFAULT_AUTO_FIELD set to BigAutoField (release notes).

yagus
  • 1,417
  • 6
  • 14
3

As stated before you could alter the table afterwards. That is a good solution.

To do that without forgetting, you can create a management module under your application package and use the post_syncdb signal.

https://docs.djangoproject.com/en/dev/ref/signals/#post-syncdb

This can cause django-admin.py flush to fail. But it is still the best alternative I know.

rogeliorv
  • 81
  • 6
2

I also had the same problem. Looks like there is no support for BigInteger auto fields in django.

I've tried to create some custom field BigIntegerAutoField but I faced a problem with south migration system (south couldn't create sequence for my field).

After giving a try couple of different approaches I decided to follow Matthew's advice and do alter table (e.g. ALTER TABLE table_name ALTER COLUMN id TYPE bigint; in postgre)

Would be great to have solution supported by django (like built in BigIntegerAutoField) and south.

dzida
  • 8,854
  • 2
  • 36
  • 57
  • I think this wouldn't help, as Django would still put id's into normal int, which is still too small. – letoosh Jun 24 '10 at 15:37
  • Not at all, the point here is how id is stored on database. (Long) integer type in python has 'unlimited' precision. You can also check how BigIntegerField is implmented in Django 1.2 - it inherits directly IntegreField, without changing internal type to store the value (which is int in this case) – dzida Jun 24 '10 at 15:51