Django 模型函数中字符串的空值

Django 模型函数中字符串的空值,django,django-models,Django,Django Models,第一个项目新手-在这个模型中,我试图创建一个字段,根据两个人的姓氏是否相同,将两个人的名字和姓氏组合在一起。如果姓氏相同,我希望它显示为“first_name1&first_name2 last_name1”。它的工作原理是,当last_name1为空时(这种情况经常出现),它会显示类似“John&Jane None”的内容。我必须将last_name1指定为字符串,否则我会得到一个错误:必须是str,而不是NoneType。我如何正确地做到这一点?在模型中,这种类型的函数叫什么?它是管理器吗?

第一个项目新手-在这个模型中,我试图创建一个字段,根据两个人的姓氏是否相同,将两个人的名字和姓氏组合在一起。如果姓氏相同,我希望它显示为“first_name1&first_name2 last_name1”。它的工作原理是,当last_name1为空时(这种情况经常出现),它会显示类似“John&Jane None”的内容。我必须将last_name1指定为字符串,否则我会得到一个错误:必须是str,而不是NoneType。我如何正确地做到这一点?在模型中,这种类型的函数叫什么?它是管理器吗?我不知道这篇文章的标题是什么

class Contact(models.Model):
    first_name1 = models.CharField(max_length=100, verbose_name='First Name', null=True)
    last_name1 = models.CharField(max_length=100, verbose_name='Last Name', null=True, blank=True)
    first_name2 = models.CharField(max_length=100, verbose_name='First Name (Second Person)', null=True, blank=True)
    last_name2 = models.CharField(max_length=100, verbose_name='Last Name (Second Person)', null=True, blank=True)

    def get_full_name(self):
        combined_name = ''

        if self.last_name1 == self.last_name2:
            combined_name = self.first_name1 + ' & ' + self.first_name2 + ' ' + str(self.last_name1)

        return '%s' % (combined_name)

    full_name = property(get_full_name)

在进行比较检查之前,您可以检查这些值是否为“Truth-y”。但是,您需要决定如何处理其他情况

@property
def get_full_name(self):
    combined_name = ''
    if self.last_name1 and self.last_name2:
        if self.last_name1 == self.last_name2:
            combined_name = self.first_name1 + ' & ' + self.first_name2 + ' ' + str(self.last_name1)
    elif self.last_name1:  # Only last_name1 is set
        pass
    elif self.last_name2:  # Only last_name2 is set
        pass
    else: # Both last_name1 and last_name2 are None or ''
        pass

    return combined_name

之所以出现last_name1必须是字符串而不是非非类型的错误,是因为您在所述字段的字段声明中将null设置为True

那么这样做有什么不对呢?当您为CharField或TextField等字段定义null=True时,您将得到None。Django约定使用空字符串


下面是一个示例,介绍如何在字段声明中使用blanknull

按照定义名称的方式,任何和所有名称都可以是
None
,如果将它们更改为空字符串,您将遇到类似的问题。为了说明这一点,让我们从编写单元测试开始(如果需要,可以用空字符串替换为None):

然后就是把问题分成几个小部分的问题,我把所有的东西都放到一个普通的类中,只是为了让它更容易测试。首先是一些辅助方法:

class Contact(object):
    def __init__(self, a, b, c, d):
        self.first_name1 = a
        self.last_name1 = b
        self.first_name2 = c
        self.last_name2 = d

    def combined_last_name(self, a, b):
        "Return ``a`` if ``a`` and ``b`` are equal, otherwise returns None."
        return a if a and b and a == b else None

    def normalize_name(self, n):
        "Returns the name or the empty string if ``n`` is None."
        return n if n else ""

    def get_name(self, f, l):
        """Returns a string containing firstname lastname and omits any of them
           if they're None.
        """         
        if f and l:
            return "%s %s" % (f, l)
        if f:
            return f
        elif l:
            return l
        return ""

    def has_second_name(self):
        "Returns true if there is a second name."
        return self.first_name2 or self.last_name2
然后我们可以定义
全名
属性:

    @property
    def full_name(self):
        """Returns a string that combines first and last names of two people
           depending if the last names are the same or not. If the last name
           is the same, it displays as::

               first_name1 & first_name2 last_name1

        """
        cln = self.combined_last_name(self.last_name1, self.last_name2)
        if cln:  # have a common last name..
            return "%s & %s %s" % (
                self.first_name1,
                self.first_name2,
                cln
            )
        elif self.has_second_name():
            return "%s & %s" % (
                self.get_name(self.first_name1, self.last_name1),
                self.get_name(self.first_name2, self.last_name2)
            )
        else:
            return self.get_name(self.first_name1, self.last_name1)
如果我们将所有内容放在名为
fullname.py
的文件中,我们可以使用
pytest
工具(
pip install pytest
)运行测试:

c:\srv\tmp> pytest --verbose fullname.py
============================= test session starts =============================
platform win32 -- Python 2.7.16, pytest-3.3.1, py-1.5.2, pluggy-0.6.0 -- c:\srv\venv\finautfaktura\scripts\python.exe
cachedir: .cache
rootdir: c:\srv\tmp, inifile:
plugins: xdist-1.20.1, forked-0.2, django-3.1.2, cov-2.5.1
collected 1 item

fullname.py::test_contact_full_name PASSED                               [100%]

========================== 1 passed in 0.20 seconds ===========================
一切都很好。。。还是这样

让我们编写另一个测试:

def test_only_second_name():
    assert Contact(None, None, None, "Simpson").full_name == "Simpson"
    assert Contact(None, None, "Lisa", "Simpson").full_name == "Lisa Simpson"
    assert Contact(None, None, "Lisa", None).full_name == "Lisa"
再次运行pytest会显示(第一个)错误:

c:\srv\tmp> pytest --verbose fullname.py
============================= test session starts =============================
platform win32 -- Python 2.7.16, pytest-3.3.1, py-1.5.2, pluggy-0.6.0 -- c:\srv\venv\finautfaktura\scripts\python.exe
cachedir: .cache
rootdir: c:\srv\tmp, inifile:
plugins: xdist-1.20.1, forked-0.2, django-3.1.2, cov-2.5.1
collected 2 items

fullname.py::test_contact_full_name PASSED                               [ 50%]
fullname.py::test_only_second_name FAILED                                [100%]

================================== FAILURES ===================================
____________________________ test_only_second_name ____________________________

    def test_only_second_name():
>       assert Contact(None, None, None, "Simpson").full_name == "Simpson"
E       AssertionError: assert ' & Simpson' == 'Simpson'
E         -  & Simpson
E         ? ---
E         + Simpson

fullname.py:83: AssertionError
===================== 1 failed, 1 passed in 0.37 seconds ======================
i、 e.对于第一个断言,属性返回了
“&Simpson”
,而不是预期的
“Simpson”

要解决此问题,我们可以使
全名
属性也处理此增加的复杂性,或者..,我们可以在其他地方解决此问题,例如在
\uuuuu init\uuu
中:

class Contact(object):
    def __init__(self, a, b, c, d):
        self.first_name1 = a
        self.last_name1 = b
        self.first_name2 = c
        self.last_name2 = d
        if not a and not b:
            # if no name1, then put name2 into name1 and set name2 to None
            self.first_name1 = self.first_name2
            self.last_name1 = self.last_name2
            self.first_name2 = self.last_name2 = None
再次运行pytest表明这修复了第二个测试

当然,您不能在Django模型中提供自己的
\uuuu init\uuuu
来解决此问题,但如果您重写
保存(…)
方法,则可以在中执行类似的操作:

def save(self, *args, **kwargs):
    if not self.first_name1 and not self.last_name1:
        self.first_name1 = self.first_name2
        self.last_name1 = self.last_name2
        self.first_name2 = self.last_name2 = None
    super(Contact, self).save(*args, **kwargs)

为什么不简单地
返回组合的\u name
?这是一个合理的问题,但可能需要实际代码的上下文。如果它只是一个case/switch语句,我会亲自返回。我知道其他开发人员不会这样做,因为他们喜欢尝试将函数的返回限制在一个点上。这是一个偏好的问题,我不认为这两种情况都能带来显著的改进,所以我把它保留了下来。我的意思是
return combined\u name
而不是
return f'{combined\u name}
——第二个版本只是在返回字符串之前将一个字符串复制到另一个字符串中…?哦。谢谢,就这样。。。我删除了这些字段上的null=True,必须删除所有数据库条目并重新添加它们。现在它工作得很好。谢谢不确定这是否重要,但你仍然可以以《简与约翰》结尾。。请注意后面的空格。
def save(self, *args, **kwargs):
    if not self.first_name1 and not self.last_name1:
        self.first_name1 = self.first_name2
        self.last_name1 = self.last_name2
        self.first_name2 = self.last_name2 = None
    super(Contact, self).save(*args, **kwargs)