The new Queryset refactor uses deepcopy for the where element of Query.clone()
in django.db.models.sql.Query.py line 169(ish)
obj.where = deepcopy(self.where)
One gets an object that has a post_init signal (or post_save). (best shown be example)
class MyObj(models.Model):
cola = models.Char...
colb = models.Char...
..
attr_a = None # none DB attribute
attr_b = None # none DB attribute
class AnotherObj(models.Model):
my_obj = models.ForeignKey(MyObj)
cola = models.Char...
colb = models.Char...
class YetAnotherObj(models.Model):
my_obj = models.ForeignKey(MyObj)
cola = models.Char...
colb = models.Char...
def after_my_obj_init(sender, instance, signal, *args, **kwargs):
# try to fill in the instances 'other attributes' with some preset
# querySets for use as properties later
#the deepcopy in the of the Querset clone
# will cause an intinite HERE (clone on the order_by)
instance.attr_a = AnotherObj.objects.filter(
my_obj = instance,
cola__contains = "Some Text"
).order_by('cola')
#No Infinite Recusrion as there is no Clone() called
instance.attr_b = AnotherObj.objects.filter(
my_obj = instance,
cola__contains = "Some Other Text"
)
dispatcher.connect(after_my_obj_init, signal=signals.post_init, sender=MyObj)
The above signal will now result in an infinite recursion .. as apparently it keeps trying to call the post_init function on 'instance' as it marches done the Where Tree.
There is a solution, but becuase it can also happen Outside the initsignal on other QuerySets?, and seems rather sporatic sometimes for instance anytime clone is used it can crash.
an_obj = MyObj.objects.get(pk = 1)
# No infinite recursion
my_stuf = YetAnotherObj.objects.filter(cola = "something", my_obj = an_obj)
my_stuf = YetAnotherObj.objects.filter(cola = "something")
#BOOM (due to the clone())
my_stuf = my_stuf.filter(my_obj = an_obj)
The work around is to use the Primary Key
#in the signal
def after_my_obj_init(sender, instance, signal, *args, **kwargs):
# try to fill in the instances 'other attributes' with some preset
# querySets for use as properties later
#the deepcopy in the of the Querset clone
# will cause an intinite HERE (clone on the order_by)
instance.attr_a = AnotherObj.objects.filter(
my_obj__pk = instance.pk,
cola__contains = "Some Text"
).order_by('cola')
#No Infinite Recusrion as there is no Clone() called
instance.attr_b = AnotherObj.objects.filter(
my_obj__pk = instance.pk,
cola__contains = "Some Other Text"
)
#in the other qset
my_stuf = my_stuf.filter(my_obj__pk = an_obj.pk)
As you can probably guess, this behavior is rather scary. Changing the "deepcopy" to a normal "copy" did seem to help the issue, but i imagine that will break other tests.