【问题标题】:Using a model's sub-classes as choice options for that model raises NameError使用模型的子类作为该模型的选择选项会引发 NameError
【发布时间】:2018-12-13 16:30:59
【问题描述】:

我们正在尝试将旧代码的 django 版本从 1.8 升级到 1.9。我们有一个这样定义的模型:

def _get_descendant_question_classes():
    stack = [Question]

    while stack:
        cls = stack.pop()
        stack.extend(cls.__subclasses__())
        yield cls

def _get_question_choices():
    question_classes = _get_descendant_question_classes()

    for cls in question_classes:
        yield (cls.slug, cls._meta.verbose_name)

class Question(models.Model):
    slug = "Question"
    type = models.CharField(max_length=10, choices=_get_question_choices(), default=slug)

class TextQuestion(Question):
    slug = "TextQuestion"

class SelectQuestion(Question):
    slug = "SelectQuestion"

...

基本上,模型希望将其子类用作其字段之一的选择选项。它通过以 DFS 方式遍历模型并产生所有子类来做到这一点。

此代码在 django 1.8 中有效,但在 django 1.9 中出现此错误:

Traceback (most recent call last):
  File "./manage.py", line 16, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 350, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 324, in execute
    django.setup()
  File "/usr/local/lib/python2.7/site-packages/django/__init__.py", line 18, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/usr/local/lib/python2.7/site-packages/django/apps/registry.py", line 108, in populate
    app_config.import_models(all_models)
  File "/usr/local/lib/python2.7/site-packages/django/apps/config.py", line 202, in import_models
    self.models_module = import_module(models_module_name)
  File "/usr/local/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/home/saeed/saeed/survey/models.py", line 85, in <module>
    class Question(models.Model):
  File "/home/saeed/saeed/survey/models.py", line 99, in Question
    type = models.CharField(max_length=10, choices=_get_question_choices(), default=slug)
  File "/usr/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 1072, in __init__
    super(CharField, self).__init__(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 161, in __init__
    choices = list(choices)
  File "/home/saeed/saeed/survey/models.py", line 65, in _get_question_choices
    for cls in question_classes:
  File "/home/saeed/saeed/survey/models.py", line 54, in _get_descendant_question_classes
    stack = [Question]
NameError: global name 'Question' is not defined

我明白这个问题我不明白这是如何在 django 1.8 中工作的?导致这种情况的 django 1.9 发生了什么变化?解决此问题的最佳方法是什么?

【问题讨论】:

  • 我在尝试重新创建时收到关于 slug 的错误。

标签: python django django-models django-1.8 django-1.9


【解决方案1】:

异常是由于,在定义方法_get_descendant_question_classes时,还没有定义类Question。所以,你指的是不存在的东西。

这里有一个设计问题,注意你有一个循环依赖:_get_descendant_question_classes 依赖于Question 和Question 依赖于_get_descendant_question_classes。

可以使用get_model:

def _get_descendant_question_classes():
    stack = [get_model('yourappnamehere', 'Question')]

我不确定您为什么需要 type 字段,但我相信您可以通过另一种更简单的方式解决您最初的问题(导致您添加 type 字段的原因)。

另外,如果您需要知道 Question 实例的“类型”,您只需要检查对象是否具有 attr textquestion_ptr 或 selectquestion_ptr 或只使用 isinstance

【讨论】:

    【解决方案2】:

    问题是,当类变量type在Question类中初始化时,Question类还没有被创建,而_get_question_choices()引用了Question类,得到了立即评估以便为type 的choices 赋值,从而产生循环引用。

    为避免此问题,您可以先用空的choices 初始化type,而不是在类声明期间立即评估_get_question_choices(),然后在Question 的__init__ 方法中为其指定首选值,仅在 Question 被实例化后调用:

    class Question(models.Model):
        type = models.CharField(max_length=10, choices=(,), default=slug)
    
        def __init__(self, *args, **kwargs):
            super(Question, self).__init__(*args, **kwargs)
            self._meta.get_field('type').choices = _get_question_choices()
    

    【讨论】:

      【解决方案3】:

      我可以重现这个;在 Django 1.8 中,python3 -m manage check 成功,而在 Django 1.9 中,它引发了 NameError。

      the Django 1.9 release notes 没有什么特别之处可以解决这种行为变化。

      我会解释这个 Django 1.8 的行为,指出 Django 臭名昭著地对代码进行“魔术”,以便允许引用代码中尚未执行的部分来定义模型。这是正常 Python 行为的异常,而 AFAICT 是未记录的。

      因此,这只是一种偶然且未记录的行为(因此不应依赖),在普通 Python 中,当在定义之前引用 Question 时,您会期望 NameError。

      Django 1.9 显然做了一个改变,恢复到预期的 Python 行为:-)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-07-04
        • 1970-01-01
        • 2020-06-17
        • 1970-01-01
        • 1970-01-01
        • 2020-01-24
        • 1970-01-01
        相关资源
        最近更新 更多