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

时间:2013-06-23 01:52:24

标签: python django django-admin django-models

我一直遇到最奇怪的错误。我有个人模型

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'更改为suggested_person时,我收到以下错误:

 Person with this Twitter id already exists.

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

这可能是什么原因?

7 个答案:

答案 0 :(得分:16)

这是旧的,但我刚才有类似的问题,虽然我会提供另一种解决方案。

我处于这样一种情况,我需要能够拥有一个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']

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

希望这有帮助。

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

答案 1 :(得分:14)

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

通常在 db 中,你可以创建一个字段null=True, unique=True,它可以正常工作......因为NULL != NULL。因此,每个空白值仍然被认为是唯一的。

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

这基本上意味着你不应该在Django中使用None。正如其他人所指出的那样,您可能不得不放弃数据库级别的唯一约束,并在模型中进行自己独特的检查。

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

答案 2 :(得分:11)

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

模型表单,包括Django管理员,will automatically set empty_value=None,如果模型CharField具有null=True

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

答案 3 :(得分:7)

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

答案 4 :(得分:4)

问题的根源是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被持久化

答案 5 :(得分:0)

由于您有null=True, blank=Trueunique=True,因此django正在考虑None或空白作为唯一条目。删除唯一约束并处理代码中的唯一性部分。

答案 6 :(得分:-1)

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

条目

你可以

  • 要么删除独特的约束
  • 删除空白= True
  • 为字段指定默认值(但默认值必须是唯一的)