首页 > 解决方案 > 带有UniqueConstraint的完整性错误django

问题描述

我有以下模型片段:

class InvoiceReference(TemplateMixin, models.Model):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
    )
    reference_prefix = models.CharField(
        max_length=100,
        validators=(RegexValidator(regex='^\S+$',
                                  message='reference prefix must not include spaces'),)
    )
    reference_offset = models.PositiveIntegerField(default=0)
    reference_suffix = models.CharField(
        max_length=100,
        validators=(RegexValidator(regex='^\S+$',
                                  message='reference suffix must not include spaces'),)
    )
    reference_separator = models.CharField(max_length=1)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=('user', 'reference_prefix', 'reference_suffix', 'reference_separator',), name='unique_reference')
        ]

我们的想法是让没有一个用户不能多次使用相同的reference_prefix、reference_separator和reference_suffix。(如果不同的用户使用相同的 reference_prefix、reference_separator 和 reference_suffix 组合,则可以。)

我目前正在使用 django-admin 模型表单,其中我已经排除了用户字段并在 save_model 中附加了一个用户:

class InvoiceReferenceAdmin(admin.ModelAdmin):
    model = InvoiceReference
    exclude = ('user',)

    def save_model(self, request, obj, form, change):
        if not obj.pk:
            obj.user = request.user
        super().save_model(request=request, obj=obj, form=form, change=change)

但是,当我尝试保存模型时,这会导致“完整性错误”。

追溯:

Traceback (most recent call last):
  File "django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "django/db/backends/sqlite3/base.py", line 383, in execute
    return Database.Cursor.execute(self, query, params)
sqlite3.IntegrityError: UNIQUE constraint failed: invoice_invoice.user_id, invoice_invoice.reference_prefix, invoice_invoice.reference_suffix, invoice_invoice.reference_separator

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

Traceback (most recent call last):
  File "django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "django/contrib/admin/options.py", line 606, in wrapper
    return self.admin_site.admin_view(view)(*args, **kwargs)
  File "django/utils/decorators.py", line 142, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "django/views/decorators/cache.py", line 44, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "django/contrib/admin/sites.py", line 223, in inner
    return view(request, *args, **kwargs)
  File "django/contrib/admin/options.py", line 1645, in add_view
    return self.changeform_view(request, None, form_url, extra_context)
  File "django/utils/decorators.py", line 45, in _wrapper
    return bound_method(*args, **kwargs)
  File "django/utils/decorators.py", line 142, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "django/contrib/admin/options.py", line 1529, in changeform_view
    return self._changeform_view(request, object_id, form_url, extra_context)
  File "django/contrib/admin/options.py", line 1572, in _changeform_view
    self.save_model(request, new_object, form, not add)
  File "/Users/leonhughes/dev/django/invoiceweb/invoice/admin.py", line 42, in save_model
    super().save_model(request=request, obj=obj, form=form, change=change)
  File "django/contrib/admin/options.py", line 1088, in save_model
    obj.save()
  File "django/db/models/base.py", line 741, in save
    force_update=force_update, update_fields=update_fields)
  File "django/db/models/base.py", line 779, in save_base
    force_update, using, update_fields,
  File "django/db/models/base.py", line 870, in _save_table
    result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
  File "django/db/models/base.py", line 908, in _do_insert
    using=using, raw=raw)
  File "django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "django/db/models/query.py", line 1186, in _insert
    return query.get_compiler(using=using).execute_sql(return_id)
  File "django/db/models/sql/compiler.py", line 1375, in execute_sql
    cursor.execute(sql, params)
  File "django/db/backends/utils.py", line 99, in execute
    return super().execute(sql, params)
  File "django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "django/db/backends/sqlite3/base.py", line 383, in execute
    return Database.Cursor.execute(self, query, params)
django.db.utils.IntegrityError: UNIQUE constraint failed: invoice_invoice.user_id, invoice_invoice.reference_prefix, invoice_invoice.reference_suffix, invoice_invoice.reference_separator

我在 django 文档中看到了这一点

通常在 full_clean() 期间不会检查约束,并且不会引发 ValidationErrors。相反,您将在 save() 上收到数据库完整性错误。没有条件的唯一约束(即非部分唯一约束)在这方面是不同的,因为它们利用现有的 validate_unique() 逻辑,从而启用两阶段验证。除了 save() 上的 IntegrityError 之外,当违反 UniqueConstraint 时,还会在模型验证期间引发 ValidationError。

由于我没有设置条件,不应该首先提出 ValidationError 吗?

标签: djangodjango-modelsdjango-admin

解决方案


实现与您的结果相似的一种方法是user在您的管理表单中创建一个隐藏字段,然后覆盖ModelAdmin.get_changeform_initial_data以将当前用户作为此隐藏输入的值传递。

这意味着用户将被包含在唯一的验证检查中,因为该字段不会从表单中排除。编辑现有对象时忽略初始数据,因此您不应覆盖任何现有用户关系

class InvoiceReferenceAdminForm(forms.ModelForm):

    class Meta:
        model = InvoiceReference
        fields = '__all__'
        widgets = {'user': forms.HiddenInput}


class InvoiceReferenceAdmin(admin.ModelAdmin):
    model = InvoiceReference
    form = InvoiceReferenceAdminForm

    def get_changeform_initial_data(self, request):
        return {'user': request.user}

推荐阅读