Reputation: 13
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!
Upvotes: 0
Views: 493
Reputation: 599826
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.
Upvotes: 2
Reputation: 2247
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
Upvotes: 0
Reputation: 10676
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()
Upvotes: 0