_element.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. # -*- test-case-name: twisted.web.test.test_template -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. from __future__ import division, absolute_import
  5. from zope.interface import implementer
  6. from twisted.web.iweb import IRenderable
  7. from twisted.web.error import MissingRenderMethod, UnexposedMethodError
  8. from twisted.web.error import MissingTemplateLoader
  9. class Expose(object):
  10. """
  11. Helper for exposing methods for various uses using a simple decorator-style
  12. callable.
  13. Instances of this class can be called with one or more functions as
  14. positional arguments. The names of these functions will be added to a list
  15. on the class object of which they are methods.
  16. @ivar attributeName: The attribute with which exposed methods will be
  17. tracked.
  18. """
  19. def __init__(self, doc=None):
  20. self.doc = doc
  21. def __call__(self, *funcObjs):
  22. """
  23. Add one or more functions to the set of exposed functions.
  24. This is a way to declare something about a class definition, similar to
  25. L{zope.interface.declarations.implementer}. Use it like this::
  26. magic = Expose('perform extra magic')
  27. class Foo(Bar):
  28. def twiddle(self, x, y):
  29. ...
  30. def frob(self, a, b):
  31. ...
  32. magic(twiddle, frob)
  33. Later you can query the object::
  34. aFoo = Foo()
  35. magic.get(aFoo, 'twiddle')(x=1, y=2)
  36. The call to C{get} will fail if the name it is given has not been
  37. exposed using C{magic}.
  38. @param funcObjs: One or more function objects which will be exposed to
  39. the client.
  40. @return: The first of C{funcObjs}.
  41. """
  42. if not funcObjs:
  43. raise TypeError("expose() takes at least 1 argument (0 given)")
  44. for fObj in funcObjs:
  45. fObj.exposedThrough = getattr(fObj, 'exposedThrough', [])
  46. fObj.exposedThrough.append(self)
  47. return funcObjs[0]
  48. _nodefault = object()
  49. def get(self, instance, methodName, default=_nodefault):
  50. """
  51. Retrieve an exposed method with the given name from the given instance.
  52. @raise UnexposedMethodError: Raised if C{default} is not specified and
  53. there is no exposed method with the given name.
  54. @return: A callable object for the named method assigned to the given
  55. instance.
  56. """
  57. method = getattr(instance, methodName, None)
  58. exposedThrough = getattr(method, 'exposedThrough', [])
  59. if self not in exposedThrough:
  60. if default is self._nodefault:
  61. raise UnexposedMethodError(self, methodName)
  62. return default
  63. return method
  64. @classmethod
  65. def _withDocumentation(cls, thunk):
  66. """
  67. Slight hack to make users of this class appear to have a docstring to
  68. documentation generators, by defining them with a decorator. (This hack
  69. should be removed when epydoc can be convinced to use some other method
  70. for documenting.)
  71. """
  72. return cls(thunk.__doc__)
  73. # Avoid exposing the ugly, private classmethod name in the docs. Luckily this
  74. # namespace is private already so this doesn't leak further.
  75. exposer = Expose._withDocumentation
  76. @exposer
  77. def renderer():
  78. """
  79. Decorate with L{renderer} to use methods as template render directives.
  80. For example::
  81. class Foo(Element):
  82. @renderer
  83. def twiddle(self, request, tag):
  84. return tag('Hello, world.')
  85. <div xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">
  86. <span t:render="twiddle" />
  87. </div>
  88. Will result in this final output::
  89. <div>
  90. <span>Hello, world.</span>
  91. </div>
  92. """
  93. @implementer(IRenderable)
  94. class Element(object):
  95. """
  96. Base for classes which can render part of a page.
  97. An Element is a renderer that can be embedded in a stan document and can
  98. hook its template (from the loader) up to render methods.
  99. An Element might be used to encapsulate the rendering of a complex piece of
  100. data which is to be displayed in multiple different contexts. The Element
  101. allows the rendering logic to be easily re-used in different ways.
  102. Element returns render methods which are registered using
  103. L{twisted.web._element.renderer}. For example::
  104. class Menu(Element):
  105. @renderer
  106. def items(self, request, tag):
  107. ....
  108. Render methods are invoked with two arguments: first, the
  109. L{twisted.web.http.Request} being served and second, the tag object which
  110. "invoked" the render method.
  111. @type loader: L{ITemplateLoader} provider
  112. @ivar loader: The factory which will be used to load documents to
  113. return from C{render}.
  114. """
  115. loader = None
  116. def __init__(self, loader=None):
  117. if loader is not None:
  118. self.loader = loader
  119. def lookupRenderMethod(self, name):
  120. """
  121. Look up and return the named render method.
  122. """
  123. method = renderer.get(self, name, None)
  124. if method is None:
  125. raise MissingRenderMethod(self, name)
  126. return method
  127. def render(self, request):
  128. """
  129. Implement L{IRenderable} to allow one L{Element} to be embedded in
  130. another's template or rendering output.
  131. (This will simply load the template from the C{loader}; when used in a
  132. template, the flattening engine will keep track of this object
  133. separately as the object to lookup renderers on and call
  134. L{Element.renderer} to look them up. The resulting object from this
  135. method is not directly associated with this L{Element}.)
  136. """
  137. loader = self.loader
  138. if loader is None:
  139. raise MissingTemplateLoader(self)
  140. return loader.load()