Search code examples
djangoinspectdb

Django inspectdb not respecting primary key on two columns (postgres)


I am hooking up to an existing Postgres database with Django, using inspectdb to generate my models.py file. The tables in the Postgres were created like this:

CREATE TABLE "a"
(
  "id" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::uuid,
  CONSTRAINT "PK.a" PRIMARY KEY ("id")
)
CREATE TABLE "c"
(
  "id" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::uuid,
  CONSTRAINT "PK.c" PRIMARY KEY ("id")
)
CREATE TABLE "b"
(
  "aid" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::uuid,
  "cid" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::uuid,
 CONSTRAINT "PK.b" PRIMARY KEY ("aid","cid"),
 CONSTRAINT "FK_a" FOREIGN KEY ("aid")
      REFERENCES "a" ("id") MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE CASCADE,
 CONSTRAINT "FK_c" FOREIGN KEY ("cid")
      REFERENCES "c" ("id") MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE CASCADE
)

I then run python manage.py inspectdb --database primary > models_test.py, which results in the following models_test.py.

class A(models.Model):
    id = models.UUIDField(primary_key=True)

    class Meta:
        managed = False
        db_table = 'a'


class B(models.Model):
    aid = models.OneToOneField(A, models.DO_NOTHING, db_column='aid', primary_key=True)
    cid = models.ForeignKey('C', models.DO_NOTHING, db_column='cid')

    class Meta:
        managed = False
        db_table = 'b'
        unique_together = (('aid', 'cid'),)


class C(models.Model):
    id = models.UUIDField(primary_key=True)

    class Meta:
        managed = False
        db_table = 'c'

note the OneToOneField defined on aid.

If I instead create table b as:

CREATE TABLE "b"
(
  "aid" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::uuid,
  "cid" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::uuid,
 CONSTRAINT "PK.b" PRIMARY KEY ("cid","aid"),
 CONSTRAINT "FK_a" FOREIGN KEY ("aid")
      REFERENCES "a" ("id") MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE CASCADE,
 CONSTRAINT "FK_c" FOREIGN KEY ("cid")
      REFERENCES "c" ("id") MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE CASCADE
)

then rerun inspectdb I get the following output:

class A(models.Model):
    id = models.UUIDField(primary_key=True)

    class Meta:
        managed = False
        db_table = 'a'


class B(models.Model):
    aid = models.ForeignKey(A, models.DO_NOTHING, db_column='aid')
    cid = models.OneToOneField('C', models.DO_NOTHING, db_column='cid', primary_key=True)

    class Meta:
        managed = False
        db_table = 'b'
        unique_together = (('cid', 'aid'),)


class C(models.Model):
    id = models.UUIDField(primary_key=True)

    class Meta:
        managed = False
        db_table = 'c'

note the OneToOneField is now on cid. I suspect this is a bug, but I am inexperienced so wanted to ask here before reporting. Secondary question: if this is a bug, is it worth reporting? Maybe the database design is very poor or uncommon?


Solution

  • I suspect this is a bug, but I am inexperienced so wanted to ask here before reporting.

    Not really. Django does not work with primary keys that span over two or more columns, or at least not at the moment of writing. It has been requested, for example by ticket #373, but the designers decided to set this on "wontfix".

    The documentation furthermore explains this:

    Do Django models support multiple-column primary keys?

    No. Only single-column primary keys are supported.

    But this isn’t an issue in practice, because there’s nothing stopping you from adding other constraints (using the unique_together model option or creating the constraint directly in your database), and enforcing the uniqueness at that level. Single-column primary keys are needed for things such as the admin interface to work; e.g., you need a single value to specify an object to edit or delete.

    Likely you will not be able to do that in the (near) future, since a lot of Django tooling is built with the assumption that primary keys are "scalar" entities.

    You thus likely should slightly redesign your existing database, and try to run inspectdb after that.