Использование подклассов модели в качестве опций выбора для этой модели вызывает NameError - PullRequest
0 голосов
/ 05 июля 2018

Мы пытаемся обновить версию устаревшего кода 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 голосов
/ 07 июля 2018

Проблема в том, что когда переменная класса type инициализируется в классе Question, класс Question не был создан, и все же _get_question_choices(), который ссылается на класс Question, получает оценивается сразу, чтобы присвоить значение type choices, что приводит к циклической ссылке.

Чтобы избежать этой проблемы, вместо оценки _get_question_choices() сразу во время объявления класса, вы можете сначала инициализировать type пустым 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()
0 голосов
/ 12 июля 2018

Я могу воспроизвести это; с Django 1.8, python3 -m manage check добивается успеха, в то время как в Django 1.9 это повышает NameError.

Нет ничего особенного в заметках о выпуске Django 1.9 , которые касаются этого изменения в поведении.

Я бы объяснил это поведение Django 1.8, отметив, что Django печально известен тем, что делает «магию» над кодом, чтобы позволить ссылаться на еще не выполненные части кода для определения моделей. Это аномалия из нормального поведения Python, а AFAICT недокументированная.

Таким образом, это было бы поведение, которое было только случайным и недокументированным (поэтому на него не следует полагаться), когда в обычном Python вы ожидаете NameError при ссылке на Question до его определения.

Django 1.9, очевидно, внес изменения, которые возвращаются к ожидаемому поведению Python: -)

0 голосов
/ 07 июля 2018

Исключение связано с тем, что при определении метода _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

...