3

I have in models.py a model called FlashNews, that can either be tied to a Race, a League, or a FantasyTeam. I used Django's CheckConstraint (version 2.2.1). There's also a type column to ensure consistency.

I want to guarantee that one and only one column is not null among race, fteam and league, consistently with type value.

The sample code is as is:

FlashNewsTypes = [
    (1, 'Race'),
    (2, 'League'),
    (3, 'Fteam')
]


class FlashNews(models.Model):

    race = models.ForeignKey(Race, on_delete=models.CASCADE, null=True, blank=True)
    league = models.ForeignKey(League, on_delete=models.CASCADE, null=True, blank=True)
    fteam = models.ForeignKey(FantasyTeam, on_delete=models.CASCADE, null=True, blank=True)
    type = models.IntegerField(choices=FlashNewsTypes, default=FlashNewsTypes[0][0])

    class Meta:
        constraints = [
            models.CheckConstraint(
                name="unique_notnull_field",
                check=(
                    models.Q(
                        type=FlashNewsTypes[0][0],
                        race__isnull=False,
                        league__isnull=True,
                        fteam__isnull=True,
                    ) | models.Q(
                        type=FlashNewsTypes[1][0],
                        race__isnull=True,
                        league__isnull=False,
                        fteam__isnull=True,
                    ) | models.Q(
                        type=FlashNewsTypes[2][0],
                        race__isnull=True,
                        league__isnull=True,
                        fteam__isnull=False,
                    )
                ),
            )
        ]

The automatically created migration:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0055_flashnews'),
    ]

    operations = [
        migrations.AddConstraint(
            model_name='flashnews',
            constraint=models.CheckConstraint(check=models.Q(models.Q(('fteam__isnull', True), ('league__isnull', True), ('race__isnull', False), ('type', 1)), models.Q(('fteam__isnull', False), ('league__isnull', True), ('race__isnull', True), ('type', 2)), models.Q(('fteam__isnull', True), ('league__isnull', False), ('race__isnull', True), ('type', 3)), _connector='OR'), name='%(app_label)s_%(class)s_value_matches_type'),
        ),
    ]

This migration is successful on my dev env, which uses Sqlite backend. But, when executed with Postgres backend, it raises this error:

Applying myapp.0056_auto_20200502_1754...Traceback (most recent call last):
  File "manage.py", line 22, in <module>
    execute_from_command_line(sys.argv)
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
    utility.execute()
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/core/management/__init__.py", line 375, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/core/management/base.py", line 323, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/core/management/base.py", line 364, in execute
    output = self.handle(*args, **options)
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/core/management/base.py", line 83, in wrapped
    res = handle_func(*args, **kwargs)
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/core/management/commands/migrate.py", line 234, in handle
    fake_initial=fake_initial,
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/db/migrations/executor.py", line 117, in migrate
    state = self._migrate_all_forwards(state, plan, full_plan, fake=fake, fake_initial=fake_initial)
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/db/migrations/executor.py", line 147, in _migrate_all_forwards
    state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/db/migrations/executor.py", line 245, in apply_migration
    state = migration.apply(state, schema_editor)
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/db/migrations/migration.py", line 124, in apply
    operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/db/migrations/operations/models.py", line 827, in database_forwards
    schema_editor.add_constraint(model, self.constraint)
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/db/backends/base/schema.py", line 345, in add_constraint
    self.execute(sql)
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/db/backends/base/schema.py", line 137, in execute
    cursor.execute(sql, params)
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/src/myapp/env/lib/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
TypeError: tuple indices must be integers or slices, not str

Are there some DB backend-specific notions I'm missing here regarding Django's constraint ?

Thanks for your help !

2 Answers 2

2

As it turns out, I had simplified my question by changing the constraint name, that was named name='%(app_label)s_%(class)s_value_matches_type in my code, for the sake of clarity.

But it seems that this name is the cause of the TypeError. Replacing the name fixes it.

Sign up to request clarification or add additional context in comments.

Comments

2

You are using Django version 2, but interpolation of %(app_label)s and %(class)s was added in version 3.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.