Commits

mitar committed 71e2344

Support for media files provided by Trac plugins.

  • Participants
  • Parent commits 9479e83

Comments (0)

Files changed (3)

cmsplugin_markup_tracwiki/components.py

+from trac.core import *
+from trac.web import IRequestHandler, HTTPNotFound
+
+class BaseHandler(Component):
+    """
+    A simple Trac request handler for use as a base handler. It simply always returns 404.
+    """
+
+    implements(IRequestHandler)
+
+    # IRequestHandler methods
+
+    def match_request(self, req):
+        return False
+
+    def process_request(self, req):
+        raise HTTPNotFound('No handler matched request to %s', req.path_info)

cmsplugin_markup_tracwiki/lazy.py

+class LazyObject(object):
+    """
+    A wrapper for another class that can be used to delay instantiation of the
+    wrapped class.
+    """
+    def __init__(self, *args, **kwargs):
+        self._wrapped = None
+        self._wrapped_args = args
+        self._wrapped_kwargs = kwargs
+
+    def __getattr__(self, name):
+        if self._wrapped is None:
+            self._setup()
+        return getattr(self._wrapped, name)
+
+    def __setattr__(self, name, value):
+        if name in ["_wrapped", "_wrapped_args", "_wrapped_kwargs"]:
+            # Assign to __dict__ to avoid infinite __setattr__ loops.
+            self.__dict__[name] = value
+        else:
+            if self._wrapped is None:
+                self._setup()
+            setattr(self._wrapped, name, value)
+
+    def __delattr__(self, name):
+        if name == ["_wrapped", "_wrapped_args", "_wrapped_kwargs"]:
+            raise TypeError("can't delete %s." % (name,))
+        if self._wrapped is None:
+            self._setup()
+        delattr(self._wrapped, name)
+
+    def _setup(self):
+        """
+        Must be implemented by subclasses to initialise the wrapped object.
+        """
+        raise NotImplementedError
+
+    # introspection support:
+    __members__ = property(lambda self: self.__dir__())
+
+    def __dir__(self):
+        if self._wrapped is None:
+            self._setup()
+        return dir(self._wrapped)
+
+    def __call__(self, *args, **kwargs):
+        if self._wrapped is None:
+            self._setup()
+        return self._wrapped(*args, **kwargs)

cmsplugin_markup_tracwiki/tracwiki.py

 import inspect
 import os
 import re
+import string
+import sys
 
 from StringIO import StringIO
 
 from django.conf import settings
 from django.contrib.sites import models as sites_models
 from django.core import urlresolvers
+from django.core.servers import basehttp
 from django.db.models import Q
+from django.utils import functional
 from django.utils import translation as django_translation
 from django.utils import safestring
 
 
 from cmsplugin_markup import plugins as markup_plugins
 
+from cmsplugin_markup_tracwiki import lazy
+
 OBJ_ADMIN_RE_PATTERN = ur'\[\[CMSPlugin\(\s*(\d+)\s*\)\]\]'
 OBJ_ADMIN_RE = re.compile(OBJ_ADMIN_RE_PATTERN)
 
     'trac.wiki.macros.KnownMimeTypesMacro',
     'trac.wiki.macros.PageOutlineMacro',
     'trac.wiki.intertrac',
+    'trac.web.chrome.Chrome',
+    'cmsplugin_markup_tracwiki.components.BaseHandler',
     'cmsplugin_markup_tracwiki.macros.CMSPluginMacro',
     'cmsplugin_markup_tracwiki.macros.URLMacro',
     'cmsplugin_markup_tracwiki.macros.NowMacro',
 
 TRACWIKI_HEADER_OFFSET = 1
 
+if not hasattr(urlresolvers, 'reverse_lazy'):
+    urlresolvers.reverse_lazy = functional.lazy(urlresolvers.reverse, str)
+
+class LazyHref(lazy.LazyObject):
+    def _setup(self):
+        self._wrapped = web.href.Href(*self._wrapped_args, **self._wrapped_kwargs)
+
+def tracwiki_base_path():
+    return urlresolvers.reverse_lazy('cmsplugin_markup_tracwiki', kwargs={'path': ''})
+
 class DjangoEnvironment(test.EnvironmentStub):
     """A Django environment for Trac."""
     
             else:
                 __import__(name=module_and_class[0], fromlist=[module_and_class[1]])
 
-        self.href = web.href.Href(urlresolvers.reverse('pages-root'))
+        self.href = LazyHref(tracwiki_base_path())
 
         self.config.set('trac', 'default_charset', 'utf-8')
         self.config.set('trac', 'never_obfuscate_mailto', True)
 
+        self.config.set('trac', 'default_handler', 'BaseHandler')
+
         # TODO: Use Django logging facilities?
         self.config.set('logging', 'log_level', 'WARN')
         self.config.set('logging', 'log_type', 'stderr')
         self.setup_log()
 
-        for (ns, conf) in getattr(settings, 'CMS_MARKUP_TRAC_INTERTRAC', {}).items():
+        for (ns, conf) in getattr(settings, 'CMS_MARKUP_TRAC_INTERTRAC', {}).iteritems():
             if 'URL' in conf:
                 if 'TITLE' in conf:
                     self.config.set('intertrac', '%s.title' % (ns,), conf['TITLE'])
 
         server_port = str(request.META.get('SERVER_PORT', '80'))
         if request.is_secure():
-            self.abs_href = web.href.Href('https://' + site.domain + (':' + server_port if server_port != '443' else '') + self.href())
+            self.abs_href = LazyHref('https://' + site.domain + (':' + server_port if server_port != '443' else '') + self.href())
         else:
-            self.abs_href = web.href.Href('http://' + site.domain + (':' + server_port if server_port != '80' else '') + self.href())
+            self.abs_href = LazyHref('http://' + site.domain + (':' + server_port if server_port != '80' else '') + self.href())
 
 class DjangoChrome(trac_chrome.Chrome):
     pass
 
+class DjangoRequestDispatcher(main.RequestDispatcher):
+    pass
+
 class DjangoRequest(web.Request):
     def __init__(self, env, request, context, placeholder):
         super(DjangoRequest, self).__init__(request.META, self._start_response)
 
+        # We override request's hrefs from environment (which are based on Django's URLs)
+        self.href = env.href
+        self.abs_href = env.abs_href
+
         self.django_request = request
         self.django_context = context
         self.django_placeholder = placeholder
+        self.django_response = None
         
         self.perm = main.FakePerm()
         self.session = main.FakeSession()
         # Django sets TZ environment variable
         return datefmt.localtz
 
-    def _write(self, data):
-        if not data:
-            return
+    def _start_response(self, status, headers, exc_info=None):
+        if exc_info:
+            try:
+                raise exc_info[0], exc_info[1], exc_info[2]
+            finally:
+                exc_info = None # Avoids dangling circular ref
 
-        # TODO: Use Django logging facilities?
-        sys.stderr.write(data)
-        sys.stderr.write("\n")
-    
-    def _start_response(self, status, headers, exc_info):
-        if exc_info:
-            raise exc_info[0], exc_info[1], exc_info[2]
+        headers = dict(headers)
 
-        # TODO: Use Django logging facilities?
-        sys.stderr.write("Trac rasponse data, %s:\n", status)
+        if 'Content-Type' in headers:
+            content_type = headers.pop('Content-Type')
+        else:
+            content_type = None
 
-        return self._write
+        self.django_response = http.HttpResponse(status=status, content_type=content_type)
+
+        for (k, v) in headers.iteritems():
+            self.django_response[k] = v
+
+        return self.django_response.write
 
 class DjangoInterWikiMap(interwiki.InterWikiMap):
     """
         Map from upper-cased namespaces to (namespace, prefix, title) values.
         """
         map = {}
-        for (ns, conf) in getattr(settings, 'CMS_MARKUP_TRAC_INTERWIKI', {}).items():
+        for (ns, conf) in getattr(settings, 'CMS_MARKUP_TRAC_INTERWIKI', {}).iteritems():
             if 'URL' in conf:
                 url = conf['URL'].strip()
                 title = conf.get('TITLE')
 
     def __init__(self, *args, **kwargs):
         self.env = DjangoEnvironment()
+        self.scripts = []
+        self.links = {}
 
     def parse(self, value, context=None, placeholder=None):
         request = _get_django_request(context=context)
         res = DjangoResource('cms', 'pages-root') # TODO: Get ID from request (and version?)
         ctx = mimeview.Context.from_request(req, res)
         out = StringIO()
+        self._early_scripts_and_links(req)
         DjangoFormatter(self.env, ctx).format(value, out)
+        self._all_scripts_and_links(req)
         return out.getvalue()
 
     def plugin_id_list(self, text):
         return OBJ_ADMIN_RE.findall(text)
 
+    def _early_scripts_and_links(self, req):
+        self._early_scripts_hrefs = [s['href'] for s in req.chrome.get('scripts', [])]
+        self._early_links_ids = ['%s:%s' % (r, l['href']) for (r, ls) in req.chrome.get('links', {}).iteritems() for l in ls]
+
+    def _all_scripts_and_links(self, req):
+        for script in req.chrome.get('scripts', []):
+            if script['href'] not in self._early_scripts_hrefs:
+                self.scripts.append({'href': script['href'], 'type': script.get('type', "text/javascript")})
+
+        for (rel, links) in req.chrome.get('links', {}).iteritems():
+            for l in links:
+                if '%s:%s' % (rel, l['href']) not in self._early_links_ids:
+                    self.links.setdefault(rel, []).append(l)
+
+    def get_scripts(self):
+        return self.scripts
+
+    def get_stylesheets(self):
+        return [{'href': s['href'], 'type': s.get('type', "text/css")} for s in self.links.get('stylesheet', [])]
+
+    def get_plugin_urls(self):
+        from django.conf.urls.defaults import patterns, url
+        
+        urls = super(Markup, self).get_plugin_urls()
+
+        trac_urls = patterns('',
+            url(r'^tracwiki/(?P<path>.*)$', self.serve_trac_path, name='cmsplugin_markup_tracwiki'),
+        )
+
+        return trac_urls + urls
+
+    def serve_trac_path(self, request, path):
+        self.env.set_abs_href(request)
+        context = template.RequestContext(request, {})
+        req = DjangoRequest(self.env, request, context, None)
+
+        base_path = req.href()
+        req.environ['PATH_INFO'] = req.environ.get('PATH_INFO', '')[len(base_path):]
+
+        # Have to encode Unicode back to UTF-8 for Trac
+        if isinstance(req.environ.get('PATH_INFO', ''), unicode):
+            req.environ['PATH_INFO'] = req.environ.get('PATH_INFO', '').encode('utf-8')
+
+        try:
+            dispatcher = DjangoRequestDispatcher(self.env)
+            dispatcher.dispatch(req)
+        except web.RequestDone:
+            pass
+        except web.HTTPNotFound, e:
+            raise http.Http404(e)
+        
+        if req._response:
+            req.django_response._container = req._response
+            req.django_response._is_string = False
+        
+        return req.django_response
+
     def replace_plugins(self, text, id_dict):
         def _replace_tag(m):
             plugin_id = int(m.groups()[0])