citusdata / django-multitenant

Python/Django support for distributed multi-tenant databases like Postgres+Citus
MIT License
707 stars 116 forks source link

django.db.utils.ProgrammingError: foreign key referenced-columns list must not contain duplicates #202

Open dom-devel opened 7 months ago

dom-devel commented 7 months ago

Hello all,

Quick summary

I'm getting

django.db.utils.ProgrammingError: foreign key referenced-columns list must not contain duplicates when I attempt to run any migrations at all on a new starter project.}

I think there must be something basic I'm missing, but I can't find it in issues or documentation.

Detailed

I'm trying to get a basic starter case going and I can't get a single migration to work, so I think I'm missing something simple

from django_multitenant.models import TenantModel
from django.db import models

class Account(TenantModel):
    name = models.CharField(max_length=255)
    contact_info = models.TextField()

    class TenantMeta:
        tenant_field_name = "id"

    class Meta:
        unique_together = ["id"]

    def __str__(self):
        return self.name

I create a basic Account tenant.

I then try to add it to the users with database backend enabled. I get a basic user migration

class User(TenantModelMixin, AbstractBaseUser, PermissionsMixin, IndexedTimeStampedModel):
    email = models.EmailField(max_length=255, unique=True)
    is_staff = models.BooleanField(
        default=False, help_text=_("Designates whether the user can log into this admin site.")
    )
    is_active = models.BooleanField(
        default=True,
        help_text=_(
            "Designates whether this user should be treated as "
            "active. Unselect this instead of deleting accounts."
        ),
    )
    name = models.CharField(max_length=255)
    # Custom fields
    account = models.ForeignKey(
        Account,
        on_delete=models.CASCADE,
        related_name="users",
        null=True,
        blank=True,
    )
    tenant_id = "account_id"
    objects = UserManager()

    USERNAME_FIELD = "email"

    class Meta:
        unique_together = ["id", "account"]

class Migration(migrations.Migration):
    initial = True

    dependencies = [
        ("auth", "0012_alter_user_first_name_max_length"),
        ("accounts", "0001_initial"),
    ]

    operations = [
        migrations.CreateModel(
            name="User",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
                    ),
                ),
                ("password", models.CharField(max_length=128, verbose_name="password")),
                (
                    "last_login",
                    models.DateTimeField(blank=True, null=True, verbose_name="last login"),
                ),
                (
                    "is_superuser",
                    models.BooleanField(
                        default=False,
                        help_text="Designates that this user has all permissions without explicitly assigning them.",
                        verbose_name="superuser status",
                    ),
                ),
                (
                    "created",
                    model_utils.fields.AutoCreatedField(
                        db_index=True,
                        default=django.utils.timezone.now,
                        editable=False,
                        verbose_name="created",
                    ),
                ),
                (
                    "modified",
                    model_utils.fields.AutoLastModifiedField(
                        db_index=True,
                        default=django.utils.timezone.now,
                        editable=False,
                        verbose_name="modified",
                    ),
                ),
                ("email", models.EmailField(max_length=255, unique=True)),
                (
                    "is_staff",
                    models.BooleanField(
                        default=False,
                        help_text="Designates whether the user can log into this admin site.",
                    ),
                ),
                (
                    "is_active",
                    models.BooleanField(
                        default=True,
                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
                    ),
                ),
                (
                    "account",
                    django_multitenant.fields.TenantForeignKey(
                        blank=True,
                        null=True,
                        on_delete=django.db.models.deletion.CASCADE,
                        related_name="users",
                        to="accounts.account",
                    ),
                ),
                (
                    "groups",
                    models.ManyToManyField(
                        blank=True,
                        help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.group",
                        verbose_name="groups",
                    ),
                ),
                (
                    "user_permissions",
                    models.ManyToManyField(
                        blank=True,
                        help_text="Specific permissions for this user.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.permission",
                        verbose_name="user permissions",
                    ),
                ),
            ],
            options={
                "unique_together": {("id", "account")},
            },
            bases=(django_multitenant.mixins.TenantModelMixin, models.Model),
        ),
    ]

And it fails because of the double account_id: foreign key referenced-columns list must not contain duplicates

CREATE TABLE "users_user" ("id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "password" varchar(128) NOT NULL, "last_login" timestamp with time zone NULL, "is_superuser" boolean NOT NULL, "created" timestamp with time zone NOT NULL, "modified" timestamp with time zone NOT NULL, "email" varchar(255) NOT NULL UNIQUE, "is_staff" boolean NOT NULL, "is_active" boolean NOT NULL, "account_id" bigint NULL);
CREATE TABLE "users_user_groups" ("id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "user_id" bigint NOT NULL, "group_id" integer NOT NULL);
CREATE TABLE "users_user_user_permissions" ("id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "user_id" bigint NOT NULL, "permission_id" integer NOT NULL);

-- The bad lines:
ALTER TABLE "users_user" ADD CONSTRAINT "users_user_id_account_id_e2e2a14b_uniq" UNIQUE ("id", "account_id");
ALTER TABLE "users_user" ADD CONSTRAINT "users_user_account_id_account_i_985fcd95_fk_accounts_" FOREIGN KEY ("account_id", "account_id") REFERENCES "accounts_account" ("id", "id") DEFERRABLE INITIALLY DEFERRED;

I can't find any other issues related to this so I think I'm just missing something very simple. Could someone point me in the right direction?