From 8a36f07e815659f3c14578e8d0c57fc75d3f887c Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Wed, 17 Nov 2010 21:19:29 -0500 Subject: made both media and templates resolve from custom skin directory, changed setting ASKBOT_EXTRA_SKIN_DIRS to ASKBOT_EXTRA_SKINS_DIR, now it has to be a string --- askbot/__init__.py | 2 +- askbot/skins/loaders.py | 10 +--- askbot/skins/utils.py | 138 +++++++++++++++++++++++++++++++++--------------- askbot/urls.py | 79 ++++++++++++++------------- askbot/views/meta.py | 14 ++++- 5 files changed, 149 insertions(+), 94 deletions(-) diff --git a/askbot/__init__.py b/askbot/__init__.py index 8098eeda..fb6dfad9 100644 --- a/askbot/__init__.py +++ b/askbot/__init__.py @@ -22,7 +22,7 @@ def get_version(): """returns version of the askbot app this version is meaningful for pypi only """ - return '0.6.29' + return '0.6.31' #todo: maybe send_mail functions belong to models #or the future API diff --git a/askbot/skins/loaders.py b/askbot/skins/loaders.py index 241897c9..f264c546 100644 --- a/askbot/skins/loaders.py +++ b/askbot/skins/loaders.py @@ -43,14 +43,8 @@ class SkinEnvironment(CoffinEnvironment): """ loaders = list() skin_name = askbot_settings.ASKBOT_DEFAULT_SKIN - skin_dirs = utils.get_skin_dirs() - - template_dirs = list() - for dir in skin_dirs: - template_dirs.append(os.path.join(dir, skin_name, 'templates')) - if skin_name != 'default': - for dir in skin_dirs: - template_dirs.append(os.path.join(dir, 'default', 'templates')) + skin_dirs = utils.get_available_skins(selected = skin_name).values() + template_dirs = [os.path.join(skin_dir, 'templates') for skin_dir in skin_dirs] loaders.append(jinja_loaders.FileSystemLoader(template_dirs)) return loaders diff --git a/askbot/skins/utils.py b/askbot/skins/utils.py index 259424da..b244da00 100644 --- a/askbot/skins/utils.py +++ b/askbot/skins/utils.py @@ -1,34 +1,90 @@ +"""utilities dealing with resolution of skin components + +the lookup resolution process for templates and media works as follows: +* look up item in selected skin +* if not found look in 'default' +* the look in 'common' +* raise an exception +""" import os import logging from django.conf import settings as django_settings +from django.utils.datastructures import SortedDict + +class MediaNotFound(Exception): + """raised when media file is not found""" + pass + +def get_skins_from_dir(directory): + """returns sorted dict with skin data, like get_available_skins + but from a specific directory + """ + skins = SortedDict() + for item in sorted(os.listdir(directory)): + item_dir = os.path.join(directory, item) + if os.path.isdir(item_dir): + skins[item] = item_dir + return skins + +def get_available_skins(selected=None): + """selected is a name of preferred skin + if it's None, then information about all skins will be returned + otherwise, only data about selected, default and common skins + will be returned + + selected skin is guaranteed to be the first item in the dictionary + + """ + skins = SortedDict() + if hasattr(django_settings, 'ASKBOT_EXTRA_SKINS_DIR'): + skins.update(get_skins_from_dir(django_settings.ASKBOT_EXTRA_SKINS_DIR)) + + stock_dir = os.path.normpath(os.path.dirname(__file__)) + stock_skins = get_skins_from_dir(stock_dir) + default_dir = stock_skins.pop('default') + common_dir = stock_skins.pop('common') + + skins.update(stock_skins) + + if selected: + if selected in skins: + selected_dir = skins[selected] + skins.clear() + skins[selected] = selected_dir + else: + assert(selected == 'default') + skins = SortedDict() -def get_skin_dirs(): - #todo: handle case of multiple skin directories - d = os.path.dirname - n = os.path.normpath - j = os.path.join - f = os.path.isfile - skin_dirs = [] - skin_dirs.append( n(j(d(d(__file__)), 'skins')) ) - if hasattr(django_settings, 'ASKBOT_EXTRA_SKIN_DIRS'): - skin_dirs += django_settings.ASKBOT_EXTRA_SKIN_DIRS - return skin_dirs + #re-insert default and common as last two items + skins['default'] = default_dir + skins['common'] = common_dir + return skins + + +def get_path_to_skin(skin): + """returns path to directory in the list of + available skin directories that contains another + directory called skin + + it is assumed that all skins are named uniquely + """ + skin_dirs = get_available_skins() + return skin_dirs.get(skin, None) def get_skin_choices(): - #todo: expand this to handle custom skin directories - skin_list = ['default'] - for directory in get_skin_dirs(): - items = os.listdir(directory) - for i in items: - item_path = os.path.join(directory, i) - if not os.path.isdir(item_path): - continue - if i == 'common': - continue - if i not in skin_list: - skin_list.append(i) - - return [(i,i) for i in skin_list] + """returns a tuple for use as a set of + choices in the form""" + skin_names = list(reversed(get_available_skins().keys())) + skin_names.remove('common') + return zip(skin_names, skin_names) + +def resolve_skin_for_media(media=None, preferred_skin = None): + #see if file exists, if not, try skins 'default', then 'common' + available_skins = get_available_skins(selected = preferred_skin).items() + for skin_name, skin_dir in available_skins: + if os.path.isfile(os.path.join(skin_dir, 'media', media)): + return skin_name + raise MediaNotFound(media) def get_media_url(url): """returns url prefixed with the skin name @@ -38,6 +94,8 @@ def get_media_url(url): if file is not found - returns None and logs an error message """ + #import datetime + #before = datetime.datetime.now() url = unicode(url) while url[0] == '/': url = url[1:] #todo: handles case of multiple skin directories @@ -66,7 +124,7 @@ def get_media_url(url): else: logging.critical('missing media resource %s' % url) - #2) if it does not exist - look in skins + #2) if it does not exist in uploaded files directory - look in skins #purpose of this try statement is to determine #which skin is currently used @@ -81,24 +139,14 @@ def get_media_url(url): use_skin = 'default' resource_revision = None - skins = get_skin_dirs()[0] - - #see if file exists, if not, try skins 'default', then 'common' - file_path = os.path.join(skins, use_skin, 'media', url) - if not os.path.isfile(file_path): - file_path = os.path.join(skins, 'default', 'media', url) - if os.path.isfile(file_path): - use_skin = 'default' - else: - file_path = os.path.join(skins, 'common', 'media', url) - if os.path.isfile(file_path): - use_skin = 'common' - else: - log_message = 'missing media resource %s in skin %s' \ - % (url, use_skin) - logging.critical(log_message) - use_skin = '' - return None + #determine from which skin take the media file + try: + use_skin = resolve_skin_for_media(media=url, preferred_skin = use_skin) + except MediaNotFound, e: + log_message = 'missing media resource %s in skin %s' \ + % (url, use_skin) + logging.critical(log_message) + return None url = use_skin + '/media/' + url url = '///' + django_settings.ASKBOT_URL + 'm/' + url @@ -111,4 +159,6 @@ def get_media_url(url): if resource_revision: url += '?v=%d' % resource_revision + #after = datetime.datetime.now() + #print after - before return url diff --git a/askbot/urls.py b/askbot/urls.py index 321010d1..7063b599 100644 --- a/askbot/urls.py +++ b/askbot/urls.py @@ -5,7 +5,7 @@ import os.path from django.conf.urls.defaults import url, patterns, include from django.conf.urls.defaults import handler500, handler404 from django.contrib import admin -from askbot import views as app +from askbot import views from askbot.feed import RssLastestQuestionsFeed from askbot.sitemap import QuestionsSitemap from django.utils.translation import ugettext as _ @@ -21,7 +21,7 @@ sitemaps = { APP_PATH = os.path.dirname(__file__) urlpatterns = patterns('', - url(r'^$', app.readers.index, name='index'), + url(r'^$', views.readers.index, name='index'), url( r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', @@ -29,9 +29,8 @@ urlpatterns = patterns('', name='sitemap' ), url( - r'^m/(?P.*)$', - 'django.views.static.serve', - {'document_root': os.path.join(APP_PATH,'skins').replace('\\','/')}, + r'^m/(?P[^/]+)/media/(?P.*)$', + views.meta.media, name='askbot_media', ), url( @@ -40,159 +39,159 @@ urlpatterns = patterns('', {'document_root': os.path.join(settings.PROJECT_ROOT, 'askbot', 'upfiles').replace('\\','/')}, name='uploaded_file', ), - url(r'^%s$' % _('about/'), app.meta.about, name='about'), - url(r'^%s$' % _('faq/'), app.meta.faq, name='faq'), - url(r'^%s$' % _('privacy/'), app.meta.privacy, name='privacy'), - url(r'^%s$' % _('logout/'), app.meta.logout, name='logout'), + url(r'^%s$' % _('about/'), views.meta.about, name='about'), + url(r'^%s$' % _('faq/'), views.meta.faq, name='faq'), + url(r'^%s$' % _('privacy/'), views.meta.privacy, name='privacy'), + url(r'^%s$' % _('logout/'), views.meta.logout, name='logout'), url( r'^%s(?P\d+)/%s$' % (_('answers/'), _('comments/')), - app.writers.answer_comments, + views.writers.answer_comments, name='answer_comments' ), url( r'^%s(?P\d+)/%s$' % (_('answers/'), _('edit/')), - app.writers.edit_answer, + views.writers.edit_answer, name='edit_answer' ), url( r'^%s(?P\d+)/%s$' % (_('answers/'), _('revisions/')), - app.readers.revisions, + views.readers.revisions, kwargs = {'object_name': 'Answer'}, name='answer_revisions' ), url( r'^%s$' % _('questions/'), - app.readers.questions, + views.readers.questions, name='questions' ), url( r'^%s%s$' % (_('questions/'), _('ask/')), - app.writers.ask, + views.writers.ask, name='ask' ), url( r'^%s(?P\d+)/%s$' % (_('questions/'), _('edit/')), - app.writers.edit_question, + views.writers.edit_question, name='edit_question' ), url( r'^%s(?P\d+)/%s$' % (_('questions/'), _('retag/')), - app.writers.retag_question, + views.writers.retag_question, name='retag_question' ), url( r'^%s(?P\d+)/%s$' % (_('questions/'), _('close/')), - app.commands.close, + views.commands.close, name='close' ), url( r'^%s(?P\d+)/%s$' % (_('questions/'), _('reopen/')), - app.commands.reopen, + views.commands.reopen, name='reopen' ), url( r'^%s(?P\d+)/%s$' % (_('questions/'), _('answer/')), - app.writers.answer, + views.writers.answer, name='answer' ), url( r'^%s(?P\d+)/%s$' % (_('questions/'), _('vote/')), - app.commands.vote, + views.commands.vote, name='vote' ), url( r'^%s(?P\d+)/%s$' % (_('questions/'), _('revisions/')), - app.readers.revisions, + views.readers.revisions, kwargs = {'object_name': 'Question'}, name='question_revisions' ), url( r'^%s(?P\d+)/%s$' % (_('questions/'), _('comments/')), - app.writers.question_comments, + views.writers.question_comments, name='question_comments' ), url( r'^%s$' % _('command/'), - app.commands.ajax_command, + views.commands.ajax_command, name='call_ajax' ), url( r'^%s(?P\d+)/%s(?P\d+)/%s$'\ % (_('questions/'), _('comments/'),_('delete/')), - app.writers.delete_comment, + views.writers.delete_comment, kwargs={'commented_object_type':'question'}, name='delete_question_comment' ), url( r'^%s(?P\d+)/%s(?P\d+)/%s$'\ % (_('answers/'), _('comments/'),_('delete/')), - app.writers.delete_comment, + views.writers.delete_comment, kwargs={'commented_object_type':'answer'}, name='delete_answer_comment' ), #place general question item in the end of other operations url( r'^%s(?P\d+)/' % _('question/'), - app.readers.question, + views.readers.question, name='question' ), url( r'^%s$' % _('tags/'), - app.readers.tags, + views.readers.tags, name='tags' ), url( r'^%s%s(?P[^/]+)/$' % (_('mark-tag/'),_('interesting/')), - app.commands.mark_tag, + views.commands.mark_tag, kwargs={'reason':'good','action':'add'}, name='mark_interesting_tag' ), url( r'^%s%s(?P[^/]+)/$' % (_('mark-tag/'),_('ignored/')), - app.commands.mark_tag, + views.commands.mark_tag, kwargs={'reason':'bad','action':'add'}, name='mark_ignored_tag' ), url( r'^%s(?P[^/]+)/$' % _('unmark-tag/'), - app.commands.mark_tag, + views.commands.mark_tag, kwargs={'action':'remove'}, name='mark_ignored_tag' ), url( r'^%s$' % _('users/'), - app.users.users, + views.users.users, name='users' ), #todo: rename as user_edit, b/c that's how template is named url( r'^%s(?P\d+)/%s$' % (_('users/'), _('edit/')), - app.users.edit_user, + views.users.edit_user, name='edit_user' ), url( r'^%s(?P\d+)/(?P.+)/$' % _('users/'), - app.users.user, + views.users.user, name='user_profile' ), url( r'^%s$' % _('badges/'), - app.meta.badges, + views.meta.badges, name='badges' ), url( r'^%s(?P\d+)//*' % _('badges/'), - app.meta.badge, + views.meta.badge, name='badge' ), url( r'^%s%s$' % (_('messages/'), _('markread/')), - app.commands.read_message, + views.commands.read_message, name='read_message' ), url( r'^manage_inbox/$', - app.commands.manage_inbox, + views.commands.manage_inbox, name='manage_inbox' ), url( @@ -201,9 +200,9 @@ urlpatterns = patterns('', {'feed_dict': feeds}, name='feeds' ), - url( r'^%s$' % _('upload/'), app.writers.upload, name='upload'), - url(r'^%s$' % _('search/'), app.readers.search, name='search'), - url(r'^%s$' % _('feedback/'), app.meta.feedback, name='feedback'), + url( r'^%s$' % _('upload/'), views.writers.upload, name='upload'), + url(r'^%s$' % _('search/'), views.readers.search, name='search'), + url(r'^%s$' % _('feedback/'), views.meta.feedback, name='feedback'), (r'^%s' % _('account/'), include('askbot.deps.django_authopenid.urls')), (r'^i18n/', include('django.conf.urls.i18n')), #url(r'^feeds/rss/$', RssLastestQuestionsFeed, name="latest_questions_feed"), diff --git a/askbot/views/meta.py b/askbot/views/meta.py index f4793ba7..d7ce7f4e 100644 --- a/askbot/views/meta.py +++ b/askbot/views/meta.py @@ -7,12 +7,14 @@ from django.shortcuts import render_to_response, get_object_or_404 from django.core.urlresolvers import reverse from django.template import RequestContext from django.http import HttpResponseRedirect, HttpResponse -from askbot.forms import FeedbackForm from django.core.urlresolvers import reverse from django.utils.translation import ugettext as _ +from django.views import static +from askbot.forms import FeedbackForm from askbot.utils.forms import get_next_url from askbot.models import Badge, Award from askbot.skins.loaders import ENV +from askbot import skins import askbot def generic_view(request, template = None): @@ -128,3 +130,13 @@ def badge(request, id): context = RequestContext(request, data) return HttpResponse(template.render(context)) +def media(request, skin, resource): + """view that serves static media from any skin + uses django static serve view, where document root is + adjusted according to the current skin selection + + in production this views should be by-passed via server configuration + for the better efficiency of serving static files + """ + dir = skins.utils.get_path_to_skin(skin) + return static.serve(request, '/media/' + resource, document_root = dir) -- cgit v1.2.3-1-g7c22