Merge lp:~bloodearnest/django-preflight/gargoyle into lp:~canonical-isd-hackers/django-preflight/gargoyle

Proposed by Simon Davy
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
Reviewer Review Type Date Requested Status
Canonical ISD hackers Pending
Review via email: mp+166838@code.launchpad.net

Description of the change

Add support for showing switches. Initially just gargoyle.

To post a comment you must log in.

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches