Django唯一,null和空白CharField在管理页面上给出"已存在"错误

noa*_*ale 13 python django django-models django-admin

我一直在收到最奇怪的错误.我有一个人模型

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)
Run Code Online (Sandbox Code Playgroud)

我最近添加了twitter_id字段.当我访问Django管理页面,并尝试将'person'更改为suggested_person时,我收到以下错误:

 Person with this Twitter id already exists.
Run Code Online (Sandbox Code Playgroud)

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

这可能是什么原因?

Ane*_*pic 21

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

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

但不幸的是,对于CharFields Django将保存一个空字符串""(因为当你提交一个表单时,一切都以字符串形式进入Django,你可能真的想保存一个空字符串""--Django不知道它是否应该转换为None)

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

如需进一步参考,请参见此处:https://code.djangoproject.com/ticket/4136
(遗憾的是,在撰写本文时尚无最佳解决方案)

  • 机票4136现已修复.在Django 1.11+中,您可以使用`CharField(unique = True,null = True,blank = True)`而无需手动将空值转换为"None". (8认同)

Lon*_*Dev 17

这是一个旧的,但我刚才有类似的问题,虽然我会提供一个替代解决方案.

我处于这样一种情况,我需要能够拥有一个CharField,其中null = True,blank = True且unique = True.如果我在管理面板中提交一个空字符串,则不会提交,因为空字符串不是唯一的.

为了解决这个问题,我重写了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']
Run Code Online (Sandbox Code Playgroud)

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

希望这可以帮助.

编辑:您需要更改我将"something_unique_or_null"放在您的字段名称的位置.例如"clean_twitter_id".


Ala*_*air 11

在Django 1.11中,form CharFields将有一个empty_value参数,None如果该字段为空,则允许您使用该参数.

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

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

  • 不过他是对的。看来你的答案第二段是错误的。 (4认同)
  • 在我的情况下(django`2.1.1`)`a = models.CharField(max_length = 100,null = True,blank = True,unique = True)`仍然保存空字符串而不是NULL :( (2认同)

kar*_*ikr 10

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

  • Django 将空白字段视为空字符串 `''`,并且 `unique=True` 不允许多个值为 `''` 的条目。然而,它允许多个带有 `twitter_id=None` 的条目。 (8认同)
  • 我删除了 Twitter_id 字段,然后在使用 South 中重新添加了它。现在工作正常,没有给我带来任何问题 (2认同)

sha*_*ker 9

在模型级别而不是在表单级别解决此问题非常重要,因为数据可以通过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()
Run Code Online (Sandbox Code Playgroud)

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()
Run Code Online (Sandbox Code Playgroud)

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),
        ),
    ]
Run Code Online (Sandbox Code Playgroud)


Vaj*_*ecz 5

问题的根源在于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
Run Code Online (Sandbox Code Playgroud)

因此,get_db_prep_value将确保null持久化