From 10950dacb19a72c861f57b5d1167a9779dffd3cf Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Sat, 1 Oct 2016 13:48:29 +0200 Subject: [PATCH] try parent path if theme retrieval fails for current path (fixes #13385) --- hobo/context_processors.py | 66 ++++++++++++++++++++++++++++++----------- tests/test_context_processor.py | 38 ++++++++++++++++++++++++ tox.ini | 1 + 3 files changed, 87 insertions(+), 18 deletions(-) create mode 100644 tests/test_context_processor.py diff --git a/hobo/context_processors.py b/hobo/context_processors.py index 31bb8ba..30a4a7e 100644 --- a/hobo/context_processors.py +++ b/hobo/context_processors.py @@ -30,43 +30,73 @@ def template_vars(request): template_vars.update({'statics_hash': statics_hash.hexdigest()}) return template_vars + class RemoteTemplate(object): def __init__(self, source): - self.source = source + self.source = urlparse.urlunparse(urlparse.urlparse(source)[:3] + ('', '', '')) + self.disable_recurse = False @property def cache_key(self): - return hashlib.md5(urlparse.urlunparse( - urlparse.urlparse(self.source)[:3] + ('', '', ''))).hexdigest() + return hashlib.md5(self.source).hexdigest() def get_template(self): + template_body = self.get_template_body() + if template_body is None: + logger.error('failed to retrieve theme') + raise Exception('failed to retrieve theme') + return Template(template_body) + + def get_template_body(self): item = cache.get(self.cache_key) if item is None: - template_body = self.update_content() - if template_body is None: - raise Exception('Failed to retrieve theme') + template_body = self.request_template_body() else: template_body, expiry_time = item if expiry_time < datetime.datetime.now(): # stale value, put it back into the cache for other consumers and # update the content in a different thread self.cache(template_body) - threading.Thread(target=self.update_content).start() - return Template(template_body) + # for background refresh we do not care about retrieval miss + self.disable_recurse = True + threading.Thread(target=self.request_template_body).start() + return template_body - def update_content(self): - r = requests.get(settings.THEME_SKELETON_URL, params={'source': self.source}) - if r.status_code != 200: - logger.error('failed to retrieve theme') + def get_template_body_recurse(self): + '''We were unable to retrieve a base template for this source URL, look for one level up.''' + if self.disable_recurse: return None - self.cache(r.text) - return r.text + parsed = urlparse.urlparse(self.source) + if not parsed.path or parsed.path == '/': + return None + if parsed.path.endswith('/'): # cd .. + new_path = urlparse.urljoin(parsed.path, '..') + else: + new_path = urlparse.urljoin(parsed.path, '.') + old_source = self.source + self.source = urlparse.urlunparse(parsed[:2] + (new_path, '', '', '')) + template_body = self.get_template_body() + self.source = old_source + return template_body - def cache(self, template_body): + def request_template_body(self): + try: + r = requests.get(settings.THEME_SKELETON_URL, params={'source': self.source}) + except requests.RequestException: + template_body = self.get_template_body_recurse() + else: + if r.status_code != 200: + template_body = self.get_template_body_recurse() + else: + template_body = r.text + self.cache_template_body(template_body) + return template_body + + def cache_template_body(self, template_body): expiry_time = datetime.datetime.now() + datetime.timedelta(seconds=CACHE_REFRESH_TIMEOUT) - cache.set(self.cache_key, - (template_body, expiry_time), - 2592000) # bypass cache level expiration time + cache.set(self.cache_key, (template_body, expiry_time), + 2592000) # bypass cache level expiration time + def theme_base(request): # this context processor adds two variables to context: diff --git a/tests/test_context_processor.py b/tests/test_context_processor.py new file mode 100644 index 0000000..72ebf0c --- /dev/null +++ b/tests/test_context_processor.py @@ -0,0 +1,38 @@ +from hobo.context_processors import theme_base +from httmock import urlmatch, HTTMock + +from django.core.cache import cache + + +def test_theme_base(settings, rf): + settings.THEME_SKELETON_URL = 'http://combo.example.com/_skeleton_/' + seen_urls = [] + TEMPLATE = 'Feeling lucky, punk?' + + @urlmatch(netloc=r'combo.example.com$') + def combo_mock(url, request): + seen_urls.append(url) + if 'page1' in url.query: + return {'status_code': 500, 'content': 'No template sorry'} + return TEMPLATE + + cache.clear() + with HTTMock(combo_mock): + context = theme_base(rf.get('/')) + assert context['theme_base']().origin.source == TEMPLATE + assert len(seen_urls) == 1 + seen_urls = [] + context = theme_base(rf.get('/')) + assert context['theme_base']().origin.source == TEMPLATE + assert len(seen_urls) == 0 + seen_urls = [] + context = theme_base(rf.get('/page1/page2/')) + assert context['theme_base']().origin.source == TEMPLATE + assert len(seen_urls) == 2 + seen_urls = [] + context = theme_base(rf.get('/page1/')) + assert context['theme_base']().origin.source == TEMPLATE + assert len(seen_urls) == 0 + context = theme_base(rf.get('/page1/page2/')) + assert context['theme_base']().origin.source == TEMPLATE + assert len(seen_urls) == 0 diff --git a/tox.ini b/tox.ini index a3eaedf..712a367 100644 --- a/tox.ini +++ b/tox.ini @@ -43,6 +43,7 @@ deps: passerelle: suds passerelle: python-memcached http://git.entrouvert.org/debian/django-tenant-schemas.git/snapshot/django-tenant-schemas-master.tar.gz + httmock commands = ./getlasso.sh hobo: py.test {env:COVERAGE:} {env:NOMIGRATIONS:} {posargs:tests/} -- 2.1.4