Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added support for theme inheritance.

Themes are now specified as an array rather than a single element. Files are
looked for in templates (and static directories) in the order they are listed.
This way, it is possible to define a new theme that makes only minor modifications of the default theme without copying all the files.
  • Loading branch information...
commit 0388917b25993a6e52ac223f133d71180dee63da 1 parent c1b6f3e
@Arachnid Arachnid authored
View
2  config.py
@@ -33,7 +33,7 @@
# Use the default YUI-based theme.
# If another string is used besides 'default', calls to static files and
# use of template files in /views will go to directory by that name.
- "theme": "default",
+ "theme": ["default"],
# Display gravatars alongside user comments?
"use_gravatars": True,
View
3  handlers/bloog/blog.py
@@ -301,8 +301,9 @@ def process_comment_submission(handler, article):
body=body)
# Render just this comment and send it to client
+ view_path = view.find_file(view.templates, "bloog/blog/comment.html")
response = template.render(
- "views/%s/bloog/blog/comment.html" % config.BLOG['theme'],
+ os.path.join("views", view_path),
{ 'comment': comment }, debug=config.DEBUG)
handler.response.out.write(response)
view.invalidate_cache()
View
1  main.py
@@ -39,6 +39,7 @@
# Import custom django libraries
webapp.template.register_template_library('utils.django_libs.gravatar')
webapp.template.register_template_library('utils.django_libs.description')
+webapp.template.register_template_library('utils.django_libs.find_static')
# Log a message each time this module get loaded.
logging.info('Loading %s, app version = %s',
View
41 utils/django_libs/find_static.py
@@ -0,0 +1,41 @@
+# The MIT License
+#
+# Copyright (c) 2008 Nick Johnson
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""
+Utility function to get the path of a static file.
+"""
+__author__ = 'Nick Johnson'
+
+import logging
+import os
+import view
+from google.appengine.ext import webapp
+
+register = webapp.template.create_template_register()
+
+def find_static_file(path):
+ static_path = view.find_file(view.staticfiles, path)
+ if static_path:
+ return "/" + os.path.join("static", static_path)
+ return ""
+
+register.filter(find_static_file)
View
8 utils/template.py
@@ -43,6 +43,7 @@
"""
+import logging
import os
try:
@@ -98,11 +99,14 @@ class below because Django requires you to load the template with a method
if not template:
directory, file_name = os.path.split(abspath)
+ if directory:
+ template_dirs = [directory] + template_dirs
+ logging.warn(template_dirs, file_name)
new_settings = {
- 'TEMPLATE_DIRS': (directory,) + template_dirs,
+ 'TEMPLATE_DIRS': template_dirs,
'TEMPLATE_DEBUG': debug,
'DEBUG': debug,
- }
+ }
old_settings = _swap_settings(new_settings)
try:
template = django.template.loader.get_template(file_name)
View
61 view.py
@@ -37,6 +37,32 @@
NUM_FULL_RENDERS = {} # Cached data for some timings.
+def do_build_tree(base, path, tree):
+ for entry in os.listdir(os.path.join(base, path)):
+ entry_path = os.path.join(path, entry)
+ if os.path.isdir(os.path.join(base, entry_path)):
+ do_build_tree(base, entry_path, tree.setdefault(entry, {}))
+ elif entry not in tree:
+ tree[entry] = entry_path
+
+def build_tree(base):
+ tree = {}
+ basedir = os.path.join(config.APP_ROOT_DIR, base)
+ for theme in config.BLOG['theme']:
+ do_build_tree(basedir, theme, tree)
+ return tree
+templates = build_tree('views')
+staticfiles = build_tree('static')
+
+def find_file(tree, path):
+ cur = tree
+ for element in path.split('/'):
+ cur = cur.get(element, {})
+ if cur:
+ return cur
+ else:
+ return None
+
def invalidate_cache():
memcache.flush_all()
@@ -93,36 +119,37 @@ def get_view_file(handler, params={}):
# Get template directory hierarchy -- Needed if we inherit from templates
# in directories above us (due to sharing with other templates).
-
- root_folder = os.path.join(
- config.APP_ROOT_DIR,
- 'views', config.BLOG['theme'])
- template_dirs = ()
- if module_name:
- template_dirs += (os.path.join(root_folder, app_name, module_name),)
- if app_name:
- template_dirs += (os.path.join(root_folder, app_name),)
- template_dirs += (root_folder,)
+ themes = config.BLOG['theme']
+ if isinstance(themes, basestring):
+ themes = [themes]
+ template_dirs = []
+ views_dir = os.path.join(config.APP_ROOT_DIR, 'views')
+ for theme in themes:
+ root_folder = os.path.join(views_dir, theme)
+ if module_name:
+ template_dirs += (os.path.join(root_folder, app_name, module_name),)
+ if app_name:
+ template_dirs += (os.path.join(root_folder, app_name),)
+ template_dirs += (root_folder,)
# Now check possible extensions for the given template file.
if module_name and handler_name:
- filename_prefix = os.path.join(root_folder, app_name, module_name, handler_name)
+ entries = templates.get(app_name, {}).get(module_name, {})
possible_roles = []
if users.is_current_user_admin():
possible_roles.append('.admin.')
if users.get_current_user():
possible_roles.append('.user.')
possible_roles.append('.')
- # Check possible template file names in order of decreasing priority
for role in possible_roles:
- filename = filename_prefix + role + verb + '.' + desired_ext
- if os.path.exists(filename):
+ filename = ''.join([handler_name, role, verb, '.', desired_ext])
+ if filename in entries:
return {'file': filename, 'dirs': template_dirs}
for role in possible_roles:
- filename = filename_prefix + role + desired_ext
- if os.path.exists(filename):
+ filename = ''.join([handler_name, role, desired_ext])
+ if filename in entries:
return {'file': filename, 'dirs': template_dirs}
- return {'file': root_folder + '/notfound.html', 'dirs': template_dirs}
+ return {'file': 'notfound.html', 'dirs': template_dirs}
class ViewPage(object):
def __init__(self, cache_time=None):
View
8 views/default/bloog/base.html
@@ -4,7 +4,7 @@
<meta http-equiv="Content-Type" content="{{ blog.html_type }}; charset={{ blog.charset }}" />
<title> {{ title }}</title>
<meta name="generator" content="Bloogle {{ bloogle_version }}" />
- <link rel="stylesheet" href="/static/default/style.css" type="text/css" media="screen" />
+ <link rel="stylesheet" href="{{"style.css"|find_static_file}}" type="text/css" media="screen" />
<link rel="alternate" type="application/rss+xml" title="{{ blog.title }} RSS Feed"
href="{{ blog.root_url }}{{ blog.master_atom_url }}" />
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.2/build/assets/skins/sam/skin.css" />
@@ -18,7 +18,7 @@
<div id="masthead" class="fix">
<h1><a href="/">{{ blog.title }}</a></h1>
<div id="authorBlurb">
- <img src="/static/default/images/avatar.png" alt="Avatar" />
+ <img src="{{"images/avatar.png"|find_static_file}}" alt="Avatar" />
<p id="authorIntro">
{{ blog.description }}
</p>
@@ -89,7 +89,7 @@
<div id="searchWrap">
<form method="get" id="searchForm" action="/search">
<input type="text" value="{{ searchterm }}" name="s" id="s" />
- <input id="searchsubmit" type="image" src="/static/default/images/btn_search.gif" alt="Search" />
+ <input id="searchsubmit" type="image" src="{{"images/btn_search.gif"|find_static_file}}" alt="Search" />
</form>
</div>
{% endblock %}
@@ -254,7 +254,7 @@
</div>
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.2/build/yahoo-dom-event/yahoo-dom-event.js"></script>
- <script type="text/javascript" src="/static/default/js/bloog_base.js"></script>
+ <script type="text/javascript" src="{{"js/bloog_base.js"|find_static_file}}"></script>
{% if user_is_admin %}
{% include "form_editor.html" %}
View
6 views/default/bloog/blog/form_comment.html
@@ -37,8 +37,8 @@
{% if not user_is_admin %}
<!-- Load scripts if not already loaded due to admin interface -->
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.5.2/build/yahoo-dom-event/yahoo-dom-event.js&2.5.2/build/animation/animation-min.js&2.5.2/build/connection/connection-min.js&2.5.2/build/container/container-min.js&2.5.2/build/menu/menu-min.js&2.5.2/build/element/element-beta-min.js&2.5.2/build/button/button-min.js&2.5.2/build/editor/editor-beta-min.js&2.5.2/build/selector/selector-beta-min.js"></script>
-<script type="text/javascript" src="/static/default/js/ojay/js-class.js"></script>
-<script type="text/javascript" src="/static/default/js/ojay/core.js"></script>
+<script type="text/javascript" src="{{"js/ojay/js-class.js"|find_static_file}}"></script>
+<script type="text/javascript" src="{{"js/ojay/core.js"|find_static_file}}"></script>
{% endif %}
-<script type="text/javascript" src="/static/default/js/bloog_comments.js"></script>
+<script type="text/javascript" src="{{"js/bloog_comments.js"|find_static_file}}"></script>
View
2  views/default/bloog/contact/contact.get.html
@@ -21,7 +21,7 @@
<p><input type="text" id="subject" name="subject" size="22" tabindex="3" />
<label for="subject"><small>Subject</small></label></p>
<p><textarea id="message" name="message" cols="60" rows="10" tabindex="4">Please enter a message.</textarea></p>
- <p><input name="submit" type="image" src="/static/default/images/btn_submit.gif" alt="Submit" id="submit" tabindex="5" value="Submit" /></p>
+ <p><input name="submit" type="image" src="{{"images/btn_submit.gif"|find_static_file}}" alt="Submit" id="submit" tabindex="5" value="Submit" /></p>
<input type="hidden" name="token" value="{{ token }}" />
<input type="hidden" name="curtime" value="{{ curtime|floatformat }}" />
</form>
View
8 views/default/bloog/form_editor.html
@@ -1,7 +1,7 @@
{% if user_is_admin %}
{% block head %}
-<link rel="stylesheet" type="text/css" href="/static/default/editor.css" />
+<link rel="stylesheet" type="text/css" href="{{"editor.css"|find_static_file}}" />
{% endblock %}
<div id="postDialog" class="popupDialog">
@@ -27,9 +27,9 @@
</div>
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.5.2/build/yahoo-dom-event/yahoo-dom-event.js&2.5.2/build/animation/animation-min.js&2.5.2/build/connection/connection-min.js&2.5.2/build/container/container-min.js&2.5.2/build/menu/menu-min.js&2.5.2/build/element/element-beta-min.js&2.5.2/build/button/button-min.js&2.5.2/build/editor/editor-beta-min.js&2.5.2/build/selector/selector-beta-min.js"></script>
-<script type="text/javascript" src="/static/default/js/ojay/js-class.js"></script>
-<script type="text/javascript" src="/static/default/js/ojay/core.js"></script>
-<script type="text/javascript" src="/static/default/js/bloog_admin.js"></script>
+<script type="text/javascript" src="{{"js/ojay/js-class.js"|find_static_file}}"></script>
+<script type="text/javascript" src="{{"js/ojay/core.js"|find_static_file}}""></script>
+<script type="text/javascript" src="{{"js/bloog_admin.js"|find_static_file}}"></script>
{% endif %}
Please sign in to comment.
Something went wrong with that request. Please try again.