11134.7.20
by Curtis Hovey
Hushed lint. |
1 |
Hierarchical menus |
2 |
================== |
|
6767.5.2
by Maris Fogels
Roughed-in a hierarchial menus doctest. |
3 |
|
6767.5.34
by Maris Fogels
Rework based on reviewer feedback. |
4 |
The location bar aids users in navigating the depths of Launchpad. It |
5 |
is built from a list of Breadcrumb objects collected during Zope's |
|
6 |
object-traversal step. |
|
6767.5.2
by Maris Fogels
Roughed-in a hierarchial menus doctest. |
7 |
|
11134.7.20
by Curtis Hovey
Hushed lint. |
8 |
A simple object hierarchy |
9 |
------------------------- |
|
6767.5.13
by Maris Fogels
Wrote out the specification for the BreadcrumbBuilder class. |
10 |
|
11 |
First, we need a hierarchy of objects to build upon: |
|
6767.5.4
by Maris Fogels
Reorganized the new test structure a bit. |
12 |
|
11134.7.20
by Curtis Hovey
Hushed lint. |
13 |
>>> from zope.component import getMultiAdapter, provideAdapter |
6767.5.2
by Maris Fogels
Roughed-in a hierarchial menus doctest. |
14 |
>>> from zope.interface import Interface, implements |
15 |
||
16 |
>>> class ICookbook(Interface): |
|
17 |
... """A cookbook for holding recipes.""" |
|
18 |
||
19 |
>>> class IRecipe(Interface): |
|
20 |
... """A recipe in a cookbook.""" |
|
21 |
||
8003.1.1
by Celso Providelo
Ignoring path segments starting with '+' in the Hierarchical navigation model. |
22 |
>>> class ICooker(Interface): |
23 |
... """A cooker.""" |
|
24 |
||
14600.2.2
by Curtis Hovey
Moved webapp to lp.services. |
25 |
>>> from lp.services.webapp.interfaces import ICanonicalUrlData |
26 |
>>> from lp.services.webapp.url import urlappend |
|
6767.5.2
by Maris Fogels
Roughed-in a hierarchial menus doctest. |
27 |
|
28 |
>>> class BaseContent: |
|
29 |
... implements(ICanonicalUrlData) |
|
30 |
... |
|
8003.1.1
by Celso Providelo
Ignoring path segments starting with '+' in the Hierarchical navigation model. |
31 |
... def __init__(self, name, parent, path_prefix=None): |
6767.5.2
by Maris Fogels
Roughed-in a hierarchial menus doctest. |
32 |
... self.name = name |
8003.1.1
by Celso Providelo
Ignoring path segments starting with '+' in the Hierarchical navigation model. |
33 |
... if path_prefix is not None: |
8003.1.4
by Celso Providelo
applying review comments, r=intellectronica. |
34 |
... self.path = urlappend(path_prefix, name) |
8003.1.1
by Celso Providelo
Ignoring path segments starting with '+' in the Hierarchical navigation model. |
35 |
... else: |
36 |
... self.path = name |
|
6767.5.2
by Maris Fogels
Roughed-in a hierarchial menus doctest. |
37 |
... self.inside = parent |
38 |
... self.rootsite = None |
|
39 |
||
40 |
>>> class Root(BaseContent): |
|
41 |
... """ The site root.""" |
|
42 |
||
43 |
>>> class Cookbook(BaseContent): |
|
44 |
... implements(ICookbook) |
|
45 |
||
46 |
>>> class Recipe(BaseContent): |
|
47 |
... implements(IRecipe) |
|
48 |
||
8003.1.1
by Celso Providelo
Ignoring path segments starting with '+' in the Hierarchical navigation model. |
49 |
>>> class Cooker(BaseContent): |
50 |
... implements(ICooker) |
|
51 |
||
6767.5.13
by Maris Fogels
Wrote out the specification for the BreadcrumbBuilder class. |
52 |
Today we'll be cooking with Spam! |
6767.5.2
by Maris Fogels
Roughed-in a hierarchial menus doctest. |
53 |
|
54 |
>>> root = Root('', None) |
|
8003.1.1
by Celso Providelo
Ignoring path segments starting with '+' in the Hierarchical navigation model. |
55 |
>>> cooker = Cooker('jamie', root, '+cooker') |
6767.5.2
by Maris Fogels
Roughed-in a hierarchial menus doctest. |
56 |
>>> cookbook = Cookbook('joy-of-cooking', root) |
57 |
>>> recipe = Recipe('spam', cookbook) |
|
6767.5.4
by Maris Fogels
Reorganized the new test structure a bit. |
58 |
|
6767.5.13
by Maris Fogels
Wrote out the specification for the BreadcrumbBuilder class. |
59 |
|
11134.7.20
by Curtis Hovey
Hushed lint. |
60 |
Discovering breadcrumbs |
61 |
----------------------- |
|
6767.5.13
by Maris Fogels
Wrote out the specification for the BreadcrumbBuilder class. |
62 |
|
63 |
The Hierarchy class builds the breadcrumbs by looking at each object in |
|
64 |
the request.traversed_objects attribute. If a traversed object can be |
|
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
65 |
adapted to IBreadcrumb, then it is added to the breadcrumbs list. |
6767.5.13
by Maris Fogels
Wrote out the specification for the BreadcrumbBuilder class. |
66 |
|
6767.5.27
by Maris Fogels
Lint and comments. |
67 |
We'll add the objects to the request's list of traversed objects so |
6767.5.13
by Maris Fogels
Wrote out the specification for the BreadcrumbBuilder class. |
68 |
the hierarchy will discover them. |
69 |
||
14606.4.9
by William Grant
Merge canonical.lazr's menu testing stuff to lp.testing.menu. |
70 |
>>> from lp.testing.menu import make_fake_request |
6767.5.5
by Maris Fogels
Updated the doctest with a sensible final form. |
71 |
>>> request = make_fake_request( |
72 |
... 'http://launchpad.dev/joy-of-cooking/spam', |
|
73 |
... [root, cookbook, recipe]) |
|
6767.5.2
by Maris Fogels
Roughed-in a hierarchial menus doctest. |
74 |
|
6767.5.39
by Maris Fogels
Typo. |
75 |
The hierarchy's list of breadcrumbs is empty since none of the objects |
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
76 |
have an IBreadcrumb adapter. |
6767.5.2
by Maris Fogels
Roughed-in a hierarchial menus doctest. |
77 |
|
6767.5.4
by Maris Fogels
Reorganized the new test structure a bit. |
78 |
>>> hierarchy = getMultiAdapter((recipe, request), name='+hierarchy') |
9225.1.3
by Michael Nelson
New styled breadcrumbs. |
79 |
>>> hierarchy.items |
6767.5.2
by Maris Fogels
Roughed-in a hierarchial menus doctest. |
80 |
[] |
81 |
||
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
82 |
The ICookbook and IRecipe breadcrumb objects show up in the hierarchy after |
83 |
IBreadcrumb adapters are registered for them. The hierarchy builds a list of |
|
84 |
breadcrumbs starting with the breadcrumb closest to the hierarchy root. |
|
6767.5.34
by Maris Fogels
Rework based on reviewer feedback. |
85 |
|
13130.1.1
by Curtis Hovey
Moved launchpad.py to lp.app.browser |
86 |
>>> from lp.app.browser.launchpad import Hierarchy |
14600.2.2
by Curtis Hovey
Moved webapp to lp.services. |
87 |
>>> from lp.services.webapp.breadcrumb import Breadcrumb |
9322.10.22
by Guilherme Salgado
Fix a bunch of tests and change makeBreadcrumbForRequestedPage() to use view.label before trying to look up a title in pagetitles.py |
88 |
|
9322.10.18
by Guilherme Salgado
Fix a few tests |
89 |
# Monkey patch Hierarchy.makeBreadcrumbForRequestedPage so that we don't |
90 |
# have to create fake views and other stuff to test breadcrumbs here. The |
|
91 |
# functionality provided by that method is tested in |
|
92 |
# webapp/tests/test_breadcrumbs.py. |
|
11889.4.16
by Tim Penhey
Fix the bad monkey patch. |
93 |
>>> make_breadcrumb_func = Hierarchy.makeBreadcrumbForRequestedPage |
9322.10.18
by Guilherme Salgado
Fix a few tests |
94 |
>>> Hierarchy.makeBreadcrumbForRequestedPage = lambda self: None |
6767.5.9
by Maris Fogels
Explicitly state and test the IBreadcrumb adapter interface contract. |
95 |
|
6767.5.19
by Maris Fogels
Fixed the doctest and added some comments to the code. |
96 |
# Note that the Hierarchy assigns the breadcrumb's URL, but we need to |
97 |
# give it a valid .text attribute. |
|
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
98 |
>>> class TextualBreadcrumb(Breadcrumb): |
6767.5.19
by Maris Fogels
Fixed the doctest and added some comments to the code. |
99 |
... @property |
100 |
... def text(self): |
|
101 |
... return self.context.name.capitalize().replace('-', ' ') |
|
102 |
||
14600.2.2
by Curtis Hovey
Moved webapp to lp.services. |
103 |
>>> from lp.services.webapp.interfaces import IBreadcrumb |
6767.5.34
by Maris Fogels
Rework based on reviewer feedback. |
104 |
|
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
105 |
>>> provideAdapter(TextualBreadcrumb, [ICookbook], IBreadcrumb) |
106 |
>>> provideAdapter(TextualBreadcrumb, [IRecipe], IBreadcrumb) |
|
6767.5.16
by Maris Fogels
Changed the system a bit to use an IBreadcrumbBuilder interface. |
107 |
|
9225.1.3
by Michael Nelson
New styled breadcrumbs. |
108 |
>>> hierarchy = getMultiAdapter((recipe, request), name='+hierarchy') |
109 |
>>> hierarchy.items |
|
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
110 |
[<TextualBreadcrumb |
6767.5.5
by Maris Fogels
Updated the doctest with a sensible final form. |
111 |
url='http://launchpad.dev/joy-of-cooking' |
6767.5.7
by Maris Fogels
Tweaked the test a bit. |
112 |
text='Joy of cooking'>, |
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
113 |
<TextualBreadcrumb |
6767.5.5
by Maris Fogels
Updated the doctest with a sensible final form. |
114 |
url='http://launchpad.dev/joy-of-cooking/spam' |
6767.5.7
by Maris Fogels
Tweaked the test a bit. |
115 |
text='Spam'>] |
6767.5.2
by Maris Fogels
Roughed-in a hierarchial menus doctest. |
116 |
|
8003.1.1
by Celso Providelo
Ignoring path segments starting with '+' in the Hierarchical navigation model. |
117 |
The ICooker object contains a path prefix, a segment of the path that |
118 |
does not correspond to any object, it's only used to split traversal |
|
119 |
domains. The `Hierarchy` model copes fine with objects like that. |
|
120 |
||
121 |
>>> cooker_request = make_fake_request( |
|
122 |
... 'http://launchpad.dev/+cooker/jamie', |
|
123 |
... [root, cooker]) |
|
124 |
||
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
125 |
>>> provideAdapter(TextualBreadcrumb, [ICooker], IBreadcrumb) |
8003.1.1
by Celso Providelo
Ignoring path segments starting with '+' in the Hierarchical navigation model. |
126 |
|
127 |
>>> cooker_hierarchy = getMultiAdapter( |
|
128 |
... (cooker, cooker_request), name='+hierarchy') |
|
9225.1.3
by Michael Nelson
New styled breadcrumbs. |
129 |
>>> cooker_hierarchy.items |
11134.7.20
by Curtis Hovey
Hushed lint. |
130 |
[<TextualBreadcrumb url='.../+cooker/jamie' text='Jamie'>] |
131 |
||
132 |
||
133 |
Displaying breadcrumbs |
|
134 |
---------------------- |
|
9225.1.7
by Michael Nelson
Added doc for HierarchyView.display_breadcrumbs. |
135 |
|
136 |
Breadcrumbs are only displayed if there is more than one breadcrumb, as |
|
137 |
otherwise the breadcrumb will simply replicate the context.title heading |
|
138 |
above it. |
|
139 |
||
140 |
>>> len(hierarchy.items) |
|
141 |
2 |
|
142 |
>>> hierarchy.display_breadcrumbs |
|
143 |
True |
|
144 |
||
145 |
>>> len(cooker_hierarchy.items) |
|
146 |
1 |
|
147 |
>>> cooker_hierarchy.display_breadcrumbs |
|
148 |
False |
|
149 |
||
9511.2.7
by Michael Nelson
Fixed bug 433852 |
150 |
Additionally, if the view implements IMajorHeadingView then the breadcrumbs |
151 |
will not be displayed. |
|
152 |
||
153 |
>>> ham_recipe = Recipe('ham', cookbook) |
|
154 |
>>> ham_request = make_fake_request( |
|
155 |
... 'http://launchpad.dev/joy-of-cooking/ham', |
|
156 |
... [root, cookbook, ham_recipe]) |
|
157 |
||
158 |
>>> ham_hierarchy = getMultiAdapter( |
|
159 |
... (ham_recipe, ham_request), name='+hierarchy') |
|
160 |
>>> hierarchy.display_breadcrumbs |
|
161 |
True |
|
162 |
||
163 |
>>> from zope.interface import alsoProvides |
|
164 |
>>> from lp.app.interfaces.headings import IMajorHeadingView |
|
165 |
>>> alsoProvides(ham_recipe, IMajorHeadingView) |
|
166 |
>>> ham_hierarchy.display_breadcrumbs |
|
167 |
False |
|
168 |
||
9225.1.7
by Michael Nelson
Added doc for HierarchyView.display_breadcrumbs. |
169 |
|
11134.7.20
by Curtis Hovey
Hushed lint. |
170 |
Building IBreadcrumb objects |
171 |
---------------------------- |
|
6767.5.34
by Maris Fogels
Rework based on reviewer feedback. |
172 |
|
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
173 |
The construction of breadcrumb objects is handled by an IBreadcrumb adapter, |
174 |
which adapts a context object and produces an IBreadcrumb object for that |
|
175 |
context. The default adapter provides the url attribute, but the breadcrumb's |
|
176 |
text must be overriden in subclasses. |
|
6767.5.34
by Maris Fogels
Rework based on reviewer feedback. |
177 |
|
178 |
>>> from zope.interface.verify import verifyObject |
|
14600.2.2
by Curtis Hovey
Moved webapp to lp.services. |
179 |
>>> from lp.services.webapp.interfaces import IBreadcrumb |
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
180 |
>>> breadcrumb = Breadcrumb(cookbook) |
6767.5.34
by Maris Fogels
Rework based on reviewer feedback. |
181 |
>>> verifyObject(IBreadcrumb, breadcrumb) |
182 |
True |
|
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
183 |
>>> print breadcrumb.text |
184 |
None |
|
9678.12.2
by Guilherme Salgado
Revert previous change and fix the test instead |
185 |
>>> print breadcrumb.url |
186 |
http://launchpad.dev/joy-of-cooking |
|
6767.5.34
by Maris Fogels
Rework based on reviewer feedback. |
187 |
|
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
188 |
As said above, the breadcrumb's attributes can be overridden with subclassing |
189 |
and Python properties. |
|
6767.5.34
by Maris Fogels
Rework based on reviewer feedback. |
190 |
|
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
191 |
>>> class DynamicBreadcrumb(Breadcrumb): |
6767.5.34
by Maris Fogels
Rework based on reviewer feedback. |
192 |
... @property |
193 |
... def text(self): |
|
194 |
... return self.context.name.capitalize().replace('-', ' ') |
|
195 |
||
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
196 |
>>> breadcrumb = DynamicBreadcrumb(cookbook) |
197 |
>>> breadcrumb |
|
198 |
<DynamicBreadcrumb |
|
6767.5.34
by Maris Fogels
Rework based on reviewer feedback. |
199 |
url='http://launchpad.dev/joy-of-cooking' |
200 |
text='Joy of cooking'> |
|
201 |
||
202 |
||
11134.7.20
by Curtis Hovey
Hushed lint. |
203 |
Customizing the hierarchy |
204 |
------------------------- |
|
6767.5.21
by Maris Fogels
Changed the hierarchy interface a bit, so that breadcrumb construction is handed off to the class. This ensures consistency in presentation. |
205 |
|
6767.5.34
by Maris Fogels
Rework based on reviewer feedback. |
206 |
We can customize the hierarchy itself by changing the list of objects |
207 |
and URLs that it uses to construct the breadcrumbs list. |
|
6767.5.21
by Maris Fogels
Changed the hierarchy interface a bit, so that breadcrumb construction is handed off to the class. This ensures consistency in presentation. |
208 |
|
6767.5.38
by Maris Fogels
Wording. |
209 |
The Hierarchy object should *not* construct the Breadcrumb objects |
210 |
itself. It should let the IBreadcrumbBuilder handle it: this ensures |
|
211 |
consistency across the site. |
|
6767.5.21
by Maris Fogels
Changed the hierarchy interface a bit, so that breadcrumb construction is handed off to the class. This ensures consistency in presentation. |
212 |
|
213 |
>>> class CustomHierarchy(Hierarchy): |
|
9087.4.4
by Guilherme Salgado
Fix one test |
214 |
... @property |
215 |
... def objects(self): |
|
216 |
... return [recipe] |
|
6767.5.21
by Maris Fogels
Changed the hierarchy interface a bit, so that breadcrumb construction is handed off to the class. This ensures consistency in presentation. |
217 |
|
218 |
>>> spammy_hierarchy = CustomHierarchy(root, request) |
|
9225.1.3
by Michael Nelson
New styled breadcrumbs. |
219 |
>>> spammy_hierarchy.items |
9209.4.1
by Guilherme Salgado
Get rid of BreadcrumbBuilder as it's not necessary anymore |
220 |
[<TextualBreadcrumb |
9087.4.4
by Guilherme Salgado
Fix one test |
221 |
url='http://launchpad.dev/joy-of-cooking/spam' |
9494.1.1
by Guilherme Salgado
Remove icons from breadcrumbs |
222 |
text='Spam'>] |
6767.5.21
by Maris Fogels
Changed the hierarchy interface a bit, so that breadcrumb construction is handed off to the class. This ensures consistency in presentation. |
223 |
|
224 |
||
11134.7.20
by Curtis Hovey
Hushed lint. |
225 |
Rendering the list |
226 |
------------------ |
|
6767.5.2
by Maris Fogels
Roughed-in a hierarchial menus doctest. |
227 |
|
6767.5.34
by Maris Fogels
Rework based on reviewer feedback. |
228 |
The Hierarchy object is responsible for rendering the HTML for the |
229 |
location bar. |
|
6767.5.12
by Maris Fogels
Added a test to show regular and Launchpad homepage breadcrumb rendering. |
230 |
|
231 |
>>> from BeautifulSoup import BeautifulSoup |
|
14578.4.1
by William Grant
Move the remains of canonical.launchpad.testing to lp.testing. |
232 |
>>> from lp.testing.pages import extract_text |
6767.5.12
by Maris Fogels
Added a test to show regular and Launchpad homepage breadcrumb rendering. |
233 |
|
14578.4.2
by William Grant
Fix a few stragglers. |
234 |
# Borrowed from lp.testing.pages.print_location() |
6767.5.12
by Maris Fogels
Added a test to show regular and Launchpad homepage breadcrumb rendering. |
235 |
>>> def print_hierarchy(html): |
236 |
... soup = BeautifulSoup(html) |
|
9225.1.3
by Michael Nelson
New styled breadcrumbs. |
237 |
... hierarchy = soup.find(attrs={'class': 'breadcrumbs'}).findAll( |
6767.5.12
by Maris Fogels
Added a test to show regular and Launchpad homepage breadcrumb rendering. |
238 |
... recursive=False) |
239 |
... segments = [extract_text(step).encode('us-ascii', 'replace') |
|
9225.1.6
by Michael Nelson
Updated the print_location test helper. |
240 |
... for step in hierarchy] |
9225.1.3
by Michael Nelson
New styled breadcrumbs. |
241 |
... print 'Location:', ' > '.join(segments) |
6767.5.12
by Maris Fogels
Added a test to show regular and Launchpad homepage breadcrumb rendering. |
242 |
|
10197.4.8
by Curtis Hovey
Fixed the breadcrumb markup. |
243 |
>>> markup = hierarchy.render() |
244 |
>>> print_hierarchy(markup) |
|
6767.5.12
by Maris Fogels
Added a test to show regular and Launchpad homepage breadcrumb rendering. |
245 |
Location: Joy of cooking > Spam |
246 |
||
10197.4.8
by Curtis Hovey
Fixed the breadcrumb markup. |
247 |
The items in the breadcrumbs are linked, except for the last one which |
248 |
represents the current location. |
|
249 |
||
250 |
>>> print markup |
|
14325.2.8
by mbp at canonical
Update breadcrumb tests to expect schema.org metadata |
251 |
<ol itemprop="breadcrumb" class="breadcrumbs"> |
10197.4.8
by Curtis Hovey
Fixed the breadcrumb markup. |
252 |
<li> |
10197.4.11
by Curtis Hovey
Removed leading white space from the breadcrumb links. Fixed the __all__ declarations |
253 |
<a href="http://launchpad.dev/joy-of-cooking">Joy of cooking</a> |
10197.4.8
by Curtis Hovey
Fixed the breadcrumb markup. |
254 |
</li> |
255 |
<li> |
|
256 |
Spam |
|
257 |
</li> |
|
258 |
</ol> |
|
259 |
||
9087.4.4
by Guilherme Salgado
Fix one test |
260 |
The Launchpad Homepage displays no items in its location bar. We are |
6767.5.12
by Maris Fogels
Added a test to show regular and Launchpad homepage breadcrumb rendering. |
261 |
considered to be on the home page if there are no breadcrumbs. |
262 |
||
263 |
# Simulate a visit to the site root |
|
264 |
>>> request = make_fake_request('http://launchpad.dev/', [root]) |
|
265 |
>>> homepage_hierarchy = getMultiAdapter( |
|
266 |
... (root, request), name='+hierarchy') |
|
267 |
||
9225.1.3
by Michael Nelson
New styled breadcrumbs. |
268 |
>>> homepage_hierarchy.items |
6767.5.12
by Maris Fogels
Added a test to show regular and Launchpad homepage breadcrumb rendering. |
269 |
[] |
270 |
||
11134.7.19
by Curtis Hovey
Removed dubious tests...templates do not control the content of launchpad header; |
271 |
>>> homepage_hierarchy.render().strip() |
11235.1.1
by Curtis Hovey
Updated test to verify the expected template content. |
272 |
u'' |
11889.4.16
by Tim Penhey
Fix the bad monkey patch. |
273 |
|
274 |
||
275 |
Put the monkey patched method back. |
|
276 |
||
277 |
>>> Hierarchy.makeBreadcrumbForRequestedPage = make_breadcrumb_func |