21

我遇到了有史以来最奇怪的错误。我有一个人模型

class Person(models.Model):
    user = models.OneToOneField(User, primary_key=True)
    facebook_id = models.CharField(max_length=225, unique=True, null=True, blank=True)
    twitter_id = models.CharField(max_length=225, unique=True, null=True, blank=True)
    suggested_person = models.BooleanField(default=False)

我最近添加了 twitter_id 字段。当我访问 Django 管理页面并尝试将“人”更改为建议的人时,我收到以下错误:

 Person with this Twitter id already exists.

我觉得这个错误非常奇怪,因为 Facebook_id 字段的设计方式与 Twitter_id 字段完全相同。

这可能是什么原因?

4

8 回答 8

44

没有一个答案清楚地描述了问题的根源。

通常在数据库中,您可以创建一个字段null=True, unique=True,它会起作用......因为NULL != NULL. 所以每个空白值仍然被认为是唯一的。

但不幸的是,对于CharFields Django 将保存一个空字符串""(因为当您提交表单时,所有内容都作为字符串进入 Django,而您可能真的想保存一个空字符串""- Django 不知道它是否应该转换为None

这基本上意味着你不应该CharField(unique=True, null=True, blank=True)在 Django 中使用。正如其他人所指出的,您可能必须放弃 db 级别的唯一约束并在模型中进行自己的唯一检查。

如需进一步参考,请参阅此处:https
://code.djangoproject.com/ticket/4136 (不幸的是,在撰写本文时没有确定好的解决方案)

于 2015-04-29T22:01:00.373 回答
23

这是一个旧问题,但我刚才遇到了类似的问题,尽管我会提供一个替代解决方案。

我处于需要能够拥有 null=True、blank=True 和 unique=True 的 CharField 的情况。如果我在管理面板中提交一个空字符串,它将不会提交,因为该空字符串不是唯一的。

为了解决这个问题,我覆盖了 ModelForm 中的 'clean' 函数,并在其中检查它是否为空字符串并相应地返回结果。

class MyModelChangeForm(forms.ModelForm):

    class Meta:
        model = models.MyModel
        fields = ['email', 'name', 'something_unique_or_null',]

    def clean_something_unique_or_null(self):
        if self.cleaned_data['something_unique_or_null'] == "":
            return None
        else:
            return self.cleaned_data['something_unique_or_null']

这为我解决了这个问题,而不必牺牲模型字段上的唯一属性。

希望这可以帮助。

编辑:您需要更改我在字段名称中放置“something_unique_or_null”的位置。例如“clean_twitter_id”。

于 2014-02-21T12:26:25.830 回答
13

在 Django 1.11 中,表单CharFields将有一个empty_value参数,允许您None在字段为空时使用。

模型表单,包括 Django 管理员,将自动设置empty_value=None模型是否CharField具有null=True.

因此,您将能够在您的模型中一起使用和null=True,而不会出现唯一约束导致问题。blank=Trueunique=TrueCharField

于 2016-09-17T15:06:24.627 回答
12

既然你有null=True, blank=Trueand unique=True,django 正在考虑Noneor 空白作为一个独特的条目。移除唯一性约束并处理代码中的唯一性部分。

于 2013-06-23T02:12:40.837 回答
12

在模型级别解决这个问题很重要,而不是在表单级别,因为数据可以通过 API、导入脚本、shell 等输入。null=True在 CharField 上设置的缺点是该列可能最终都为空字符串和 NULL,这有点模棱两可,但根据我的经验,这通常不是问题。如果您愿意忍受这种模棱两可的情况,请按照以下步骤操作:

1)null=True, blank=True在现场设置并在更改中迁移。

2)按摩您的数据,以便将所有现有的空字符串更改为 NULL:

items = Foo.objects.all()
for item in items:
  if not item.somefield:
    item.somefield = None
    item.save()

3)save()向您的模型添加自定义方法:

def save(self, *args, **kwargs):
    # Empty strings are not unique, but we can save multiple NULLs
    if not self.somefield:
        self.somefield = None

    super().save(*args, **kwargs)  # Python3-style super()

4)unique=True在现场设置并迁移。

现在,somefield无论您使用管理员还是任何其他数据输入方法,您都可以将其存储为空值或唯一值。

如果您不想进行多次迁移,以下是如何在一次迁移中进行的示例:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models

def set_nulls(apps, schema_editor):
    Event = apps.get_model("events", "Event")
    events = Event.objects.all()
    for e in events:
        if not e.wdid:
            e.wdid = None
            e.save()


class Migration(migrations.Migration):

    dependencies = [
        ('events', '0008_something'),
    ]

    operations = [
        migrations.AlterField(
            model_name='event',
            name='wdid',
            field=models.CharField(blank=True, max_length=32, null=True),
        ),
        migrations.RunPython(set_nulls),
        migrations.AlterField(
            model_name='event',
            name='wdid',
            field=models.CharField(blank=True, max_length=32, null=True, unique=True),
        ),
    ]
于 2016-07-27T19:01:29.050 回答
7

问题的根源在于 Django 将空值保留为空字符串,而不是 null。要解决此问题,您可以CharField按如下方式进行子类化:

class CharNullField(models.CharField):
    description = "CharField that stores NULL"
    
    def get_db_prep_value(self, value, connection=None, prepared=False):
        value = super(CharNullField, self).get_db_prep_value(value, connection, prepared)
        if value=="":
            return None
        else:
            return value

因此get_db_prep_value将确保 null 被持久化。

于 2015-11-04T15:28:59.623 回答
0

您必须default=None在现场提供

facebook_id = models.CharField(max_length=225,unique=True, null=True,blank=True,default=None)

这对我有用。我在电话号码中使用了它。因此,如果输入并且不是强制性的,它可以是唯一的。

于 2022-02-28T07:16:10.443 回答
-1

您正在接受空白值并期望它们是唯一的。这意味着只能有一个带有空白 twitter_id 的条目

你可以

  • 要么删除唯一的约束
  • 去掉空格=True
  • 为字段提供默认值(但默认值必须是唯一的)
于 2013-06-23T02:17:36.907 回答