将模型的子类用作该模型的选择选项会引发NameError

时间:2018-07-05 08:53:30

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

我们正在尝试将旧代码的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中发生了什么变化,导致了这一点?解决此问题的最佳方法是什么?

3 个答案:

答案 0 :(得分:1)

我可以复制这个;在Django 1.8中,import Vue from 'vue' import { mount } from '@vue/test-utils' import { expect } from 'chai' import sinon from 'sinon' import Item from '../src/components/Item.vue' import Vuex from 'vuex' Vue.use(Vuex); describe('Item.vue', () => { let componentProps = {}; let wrapper; let actions; let store; beforeEach(() => { let name = 'Item Name'; // mock vuex action on click actions = { filterData: sinon.stub() } let moduleA = { state: {}, actions } store = new Vuex.Store({ modules: { moduleA } }); componentProps.itemName = name; wrapper = mount(Item, { store: store, propsData: componentProps }); }) it('Has a root element of list-item', () => { expect(wrapper.is('.list-item')).to.equal(true); }) it('Item getting prop name', () => { expect(wrapper.text()).to.equal('Item Name'); }) it('Item is not active on load', () => { expect(wrapper.vm.$data.isActive).to.equal(false); }) it('Item is active after click', () => { wrapper.trigger('click'); expect(wrapper.vm.$data.isActive).to.equal(true); }) it('Item is not active after two clicks', () => { wrapper.trigger('click'); wrapper.trigger('click'); expect(wrapper.vm.$data.isActive).to.equal(false); }) }) 成功,而在Django 1.9中它引发了python3 -m manage check

the Django 1.9 release notes中没有什么能解决这种行为上的变化。

我将通过解释Django在代码上做“魔术”而臭名昭著,以便允许引用代码中尚未执行的部分来定义模型,以此来解释Django 1.8的行为。这是正常Python行为的异常,而AFAICT是未记录的异常。

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

Django 1.9显然进行了更改,恢复为预期的Python行为:-)

答案 1 :(得分:0)

该异常是由于在定义方法_get_descendant_question_classes时尚未定义类Question。因此,您指的是不存在的东西。

这里有一个设计问题,请注意您具有周期性依赖关系:_get_descendant_question_classes依赖于QuestionQuestion依赖于_get_descendant_question_classes

一种快速的解决方法是使用get_model

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

我不确定您为什么需要该type字段,但是我相信您可以用另一种更简单的方法解决您的原始问题(导致您添加该type字段)。

此外,如果您需要了解Question实例的“类型”,则只需检查对象是否具有attr textquestion_ptrselectquestion_ptr或只使用{ {1}}

答案 2 :(得分:0)

问题在于,当在type类中初始化类变量Question时,尚未创建Question类,而尚未创建_get_question_choices()引用Question类,立即进行求值,以便为type的{​​{1}}分配值,从而产生循环引用。

为避免此问题,可以在类声明时立即对choices进行初始化,而无需在类声明期间立即评估_get_question_choices(),然后在{{1 }}的type方法,仅在实例化choices之后才调用:

Question