[CalendarServer-changes] [8074] CalendarServer/branches/users/glyph/other-html/twistedcaldav/ extensions.py

source_changes at macosforge.org source_changes at macosforge.org
Tue Sep 13 12:09:55 PDT 2011


Revision: 8074
          http://trac.macosforge.org/projects/calendarserver/changeset/8074
Author:   glyph at apple.com
Date:     2011-09-13 12:09:55 -0700 (Tue, 13 Sep 2011)
Log Message:
-----------
Render directory listing via a template.

Modified Paths:
--------------
    CalendarServer/branches/users/glyph/other-html/twistedcaldav/extensions.py

Modified: CalendarServer/branches/users/glyph/other-html/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/branches/users/glyph/other-html/twistedcaldav/extensions.py	2011-09-13 19:09:48 UTC (rev 8073)
+++ CalendarServer/branches/users/glyph/other-html/twistedcaldav/extensions.py	2011-09-13 19:09:55 UTC (rev 8074)
@@ -32,11 +32,15 @@
 import urllib
 import cgi
 import time
+from itertools import cycle
 
-from twisted.internet.defer import succeed, DeferredList, maybeDeferred
+from twisted.internet.defer import succeed, maybeDeferred
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.cred.error import LoginFailed, UnauthorizedLogin
 
+from twisted.web.template import Element, XMLFile, renderer, tags, flattenString
+from twisted.python.modules import getModule
+
 from twext.web2 import responsecode, server
 from twext.web2.auth.wrapper import UnauthorizedResponse
 from twext.web2.http import HTTPError, Response, RedirectResponse
@@ -51,7 +55,10 @@
 from twext.web2.dav.idav import IDAVPrincipalResource
 from twext.web2.dav.static import DAVFile as SuperDAVFile
 from twext.web2.dav.resource import DAVResource as SuperDAVResource
-from twext.web2.dav.resource import DAVPrincipalResource as SuperDAVPrincipalResource
+from twext.web2.dav.resource import (
+    DAVPrincipalResource as SuperDAVPrincipalResource
+)
+from twisted.internet.defer import gatherResults
 from twext.web2.dav.method import prop_common
 from twext.web2.dav.method.report import max_number_of_matches
 
@@ -59,11 +66,14 @@
 
 from twistedcaldav import customxml
 from twistedcaldav.customxml import calendarserver_namespace
-from twistedcaldav.util import Alternator
+
 from twistedcaldav.directory.sudo import SudoDirectoryService
 from twistedcaldav.directory.directory import DirectoryService
 from twistedcaldav.method.report import http_REPORT
 
+
+thisModule = getModule(__name__)
+
 log = Logger()
 
 
@@ -429,197 +439,146 @@
 
         if resultsWereLimited is not None:
             if resultsWereLimited[0] == "server":
-                log.err("Too many matching resources in principal-property-search report")
+                log.err("Too many matching resources in "
+                        "principal-property-search report")
             responses.append(davxml.StatusResponse(
                 davxml.HRef.fromString(request.uri),
-                davxml.Status.fromResponseCode(responsecode.INSUFFICIENT_STORAGE_SPACE),
+                davxml.Status.fromResponseCode(
+                    responsecode.INSUFFICIENT_STORAGE_SPACE
+                ),
                 davxml.Error(davxml.NumberOfMatchesWithinLimits()),
-                davxml.ResponseDescription("Results limited by %s at %d" % resultsWereLimited),
+                davxml.ResponseDescription("Results limited by %s at %d"
+                                           % resultsWereLimited),
             ))
         returnValue(MultiStatusResponse(responses))
 
 
-class DirectoryRenderingMixIn(object):
 
-    def directoryStyleSheet(self):
-        return (
-            "th, .even td, .odd td { padding-right: 0.5em; font-family: monospace}"
-            ".even-dir { background-color: #efe0ef }"
-            ".even { background-color: #eee }"
-            ".odd-dir {background-color: #f0d0ef }"
-            ".odd { background-color: #dedede }"
-            ".icon { text-align: center }"
-            ".listing {"
-              "margin-left: auto;"
-              "margin-right: auto;"
-              "width: 50%;"
-              "padding: 0.1em;"
-            "}"
-            "body { border: 0; padding: 0; margin: 0; background-color: #efefef;}"
-            "h1 {padding: 0.1em; background-color: #777; color: white; border-bottom: thin white dashed;}"
-        )
+class DirectoryElement(Element):
+    """
+    A L{DirectoryElement} is an L{Element} for rendering the contents of a
+    L{DirectoryRenderingMixIn} resource as HTML.
+    """
 
-    def renderDirectory(self, request):
+    loader = XMLFile(
+        thisModule.filePath.sibling("directory-listing.html").open()
+    )
+
+    def __init__(self, resource):
         """
-        Render a directory listing.
+        @param resource: the L{DirectoryRenderingMixIn} resource being
+            listed.
         """
-        output = [
-            """<html>"""
-            """<head>"""
-            """<title>Collection listing for %(path)s</title>"""
-            """<style>%(style)s</style>"""
-            """</head>"""
-            """<body>"""
-            % {
-                "path": "%s" % cgi.escape(urllib.unquote(request.path)),
-                "style": self.directoryStyleSheet(),
-            }
-        ]
+        super(DirectoryElement, self).__init__()
+        self.resource = resource
 
-        def gotBody(body, output=output):
-            output.append(body)
-            output.append("</body></html>")
 
-            output = "".join(output)
+    @renderer
+    def children(self, request, tag):
+        """
+        Renderer which yields all child object tags as table rows.
+        """
+        whenChildren = (
+            maybeDeferred(self.resource.listChildren)
+            .addCallback(sorted)
+            .addCallback(
+                lambda names: gatherResults(
+                    [maybeDeferred(self.resource.getChild, x) for x in names]
+                )
+                .addCallback(lambda children: zip(children, names))
+            )
+        )
+        @whenChildren.addCallback
+        def gotChildren(children):
+            for even, [child, name] in zip(cycle(["even", "odd"]), children):
+                [url, name, size, lastModified, contentType] = (
+                    self.resource.getChildDirectoryEntry(child, name, request)
+                )
+                yield tag.clone().fillSlots(
+                    url=url, name=name, size=str(size), lastModified=lastModified,
+                    even=even, type=contentType,
+                )
+        return whenChildren
 
-            if isinstance(output, unicode):
-                output = output.encode("utf-8")
 
-            mime_params = {"charset": "utf-8"}
-
-            response = Response(200, {}, output)
-            response.headers.setHeader("content-type", MimeType("text", "html", mime_params))
-            return response
-
-        d = self.renderDirectoryBody(request)
-        d.addCallback(gotBody)
-        return d
-
-    @inlineCallbacks
-    def renderDirectoryBody(self, request):
+    @renderer
+    def main(self, request, tag):
         """
-        Generate a directory listing table in HTML.
+        Main renderer; fills slots for title, etc.
         """
-        output = [
-            """<div class="directory-listing">"""
-            """<h1>Collection Listing</h1>"""
-            """<table>"""
-            """<tr><th>Name</th> <th>Size</th> <th>Last Modified</th> <th>MIME Type</th></tr>"""
-        ]
+        return tag.fillSlots(name=request.path)
 
-        even = Alternator()
-        for name in sorted((yield self.listChildren())):
-            child = (yield maybeDeferred(self.getChild, name))
 
-            url, name, size, lastModified, contentType = self.getChildDirectoryEntry(child, name, request)
-
-            # FIXME: gray out resources that are not readable
-            output.append(
-                """<tr class="%(even)s">"""
-                """<td><a href="%(url)s">%(name)s</a></td>"""
-                """<td align="right">%(size)s</td>"""
-                """<td>%(lastModified)s</td>"""
-                """<td>%(type)s</td>"""
-                """</tr>"""
-                % {
-                    "even": even.state() and "even" or "odd",
-                    "url": url,
-                    "name": cgi.escape(name),
-                    "size": size,
-                    "lastModified": lastModified,
-                    "type": contentType,
-                }
-            )
-
-        output.append(
-            """</table></div>"""
-            """<div class="directory-listing">"""
-            """<h1>Properties</h1>"""
-            """<table>"""
-            """<tr><th>Name</th> <th>Value</th></tr>"""
-        )
-
+    @renderer
+    def properties(self, request, tag):
+        """
+        Renderer which yields all properties as table row tags.
+        """
+        whenPropertiesListed = self.resource.listProperties(request)
+        @whenPropertiesListed.addCallback
         def gotProperties(qnames):
-            ds = []
-
             noneValue         = object()
             accessDeniedValue = object()
 
-            def gotProperty(property):
-                if property is None:
-                    name = "{%s}%s" % qname
-                    value = noneValue
-                else:
-                    name = property.sname()
-                    value = property.toxml()
-
-                return (name, value)
-
-            def gotError(f, qname):
+            def gotError(f, name):
                 f.trap(HTTPError)
-
-                name = "{%s}%s" % qname
                 code = f.value.response.code
-
                 if code == responsecode.NOT_FOUND:
-                    log.err("Property {%s}%s was returned by listProperties() but does not exist for resource %s."
-                            % (qname[0], qname[1], self))
+                    log.err("Property %s was returned by listProperties() "
+                            "but does not exist for resource %s."
+                            % (name, self.resource))
                     return (name, None)
-
                 if code == responsecode.UNAUTHORIZED:
                     return (name, accessDeniedValue)
-
                 return f
 
-            for qname in sorted(qnames):
-                d = self.readProperty(qname, request)
-                d.addCallback(gotProperty)
-                d.addErrback(gotError, qname)
-                ds.append(d)
+            whenAllProperties = gatherResults([
+                maybeDeferred(self.resource.readProperty, qn, request)
+                .addCallback(lambda p: (p.sname(), p.toxml()))
+                .addErrback(gotError, "{%s}%s" % qn)
+                for qn in sorted(qnames)
+            ])
 
-            even = Alternator()
-
+            @whenAllProperties.addCallback
             def gotValues(items):
-                for result, (name, value) in items:
-                    if not result:
-                        continue
-
+                for even, [name, value] in zip(cycle(["even", "odd"]), items):
                     if value is None:
                         # An AssertionError might be appropriate, but
                         # we may as well continue rendering.
-                        log.err("Unexpected None value for property: %s" % (name,))
+                        log.err("Unexpected None value for property: %s" %
+                                (name,))
                         continue
                     elif value is noneValue:
-                        value = "<i>(no value)</i>"
+                        value = tags.i("(no value)")
                     elif value is accessDeniedValue:
-                        value = "<i>(access forbidden)</i>"
+                        value = tags.i("(access forbidden)")
                     else:
                         value = cgi.escape(value)
-
-                    output.append(
-                        str("""<tr class="%(even)s">"""
-                            """<td valign="top">%(name)s</td>"""
-                            """<td><pre>%(value)s</pre></td>"""
-                            """</tr>"""
-                            % {
-                                "even": even.state() and "even" or "odd",
-                                "name": name,
-                                "value": value,
-                            }
-                        )
+                    yield tag.clone().fillSlots(
+                        even=even, name=name, value=value,
                     )
+            return whenAllProperties
+        return whenPropertiesListed
 
-                output.append("</div>")
-                return "".join(output)
 
-            d = DeferredList(ds)
-            d.addCallback(gotValues)
-            return d
 
-        qnames = (yield self.listProperties(request))
-        result = (yield gotProperties(qnames))
-        returnValue(result)
+class DirectoryRenderingMixIn(object):
 
+    def renderDirectory(self, request):
+        """
+        Render a directory listing.
+        """
+        def gotBody(output):
+            mime_params = {"charset": "utf-8"}
+            response = Response(200, {}, output)
+            response.headers.setHeader(
+                "content-type",
+                MimeType("text", "html", mime_params)
+            )
+            return response
+        return flattenString(request, DirectoryElement(self)).addCallback(gotBody)
+
+
     def getChildDirectoryEntry(self, child, name, request):
         def orNone(value, default="?", f=None):
             if value is None:
@@ -628,7 +587,7 @@
                 return f(value)
             else:
                 return value
-            
+
         url = urllib.quote(name, '/')
         if isinstance(child, DAVResource) and child.isCollection():
             url += "/"
@@ -666,7 +625,6 @@
                     rtypes.append(rtype.name)
                 if rtypes:
                     contentType = "(%s)" % (", ".join(rtypes),)
-                
 
         return ((
             url,
@@ -681,6 +639,7 @@
          ))
 
 
+
 def updateCacheTokenOnCallback(f):
     def wrapper(self, *args, **kwargs):
         if hasattr(self, "cacheNotifier"):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110913/96339d0b/attachment-0001.html>


More information about the calendarserver-changes mailing list