Pergunta

This is my first post on stackoverflow, so any criticisms about how I ask the question are welcome.

In my code, I get this error:

RuntimeError: maximum recursion depth exceeded

Here is the code (the content is irrelevant, I just recreated the error in the simplest way possible). Basically I am trying to override __init__. I want to do something if the object is in the database and something else if it is not.

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

The error occurs when calling the constructor:

Question('how are you?')

I understand that the problem comes from the get_or_create method. Looking at the error message,

---> 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 calls the constructor of the object at some point. Which then calls get_or_create again etc...

EDIT: What I would like to achieve is basically being able to write:

Question('How are you?')

and having the object returned if it's in the database, or the newly created (and saved) one if it's not. Instead of something like:

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

So I guess that the only way to achieve this is by overriding the __init__. Isn't it possible or is it conceptually wrong (or both)? Thanks!

Foi útil?

Solução

You shouldn't really try and to this in the __init__. (In fact, it's best to leave the __init__ of Django models well alone.) It should go in the form, or the view.

In any case, you can't overwrite an instance inside itself by assigning to self - that's just a local variable like any other and will go out of scope at the end of the method.

Also note that you can use the defaults parameter to get_or_create to pass default values to be set on the new instance if an existing one is not found:

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

Edit after question update Your edit makes it even clearer that __init__ is really not the place to be doing this. Don't forget, even evaluating a normal queryset will instantiate model objects, which means calling __init__ - so just getting an instance from the database will run into problems. Don't do this.

Instead, if you really need this to be provided by the model - even though it's just one line of code, as shown above - you could define a classmethod:

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

Then you could do Question.query('How are you') and it would return either the new or the existing item.

Outras dicas

The logic within your __init__ needs to be moved somewhere else, like to a view. get_or_create returns two values: 1) the object and 2) if the object had to be created or not. 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()

Like @Scott Woodall said, you need to move you init logic. that is What's happening:

When you call Question('how are you?'), you go to __init__ method. The __init__ calls Question.objects.get_or_create(text=text), who does't find a Question with that text, so try to create a new Question, calling __init__ (again). You are re-entering in the method call forever.

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...

I think you should add a unique=True to text Question field.

See the @Scott answer

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top