使用模型的子类作为该模型的选择选项会引发命名

2024-10-01 09:16:07 发布

您现在位置:Python中文网/ 问答频道 /正文

我们正在尝试将遗留代码的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

我知道问题是我不明白Django1.8是如何工作的?Django1.9中的哪些变化导致了这种情况?最好的解决方法是什么?你知道吗


Tags: djangoinpygetinitmodelslibusr
3条回答

问题是,当类变量typeQuestion类中初始化时,Question类尚未创建,而引用Question类的_get_question_choices()会立即求值,以便为typechoices赋值,从而产生循环引用。你知道吗

为避免此问题,您可以先用空的choices初始化type,然后在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()

异常是由于在定义方法_get_descendant_question_classes时,类Question尚未定义。所以,你指的是不存在的东西。你知道吗

这里有一个设计问题,注意有一个循环依赖:_get_descendant_question_classes依赖于QuestionQuestion依赖于_get_descendant_question_classes。你知道吗

一个快速解决方法是使用^{}

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

我不知道为什么需要type字段,但我相信您可以用另一种更简单的方式解决原来的问题(是什么导致您添加type字段)。你知道吗

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

我可以重现这种情况;在django1.8中,python3 -m manage check成功,而在django1.9中,它引发了NameError。你知道吗

the Django 1.9 release notes中没有什么特别的地方可以解决这种行为上的变化。你知道吗

我想解释一下django1.8的这种行为,因为Django在代码上做了“魔术”,以便允许引用代码中尚未执行的部分来定义模型。这是Python正常行为的一个异常,而AFAICT是一个没有文档记录的异常。你知道吗

因此,这将是一种只是偶然的、没有文档记录的行为(因此不应依赖),在普通Python中,在定义之前引用Question时,您会期望出现NameError。你知道吗

Django1.9显然做了一个更改,恢复到了预期的Python行为:-)

相关问题 更多 >