Django - get_or_create触发RunTimeError:超出最大递归深度

时间:2013-07-02 13:03:49

标签: django recursion init depth

这是我关于stackoverflow的第一篇文章,所以对我如何提出这个问题的任何批评都是受欢迎的。

在我的代码中,我收到此错误:

RuntimeError: maximum recursion depth exceeded

这是代码(内容无关紧要,我只是以最简单的方式重新创建错误)。基本上我试图覆盖__init__。如果对象在数据库中,我想做某事,如果不在,则我想做其他事情。

class Question(models.Model):

    text = models.CharField(max_length=140)
    asked = models.BooleanField(default=False)

    def __init__(self, text, *args):
        #called the __init__ of the superclass.
        super(Question, self).__init__()

        self, c = Question.objects.get_or_create(text=text)
        if c:
            print 'This question will be asked!'
            self.asked = True
            self.save()
        else:
            print 'This question was already asked'
            assert self.asked == True

调用构造函数时发生错误:

Question('how are you?')

我知道问题来自get_or_create方法。查看错误消息,

---> 12         self, c = Question.objects.get_or_create(text=text)
...
---> 146         return self.get_query_set().get_or_create(**kwargs)
...
---> 464                 obj = self.model(**params)

get_or_create在某个时刻调用对象的构造函数。然后再调用get_or_create等......

编辑:我想要实现的基本上是能够写:

Question('How are you?')

如果对象在数据库中,则返回该对象;如果不是,则返回新创建的(并保存)对象。而不是像:

> try:
>     q = Question.objects.get(text='How are you?')
> except Question.DoesNotExist:
>     q = Question(text='How are you?')
>     q.save()

所以我认为实现这一目标的唯一方法是重写__init__。不可能或概念上是错误的(或两者兼而有之)?谢谢!

3 个答案:

答案 0 :(得分:2)

你不应该在__init__中尝试这一点。 (事实上​​,最好不要单独留下__init__ Django模型。)它应该以表格或视图的形式出现。

在任何情况下,您都无法通过分配self来覆盖自身内部的实例 - 这只是一个局部变量,就像其他任何一个一样,并且会在结束时超出范围。方法

另请注意,如果找不到现有实例,您可以使用defaults参数get_or_create来传递要在新实例上设置的默认值:

question, created = Question.objects.get_or_create(text=text, defaults={'asked': True})

在问题更新后进行修改您的编辑更清楚地表明__init__确实不是这样做的地方。别忘了,即使评估正常的查询集也会实例化模型对象,这意味着调用__init__ - 所以只是从数据库中获取实例会遇到问题。不要这样做。

相反,如果你真的需要这个由模型提供 - 即使它只是一行代码,如上所示 - 你可以定义一个类方法:

class Question(models.Model):
    ...
    @classmethod
    def query(cls, text):
         question, _ = cls.objects.get_or_create(text=text, defaults={'asked': True})
         return question

然后你可以Question.query('How are you'),它会返回新项目或现有项目。

答案 1 :(得分:0)

__init__中的逻辑需要移动到其他位置,例如视图。 get_or_create返回两个值:1)对象和2)如果必须创建对象。 See the docs for more details.

def some_view(request):
    c, created = Question.objects.get_or_create(text=text)

    if not created:
        print 'This question was already asked'
    else:
        print 'This question will be asked!'
        c.asked = True
        c.save()

答案 2 :(得分:0)

就像@Scott Woodall所说,你需要移动初始逻辑。 那是怎么回事:

当您致电Question('how are you?')时,您会转到__init__方法。 __init__调用Question.objects.get_or_create(text=text),但Question找不到text,因此请尝试创建新问题,再次调用__init__。 您将永远重新进入方法调用。

Question('how are you?') # <-- You call this method
  |
  +-- def __init__(self, text, *args): 
      |
      +-- Question.objects.get_or_create(text=text) # This try to create a model, calling __init__ method!
          |
          +-- def __init__(self, text, *args): 
              |
              +-- Question.objects.get_or_create(text=text) # This try to create a model, calling __init__ method!
                  |
                  +  # So on...

我认为您应该在文本问题字段中添加unique=True

请参阅@Scott答案