~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to utilities/list-pages

  • Committer: Launchpad Patch Queue Manager
  • Date: 2011-03-10 15:09:55 UTC
  • mfrom: (12548.2.10 what-is-in-the-web-ui)
  • Revision ID: launchpad@pqm.canonical.com-20110310150955-iruq05tqeedoa5bs
[r=allenap][ui=none][no-qa] Add a list-pages script that lists all
 the pages in Launchpad

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python -S
 
2
#
 
3
# Copyright 2011 Canonical Ltd.  This software is licensed under the
 
4
# GNU Affero General Public License version 3 (see the file LICENSE).
 
5
 
 
6
"""list-pages -- list the pages that are in Launchpad
 
7
 
 
8
Prints out a CSV of all of the pages that exist in Launchpad. A "page" is a
 
9
whole, actual, web page. Something you might actually want to see filling a
 
10
browser window. Page fragments, portlets etc. are excluded.
 
11
 
 
12
The output contains the page ID, content interface, layer, template and
 
13
example URL.  Here 'layer' is a clue as to the subdomain.
 
14
 
 
15
The example URLs will look like::
 
16
 
 
17
  https://launchpad.dev/~<ITeam.name>/+addmember
 
18
 
 
19
Which means all segments are present, and you can get a real URL by
 
20
substituting data (in this case, a team name),
 
21
 
 
22
or::
 
23
 
 
24
  https://launchpad.dev/[[ITranslator]]/+admin
 
25
 
 
26
Which means we couldn't actually figure out the proper URL. [[ITranslator]]
 
27
might actually be made of multiple URL segments,
 
28
 
 
29
or::
 
30
 
 
31
  <IPOFile>/+export
 
32
 
 
33
Which means we have no clue how to figure out the URL.  This is generally
 
34
because our cheat objects don't match the app-encoded business logic.
 
35
 
 
36
"""
 
37
 
 
38
# The implementation approach here is fairly simple:
 
39
# - load the ZCML
 
40
# - go through all of the adapters, find the ones that look like they might
 
41
#   be for pages.
 
42
# - bang the adapter nut against the hardest rock we can find until it cracks
 
43
# - print the nourishing nutty goodness to stdout
 
44
 
 
45
import _pythonpath
 
46
_pythonpath
 
47
 
 
48
from inspect import getmro
 
49
import os
 
50
 
 
51
from zope.app.pagetemplate.simpleviewclass import simple
 
52
from zope.app.testing.functional import FunctionalTestSetup
 
53
from zope.component import adapts, getGlobalSiteManager
 
54
from zope.interface import directlyProvides, implements
 
55
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
 
56
 
 
57
from canonical.config import config
 
58
from canonical.launchpad import pagetitles
 
59
from canonical.launchpad.scripts import execute_zcml_for_scripts
 
60
from canonical.launchpad.webapp.interfaces import ICanonicalUrlData
 
61
from canonical.launchpad.webapp.publisher import canonical_url
 
62
 
 
63
 
 
64
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
 
65
 
 
66
 
 
67
def load_zcml(zopeless=False):
 
68
    """Load ZCML the way we do in the web site."""
 
69
    # We can't load ZCML without generating configuration.
 
70
    config.generate_overrides()
 
71
    if zopeless:
 
72
        execute_zcml_for_scripts()
 
73
    else:
 
74
        FunctionalTestSetup(os.path.join(ROOT, 'site.zcml')).setUp()
 
75
 
 
76
 
 
77
def is_page_adapter(adapter):
 
78
    """Is 'adapter' a page adapter?
 
79
 
 
80
    We figure this out by checking to see whether it is adapting from
 
81
    IDefaultBrowserLayer or one of its subclasses.
 
82
    """
 
83
    for interface in adapter.required:
 
84
        if issubclass(interface, IDefaultBrowserLayer):
 
85
            return True
 
86
    return False
 
87
 
 
88
 
 
89
def get_view(adapter):
 
90
    """Get the view factory associated with 'adapter'."""
 
91
    return adapter.factory
 
92
 
 
93
 
 
94
def get_template_filename(view):
 
95
    """Return the template filename associated with 'view'."""
 
96
    try:
 
97
        filename = view.index.filename
 
98
    except AttributeError:
 
99
        return None
 
100
    return os.path.abspath(filename)
 
101
 
 
102
 
 
103
def has_page_title(adapter):
 
104
    """Does 'adapter' have a page title?
 
105
 
 
106
    We use this to tell if 'adapter' is indeed an adapter for rendering an
 
107
    entire page. The theory goes that if there's a page title associated with
 
108
    something, then it's a page.
 
109
    """
 
110
    view = get_view(adapter)
 
111
    marker = object()
 
112
    template = get_template_filename(view)
 
113
    if template is None:
 
114
        return False
 
115
    name = os.path.splitext(os.path.basename(template))[0].replace('-', '_')
 
116
    if (getattr(view, 'page_title', marker) is marker and
 
117
        getattr(pagetitles, name, marker) is marker):
 
118
        return False
 
119
    return has_html_element(template)
 
120
 
 
121
 
 
122
def has_html_element(template):
 
123
    return '</html>' in open(template).read()
 
124
 
 
125
 
 
126
def iter_page_adapters():
 
127
    """Iterate over adapters for browser:page directives."""
 
128
    gsm = getGlobalSiteManager()
 
129
    return (a for a in gsm.registeredAdapters()
 
130
            if is_page_adapter(a) and has_page_title(a))
 
131
 
 
132
 
 
133
def iter_url_adapters():
 
134
    gsm = getGlobalSiteManager()
 
135
    return (a for a in gsm.registeredAdapters()
 
136
            if issubclass(a.provided, ICanonicalUrlData))
 
137
 
 
138
 
 
139
def format_page_adapter(a):
 
140
    """Format the adapter 'a' for end-user display.
 
141
 
 
142
    :return: A string of the form, "pageid,content,layer,template".
 
143
    """
 
144
    factory = a.factory
 
145
    try:
 
146
        bases = getmro(factory)
 
147
    except AttributeError:
 
148
        return a.name
 
149
    # Zope, bless it, does all sorts of type shenanigans to obscure the name
 
150
    # of the factory as it appears in our code. (See
 
151
    # z.browserpage.metaconfigure.page and
 
152
    # z.app.pagetemplate.simpleviewclass). Ideally, we'd just construct the
 
153
    # view, but that's hard, since in general they won't construct without an
 
154
    # object that implements the interface they need.
 
155
    bases = [b for b in bases
 
156
             if b is not simple
 
157
             and 'SimpleViewClass' not in b.__name__]
 
158
    template = get_template_filename(get_view(a))
 
159
    if template:
 
160
        template = os.path.relpath(template, ROOT)
 
161
    url = '%s/%s' % (get_example_canonical_url(a.required[0]), a.name)
 
162
    return '%s:%s,%s,%s,%s,%s' % (
 
163
        bases[0].__name__,
 
164
        a.name,
 
165
        a.required[0].__name__,
 
166
        a.required[1].__name__,
 
167
        template,
 
168
        url,
 
169
        )
 
170
 
 
171
 
 
172
_BLACKLIST = [
 
173
    '__conform__',
 
174
    ]
 
175
class Whatever(object):
 
176
 
 
177
    def __init__(self, name, interface=None):
 
178
        self._name = name
 
179
        self._interface = interface
 
180
        if interface is not None:
 
181
            directlyProvides(self, interface)
 
182
 
 
183
    def __str__(self):
 
184
        return '<%s>' % (self._name,)
 
185
 
 
186
    def __repr__(self):
 
187
        return '<%s>' % str(self)
 
188
 
 
189
    def __int__(self):
 
190
        return 1
 
191
 
 
192
    def __call__(self, *args, **kwargs):
 
193
        args = map(repr, args)
 
194
        args.extend('%s=%r' % (k, v) for k, v in kwargs.iteritems())
 
195
        # If we're being called with no args, assume this is part of crazy
 
196
        # TALES stuff:
 
197
        #   lib/canonical/launchpad/webapp/metazcml.py(365)path()
 
198
        #   -> return self._compiled_path_expression(self._expression_context)
 
199
        if args:
 
200
            name = '%s(%s)' % (self._name, ', '.join(args))
 
201
            return Whatever(name)
 
202
        else:
 
203
            return str(self)
 
204
 
 
205
    def __getattr__(self, name):
 
206
        if name in _BLACKLIST:
 
207
            raise AttributeError
 
208
        if self._interface:
 
209
            interface = getattr(self._interface[name], 'schema', None)
 
210
        else:
 
211
            interface = None
 
212
        child = Whatever('%s.%s' % (self._name, name), interface)
 
213
        if 'name' in name:
 
214
            # Then it's probably displayname, or something.
 
215
            return str(child)
 
216
        else:
 
217
            return child
 
218
 
 
219
 
 
220
class DefaultCanonicalUrlData(object):
 
221
    implements(ICanonicalUrlData)
 
222
    adapts(object)
 
223
 
 
224
    def __init__(self, name):
 
225
        self.path = '[[%s]]' % (name,)
 
226
        self.rootsite = None
 
227
        self.inside = None
 
228
 
 
229
 
 
230
def make_fake_object(interface):
 
231
    return Whatever(interface.__name__, interface)
 
232
 
 
233
 
 
234
def get_example_canonical_url(interface):
 
235
    fake_obj = make_fake_object(interface)
 
236
    try:
 
237
        return canonical_url(fake_obj)
 
238
    except (KeyError, TypeError, AssertionError, AttributeError):
 
239
        # If we get any of the errors that we know about, just return the
 
240
        # object. In general, these errors come from app-specific logic that
 
241
        # we can't cheat our way around.
 
242
        return str(fake_obj)
 
243
 
 
244
 
 
245
def main():
 
246
    load_zcml()
 
247
    gsm = getGlobalSiteManager()
 
248
    gsm.registerAdapter(DefaultCanonicalUrlData)
 
249
    for adapter in iter_page_adapters():
 
250
        print format_page_adapter(adapter)
 
251
 
 
252
 
 
253
if __name__ == '__main__':
 
254
    main()