Двойное значение ключа нарушает уникальное ограничение на инициализацию базы данных

У меня есть модель для блога, где я хочу установить finished поле равно True если остальные поля не пусты. Я заполняю базу данных (Postgres) с помощью скрипта, но кое-что не работает при инициализации (база данных пуста, но после миграции, поэтому таблицы существуют).

мой models.py:

class Post(models.Model):
    tags = TaggableManager(blank=True)
    ...

    def save(self, *args, **kwargs):
        super(Post, self).save(*args, **kwargs)
        if self.title_it!='' and self.title_en!='' and self.text_it!='' and self.text_en!='' and self.tags!='':
            self.finished=True
            super(Post, self).save(*args, **kwargs)

Мой сценарий init.py:

def add_post(author, title_it, title_en, text_it, text_en, created_date, 
    published_date, tags, views):
    p = Post.objects.get_or_create(author=author, title_it=title_it, 
        title_en=title_en, text_it=text_it, text_en=text_en, 
        created_date=created_date, published_date=published_date, 
        views=views)[0]
    for t in tags:
        p.tags.add(t)
    p.save()
    return p

и ошибка при запуске скрипта:

django.db.utils.IntegrityError: ERROR: duplicate key value violates unique constraint "blog_post_pkey"
DETAIL:  Key (id)=(1) already exists.

Вот полный след:

Traceback (most recent call last):
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\query.p
y", line 487, in get_or_create
    return self.get(**lookup), False
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\query.p
y", line 403, in get
    self.model._meta.object_name
blog.models.DoesNotExist: Post matching query does not exist.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\backends\utils
.py", line 85, in _execute
    return self.cursor.execute(sql, params)
psycopg2.IntegrityError: ERRORE:  un valore chiave duplicato viola il vincolo un
ivoco "blog_post_pkey"
DETAIL:  La chiave (id)=(1) esiste già.


The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "init_poss.py", line 8374, in <module>
    populate()
  File "init_poss.py", line 7311, in populate
    ['Servizio'], 1) #tzinfo=<UTC>
  File "init_poss.py", line 8323, in add_post
    views=views)[0]
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\manager
.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\query.p
y", line 489, in get_or_create
    return self._create_object_from_params(lookup, params)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\query.p
y", line 528, in _create_object_from_params
    raise e
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\query.p
y", line 521, in _create_object_from_params
    obj = self.create(**params)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\query.p
y", line 417, in create
    obj.save(force_insert=True, using=self.db)
  File "D:\progetti\possedimenti\sitopossedimenti\blog\models.py", line 58, in s
ave
    super(Post, self).save(*args, **kwargs)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\base.py
", line 729, in save
    force_update=force_update, update_fields=update_fields)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\base.py
", line 759, in save_base
    updated = self._save_table(raw, cls, force_insert, force_update, using, upda
te_fields)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\base.py
", line 842, in _save_table
    result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\base.py
", line 880, in _do_insert
    using=using, raw=raw)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\manager
.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\query.p
y", line 1125, in _insert
    return query.get_compiler(using=using).execute_sql(return_id)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\sql\com
piler.py", line 1283, in execute_sql
    cursor.execute(sql, params)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\backends\utils
.py", line 100, in execute
    return super().execute(sql, params)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\backends\utils
.py", line 68, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._e
xecute)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\backends\utils
.py", line 77, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\backends\utils
.py", line 85, in _execute
    return self.cursor.execute(sql, params)
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\utils.py", lin
e 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\backends\utils
.py", line 85, in _execute
    return self.cursor.execute(sql, params)
django.db.utils.IntegrityError: ERRORE:  un valore chiave duplicato viola il vin
colo univoco "blog_post_pkey"
DETAIL:  La chiave (id)=(1) esiste già.

1 ответ

Решение

Вот:

def save(self, *args, **kwargs):
    super(Post, self).save(*args, **kwargs)
    if self.title_it!='' and self.title_en!='' and self.text_it!='' and self.text_en!='' and self.tags!='':
        self.finished=True
        super(Post, self).save(*args, **kwargs)

Ты звонишь super().save() во второй раз с тем же kwarg. так как force_insert arg установлен в True как вы можете видеть из трассировки:

File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\query.py", line 521, in _create_object_from_params
    obj = self.create(**params)
File "D:\progetti\Envs\possedimenti\lib\site-packages\django\db\models\query.py", line 417, in create
    obj.save(force_insert=True, using=self.db)

вы в конечном итоге просите ORM создать вторую запись, и с этого времени был установлен pk (первым super.save() вызов), вы действительно получите уникальное нарушение ограничения.

Вы можете попытаться поиграться с kwargs, но на самом деле это плохая идея (лучше оставить эти флаги в ORM) - простое решение - убедиться, что вы звоните super.save() только однажды:

def save(self, *args, **kwargs):
    # non-empty strings have a true value
    # so no need to explicitely test against
    # the empty string.
    # Note that this test will probably not
    # behave how you expect with strings containing
    # only space characters but that was your original
    # code behaviour too so I left this alone.
    self.finished = (
      self.title_it and self.title_en 
      and self.text_it and self.text_en 
      and self.tags
      )
    super(Post, self).save(*args, **kwargs)

Редактировать: с self.tags на самом деле смежное поле (из taggit приложение) вы не можете проверить self.tags безусловно, так как вам нужно, чтобы экземпляр был сохранен в БД, прежде чем вы сможете получить доступ к любому связанному объекту. Решение здесь состоит в том, чтобы проверить self.pk сначала и только проверьте остальные поля, если они уместны:

def save(self, *args, **kwargs):
    if self.pk:
        self.finished = (
          self.title_it and self.title_en 
          and self.text_it and self.text_en 
          # unless `taggit` does some weird things
          # wrt/ tags storage, this should be the 
          # right test
          and self.tags.exists()
          )
    else:
        # no pk so no flags so it can not be finished
        self.finished = False

    # and call `super.save()` whatever the case
    super(Post, self).save(*args, **kwargs)
Другие вопросы по тегам