Skip to content

Commit

Permalink
[1.1.X] Fixed #12057 -- Corrected regression of caching performance w…
Browse files Browse the repository at this point in the history
…hen a model contained a callable default. Thanks to Michael Thornhill for the excellent assistance tracking this problem.

Backport of r11681 from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.1.X@11682 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
freakboy3742 committed Oct 29, 2009
1 parent b1151bc commit 7293329
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 3 deletions.
2 changes: 1 addition & 1 deletion AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ answer newbie questions, and generally made Django that much better:
Travis Terry <tdterry7@gmail.com>
thebjorn <bp@datakortet.no>
Zach Thompson <zthompson47@gmail.com>
Michael Thornhill
Michael Thornhill <michael.thornhill@gmail.com>
Deepak Thukral <deep.thukral@gmail.com>
tibimicu@gmx.net
tobias@neuyork.de
Expand Down
12 changes: 10 additions & 2 deletions django/db/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,14 @@ def __init__(self, *args, **kwargs):
if rel_obj is None and field.null:
val = None
else:
val = kwargs.pop(field.attname, field.get_default())
try:
val = kwargs.pop(field.attname)
except KeyError:
# This is done with an exception rather than the
# default argument on pop because we don't want
# get_default() to be evaluated, and then not used.
# Refs #12057.
val = field.get_default()
else:
val = field.get_default()
if is_related_object:
Expand Down Expand Up @@ -353,7 +360,7 @@ def __reduce__(self):
"""
data = self.__dict__
if not self._deferred:
return (self.__class__, (), data)
return super(Model, self).__reduce__()
defers = []
pk_val = None
for field in self._meta.fields:
Expand All @@ -366,6 +373,7 @@ def __reduce__(self):
# once.
obj = self.__class__.__dict__[field.attname]
model = obj.model_ref()

return (model_unpickle, (model, defers), data)

def _get_pk_val(self, meta=None):
Expand Down
11 changes: 11 additions & 0 deletions tests/regressiontests/cache/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.db import models
from datetime import datetime

def expensive_calculation():
expensive_calculation.num_runs += 1
return datetime.now()

class Poll(models.Model):
question = models.CharField(max_length=200)
answer = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published', default=expensive_calculation)
42 changes: 42 additions & 0 deletions tests/regressiontests/cache/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from django.http import HttpResponse, HttpRequest
from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key
from django.utils.hashcompat import md5_constructor
from regressiontests.cache.models import Poll, expensive_calculation

# functions/classes for complex data type tests
def f():
Expand Down Expand Up @@ -211,6 +212,47 @@ def test_data_types(self):
self.cache.set("stuff", stuff)
self.assertEqual(self.cache.get("stuff"), stuff)

def test_cache_read_for_model_instance(self):
# Don't want fields with callable as default to be called on cache read
expensive_calculation.num_runs = 0
Poll.objects.all().delete()
my_poll = Poll.objects.create(question="Well?")
self.assertEqual(Poll.objects.count(), 1)
pub_date = my_poll.pub_date
self.cache.set('question', my_poll)
cached_poll = self.cache.get('question')
self.assertEqual(cached_poll.pub_date, pub_date)
# We only want the default expensive calculation run once
self.assertEqual(expensive_calculation.num_runs, 1)

def test_cache_write_for_model_instance_with_deferred(self):
# Don't want fields with callable as default to be called on cache write
expensive_calculation.num_runs = 0
Poll.objects.all().delete()
my_poll = Poll.objects.create(question="What?")
self.assertEqual(expensive_calculation.num_runs, 1)
defer_qs = Poll.objects.all().defer('question')
self.assertEqual(defer_qs.count(), 1)
self.assertEqual(expensive_calculation.num_runs, 1)
self.cache.set('deferred_queryset', defer_qs)
# cache set should not re-evaluate default functions
self.assertEqual(expensive_calculation.num_runs, 1)

def test_cache_read_for_model_instance_with_deferred(self):
# Don't want fields with callable as default to be called on cache read
expensive_calculation.num_runs = 0
Poll.objects.all().delete()
my_poll = Poll.objects.create(question="What?")
self.assertEqual(expensive_calculation.num_runs, 1)
defer_qs = Poll.objects.all().defer('question')
self.assertEqual(defer_qs.count(), 1)
self.cache.set('deferred_queryset', defer_qs)
self.assertEqual(expensive_calculation.num_runs, 1)
runs_before_cache_read = expensive_calculation.num_runs
cached_polls = self.cache.get('deferred_queryset')
# We only want the default expensive calculation run on creation and set
self.assertEqual(expensive_calculation.num_runs, runs_before_cache_read)

def test_expiration(self):
# Cache values can be set to expire
self.cache.set('expire1', 'very quickly', 1)
Expand Down

0 comments on commit 7293329

Please sign in to comment.