当前位置:  开发笔记 > 编程语言 > 正文

允许Django中的空值的唯一字段

如何解决《允许Django中的空值的唯一字段》经验,为你挑选了5个好方法。

我有模型Foo有字段栏.bar字段应该是唯一的,但允许空值,这意味着如果bar字段是null,我想允许多个记录,但如果不是,null则值必须是唯一的.

这是我的模型:

class Foo(models.Model):
    name = models.CharField(max_length=40)
    bar = models.CharField(max_length=40, unique=True, blank=True, null=True, default=None)

这是表的相应SQL:

CREATE TABLE appl_foo
(
    id serial NOT NULL,
     "name" character varying(40) NOT NULL,
    bar character varying(40),
    CONSTRAINT appl_foo_pkey PRIMARY KEY (id),
    CONSTRAINT appl_foo_bar_key UNIQUE (bar)
)   

当使用管理界面创建多个bar为空的foo对象时,它会给出一个错误:"Foo with this Bar已经存在."

但是当我插入数据库(PostgreSQL)时:

insert into appl_foo ("name", bar) values ('test1', null)
insert into appl_foo ("name", bar) values ('test2', null)

这很好用,它允许我插入超过1条记录,条形为空,所以数据库允许我做我想要的,这只是Django模型的错误.有任何想法吗?

编辑

就DB而言,解决方案的可移植性不是问题,我们对Postgres感到满意.我已经尝试设置一个可调用的唯一,这是我的函数返回True/False为特定的bar值,它没有给出任何错误,但是如果它没有任何影响那么缝合.

到目前为止,我已经从bar属性中删除了唯一的说明符并处理了应用程序中的bar唯一性,但仍然在寻找更优雅的解决方案.有什么建议?



1> 小智..:

由于票证#9039已修复,Django并未将NULL视为等于NULL以进行唯一性检查,请参阅:

http://code.djangoproject.com/ticket/9039

这里的问题是表单CharField的规范化"空白"值是空字符串,而不是None.因此,如果将该字段留空,则会在DB中存储一个空字符串,而不是NULL.在Django和数据库规则下,空字符串等于唯一性检查的空字符串.

您可以通过为foo提供自己的自定义模型表单来强制管理接口为空字符串存储NULL,并使用clean_bar方法将空字符串转换为None:

class FooForm(forms.ModelForm):
    class Meta:
        model = Foo
    def clean_bar(self):
        return self.cleaned_data['bar'] or None

class FooAdmin(admin.ModelAdmin):
    form = FooForm


此答案仅对基于表单的数据输入有帮助,但对实际保护数据完整性没有任何作用.可以通过导入脚本,shell,API或任何其他方式输入数据.覆盖save()方法比为每个可能触及数据的表单创建自定义案例要好得多.

2> mightyhal..:

**编辑11/30/2015:在python 3中,不再支持 module-global __metaclass__变量.另外,在课堂上已被弃用:Django 1.10SubfieldBase

来自文档:

django.db.models.fields.subclassing.SubfieldBase已被弃用,将在Django 1.10中删除.从历史上看,它用于处理从数据库加载时需要进行类型转换的字段,但它不用于.values()调用或聚合中.它已被取代from_db_value(). 请注意,新方法不会像to_python()分配的情况那样调用赋值方法SubfieldBase.

因此,正如from_db_value() 文档和此示例所示,此解决方案必须更改为:

class CharNullField(models.CharField):

    """
    Subclass of the CharField that allows empty strings to be stored as NULL.
    """

    description = "CharField that stores NULL but returns ''."

    def from_db_value(self, value, expression, connection, contex):
        """
        Gets value right out of the db and changes it if its ``None``.
        """
        if value is None:
            return ''
        else:
            return value


    def to_python(self, value):
        """
        Gets value right out of the db or an instance, and changes it if its ``None``.
        """
        if isinstance(value, models.CharField):
            # If an instance, just return the instance.
            return value
        if value is None:
            # If db has NULL, convert it to ''.
            return ''

        # Otherwise, just return the value.
        return value

    def get_prep_value(self, value):
        """
        Catches value right before sending to db.
        """
        if value == '':
            # If Django tries to save an empty string, send the db None (NULL).
            return None
        else:
            # Otherwise, just pass the value.
            return value

我认为比覆盖管理员中的cleaning_data更好的方法是对charfield进行子类化 - 这种方式无论采用何种形式访问字段,它都将"正常工作".你可以''在它被发送到数据库之前捕获它,并在它从数据库出来之后捕获NULL,而Django的其余部分将不知道/关心.一个快速而肮脏的例子:

from django.db import models


class CharNullField(models.CharField):  # subclass the CharField
    description = "CharField that stores NULL but returns ''"
    __metaclass__ = models.SubfieldBase  # this ensures to_python will be called

    def to_python(self, value):
        # this is the value right out of the db, or an instance
        # if an instance, just return the instance
        if isinstance(value, models.CharField):
            return value 
        if value is None:  # if the db has a NULL (None in Python)
            return ''      # convert it into an empty string
        else:
            return value   # otherwise, just return the value

    def get_prep_value(self, value):  # catches value right before sending to db
        if value == '':   
            # if Django tries to save an empty string, send the db None (NULL)
            return None
        else:
            # otherwise, just pass the value
            return value  

对于我的项目,我将其转储到一个extras.py位于我网站根目录的文件中,然后我可以from mysite.extras import CharNullField在我的应用程序models.py文件中.该字段就像CharField一样 - 只记得blank=True, null=True在声明字段时设置,否则Django将抛出验证错误(需要字段)或创建不接受NULL的db列.


在get_prep_value中,如果值有多个空格,则应该去除该值.
如果要将`CharField`更新为`CharNullField`,则需要分三步完成.首先,将"null = True"添加到该字段,然后进行迁移.然后,执行数据迁移以更新任何空值,使它们为空.最后,将字段转换为CharNullField.如果在执行数据迁移之前转换字段,则数据迁移将不会执行任何操作.
注意,在更新的解决方案中,from_db_value()应该没有额外的contex参数。它应该是`def from_db_value(self,value,expression,connection):`

3> e-satis..:

快速解决方法是:

def save(self, *args, **kwargs):

    if not self.bar:
        self.bar = None

    super(Foo, self).save(*args, **kwargs)


请注意,使用`MyModel.objects.bulk_create()`会绕过此方法.

4> tBuLi..:

因为我是stackoverflow的新手,我还不能回答答案,但我想指出,从哲学的角度来看,我不能同意这个问题最受欢迎的答案.(作者:Karen Tracey)

如果OP具有值,则OP要求其bar字段是唯一的,否则为null.然后必须是模型本身确保这种情况.它不能留给外部代码来检查,因为这意味着它可以被绕过.(或者如果你将来写一个新的视图,你可以忘记检查它)

因此,要保持代码真正的OOP,必须使用Foo模型的内部方法.修改save()方法或字段是很好的选择,但使用表单来执行此操作肯定不是.

我个人更喜欢使用建议的CharNullField,以便将来可能定义的模型的可移植性.



5> Radagast..:

另一种可能的解决

class Foo(models.Model):
    value = models.CharField(max_length=255, unique=True)

class Bar(models.Model):
    foo = models.OneToOneField(Foo, null=True)

推荐阅读
linjiabin43
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有