Merge lp:~bloodearnest/django-preflight/gargoyle into lp:~canonical-isd-hackers/django-preflight/gargoyle
- gargoyle
- Merge into gargoyle
Status: | Superseded |
---|---|
Proposed branch: | lp:~bloodearnest/django-preflight/gargoyle |
Merge into: | lp:~canonical-isd-hackers/django-preflight/gargoyle |
Diff against target: |
1016 lines (+274/-327) 20 files modified
MANIFEST.in (+1/-0) debian/changelog (+9/-0) debian/rules (+0/-1) doc/conf.py (+1/-1) doc/install.rst (+13/-9) example_project/app/preflight.py (+0/-38) example_project/app/templates/base.html (+0/-57) example_project/manage.py (+0/-13) example_project/run.py (+0/-31) example_project/settings.py (+0/-44) example_project/urls.py (+0/-15) preflight/__init__.py (+1/-1) preflight/conf.py (+6/-0) preflight/models.py (+41/-8) preflight/templates/preflight/overview.html (+32/-14) preflight/tests.py (+117/-31) preflight/views.py (+8/-9) setup.py (+17/-13) tox (+0/-6) tox.ini (+28/-36) |
To merge this branch: | bzr merge lp:~bloodearnest/django-preflight/gargoyle |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Canonical ISD hackers | Pending | ||
Review via email: mp+166838@code.launchpad.net |
Commit message
Description of the change
Add support for showing switches. Initially just gargoyle.
Unmerged revisions
- 26. By Simon Davy
-
more generic (less gargoyle specific), more gargoyle tests, embedded json switch data for automated scraping
- 25. By Simon Davy
-
Merge mfoord's branch into trunk, and make gargoyle optional in the process
- 24. By Łukasz Czyżykowski
-
[r=ricardokirkner] Updated code and tests to work with Django 1.5; dropped support for Python 2.5
- 23. By Anthony Lenton
-
[r=james-w] Fixed a silly bug that meant that hidden settings weren't really configurable via the PREFLIGHT_
HIDDEN_ SETTINGS setting. - 22. By Łukasz Czyżykowski
-
Updated debian/rules to be compatible with precise.
- 21. By Łukasz Czyżykowski
-
Added entry in the changelog
- 20. By Łukasz Czyżykowski
-
Merged the 1.4-release branch
- 19. By Anthony Lenton
-
[r=lukasz-
czyzykowski] Added configurable hidden settings. - 18. By Māris Fogels
-
[r=lukasz-
czyzykowski] Changed the default display template to the Django admin theme. - 17. By Jonathan Lange
-
[r=lukasz-
czyzykowski] Add platform to default versions
Preview Diff
1 | === added file 'MANIFEST.in' |
2 | --- MANIFEST.in 1970-01-01 00:00:00 +0000 |
3 | +++ MANIFEST.in 2013-05-31 15:53:26 +0000 |
4 | @@ -0,0 +1,1 @@ |
5 | +include preflight/templates/preflight/*.html |
6 | \ No newline at end of file |
7 | |
8 | === modified file 'debian/changelog' |
9 | --- debian/changelog 2011-10-04 14:33:24 +0000 |
10 | +++ debian/changelog 2013-05-31 15:53:26 +0000 |
11 | @@ -1,3 +1,12 @@ |
12 | +django-preflight (0.1.4) precise; urgency=low |
13 | + |
14 | + * Added display of settings on the preflight page |
15 | + * Added configurable list of settings to hide |
16 | + * Changed default base tamplate to Django admin base |
17 | + * Added platform to default versions |
18 | + |
19 | + -- Łukasz Czyżykowski <lukasz.czyzykowski@canonical.com> Wed, 03 Oct 2012 09:45:22 +0000 |
20 | + |
21 | django-preflight (0.1.1) lucid; urgency=low |
22 | |
23 | * Use RequestContext instance when rendering the overview view |
24 | |
25 | === modified file 'debian/rules' |
26 | --- debian/rules 2011-02-07 16:06:51 +0000 |
27 | +++ debian/rules 2013-05-31 15:53:26 +0000 |
28 | @@ -4,4 +4,3 @@ |
29 | |
30 | include /usr/share/cdbs/1/rules/debhelper.mk |
31 | include /usr/share/cdbs/1/class/python-distutils.mk |
32 | -include /usr/share/cdbs/1/rules/langpack.mk |
33 | |
34 | === modified file 'doc/conf.py' |
35 | --- doc/conf.py 2011-02-09 16:26:04 +0000 |
36 | +++ doc/conf.py 2013-05-31 15:53:26 +0000 |
37 | @@ -18,7 +18,7 @@ |
38 | # documentation root, use os.path.abspath to make it absolute, like shown here. |
39 | sys.path.append(os.path.abspath('..')) |
40 | |
41 | -os.environ['DJANGO_SETTINGS_MODULE'] = 'example_project.settings' |
42 | +os.environ['DJANGO_SETTINGS_MODULE'] = 'preflight_example_project.settings' |
43 | |
44 | # -- General configuration ----------------------------------------------------- |
45 | |
46 | |
47 | === modified file 'doc/install.rst' |
48 | --- doc/install.rst 2012-05-25 16:34:25 +0000 |
49 | +++ doc/install.rst 2013-05-31 15:53:26 +0000 |
50 | @@ -38,19 +38,21 @@ |
51 | |
52 | django-preflight by itself doesn't have any extra dependecies. |
53 | |
54 | -Because this project doesn't include any database models, there's no |
55 | -need of updating your database schema. |
56 | +Because this project doesn't include any models, there's no need of |
57 | +updating your database schema. |
58 | |
59 | |
60 | Update ``urls.py`` |
61 | ------------------ |
62 | |
63 | -Last bit of configuration is to include django-preflight into the |
64 | -project's ``urls.py`` file. It should look like the following: |
65 | +Last bit of configuration is to run preflight discovery code and to |
66 | +include it somewhere in the url's definition. The easiest way for |
67 | +doing both of those steps is to modify project's ``urls.py`` file. It |
68 | +should look like the following: |
69 | |
70 | .. testcode:: |
71 | |
72 | - from django.conf.urls.defaults import * |
73 | + from django.conf.urls import * |
74 | |
75 | import preflight |
76 | import preflight.urls |
77 | @@ -78,10 +80,12 @@ |
78 | Compatibility |
79 | ------------- |
80 | |
81 | -django-preflight is tested with released Django versions from |
82 | -1.1 to 1.4. To be sure about this it's tested |
83 | -with tox_ against all of these versions. |
84 | +django-preflight is compatible with released Django versions since |
85 | +1.1, up to current 1.5. To be sure about this it's tested using tox_ |
86 | +against all of them. |
87 | |
88 | -Additionally it's tested on Python 2.5, 2.6 and 2.7, all on Ubuntu Linux. |
89 | +Additionally it's tested on Python 2.6 and 2.7, all of that on `Ubuntu |
90 | +Linux`_. |
91 | |
92 | .. _tox: http://codespeak.net/tox/ |
93 | + _Ubuntu Linux: http://www.ubuntu.com |
94 | |
95 | === removed directory 'example_project' |
96 | === removed file 'example_project/__init__.py' |
97 | === removed directory 'example_project/app' |
98 | === removed file 'example_project/app/__init__.py' |
99 | === removed file 'example_project/app/models.py' |
100 | === removed file 'example_project/app/preflight.py' |
101 | --- example_project/app/preflight.py 2011-02-07 16:06:51 +0000 |
102 | +++ example_project/app/preflight.py 1970-01-01 00:00:00 +0000 |
103 | @@ -1,38 +0,0 @@ |
104 | -# Copyright 2010 Canonical Ltd. This software is licensed under the |
105 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
106 | - |
107 | -from __future__ import absolute_import |
108 | - |
109 | -from django.conf import settings |
110 | - |
111 | -import os.path |
112 | -import preflight |
113 | -import tempfile |
114 | - |
115 | - |
116 | -class AppPreflight(preflight.Preflight): |
117 | - |
118 | - def authenticate(self, request): |
119 | - # Allow everybody to access this page. Default implementation restricts |
120 | - # this to people who can access /admin/ pages (user.is_stuff == True). |
121 | - return True |
122 | - |
123 | - def versions(self): |
124 | - return [{'name': 'foo-bar', 'version': '1.2.3'}] |
125 | - |
126 | - def check_media_root_is_writable(self): |
127 | - """Check if current MEDIA_ROOT directory is writable.""" |
128 | - path = os.path.realpath(settings.MEDIA_ROOT) |
129 | - |
130 | - # Try writing to temporary file in MEDIA_ROOT |
131 | - tmp = tempfile.TemporaryFile(dir=path) |
132 | - tmp.write("test") |
133 | - tmp.close() |
134 | - |
135 | - # If we reached this point this means the directory itself is |
136 | - # writable. In case of any errors an exception would cause the check to |
137 | - # fail. Return value informs preflight that everything went right. |
138 | - return True |
139 | - |
140 | - |
141 | -preflight.register(AppPreflight) |
142 | |
143 | === removed directory 'example_project/app/templates' |
144 | === removed file 'example_project/app/templates/base.html' |
145 | --- example_project/app/templates/base.html 2011-02-07 16:06:51 +0000 |
146 | +++ example_project/app/templates/base.html 1970-01-01 00:00:00 +0000 |
147 | @@ -1,57 +0,0 @@ |
148 | -{# Copyright 2010 Canonical Ltd. This software is licensed under the #} |
149 | -{# GNU Affero General Public License version 3 (see the file LICENSE). #} |
150 | -<html> |
151 | - <head> |
152 | - <title>{% block title %}{% endblock %}</title> |
153 | - <style> |
154 | - #content { |
155 | - font: 13.34px helvetica, arial, freesans, clean, sans-serif; |
156 | - width: 600px; |
157 | - margin: 32px auto; |
158 | - border: 1px solid #ddd; |
159 | - padding: 2em; |
160 | - background-color: #f0f0f0; |
161 | - } |
162 | - #content p { |
163 | - margin-top: 32px; |
164 | - border-top: 1px solid #999; |
165 | - text-align: center; |
166 | - } |
167 | - #content h1 { |
168 | - margin: 64px 0 32px; |
169 | - border-bottom: 3px solid #555; |
170 | - } |
171 | - #content h2 { |
172 | - margin: 32px 0; |
173 | - border-bottom: 3px solid #999; |
174 | - } |
175 | - #content table { |
176 | - padding: 0; |
177 | - border-spacing: 0; |
178 | - width: 100%; |
179 | - } |
180 | - #content table th { |
181 | - border-bottom: 1px solid #aaa; |
182 | - } |
183 | - #content table td, |
184 | - #content table th { |
185 | - padding: 0.3em; |
186 | - } |
187 | - #content .ok { |
188 | - color: green; |
189 | - } |
190 | - #content .error { |
191 | - color: red; |
192 | - } |
193 | - #content td.status { |
194 | - text-align: center; |
195 | - } |
196 | - #content td.description { |
197 | - font-size: 80%; |
198 | - } |
199 | - </style> |
200 | - </head> |
201 | - <body> |
202 | - <div id="content">{% block content %}{% endblock %}</div> |
203 | - </body> |
204 | -</html> |
205 | |
206 | === removed file 'example_project/manage.py' |
207 | --- example_project/manage.py 2011-02-07 16:06:51 +0000 |
208 | +++ example_project/manage.py 1970-01-01 00:00:00 +0000 |
209 | @@ -1,13 +0,0 @@ |
210 | -#!/usr/bin/python |
211 | -import sys |
212 | -sys.path.append('..') |
213 | - |
214 | -from django.core.management import execute_manager |
215 | -try: |
216 | - import settings # Assumed to be in the same directory. |
217 | -except ImportError: |
218 | - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) |
219 | - sys.exit(1) |
220 | - |
221 | -if __name__ == "__main__": |
222 | - execute_manager(settings) |
223 | |
224 | === removed file 'example_project/run.py' |
225 | --- example_project/run.py 2011-02-07 16:06:51 +0000 |
226 | +++ example_project/run.py 1970-01-01 00:00:00 +0000 |
227 | @@ -1,31 +0,0 @@ |
228 | -import os |
229 | -import sys |
230 | - |
231 | -os.environ['DJANGO_SETTINGS_MODULE'] = 'example_project.settings' |
232 | -sys.path.insert(0, 'example_project') |
233 | - |
234 | -try: |
235 | - from django.test.utils import get_runner |
236 | -except ImportError: |
237 | - from django.test.simple import run_tests |
238 | - get_runner = lambda s: run_tests |
239 | - |
240 | -from django.conf import settings |
241 | - |
242 | - |
243 | - |
244 | -def tests(): |
245 | - TestRunner = get_runner(settings) |
246 | - if hasattr(TestRunner, 'func_name'): |
247 | - # Before change in Django 1.2 |
248 | - failures = TestRunner(['preflight']) |
249 | - else: |
250 | - test_runner = TestRunner() |
251 | - failures = test_runner.run_tests(['preflight']) |
252 | - |
253 | - sys.exit(bool(failures)) |
254 | - |
255 | - |
256 | - |
257 | -if __name__ == '__main__': |
258 | - tests() |
259 | |
260 | === removed file 'example_project/settings.py' |
261 | --- example_project/settings.py 2012-05-25 16:31:15 +0000 |
262 | +++ example_project/settings.py 1970-01-01 00:00:00 +0000 |
263 | @@ -1,44 +0,0 @@ |
264 | -import os |
265 | - |
266 | -DEBUG = True |
267 | -TEMPLATE_DEBUG = DEBUG |
268 | - |
269 | -ADMINS = () |
270 | -MANAGERS = ADMINS |
271 | - |
272 | -DATABASES = { |
273 | - 'default': { |
274 | - 'ENGINE': 'django.db.backends.sqlite3', |
275 | - 'NAME': 'database.sqlit3', |
276 | - } |
277 | -} |
278 | - |
279 | -# Old db setup |
280 | -DATABASE_ENGINE = 'sqlite3' |
281 | -DATABASE_NAME = 'database.sqlite3' |
282 | - |
283 | -TIME_ZONE = 'America/Chicago' |
284 | -LANGUAGE_CODE = 'en-us' |
285 | -SITE_ID = 1 |
286 | -MEDIA_ROOT = '' |
287 | -MEDIA_URL = '' |
288 | -ADMIN_MEDIA_PREFIX = '/media/' |
289 | -SECRET_KEY = 'j0-wo!y4zwi)tejv1u(0skc41_379ls^@qbh91%#5o=greje6(' |
290 | - |
291 | -ROOT_URLCONF = 'example_project.urls' |
292 | - |
293 | -TEMPLATE_DIRS = ( |
294 | - os.path.join(os.path.realpath(os.path.dirname(__file__)), 'templates'), |
295 | -) |
296 | - |
297 | -INSTALLED_APPS = ( |
298 | - 'django.contrib.auth', |
299 | - 'django.contrib.contenttypes', |
300 | - 'django.contrib.sessions', |
301 | - 'django.contrib.sites', |
302 | - 'preflight', |
303 | - 'app', |
304 | - 'gargoyle', |
305 | -) |
306 | - |
307 | -PREFLIGHT_BASE_TEMPLATE = 'base.html' |
308 | |
309 | === removed file 'example_project/urls.py' |
310 | --- example_project/urls.py 2011-10-04 15:54:50 +0000 |
311 | +++ example_project/urls.py 1970-01-01 00:00:00 +0000 |
312 | @@ -1,15 +0,0 @@ |
313 | -# Copyright 2010 Canonical Ltd. This software is licensed under the |
314 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
315 | - |
316 | -from django.conf.urls.defaults import ( |
317 | - handler404, handler500, include, patterns |
318 | -) |
319 | - |
320 | -import preflight |
321 | - |
322 | - |
323 | -preflight.autodiscover() |
324 | - |
325 | -urlpatterns = patterns('', |
326 | - (r'^preflight/', include('preflight.urls')), |
327 | -) |
328 | |
329 | === modified file 'preflight/__init__.py' |
330 | --- preflight/__init__.py 2011-03-19 15:15:41 +0000 |
331 | +++ preflight/__init__.py 2013-05-31 15:53:26 +0000 |
332 | @@ -5,7 +5,7 @@ |
333 | |
334 | from .models import REGISTRY |
335 | |
336 | -__version__ = "0.1.1" |
337 | +__version__ = "0.1.5" |
338 | |
339 | |
340 | def autodiscover(): |
341 | |
342 | === added file 'preflight/conf.py' |
343 | --- preflight/conf.py 1970-01-01 00:00:00 +0000 |
344 | +++ preflight/conf.py 2013-05-31 15:53:26 +0000 |
345 | @@ -0,0 +1,6 @@ |
346 | +from django.conf import settings |
347 | + |
348 | + |
349 | +BASE_TEMPLATE = getattr( |
350 | + settings, 'PREFLIGHT_BASE_TEMPLATE', "admin/base.html") |
351 | +TABLE_CLASS = getattr(settings, 'PREFLIGHT_TABLE_CLASS', "listing") |
352 | |
353 | === modified file 'preflight/models.py' |
354 | --- preflight/models.py 2012-05-25 16:31:15 +0000 |
355 | +++ preflight/models.py 2013-05-31 15:53:26 +0000 |
356 | @@ -2,6 +2,8 @@ |
357 | # GNU Affero General Public License version 3 (see the file LICENSE). |
358 | |
359 | from __future__ import absolute_import |
360 | + |
361 | +import re |
362 | from django.conf import settings |
363 | from django.views.debug import HIDDEN_SETTINGS |
364 | |
365 | @@ -75,11 +77,13 @@ |
366 | data. |
367 | |
368 | """ |
369 | + import platform |
370 | import sys |
371 | import django |
372 | import preflight |
373 | |
374 | items = [ |
375 | + {'name': 'Platform', 'version': platform.platform()}, |
376 | {'name': 'Django', 'version': django.get_version()}, |
377 | {'name': 'Python', 'version': sys.version}, |
378 | {'name': 'preflight', 'version': preflight.__version__} |
379 | @@ -115,8 +119,13 @@ |
380 | names = sorted([x for x in settings._wrapped.__dict__ |
381 | if x.isupper() and not x.startswith('_')]) |
382 | settings_list = [] |
383 | + hidden_settings = getattr(settings, 'PREFLIGHT_HIDDEN_SETTINGS', None) |
384 | + if hidden_settings: |
385 | + hidden_settings = re.compile(hidden_settings) |
386 | + else: |
387 | + hidden_settings = HIDDEN_SETTINGS |
388 | for name in names: |
389 | - if HIDDEN_SETTINGS.match(name): |
390 | + if hidden_settings.search(name): |
391 | value = '******************' |
392 | else: |
393 | value = getattr(settings, name, None) |
394 | @@ -135,16 +144,40 @@ |
395 | return all(class_().authenticate(request) for class_ in REGISTRY) |
396 | |
397 | |
398 | +def gather_switches(): |
399 | + """Gather all switches into one list.""" |
400 | + switches = {} |
401 | + if settings.USE_GARGOYLE: |
402 | + gargoyle_switches = gather_gargoyle() |
403 | + if gargoyle_switches: |
404 | + switches['gargoyle'] = gargoyle_switches |
405 | + return switches |
406 | + |
407 | + |
408 | def gather_gargoyle(): |
409 | + if not settings.USE_GARGOYLE: |
410 | + return None |
411 | try: |
412 | from gargoyle.models import Switch |
413 | except ImportError: |
414 | return None |
415 | - switches = Switch.objects.all() |
416 | - return [_format_switch(switch) for switch in switches] |
417 | - |
418 | -def _format_switch(switch): |
419 | + return [_format_gargoyle(switch) for switch in Switch.objects.all()] |
420 | + |
421 | + |
422 | +def _format_gargoyle(switch): |
423 | + from gargoyle import gargoyle |
424 | + """Formats a gargoyle switch into std format for display""" |
425 | + d = switch.to_dict(gargoyle) |
426 | + conditions = [] |
427 | + for condition in d['conditions']: |
428 | + params = ','.join('%s=%s' % (c[0], c[2]) |
429 | + for c in condition['conditions']) |
430 | + conditions.append("%s(%s)" % (condition['label'], params)) |
431 | + |
432 | return dict( |
433 | - name=switch.key, description=switch.description or '', |
434 | - value=switch.value, status=switch.get_status_label() |
435 | - ) |
436 | \ No newline at end of file |
437 | + name=d['key'], |
438 | + description=d['description'], |
439 | + status=d['status'], |
440 | + status_text=d['statusLabel'], |
441 | + conditions=conditions, |
442 | + ) |
443 | |
444 | === modified file 'preflight/templates/preflight/overview.html' |
445 | --- preflight/templates/preflight/overview.html 2012-05-25 16:31:15 +0000 |
446 | +++ preflight/templates/preflight/overview.html 2013-05-31 15:53:26 +0000 |
447 | @@ -17,7 +17,9 @@ |
448 | {% endfor %} |
449 | <li><a href="#versions">Versions</a></li> |
450 | <li><a href="#settings">Settings</a></li> |
451 | - <li><a href="#gargoyle">Gargoyle</a></li> |
452 | + {% if switches %} |
453 | + <li><a href="#switches">Switches</a></li> |
454 | + {% endif %} |
455 | </ul> |
456 | |
457 | {% for application in applications %} |
458 | @@ -81,32 +83,48 @@ |
459 | {% endfor %} |
460 | </dl> |
461 | |
462 | + {% if switches %} |
463 | <a href="#links" style="text-decoration : none;"> |
464 | - <h1 id="gargoyle">{% trans "Gargoyle Switches" %}</h1> |
465 | + <h1 id="switches">{% trans "Switches" %}</h1> |
466 | </a> |
467 | - {% if gargoyle %} |
468 | - <table id="gargoyle-table" class="{{ preflight_table_class }}"> |
469 | + <table id="switches-table" class="{{ preflight_table_class }}"> |
470 | <thead> |
471 | <tr> |
472 | <th>{% trans "Name" %}</th> |
473 | <th>{% trans "Description" %}</th> |
474 | - <th>{% trans "value" %}</th> |
475 | + <th>{% trans "Conditions" %}</th> |
476 | <th>{% trans "Status" %}</th> |
477 | </tr> |
478 | </thead> |
479 | <tbody> |
480 | - {% for item in gargoyle %} |
481 | - <tr> |
482 | - <td>{{ item.name }}</td> |
483 | - <td>{{ item.description }}</td> |
484 | - <td>{{ item.value }}</td> |
485 | - <td>{{ item.status }}</td> |
486 | - </tr> |
487 | + {% for type, type_switches in switches.items %} |
488 | + <tr><th colspan="4">{{ type }}</th></tr> |
489 | + {% for switch in type_switches %} |
490 | + <tr class="switch"> |
491 | + <td>{{ switch.name }}</td> |
492 | + <td>{{ switch.description }}</td> |
493 | + <td> |
494 | + {% if switch.conditions %} |
495 | + <ul> |
496 | + {% for condition in switch.conditions %} |
497 | + <li>{{ condition|first }}: {{ condition|last }}</li> |
498 | + {% endfor %} |
499 | + </ul> |
500 | + {% endif %} |
501 | + </td> |
502 | + <td>{{ switch.status_text }}</td> |
503 | + {% endfor %} |
504 | + </tr> |
505 | {% endfor %} |
506 | </tbody> |
507 | - </table> |
508 | + </table> |
509 | + <p>Switch data is included in JSON format in a script tag with id |
510 | + "switches-json" to aid automated scripting</p> |
511 | + <script id="switches-json"> |
512 | + {{ switches_json|safe }} |
513 | + </script> |
514 | {% else %} |
515 | - <p>No gargoyle switches.</p> |
516 | + <p>No switches.</p> |
517 | {% endif %} |
518 | </div> |
519 | |
520 | |
521 | === modified file 'preflight/tests.py' |
522 | --- preflight/tests.py 2012-05-25 17:19:29 +0000 |
523 | +++ preflight/tests.py 2013-05-31 15:53:26 +0000 |
524 | @@ -1,19 +1,26 @@ |
525 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
526 | # GNU Affero General Public License version 3 (see the file LICENSE). |
527 | import sys |
528 | +import json |
529 | +try: |
530 | + from unittest import skipIf |
531 | +except ImportError: |
532 | + from unittest2 import skipIf # NOQA |
533 | |
534 | +from django.conf import settings |
535 | from django.core.management import call_command |
536 | from django.core.urlresolvers import reverse |
537 | from django.http import HttpResponse |
538 | from django.test import TestCase |
539 | -from django.template import RequestContext, TemplateDoesNotExist |
540 | - |
541 | +from django.template import RequestContext |
542 | +from django.template.loader import render_to_string |
543 | from mock import ( |
544 | Mock, |
545 | patch, |
546 | ) |
547 | |
548 | from cStringIO import StringIO |
549 | +from pyquery import PyQuery |
550 | |
551 | from . import Preflight |
552 | from .models import ( |
553 | @@ -22,11 +29,12 @@ |
554 | REGISTRY, |
555 | authenticate, |
556 | gather_checks, |
557 | + gather_switches, |
558 | gather_gargoyle, |
559 | gather_settings, |
560 | gather_versions, |
561 | ) |
562 | - |
563 | +from preflight.conf import BASE_TEMPLATE, TABLE_CLASS |
564 | |
565 | |
566 | class CheckTestCase(TestCase): |
567 | @@ -48,7 +56,8 @@ |
568 | self.assertEquals(check.description, "description") |
569 | |
570 | def test_initialisation_when_doc_is_none(self): |
571 | - def method(): pass |
572 | + def method(): |
573 | + pass |
574 | |
575 | check = Check("check_name", method) |
576 | |
577 | @@ -144,6 +153,7 @@ |
578 | def authenticate(self, request): |
579 | return False |
580 | |
581 | + |
582 | def clear_registry(): |
583 | while REGISTRY: |
584 | REGISTRY.pop() |
585 | @@ -184,6 +194,7 @@ |
586 | def versions(self): |
587 | return [{'name': 'spam', 'version': 'ni'}] |
588 | |
589 | + |
590 | class ExtraVersionAsList(Preflight): |
591 | |
592 | versions = [{'name': 'eggs', 'version': 'peng'}] |
593 | @@ -196,7 +207,7 @@ |
594 | |
595 | versions = gather_versions() |
596 | |
597 | - self.assertEquals(len(versions), 3) |
598 | + self.assertEquals(len(versions), 4) |
599 | for item in versions: |
600 | self.assertTrue('name' in item and 'version' in item) |
601 | |
602 | @@ -206,7 +217,7 @@ |
603 | |
604 | versions = gather_versions() |
605 | |
606 | - self.assertEquals(len(versions), 4) |
607 | + self.assertEquals(len(versions), 5) |
608 | self.assertEquals(versions[-1]['name'], 'spam') |
609 | |
610 | def test_get_extra_version_information_from_class_attribute(self): |
611 | @@ -215,7 +226,7 @@ |
612 | |
613 | versions = gather_versions() |
614 | |
615 | - self.assertEquals(len(versions), 4) |
616 | + self.assertEquals(len(versions), 5) |
617 | self.assertEquals(versions[-1]['version'], 'peng') |
618 | |
619 | |
620 | @@ -241,8 +252,9 @@ |
621 | def test_overview_when_not_authenticated(self, mock_authenticate): |
622 | mock_authenticate.return_value = False |
623 | |
624 | - self.assertRaises(TemplateDoesNotExist, self.client.get, |
625 | - reverse('preflight-overview')) |
626 | + response = self.client.get(reverse('preflight-overview')) |
627 | + |
628 | + self.assertEqual(response.status_code, 404) |
629 | |
630 | @patch('preflight.views.render_to_response') |
631 | @patch('preflight.views.authenticate') |
632 | @@ -262,6 +274,7 @@ |
633 | def test_gather_settings_no_location(self, mock_settings): |
634 | mock_settings._wrapped.FOO = 'bar' |
635 | mock_settings.FOO = 'bar' |
636 | + mock_settings.PREFLIGHT_HIDDEN_SETTINGS = '' |
637 | settings = gather_settings() |
638 | expected = [{'name': 'FOO', 'value': 'bar', 'location': ''}] |
639 | self.assertEqual(expected, settings) |
640 | @@ -270,6 +283,7 @@ |
641 | def test_gather_settings_with_location(self, mock_settings): |
642 | mock_settings._wrapped.FOO = 'bar' |
643 | mock_settings.FOO = 'bar' |
644 | + mock_settings.PREFLIGHT_HIDDEN_SETTINGS = '' |
645 | parser = Mock() |
646 | parser.locate.return_value = '/tmp/baz' |
647 | mock_settings.__CONFIGGLUE_PARSER__ = parser |
648 | @@ -279,43 +293,115 @@ |
649 | |
650 | @patch('preflight.models.settings') |
651 | def test_gather_settings_hidden(self, mock_settings): |
652 | - mock_settings._wrapped.SECRET_FOO = 'bar' |
653 | - mock_settings.SECRET_FOO = 'bar' |
654 | + mock_settings._wrapped.FOO_ZOGGLES = 'bar' |
655 | + mock_settings.FOO_ZOGGLES = 'bar' |
656 | + mock_settings.PREFLIGHT_HIDDEN_SETTINGS = 'ZOGGLES' |
657 | settings = gather_settings() |
658 | - expected = [{'name': 'SECRET_FOO', 'value': '*' * 18, 'location': ''}] |
659 | + expected = [{'name': 'FOO_ZOGGLES', 'value': '*' * 18, 'location': ''}] |
660 | self.assertEqual(expected, settings) |
661 | |
662 | @patch('preflight.models.settings') |
663 | def test_gather_settings_omitted(self, mock_settings): |
664 | mock_settings._wrapped._FOO = 'bar' |
665 | mock_settings._FOO = 'bar' |
666 | + mock_settings.PREFLIGHT_HIDDEN_SETTINGS = '' |
667 | settings = gather_settings() |
668 | self.assertEqual([], settings) |
669 | |
670 | |
671 | +@skipIf(not settings.USE_GARGOYLE, 'skipping for Django 1.1') |
672 | class GargoyleTestCase(TestCase): |
673 | + from gargoyle import gargoyle # NOQA |
674 | + from gargoyle.models import ( # NOQA |
675 | + Switch, |
676 | + DISABLED, |
677 | + SELECTIVE, |
678 | + GLOBAL, |
679 | + INHERIT |
680 | + ) |
681 | + |
682 | + def setUp(self): |
683 | + super(GargoyleTestCase, self).setUp() |
684 | + from gargoyle.builtins import IPAddressConditionSet |
685 | + |
686 | + self.gargoyle.register(IPAddressConditionSet()) |
687 | + |
688 | + def get_switches(self): |
689 | + switches = [ |
690 | + self.Switch(key='DISABLED', status=self.DISABLED, |
691 | + description='switch 1'), |
692 | + self.Switch(key='SELECTIVE_1', status=self.SELECTIVE), |
693 | + self.Switch(key='SELECTIVE_2', status=self.SELECTIVE), |
694 | + self.Switch(key='GLOBAL', status=self.GLOBAL), |
695 | + self.Switch(key='INHERIT', status=self.INHERIT), |
696 | + ] |
697 | + selective = switches[2] |
698 | + selective.add_condition( |
699 | + self.gargoyle, |
700 | + condition_set='gargoyle.builtins.IPAddressConditionSet', |
701 | + field_name='ip_address', |
702 | + condition='127.0.0.1', |
703 | + ) |
704 | + return switches |
705 | |
706 | @patch.dict(sys.modules, **{'gargoyle.models': None}) |
707 | - def test_gather_gargoyle_no_gargoyle(self): |
708 | + def test_gather_switches_no_gargoyle(self): |
709 | self.assertEqual(gather_gargoyle(), None) |
710 | |
711 | - @patch.dict(sys.modules, **{'gargoyle.models': Mock()}) |
712 | - def test_gather_gargoyle(self): |
713 | - # Inner import to get the mocked version |
714 | - from gargoyle.models import Switch as MockSwitch |
715 | - |
716 | - mocks = [Mock(), Mock(), Mock()] |
717 | - MockSwitch.objects.all.return_value = mocks |
718 | - switches = [ |
719 | - |
720 | - ] |
721 | - for switch, mock in zip(switches, mocks): |
722 | - for attr, value in switch: |
723 | - setattr(mock, attr, value) |
724 | - |
725 | + def assert_switches_dict(self, actual): |
726 | expected = [ |
727 | - {}, |
728 | - {}, |
729 | - {} |
730 | + dict(name='DISABLED', |
731 | + status=self.DISABLED, |
732 | + description='switch 1', |
733 | + status_text=self.Switch.STATUS_LABELS[self.DISABLED], |
734 | + conditions=[]), |
735 | + dict(name='SELECTIVE_1', |
736 | + status=self.SELECTIVE, |
737 | + description=None, |
738 | + status_text=self.Switch.STATUS_LABELS[self.GLOBAL], |
739 | + conditions=[]), |
740 | + dict(name='SELECTIVE_2', status=self.SELECTIVE, |
741 | + description=None, |
742 | + status_text=self.Switch.STATUS_LABELS[self.SELECTIVE], |
743 | + conditions=['IP Address(ip_address=127.0.0.1)']), |
744 | + dict(name='GLOBAL', status=self.GLOBAL, |
745 | + description=None, |
746 | + status_text=self.Switch.STATUS_LABELS[self.GLOBAL], |
747 | + conditions=[]), |
748 | + dict(name='INHERIT', status=self.INHERIT, |
749 | + description=None, |
750 | + status_text=self.Switch.STATUS_LABELS[self.INHERIT], |
751 | + conditions=[]), |
752 | ] |
753 | - #self.assertEqual(gather_gargoyle(), expected) |
754 | + self.assertEqual(actual, expected) |
755 | + |
756 | + @patch('gargoyle.models.Switch.objects.all') |
757 | + def test_gather_switches(self, mock_all): |
758 | + mock_all.return_value = self.get_switches() |
759 | + self.assert_switches_dict(gather_gargoyle()) |
760 | + |
761 | + @patch('gargoyle.models.Switch.objects.all') |
762 | + def test_gargoyle_template(self, mock_all): |
763 | + switches = self.get_switches() |
764 | + mock_all.return_value = switches |
765 | + the_switches = gather_switches() |
766 | + context = { |
767 | + "switches": the_switches, |
768 | + "switches_json": json.dumps(the_switches), |
769 | + "preflight_base_template": BASE_TEMPLATE, |
770 | + "preflight_table_class": TABLE_CLASS, |
771 | + } |
772 | + response = render_to_string('preflight/overview.html', context) |
773 | + dom = PyQuery(response) |
774 | + table = dom.find('#switches-table tbody') |
775 | + self.assertEqual(table.find('tr')[0][0].text, 'gargoyle') |
776 | + |
777 | + for row, switch in zip(table.find('tr.switch'), switches): |
778 | + self.assertEqual(row[0].text, switch.key) |
779 | + self.assertEqual(row[1].text, str(switch.description)) |
780 | + self.assertEqual(row[3].text, switch.get_status_label()) |
781 | + |
782 | + data = json.loads(dom.find('#switches-json').text()) |
783 | + self.assertTrue('gargoyle' in data) |
784 | + json_switches = data['gargoyle'] |
785 | + self.assert_switches_dict(json_switches) |
786 | |
787 | === modified file 'preflight/views.py' |
788 | --- preflight/views.py 2012-05-18 13:13:41 +0000 |
789 | +++ preflight/views.py 2013-05-31 15:53:26 +0000 |
790 | @@ -2,10 +2,10 @@ |
791 | # GNU Affero General Public License version 3 (see the file LICENSE). |
792 | |
793 | from datetime import datetime |
794 | +import json |
795 | |
796 | from django.views.decorators.cache import never_cache |
797 | from django.http import Http404 |
798 | -from django.conf import settings |
799 | from django.shortcuts import render_to_response |
800 | from django.template import RequestContext |
801 | |
802 | @@ -14,8 +14,9 @@ |
803 | gather_checks, |
804 | gather_settings, |
805 | gather_versions, |
806 | - gather_gargoyle, |
807 | + gather_switches, |
808 | ) |
809 | +from .conf import BASE_TEMPLATE, TABLE_CLASS |
810 | |
811 | |
812 | @never_cache |
813 | @@ -23,17 +24,15 @@ |
814 | if not authenticate(request): |
815 | raise Http404 |
816 | |
817 | - base_template = getattr(settings, 'PREFLIGHT_BASE_TEMPLATE', |
818 | - "index.1col.html") |
819 | - table_class = getattr(settings, 'PREFLIGHT_TABLE_CLASS', "listing") |
820 | - |
821 | + switches = gather_switches() |
822 | context = RequestContext(request, { |
823 | "applications": gather_checks(), |
824 | "versions": gather_versions(), |
825 | "settings": gather_settings(), |
826 | - "gargoyle": gather_gargoyle(), |
827 | + "switches": switches, |
828 | + "switches_json": json.dumps(switches), |
829 | "now": datetime.now(), |
830 | - "preflight_base_template": base_template, |
831 | - 'preflight_table_class': table_class, |
832 | + "preflight_base_template": BASE_TEMPLATE, |
833 | + "preflight_table_class": TABLE_CLASS, |
834 | }) |
835 | return render_to_response("preflight/overview.html", context) |
836 | |
837 | === modified file 'setup.py' |
838 | --- setup.py 2011-02-11 13:06:42 +0000 |
839 | +++ setup.py 2013-05-31 15:53:26 +0000 |
840 | @@ -1,6 +1,8 @@ |
841 | # -*- encoding: utf-8 -*- |
842 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
843 | # GNU Affero General Public License version 3 (see the file LICENSE). |
844 | +import sys |
845 | + |
846 | try: |
847 | from ast import PyCF_ONLY_AST |
848 | except ImportError: |
849 | @@ -14,24 +16,31 @@ |
850 | for line in open('preflight/__init__.py') |
851 | if line.startswith('__version__')][0] |
852 | |
853 | +tests_require = [ |
854 | + 'mock > 0.6', |
855 | + 'gargoyle >= 0.6.0', |
856 | + 'pyquery', |
857 | +] |
858 | + |
859 | +if sys.version_info[:2] < (2, 7): |
860 | + tests_require.append('unittest2') |
861 | |
862 | setup( |
863 | name='django-preflight', |
864 | version=get_version(), |
865 | - author='Łukasz Czyżykowski', |
866 | + author='Lukasz Czyzykowski', |
867 | author_email='lukasz.czyzykowski@canonical.com', |
868 | description="Create a page for making sure all settings are correct.", |
869 | long_description=open('README').read(), |
870 | url='https://launchpad.net/django-preflight', |
871 | download_url='https://launchpad.net/django-preflight/+download', |
872 | classifiers=[ |
873 | - "Development Status :: 4 - Beta", |
874 | + "Development Status :: 5 - Production/Stable", |
875 | "Environment :: Web Environment", |
876 | "Framework :: Django", |
877 | "Intended Audience :: Developers", |
878 | "License :: OSI Approved :: GNU Affero General Public License v3", |
879 | "Programming Language :: Python", |
880 | - "Programming Language :: Python :: 2.5", |
881 | "Programming Language :: Python :: 2.6", |
882 | "Programming Language :: Python :: 2.7", |
883 | "Topic :: Internet :: WWW/HTTP :: Site Management", |
884 | @@ -44,19 +53,14 @@ |
885 | 'preflight.management.commands', |
886 | ), |
887 | package_data={ |
888 | - 'preflight': [ |
889 | - 'templates/preflight/*', |
890 | - ], |
891 | + 'preflight': ['templates/preflight/*.html'], |
892 | }, |
893 | - zip_safe=False, |
894 | install_requires=[ |
895 | - 'django >= 1.0' |
896 | - ], |
897 | - tests_require=[ |
898 | - 'mock > 0.6' |
899 | - ], |
900 | + 'django >= 1.1', |
901 | + ], |
902 | + tests_require=tests_require, |
903 | extras_require={ |
904 | 'docs': ['Sphinx'], |
905 | }, |
906 | - test_suite='example_project.run.tests', |
907 | + test_suite='preflight_example_project.run.tests', |
908 | ) |
909 | |
910 | === removed file 'tox' |
911 | --- tox 2011-02-10 12:43:01 +0000 |
912 | +++ tox 1970-01-01 00:00:00 +0000 |
913 | @@ -1,6 +0,0 @@ |
914 | -#!/usr/bin/env python |
915 | -import urllib |
916 | -url = "https://pytox.googlecode.com/hg/toxbootstrap.py" |
917 | -d = dict(__file__='toxbootstrap.py') |
918 | -exec urllib.urlopen(url).read() in d |
919 | -d['cmdline'](['--recreate']) |
920 | \ No newline at end of file |
921 | |
922 | === modified file 'tox.ini' |
923 | --- tox.ini 2012-05-18 13:13:41 +0000 |
924 | +++ tox.ini 2013-05-31 15:53:26 +0000 |
925 | @@ -1,8 +1,10 @@ |
926 | [tox] |
927 | -envlist = |
928 | - py2.5-django1.2, py2.5-django1.1, py2.5-django1.4, py2.5-django1.3, |
929 | - py2.6-django1.2, py2.6-django1.1, py2.6-django1.4, py2.6-django1.3, |
930 | - py2.7-django1.2, py2.7-django1.1, py2.7-django1.4, py2.7-django1.3, |
931 | +envlist = |
932 | + py2.6-django1.1, py2.7-django1.1, |
933 | + py2.6-django1.2, py2.7-django1.2, |
934 | + py2.6-django1.3, py2.7-django1.3, |
935 | + py2.6-django1.4, py2.7-django1.4, |
936 | + py2.6-django1.5, py2.7-django1.5, |
937 | docs |
938 | |
939 | [testenv] |
940 | @@ -16,54 +18,44 @@ |
941 | sphinx-build -b doctest -d {envtmpdir}/doctrees . {envtmpdir}/doctest |
942 | sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html |
943 | |
944 | -# Python 2.5 |
945 | -[testenv:py2.5-django1.2] |
946 | -basepython = python2.5 |
947 | -deps = django >= 1.2, < 1.3 |
948 | - |
949 | -[testenv:py2.5-django1.1] |
950 | -basepython = python2.5 |
951 | -deps = django >= 1.1, < 1.2 |
952 | - |
953 | -[testenv:py2.5-django1.4] |
954 | -basetpython = python2.5 |
955 | -deps = django >= 1.4, < 1.1 |
956 | - |
957 | -[testenv:py2.5-django1.3] |
958 | -basepython = python2.5 |
959 | -deps = django >= 1.3, < 1.4 |
960 | - |
961 | # Python 2.6 |
962 | +[testenv:py2.6-django1.1] |
963 | +basepython = python2.6 |
964 | +deps = django >= 1.1, < 1.2 |
965 | + |
966 | [testenv:py2.6-django1.2] |
967 | basepython = python2.6 |
968 | deps = django >= 1.2, < 1.3 |
969 | |
970 | -[testenv:py2.6-django1.1] |
971 | +[testenv:py2.6-django1.3] |
972 | basepython = python2.6 |
973 | -deps = django >= 1.1, < 1.2 |
974 | +deps = django >= 1.3, < 1.4 |
975 | |
976 | [testenv:py2.6-django1.4] |
977 | -basetpython = python2.6 |
978 | -deps = django >= 1.4, < 1.1 |
979 | +basepython = python2.6 |
980 | +deps = django >= 1.4, < 1.5 |
981 | |
982 | -[testenv:py2.6-django1.3] |
983 | +[testenv:py2.6-django1.5] |
984 | basepython = python2.6 |
985 | -deps = django >= 1.3, < 1.4 |
986 | +deps = django >= 1.5, < 1.6 |
987 | |
988 | # Python 2.7 |
989 | +[testenv:py2.7-django1.1] |
990 | +basepython = python2.7 |
991 | +deps = django >= 1.1, < 1.2 |
992 | + |
993 | [testenv:py2.7-django1.2] |
994 | basepython = python2.7 |
995 | deps = django >= 1.2, < 1.3 |
996 | |
997 | -[testenv:py2.7-django1.1] |
998 | +[testenv:py2.7-django1.3] |
999 | basepython = python2.7 |
1000 | -deps = django >= 1.1, < 1.2 |
1001 | +deps = django >= 1.3, < 1.4 |
1002 | |
1003 | [testenv:py2.7-django1.4] |
1004 | -basetpython = python2.7 |
1005 | -deps = django >= 1.4, < 1.1 |
1006 | - |
1007 | -[testenv:py2.7-django1.3] |
1008 | -basepython = python2.7 |
1009 | -deps = django >= 1.3, < 1.4 |
1010 | - |
1011 | +basepython = python2.7 |
1012 | +deps = django >= 1.4, < 1.5 |
1013 | + |
1014 | +[testenv:py2.7-django1.5] |
1015 | +basepython = python2.7 |
1016 | +deps = django >= 1.5, < 1.6 |