Merge lp:~thumper/wikkid/script-name into lp:wikkid
- script-name
- Merge into trunk
Proposed by
Tim Penhey
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Tim Penhey | ||||
Approved revision: | 85 | ||||
Merged at revision: | 70 | ||||
Proposed branch: | lp:~thumper/wikkid/script-name | ||||
Merge into: | lp:wikkid | ||||
Diff against target: |
622 lines (+193/-80) 15 files modified
bin/wikkid-serve (+5/-1) setup.py (+2/-1) wikkid/app.py (+35/-19) wikkid/context.py (+13/-4) wikkid/skin/default/base.html (+4/-4) wikkid/tests/factory.py (+2/-2) wikkid/tests/test_app.py (+40/-12) wikkid/tests/views/test_breadcrumbs.py (+13/-0) wikkid/tests/views/test_root.py (+33/-2) wikkid/tests/views/test_urls.py (+22/-17) wikkid/view/base.py (+13/-9) wikkid/view/directory.py (+5/-5) wikkid/view/edit.py (+1/-1) wikkid/view/root.py (+2/-0) wikkid/view/urls.py (+3/-3) |
||||
To merge this branch: | bzr merge lp:~thumper/wikkid/script-name | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Penhey | Approve | ||
Review via email: mp+106141@code.launchpad.net |
Commit message
Description of the change
Allow wikkid-serve to specify a script-name. This is the assumed to be the start of any request for a wikkid served page. If not, a 404 is returned.
Styling and links are updated to use the script-name.
To post a comment you must log in.
lp:~thumper/wikkid/script-name
updated
- 86. By Tim Penhey
-
Have the views use their own canonical_url function that uses the request object.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'bin/wikkid-serve' | |||
2 | --- bin/wikkid-serve 2011-03-09 21:34:46 +0000 | |||
3 | +++ bin/wikkid-serve 2012-05-17 11:13:18 +0000 | |||
4 | @@ -58,12 +58,16 @@ | |||
5 | 58 | '--default-format', type='string', default=DEFAULT_FORMAT, | 58 | '--default-format', type='string', default=DEFAULT_FORMAT, |
6 | 59 | help=("Specify the default wiki format to use. Defaults to %r" | 59 | help=("Specify the default wiki format to use. Defaults to %r" |
7 | 60 | % DEFAULT_FORMAT)) | 60 | % DEFAULT_FORMAT)) |
8 | 61 | parser.add_option( | ||
9 | 62 | '--script-name', | ||
10 | 63 | help=('The SCRIPT_NAME for the environment. This is the prefix for the URLs')) | ||
11 | 61 | options, args = parser.parse_args(sys.argv[1:]) | 64 | options, args = parser.parse_args(sys.argv[1:]) |
12 | 62 | 65 | ||
13 | 63 | execution_context = ExecutionContext( | 66 | execution_context = ExecutionContext( |
14 | 64 | host=options.host, | 67 | host=options.host, |
15 | 65 | port=options.port, | 68 | port=options.port, |
17 | 66 | default_format=options.default_format) | 69 | default_format=options.default_format, |
18 | 70 | script_name=options.script_name) | ||
19 | 67 | 71 | ||
20 | 68 | if len(args) == 0: | 72 | if len(args) == 0: |
21 | 69 | print "No branch location specified.\n" | 73 | print "No branch location specified.\n" |
22 | 70 | 74 | ||
23 | === modified file 'setup.py' | |||
24 | --- setup.py 2011-06-29 09:21:55 +0000 | |||
25 | +++ setup.py 2012-05-17 11:13:18 +0000 | |||
26 | @@ -31,7 +31,8 @@ | |||
27 | 31 | 'zope.interface', | 31 | 'zope.interface', |
28 | 32 | ], | 32 | ], |
29 | 33 | test_requires=[ | 33 | test_requires=[ |
31 | 34 | 'beautifulsoup', | 34 | 'bs4', |
32 | 35 | 'bzrlib.tests', | ||
33 | 35 | 'testtools', | 36 | 'testtools', |
34 | 36 | ], | 37 | ], |
35 | 37 | test_suite='wikkid.tests', | 38 | test_suite='wikkid.tests', |
36 | 38 | 39 | ||
37 | === modified file 'wikkid/app.py' | |||
38 | --- wikkid/app.py 2010-11-22 00:00:31 +0000 | |||
39 | +++ wikkid/app.py 2012-05-17 11:13:18 +0000 | |||
40 | @@ -11,6 +11,7 @@ | |||
41 | 11 | import mimetypes | 11 | import mimetypes |
42 | 12 | import os.path | 12 | import os.path |
43 | 13 | import urllib | 13 | import urllib |
44 | 14 | from wsgiref.util import shift_path_info | ||
45 | 14 | 15 | ||
46 | 15 | from bzrlib import urlutils | 16 | from bzrlib import urlutils |
47 | 16 | from webob import Request, Response | 17 | from webob import Request, Response |
48 | @@ -59,35 +60,50 @@ | |||
49 | 59 | self.skin = Skin(skin_name) | 60 | self.skin = Skin(skin_name) |
50 | 60 | self.logger = logging.getLogger('wikkid') | 61 | self.logger = logging.getLogger('wikkid') |
51 | 61 | 62 | ||
54 | 62 | def __call__(self, environ, start_response): | 63 | def process_call(self, environ): |
55 | 63 | """The WSGI bit.""" | 64 | """The actual implementation of dealing with the call.""" |
56 | 65 | # TODO: reject requests that aren't GET or POST | ||
57 | 64 | request = Request(environ) | 66 | request = Request(environ) |
58 | 65 | |||
59 | 66 | # TODO: reject requests that aren't GET or POST | ||
60 | 67 | path = urllib.unquote(request.path) | 67 | path = urllib.unquote(request.path) |
61 | 68 | script_name = self.execution_context.script_name | ||
62 | 69 | if not path.startswith(script_name + '/'): | ||
63 | 70 | return HTTPNotFound() | ||
64 | 71 | |||
65 | 72 | shifted_prefix = '' | ||
66 | 73 | while shifted_prefix != script_name: | ||
67 | 74 | shifted = shift_path_info(environ) | ||
68 | 75 | shifted_prefix = '{0}/{1}'.format(shifted_prefix, shifted) | ||
69 | 76 | # Now we are just interested in the path_info having ignored the | ||
70 | 77 | # script name. | ||
71 | 78 | path = urllib.unquote(request.path_info) | ||
72 | 79 | |||
73 | 68 | if path == '/favicon.ico': | 80 | if path == '/favicon.ico': |
74 | 69 | if self.skin.favicon is not None: | 81 | if self.skin.favicon is not None: |
76 | 70 | response = serve_file(self.skin.favicon) | 82 | return serve_file(self.skin.favicon) |
77 | 71 | else: | 83 | else: |
80 | 72 | response = HTTPNotFound() | 84 | return HTTPNotFound() |
81 | 73 | elif path.startswith('/static/'): | 85 | |
82 | 86 | if path.startswith('/static/'): | ||
83 | 74 | if self.skin.static_dir is not None: | 87 | if self.skin.static_dir is not None: |
84 | 75 | static_dir = self.skin.static_dir.rstrip(os.sep) + os.sep | 88 | static_dir = self.skin.static_dir.rstrip(os.sep) + os.sep |
85 | 76 | static_file = os.path.abspath( | 89 | static_file = os.path.abspath( |
86 | 77 | urlutils.joinpath(static_dir, path[8:])) | 90 | urlutils.joinpath(static_dir, path[8:])) |
87 | 78 | if static_file.startswith(static_dir): | 91 | if static_file.startswith(static_dir): |
89 | 79 | response = serve_file(static_file) | 92 | return serve_file(static_file) |
90 | 80 | else: | 93 | else: |
92 | 81 | response = HTTPNotFound() | 94 | return HTTPNotFound() |
93 | 82 | else: | 95 | else: |
104 | 83 | response = HTTPNotFound() | 96 | return HTTPNotFound() |
105 | 84 | else: | 97 | |
106 | 85 | resource_path, action = parse_url(path) | 98 | resource_path, action = parse_url(path) |
107 | 86 | model = self.resource_factory.get_resource_at_path(resource_path) | 99 | model = self.resource_factory.get_resource_at_path(resource_path) |
108 | 87 | try: | 100 | try: |
109 | 88 | view = get_view(model, action, request, self.execution_context) | 101 | view = get_view(model, action, request, self.execution_context) |
110 | 89 | response = view.render(self.skin) | 102 | return view.render(self.skin) |
111 | 90 | except HTTPException, e: | 103 | except HTTPException, e: |
112 | 91 | response = e | 104 | return e |
113 | 92 | 105 | ||
114 | 106 | def __call__(self, environ, start_response): | ||
115 | 107 | """The WSGI bit.""" | ||
116 | 108 | response = self.process_call(environ) | ||
117 | 93 | return response(environ, start_response) | 109 | return response(environ, start_response) |
118 | 94 | 110 | ||
119 | === modified file 'wikkid/context.py' | |||
120 | --- wikkid/context.py 2010-11-22 00:00:31 +0000 | |||
121 | +++ wikkid/context.py 2012-05-17 11:13:18 +0000 | |||
122 | @@ -1,6 +1,6 @@ | |||
123 | 1 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
124 | 2 | # | 2 | # |
126 | 3 | # Copyright (C) 2010 Wikkid Developers. | 3 | # Copyright (C) 2010-2012 Wikkid Developers. |
127 | 4 | # | 4 | # |
128 | 5 | # This software is licensed under the GNU Affero General Public License | 5 | # This software is licensed under the GNU Affero General Public License |
129 | 6 | # version 3 (see the file LICENSE). | 6 | # version 3 (see the file LICENSE). |
130 | @@ -13,9 +13,13 @@ | |||
131 | 13 | 13 | ||
132 | 14 | 14 | ||
133 | 15 | class ExecutionContext(object): | 15 | class ExecutionContext(object): |
137 | 16 | """Store run-time execution context data.""" | 16 | """Store run-time execution context data. |
138 | 17 | 17 | ||
139 | 18 | def __init__(self, host=None, port=None, default_format=None): | 18 | This is the Encapsulate Context pattern. |
140 | 19 | """ | ||
141 | 20 | |||
142 | 21 | def __init__(self, host=None, port=None, default_format=None, | ||
143 | 22 | script_name=None): | ||
144 | 19 | """Create an execution context for the application. | 23 | """Create an execution context for the application. |
145 | 20 | 24 | ||
146 | 21 | :param host: The hostname that content is being served from. | 25 | :param host: The hostname that content is being served from. |
147 | @@ -32,3 +36,8 @@ | |||
148 | 32 | self.host = host | 36 | self.host = host |
149 | 33 | self.port = port | 37 | self.port = port |
150 | 34 | self.default_format = default_format | 38 | self.default_format = default_format |
151 | 39 | # TODO: make sure the script_name if set starts with a slash and | ||
152 | 40 | # doesn't finish with one. | ||
153 | 41 | if script_name is None: | ||
154 | 42 | script_name = '' | ||
155 | 43 | self.script_name = script_name | ||
156 | 35 | 44 | ||
157 | === modified file 'wikkid/skin/default/base.html' | |||
158 | --- wikkid/skin/default/base.html 2010-07-13 12:49:52 +0000 | |||
159 | +++ wikkid/skin/default/base.html 2012-05-17 11:13:18 +0000 | |||
160 | @@ -5,14 +5,14 @@ | |||
161 | 5 | <title>{% block title %}{% endblock %} - Wikkid</title> | 5 | <title>{% block title %}{% endblock %} - Wikkid</title> |
162 | 6 | 6 | ||
163 | 7 | <link type="text/css" rel="stylesheet" media="screen, print" | 7 | <link type="text/css" rel="stylesheet" media="screen, print" |
166 | 8 | href="/static/default.css" /> | 8 | href="{{ request.script_name }}/static/default.css" /> |
167 | 9 | <link rel="shortcut icon" href="/favicon.ico" /> | 9 | <link rel="shortcut icon" href="{{ request.script_name }}/favicon.ico" /> |
168 | 10 | {% endblock %} | 10 | {% endblock %} |
169 | 11 | </head> | 11 | </head> |
170 | 12 | <body> | 12 | <body> |
171 | 13 | <div id="container"> | 13 | <div id="container"> |
172 | 14 | <div id="header"> | 14 | <div id="header"> |
174 | 15 | <div class="wikkidlogo"><a href="/"><strong>Wikkid Wiki</strong></a></div> | 15 | <div class="wikkidlogo"><a href="{{ request.script_name }}/"><strong>Wikkid Wiki</strong></a></div> |
175 | 16 | <div id="logged"> | 16 | <div id="logged"> |
176 | 17 | {% if view.user %} | 17 | {% if view.user %} |
177 | 18 | 18 | ||
178 | @@ -44,7 +44,7 @@ | |||
179 | 44 | </div> | 44 | </div> |
180 | 45 | {% block footer %}{% endblock %} | 45 | {% block footer %}{% endblock %} |
181 | 46 | <div id="footer"> | 46 | <div id="footer"> |
183 | 47 | © 2010 | 47 | © 2010-2012 |
184 | 48 | <a href="https://launchpad.net/~wikkid">Wikkid Hackers</a>, | 48 | <a href="https://launchpad.net/~wikkid">Wikkid Hackers</a>, |
185 | 49 | All rights reserved. | 49 | All rights reserved. |
186 | 50 | </div> | 50 | </div> |
187 | 51 | 51 | ||
188 | === modified file 'wikkid/tests/factory.py' | |||
189 | --- wikkid/tests/factory.py 2010-06-15 09:05:29 +0000 | |||
190 | +++ wikkid/tests/factory.py 2012-05-17 11:13:18 +0000 | |||
191 | @@ -26,7 +26,7 @@ | |||
192 | 26 | class ViewTestCase(FactoryTestCase): | 26 | class ViewTestCase(FactoryTestCase): |
193 | 27 | """A factory test case that can create views.""" | 27 | """A factory test case that can create views.""" |
194 | 28 | 28 | ||
196 | 29 | def get_view(self, factory, path, name=None): | 29 | def get_view(self, factory, path, name=None, base_url=None): |
197 | 30 | info = factory.get_resource_at_path(path) | 30 | info = factory.get_resource_at_path(path) |
199 | 31 | request = Request.blank(path) | 31 | request = Request.blank(path, base_url=base_url) |
200 | 32 | return get_view(info, name, request) | 32 | return get_view(info, name, request) |
201 | 33 | 33 | ||
202 | === modified file 'wikkid/tests/test_app.py' | |||
203 | --- wikkid/tests/test_app.py 2011-04-23 08:18:58 +0000 | |||
204 | +++ wikkid/tests/test_app.py 2012-05-17 11:13:18 +0000 | |||
205 | @@ -11,25 +11,28 @@ | |||
206 | 11 | from webob.request import environ_from_url | 11 | from webob.request import environ_from_url |
207 | 12 | 12 | ||
208 | 13 | from wikkid.app import WikkidApp | 13 | from wikkid.app import WikkidApp |
209 | 14 | from wikkid.context import ExecutionContext | ||
210 | 14 | from wikkid.filestore.volatile import FileStore | 15 | from wikkid.filestore.volatile import FileStore |
211 | 15 | from wikkid.tests import TestCase | 16 | from wikkid.tests import TestCase |
212 | 16 | 17 | ||
213 | 17 | 18 | ||
214 | 18 | class TestApp(TestCase): | 19 | class TestApp(TestCase): |
215 | 19 | 20 | ||
216 | 21 | def assert_not_found(self, status, headers): | ||
217 | 22 | self.assertEqual("404 Not Found", status) | ||
218 | 23 | |||
219 | 24 | def assert_ok(self, status, headers): | ||
220 | 25 | self.assertEqual("200 OK", status) | ||
221 | 26 | |||
222 | 20 | def test_traverse_above_static_not_possible_with_relative_path(self): | 27 | def test_traverse_above_static_not_possible_with_relative_path(self): |
223 | 21 | """ | 28 | """ |
224 | 22 | Traversal above the static folder, by forging a malicious request with | 29 | Traversal above the static folder, by forging a malicious request with |
225 | 23 | a relative path for example, is not possible. | 30 | a relative path for example, is not possible. |
226 | 24 | """ | 31 | """ |
227 | 25 | environ = environ_from_url("/static/../page.html") | 32 | environ = environ_from_url("/static/../page.html") |
228 | 26 | |||
229 | 27 | def start_response(status, headers): | ||
230 | 28 | self.assertEqual("404 Not Found", status) | ||
231 | 29 | |||
232 | 30 | filestore = FileStore() | 33 | filestore = FileStore() |
233 | 31 | app = WikkidApp(filestore) | 34 | app = WikkidApp(filestore) |
235 | 32 | app(environ, start_response) | 35 | app(environ, self.assert_not_found) |
236 | 33 | 36 | ||
237 | 34 | def test_traverse_above_static_not_possible_with_absolute_path(self): | 37 | def test_traverse_above_static_not_possible_with_absolute_path(self): |
238 | 35 | """ | 38 | """ |
239 | @@ -38,10 +41,35 @@ | |||
240 | 38 | """ | 41 | """ |
241 | 39 | this_file = os.path.abspath(__file__) | 42 | this_file = os.path.abspath(__file__) |
242 | 40 | environ = environ_from_url("/static/" + this_file) | 43 | environ = environ_from_url("/static/" + this_file) |
250 | 41 | 44 | filestore = FileStore() | |
251 | 42 | def start_response(status, headers): | 45 | app = WikkidApp(filestore) |
252 | 43 | self.assertEqual("404 Not Found", status) | 46 | app(environ, self.assert_not_found) |
253 | 44 | 47 | ||
254 | 45 | filestore = FileStore() | 48 | def test_getting_static_style_css_works(self): |
255 | 46 | app = WikkidApp(filestore) | 49 | |
256 | 47 | app(environ, start_response) | 50 | environ = environ_from_url("/static/default.css") |
257 | 51 | filestore = FileStore() | ||
258 | 52 | app = WikkidApp(filestore) | ||
259 | 53 | app(environ, self.assert_ok) | ||
260 | 54 | |||
261 | 55 | def test_getting_static_style_css_works_with_script_name(self): | ||
262 | 56 | |||
263 | 57 | environ = environ_from_url("/test/static/default.css") | ||
264 | 58 | filestore = FileStore() | ||
265 | 59 | context = ExecutionContext(script_name="/test") | ||
266 | 60 | app = WikkidApp(filestore, execution_context=context) | ||
267 | 61 | app(environ, self.assert_ok) | ||
268 | 62 | |||
269 | 63 | def test_getting_static_style_css_works_with_script_name_multiple_segments(self): | ||
270 | 64 | environ = environ_from_url("/p/project-name/wiki/static/default.css") | ||
271 | 65 | filestore = FileStore() | ||
272 | 66 | context = ExecutionContext(script_name="/p/project-name/wiki") | ||
273 | 67 | app = WikkidApp(filestore, execution_context=context) | ||
274 | 68 | app(environ, self.assert_ok) | ||
275 | 69 | |||
276 | 70 | def test_getting_anything_outside_script_name_fails(self): | ||
277 | 71 | environ = environ_from_url("/foo/bar") | ||
278 | 72 | filestore = FileStore() | ||
279 | 73 | context = ExecutionContext(script_name="/test") | ||
280 | 74 | app = WikkidApp(filestore, execution_context=context) | ||
281 | 75 | app(environ, self.assert_not_found) | ||
282 | 48 | 76 | ||
283 | === modified file 'wikkid/tests/views/test_breadcrumbs.py' | |||
284 | --- wikkid/tests/views/test_breadcrumbs.py 2010-06-15 09:05:29 +0000 | |||
285 | +++ wikkid/tests/views/test_breadcrumbs.py 2012-05-17 11:13:18 +0000 | |||
286 | @@ -107,3 +107,16 @@ | |||
287 | 107 | ('wiki root', '/+listing'), | 107 | ('wiki root', '/+listing'), |
288 | 108 | ('SomePage', '/SomePage/+listing'), | 108 | ('SomePage', '/SomePage/+listing'), |
289 | 109 | ('SubPage', '/SomePage/SubPage')]) | 109 | ('SubPage', '/SomePage/SubPage')]) |
290 | 110 | |||
291 | 111 | def test_directory_breadcrumbs_nested_with_script_name(self): | ||
292 | 112 | # For each directory after the root, a listing crumb is added. | ||
293 | 113 | # Names are not wiki expanded. | ||
294 | 114 | factory = self.make_factory([ | ||
295 | 115 | ('SomePage/SubPage/Nested.txt', 'some text')]) | ||
296 | 116 | view = self.get_view(factory, '/SomePage/SubPage', 'listing', '/p/wiki') | ||
297 | 117 | self.assertBreadcrumbs( | ||
298 | 118 | view, | ||
299 | 119 | [('Home', '/p/wiki/Home'), | ||
300 | 120 | ('wiki root', '/p/wiki/+listing'), | ||
301 | 121 | ('SomePage', '/p/wiki/SomePage/+listing'), | ||
302 | 122 | ('SubPage', '/p/wiki/SomePage/SubPage')]) | ||
303 | 110 | 123 | ||
304 | === modified file 'wikkid/tests/views/test_root.py' | |||
305 | --- wikkid/tests/views/test_root.py 2010-06-17 10:45:52 +0000 | |||
306 | +++ wikkid/tests/views/test_root.py 2012-05-17 11:13:18 +0000 | |||
307 | @@ -6,16 +6,19 @@ | |||
308 | 6 | 6 | ||
309 | 7 | """Test views for the root object.""" | 7 | """Test views for the root object.""" |
310 | 8 | 8 | ||
311 | 9 | from bs4 import BeautifulSoup | ||
312 | 10 | from testtools.matchers import Equals | ||
313 | 9 | from webob.exc import HTTPSeeOther | 11 | from webob.exc import HTTPSeeOther |
314 | 10 | 12 | ||
315 | 13 | from wikkid.skin.loader import Skin | ||
316 | 11 | from wikkid.tests.factory import ViewTestCase | 14 | from wikkid.tests.factory import ViewTestCase |
317 | 12 | 15 | ||
318 | 13 | 16 | ||
319 | 14 | class TestRootViews(ViewTestCase): | 17 | class TestRootViews(ViewTestCase): |
320 | 15 | """Test the views on the root object.""" | 18 | """Test the views on the root object.""" |
321 | 16 | 19 | ||
324 | 17 | def test_last_modified_by(self): | 20 | def test_root_redirects(self): |
325 | 18 | """Test that the last committer is displayed properly""" | 21 | """Going to / redirects to the Home page.""" |
326 | 19 | factory = self.make_factory() | 22 | factory = self.make_factory() |
327 | 20 | view = self.get_view(factory, '/') | 23 | view = self.get_view(factory, '/') |
328 | 21 | error = self.assertRaises( | 24 | error = self.assertRaises( |
329 | @@ -23,3 +26,31 @@ | |||
330 | 23 | view.render, | 26 | view.render, |
331 | 24 | None) | 27 | None) |
332 | 25 | self.assertEqual('/Home', error.headers['Location']) | 28 | self.assertEqual('/Home', error.headers['Location']) |
333 | 29 | |||
334 | 30 | def test_root_redirects_with_script_name(self): | ||
335 | 31 | """Redirection works and respects the script name""" | ||
336 | 32 | factory = self.make_factory() | ||
337 | 33 | view = self.get_view(factory, '/', base_url='/p/test') | ||
338 | 34 | error = self.assertRaises( | ||
339 | 35 | HTTPSeeOther, | ||
340 | 36 | view.render, | ||
341 | 37 | None) | ||
342 | 38 | self.assertEqual('/p/test/Home', error.headers['Location']) | ||
343 | 39 | |||
344 | 40 | def test_home_rendering(self): | ||
345 | 41 | """Render the home page and test the elements.""" | ||
346 | 42 | factory = self.make_factory() | ||
347 | 43 | view = self.get_view(factory, '/Home') | ||
348 | 44 | content = view.render(Skin('default')) | ||
349 | 45 | soup = BeautifulSoup(content.text) | ||
350 | 46 | [style] = soup.find_all('link', {'rel':'stylesheet'}) | ||
351 | 47 | self.assertThat(style['href'], Equals('/static/default.css')) | ||
352 | 48 | |||
353 | 49 | def test_home_rendering_with_script_name(self): | ||
354 | 50 | """Render the home page and test the elements.""" | ||
355 | 51 | factory = self.make_factory() | ||
356 | 52 | view = self.get_view(factory, '/Home', base_url='/p/test') | ||
357 | 53 | content = view.render(Skin('default')) | ||
358 | 54 | soup = BeautifulSoup(content.text) | ||
359 | 55 | [style] = soup.find_all('link', {'rel':'stylesheet'}) | ||
360 | 56 | self.assertThat(style['href'], Equals('/p/test/static/default.css')) | ||
361 | 26 | 57 | ||
362 | === modified file 'wikkid/tests/views/test_urls.py' | |||
363 | --- wikkid/tests/views/test_urls.py 2010-06-16 10:29:35 +0000 | |||
364 | +++ wikkid/tests/views/test_urls.py 2012-05-17 11:13:18 +0000 | |||
365 | @@ -6,6 +6,9 @@ | |||
366 | 6 | 6 | ||
367 | 7 | """Tests the edit views.""" | 7 | """Tests the edit views.""" |
368 | 8 | 8 | ||
369 | 9 | from testtools.matchers import Equals | ||
370 | 10 | from webob import Request | ||
371 | 11 | |||
372 | 9 | from wikkid.tests import TestCase | 12 | from wikkid.tests import TestCase |
373 | 10 | from wikkid.tests.factory import FactoryTestCase | 13 | from wikkid.tests.factory import FactoryTestCase |
374 | 11 | from wikkid.view.urls import canonical_url, parse_url | 14 | from wikkid.view.urls import canonical_url, parse_url |
375 | @@ -14,84 +17,86 @@ | |||
376 | 14 | class TestCanonicalUrl(FactoryTestCase): | 17 | class TestCanonicalUrl(FactoryTestCase): |
377 | 15 | """Test the wikkid.view.base.canonical_url.""" | 18 | """Test the wikkid.view.base.canonical_url.""" |
378 | 16 | 19 | ||
379 | 20 | def assertUrl(self, resource, url, view_name=None, base_url=None): | ||
380 | 21 | request = Request.blank('/', base_url=base_url) | ||
381 | 22 | self.assertThat(canonical_url(resource, request, view_name), | ||
382 | 23 | Equals(url)) | ||
383 | 24 | |||
384 | 17 | def test_root(self): | 25 | def test_root(self): |
385 | 18 | factory = self.make_factory() | 26 | factory = self.make_factory() |
386 | 19 | root = factory.get_resource_at_path('/') | 27 | root = factory.get_resource_at_path('/') |
388 | 20 | self.assertEqual('/', canonical_url(root)) | 28 | self.assertUrl(root, '/') |
389 | 21 | 29 | ||
390 | 22 | def test_root_listing(self): | 30 | def test_root_listing(self): |
391 | 23 | factory = self.make_factory() | 31 | factory = self.make_factory() |
392 | 24 | root = factory.get_resource_at_path('/') | 32 | root = factory.get_resource_at_path('/') |
394 | 25 | self.assertEqual('/+listing', canonical_url(root, 'listing')) | 33 | self.assertUrl(root, '/+listing', view_name='listing') |
395 | 26 | 34 | ||
396 | 27 | def test_default(self): | 35 | def test_default(self): |
397 | 28 | factory = self.make_factory([ | 36 | factory = self.make_factory([ |
398 | 29 | ('Home.txt', 'Some content'), | 37 | ('Home.txt', 'Some content'), |
399 | 30 | ]) | 38 | ]) |
400 | 31 | root = factory.get_resource_at_path('/') | 39 | root = factory.get_resource_at_path('/') |
402 | 32 | self.assertEqual('/Home', canonical_url(root.default_resource)) | 40 | self.assertUrl(root.default_resource, '/Home') |
403 | 33 | 41 | ||
404 | 34 | def test_default_view(self): | 42 | def test_default_view(self): |
405 | 35 | factory = self.make_factory([ | 43 | factory = self.make_factory([ |
406 | 36 | ('Home.txt', 'Some content'), | 44 | ('Home.txt', 'Some content'), |
407 | 37 | ]) | 45 | ]) |
408 | 38 | root = factory.get_resource_at_path('/') | 46 | root = factory.get_resource_at_path('/') |
412 | 39 | self.assertEqual( | 47 | self.assertUrl(root.default_resource, '/Home/+edit', view_name='edit') |
410 | 40 | '/Home/+edit', | ||
411 | 41 | canonical_url(root.default_resource, 'edit')) | ||
413 | 42 | 48 | ||
414 | 43 | def test_wiki_page(self): | 49 | def test_wiki_page(self): |
415 | 44 | factory = self.make_factory([ | 50 | factory = self.make_factory([ |
416 | 45 | ('SomeDir/SomePage.txt', 'Some content'), | 51 | ('SomeDir/SomePage.txt', 'Some content'), |
417 | 46 | ]) | 52 | ]) |
418 | 47 | page = factory.get_resource_at_path('/SomeDir/SomePage') | 53 | page = factory.get_resource_at_path('/SomeDir/SomePage') |
420 | 48 | self.assertEqual('/SomeDir/SomePage', canonical_url(page)) | 54 | self.assertUrl(page, '/SomeDir/SomePage') |
421 | 49 | 55 | ||
422 | 50 | def test_wiki_page_view(self): | 56 | def test_wiki_page_view(self): |
423 | 51 | factory = self.make_factory([ | 57 | factory = self.make_factory([ |
424 | 52 | ('SomeDir/SomePage.txt', 'Some content'), | 58 | ('SomeDir/SomePage.txt', 'Some content'), |
425 | 53 | ]) | 59 | ]) |
426 | 54 | page = factory.get_resource_at_path('/SomeDir/SomePage') | 60 | page = factory.get_resource_at_path('/SomeDir/SomePage') |
429 | 55 | self.assertEqual( | 61 | self.assertUrl(page, '/SomeDir/SomePage/+edit', view_name='edit') |
428 | 56 | '/SomeDir/SomePage/+edit', canonical_url(page, 'edit')) | ||
430 | 57 | 62 | ||
431 | 58 | def test_wiki_page_full_url(self): | 63 | def test_wiki_page_full_url(self): |
432 | 59 | factory = self.make_factory([ | 64 | factory = self.make_factory([ |
433 | 60 | ('SomeDir.txt', 'Some content'), | 65 | ('SomeDir.txt', 'Some content'), |
434 | 61 | ]) | 66 | ]) |
435 | 62 | page = factory.get_resource_at_path('/SomeDir.txt') | 67 | page = factory.get_resource_at_path('/SomeDir.txt') |
437 | 63 | self.assertEqual('/SomeDir', canonical_url(page)) | 68 | self.assertUrl(page, '/SomeDir') |
438 | 64 | 69 | ||
439 | 65 | def test_wiki_page_full_url_with_view(self): | 70 | def test_wiki_page_full_url_with_view(self): |
440 | 66 | factory = self.make_factory([ | 71 | factory = self.make_factory([ |
441 | 67 | ('SomeDir.txt', 'Some content'), | 72 | ('SomeDir.txt', 'Some content'), |
442 | 68 | ]) | 73 | ]) |
443 | 69 | page = factory.get_resource_at_path('/SomeDir.txt') | 74 | page = factory.get_resource_at_path('/SomeDir.txt') |
445 | 70 | self.assertEqual('/SomeDir/+edit', canonical_url(page, 'edit')) | 75 | self.assertUrl(page, '/SomeDir/+edit', view_name='edit') |
446 | 71 | 76 | ||
447 | 72 | def test_other_file(self): | 77 | def test_other_file(self): |
448 | 73 | factory = self.make_factory([ | 78 | factory = self.make_factory([ |
449 | 74 | ('simple.py', '#!/usr/bin/python'), | 79 | ('simple.py', '#!/usr/bin/python'), |
450 | 75 | ]) | 80 | ]) |
451 | 76 | page = factory.get_resource_at_path('/simple.py') | 81 | page = factory.get_resource_at_path('/simple.py') |
453 | 77 | self.assertEqual('/simple.py', canonical_url(page)) | 82 | self.assertUrl(page, '/simple.py') |
454 | 78 | 83 | ||
455 | 79 | def test_other_file_view(self): | 84 | def test_other_file_view(self): |
456 | 80 | factory = self.make_factory([ | 85 | factory = self.make_factory([ |
457 | 81 | ('simple.py', '#!/usr/bin/python'), | 86 | ('simple.py', '#!/usr/bin/python'), |
458 | 82 | ]) | 87 | ]) |
459 | 83 | page = factory.get_resource_at_path('/simple.py') | 88 | page = factory.get_resource_at_path('/simple.py') |
461 | 84 | self.assertEqual('/simple.py/+edit', canonical_url(page, 'edit')) | 89 | self.assertUrl(page, '/simple.py/+edit', view_name='edit') |
462 | 85 | 90 | ||
463 | 86 | def test_missing(self): | 91 | def test_missing(self): |
464 | 87 | factory = self.make_factory() | 92 | factory = self.make_factory() |
467 | 88 | root = factory.get_resource_at_path('/MissingPage') | 93 | missing = factory.get_resource_at_path('/MissingPage') |
468 | 89 | self.assertEqual('/MissingPage', canonical_url(root)) | 94 | self.assertUrl(missing, '/MissingPage') |
469 | 90 | 95 | ||
470 | 91 | def test_missing_view(self): | 96 | def test_missing_view(self): |
471 | 92 | factory = self.make_factory() | 97 | factory = self.make_factory() |
474 | 93 | root = factory.get_resource_at_path('/MissingPage') | 98 | missing = factory.get_resource_at_path('/MissingPage') |
475 | 94 | self.assertEqual('/MissingPage/+edit', canonical_url(root, 'edit')) | 99 | self.assertUrl(missing, '/MissingPage/+edit', view_name='edit') |
476 | 95 | 100 | ||
477 | 96 | 101 | ||
478 | 97 | class TestParseUrl(TestCase): | 102 | class TestParseUrl(TestCase): |
479 | 98 | 103 | ||
480 | === modified file 'wikkid/view/base.py' | |||
481 | --- wikkid/view/base.py 2010-11-22 09:23:40 +0000 | |||
482 | +++ wikkid/view/base.py 2012-05-17 11:13:18 +0000 | |||
483 | @@ -29,8 +29,8 @@ | |||
484 | 29 | class Breadcrumb(object): | 29 | class Breadcrumb(object): |
485 | 30 | """Breadcrumbs exist to give the user quick links up the path chain.""" | 30 | """Breadcrumbs exist to give the user quick links up the path chain.""" |
486 | 31 | 31 | ||
489 | 32 | def __init__(self, context, view=None, title=None): | 32 | def __init__(self, context, request, view=None, title=None): |
490 | 33 | self.path = canonical_url(context, view) | 33 | self.path = canonical_url(context, request, view) |
491 | 34 | if title is None: | 34 | if title is None: |
492 | 35 | self.title = title_for_filename(context.base_name) | 35 | self.title = title_for_filename(context.base_name) |
493 | 36 | else: | 36 | else: |
494 | @@ -54,14 +54,14 @@ | |||
495 | 54 | self.logger = logging.getLogger('wikkid') | 54 | self.logger = logging.getLogger('wikkid') |
496 | 55 | 55 | ||
497 | 56 | def _create_breadcrumbs(self): | 56 | def _create_breadcrumbs(self): |
499 | 57 | crumbs = [Breadcrumb(self.context)] | 57 | crumbs = [Breadcrumb(self.context, self.request)] |
500 | 58 | current = self.context.parent | 58 | current = self.context.parent |
501 | 59 | while not IRootResource.providedBy(current): | 59 | while not IRootResource.providedBy(current): |
503 | 60 | crumbs.append(Breadcrumb(current)) | 60 | crumbs.append(Breadcrumb(current, self.request)) |
504 | 61 | current = current.parent | 61 | current = current.parent |
505 | 62 | # And add in the default page if the context isn't the default. | 62 | # And add in the default page if the context isn't the default. |
506 | 63 | if not IDefaultPage.providedBy(self.context): | 63 | if not IDefaultPage.providedBy(self.context): |
508 | 64 | crumbs.append(Breadcrumb(current.default_resource)) | 64 | crumbs.append(Breadcrumb(current.default_resource, self.request)) |
509 | 65 | return reversed(crumbs) | 65 | return reversed(crumbs) |
510 | 66 | 66 | ||
511 | 67 | def initialize(self): | 67 | def initialize(self): |
512 | @@ -89,6 +89,9 @@ | |||
513 | 89 | def before_render(self): | 89 | def before_render(self): |
514 | 90 | """A hook to do things before rendering.""" | 90 | """A hook to do things before rendering.""" |
515 | 91 | 91 | ||
516 | 92 | def canonical_url(self, context, view=None): | ||
517 | 93 | return canonical_url(context, self.request, view) | ||
518 | 94 | |||
519 | 92 | def template_args(self): | 95 | def template_args(self): |
520 | 93 | """Needs to be implemented in the derived classes. | 96 | """Needs to be implemented in the derived classes. |
521 | 94 | 97 | ||
522 | @@ -99,7 +102,7 @@ | |||
523 | 99 | 'user': self.user, | 102 | 'user': self.user, |
524 | 100 | 'context': self.context, | 103 | 'context': self.context, |
525 | 101 | 'request': self.request, | 104 | 'request': self.request, |
527 | 102 | 'canonical_url': canonical_url, | 105 | 'canonical_url': self.canonical_url, |
528 | 103 | } | 106 | } |
529 | 104 | 107 | ||
530 | 105 | def _render(self, skin): | 108 | def _render(self, skin): |
531 | @@ -139,12 +142,13 @@ | |||
532 | 139 | view = None | 142 | view = None |
533 | 140 | while not IRootResource.providedBy(current): | 143 | while not IRootResource.providedBy(current): |
534 | 141 | crumbs.append(Breadcrumb( | 144 | crumbs.append(Breadcrumb( |
536 | 142 | current, view, title=current.base_name)) | 145 | current, self.request, view, title=current.base_name)) |
537 | 143 | current = current.parent | 146 | current = current.parent |
538 | 144 | # Add listings to subsequent urls. | 147 | # Add listings to subsequent urls. |
539 | 145 | view = 'listing' | 148 | view = 'listing' |
540 | 146 | # Add in the root dir. | 149 | # Add in the root dir. |
542 | 147 | crumbs.append(Breadcrumb(current, 'listing', title='wiki root')) | 150 | crumbs.append(Breadcrumb(current, self.request, 'listing', |
543 | 151 | title='wiki root')) | ||
544 | 148 | # And add in the default page. | 152 | # And add in the default page. |
546 | 149 | crumbs.append(Breadcrumb(current.default_resource)) | 153 | crumbs.append(Breadcrumb(current.default_resource, self.request)) |
547 | 150 | return reversed(crumbs) | 154 | return reversed(crumbs) |
548 | 151 | 155 | ||
549 | === modified file 'wikkid/view/directory.py' | |||
550 | --- wikkid/view/directory.py 2010-06-23 10:57:14 +0000 | |||
551 | +++ wikkid/view/directory.py 2012-05-17 11:13:18 +0000 | |||
552 | @@ -14,9 +14,9 @@ | |||
553 | 14 | class ListingItem(object): | 14 | class ListingItem(object): |
554 | 15 | """An item to be shown in the directory listing.""" | 15 | """An item to be shown in the directory listing.""" |
555 | 16 | 16 | ||
557 | 17 | def __init__(self, context, view, css_class, name=None): | 17 | def __init__(self, context, request, view, css_class, name=None): |
558 | 18 | self.context = context | 18 | self.context = context |
560 | 19 | self.url = canonical_url(self.context, view) | 19 | self.url = canonical_url(self.context, request, view) |
561 | 20 | if name is None: | 20 | if name is None: |
562 | 21 | name = context.base_name | 21 | name = context.base_name |
563 | 22 | self.name = name | 22 | self.name = name |
564 | @@ -53,11 +53,11 @@ | |||
565 | 53 | if self.context.path != '/': | 53 | if self.context.path != '/': |
566 | 54 | parent = self.context.parent | 54 | parent = self.context.parent |
567 | 55 | items.append( | 55 | items.append( |
569 | 56 | ListingItem(parent, 'listing', 'up', name='..')) | 56 | ListingItem(parent, self.request, 'listing', 'up', name='..')) |
570 | 57 | for item in sorted(directories, key=sort_key): | 57 | for item in sorted(directories, key=sort_key): |
572 | 58 | items.append(ListingItem(item, 'listing', 'directory')) | 58 | items.append(ListingItem(item, self.request, 'listing', 'directory')) |
573 | 59 | for item in sorted(files, key=sort_key): | 59 | for item in sorted(files, key=sort_key): |
575 | 60 | items.append(ListingItem(item, None, 'file')) | 60 | items.append(ListingItem(item, self.request, None, 'file')) |
576 | 61 | self.items = items | 61 | self.items = items |
577 | 62 | 62 | ||
578 | 63 | @property | 63 | @property |
579 | 64 | 64 | ||
580 | === modified file 'wikkid/view/edit.py' | |||
581 | --- wikkid/view/edit.py 2010-06-17 10:45:52 +0000 | |||
582 | +++ wikkid/view/edit.py 2012-05-17 11:13:18 +0000 | |||
583 | @@ -24,7 +24,7 @@ | |||
584 | 24 | @property | 24 | @property |
585 | 25 | def save_url(self): | 25 | def save_url(self): |
586 | 26 | """The link for the cancel button.""" | 26 | """The link for the cancel button.""" |
588 | 27 | return canonical_url(self.context, 'save') | 27 | return canonical_url(self.context, self.request, 'save') |
589 | 28 | 28 | ||
590 | 29 | @property | 29 | @property |
591 | 30 | def cancel_url(self): | 30 | def cancel_url(self): |
592 | 31 | 31 | ||
593 | === modified file 'wikkid/view/root.py' | |||
594 | --- wikkid/view/root.py 2010-06-17 10:45:52 +0000 | |||
595 | +++ wikkid/view/root.py 2012-05-17 11:13:18 +0000 | |||
596 | @@ -23,4 +23,6 @@ | |||
597 | 23 | """Redirect to Home (or the default page).""" | 23 | """Redirect to Home (or the default page).""" |
598 | 24 | default_resource = self.context.default_resource | 24 | default_resource = self.context.default_resource |
599 | 25 | preferred = default_resource.preferred_path | 25 | preferred = default_resource.preferred_path |
600 | 26 | if self.request.script_name: | ||
601 | 27 | preferred = self.request.script_name + preferred | ||
602 | 26 | raise HTTPSeeOther(location=preferred) | 28 | raise HTTPSeeOther(location=preferred) |
603 | 27 | 29 | ||
604 | === modified file 'wikkid/view/urls.py' | |||
605 | --- wikkid/view/urls.py 2010-06-16 10:29:35 +0000 | |||
606 | +++ wikkid/view/urls.py 2012-05-17 11:13:18 +0000 | |||
607 | @@ -24,12 +24,12 @@ | |||
608 | 24 | return (path, None) | 24 | return (path, None) |
609 | 25 | 25 | ||
610 | 26 | 26 | ||
612 | 27 | def canonical_url(context, view=None): | 27 | def canonical_url(context, request, view=None): |
613 | 28 | """The one true URL for the context object.""" | 28 | """The one true URL for the context object.""" |
614 | 29 | path = context.preferred_path | 29 | path = context.preferred_path |
615 | 30 | if view is None: | 30 | if view is None: |
617 | 31 | return path | 31 | return '{0}{1}'.format(request.script_name, path) |
618 | 32 | else: | 32 | else: |
619 | 33 | if path == '/': | 33 | if path == '/': |
620 | 34 | path = '' | 34 | path = '' |
622 | 35 | return '{0}/+{1}'.format(path, view) | 35 | return '{0}{1}/+{2}'.format(request.script_name, path, view) |
I approve :-)