Merge lp:~bloodearnest/django-preflight/gargoyle2 into lp:django-preflight

Proposed by Simon Davy
Status: Merged
Approved by: Michael Foord
Approved revision: 26
Merged at revision: 25
Proposed branch: lp:~bloodearnest/django-preflight/gargoyle2
Merge into: lp:django-preflight
Diff against target: 490 lines (+248/-22)
10 files modified
.bzrignore (+1/-0)
doc/conf.py (+1/-1)
doc/install.rst (+3/-3)
preflight/models.py (+36/-0)
preflight/templates/preflight/overview.html (+68/-7)
preflight/tests.py (+111/-1)
preflight/views.py (+5/-0)
preflight_example_project/run.py (+2/-2)
preflight_example_project/settings.py (+7/-2)
setup.py (+14/-6)
To merge this branch: bzr merge lp:~bloodearnest/django-preflight/gargoyle2
Reviewer Review Type Date Requested Status
Michael Foord (community) Approve
Review via email: mp+166845@code.launchpad.net

Commit message

Add support for gargoyle flags in preflight

Description of the change

Add support for gargoyle flags in preflight

To post a comment you must log in.
Revision history for this message
Simon Davy (bloodearnest) wrote :
Revision history for this message
Michael Foord (mfoord) wrote :

The code should not depend on settings.USE_GARGOYLE.

Other than that all looks good.

review: Needs Fixing
Revision history for this message
Ricardo Kirkner (ricardokirkner) wrote :

- swap l. 72 and 73 around
- l 109, 120, 143, 155: fix whitespace before :
- l.280: why not add in l.270?
- l.398: what's the need of renaming the example_project folder?
- l.428: since you renamed the db, why add the old name to bzrignore? (l. 9)

Revision history for this message
Ricardo Kirkner (ricardokirkner) wrote :

Can you also include a screenshot of how the new template looks like with the flags?

Revision history for this message
Simon Davy (bloodearnest) wrote :

> The code should not depend on settings.USE_GARGOYLE.
>
> Other than that all looks good.

Done

Revision history for this message
Simon Davy (bloodearnest) wrote :

> - swap l. 72 and 73 around
> - l 109, 120, 143, 155: fix whitespace before :
> - l.280: why not add in l.270?

Done the above

> - l.398: what's the need of renaming the example_project folder?

This is unfortunately necessary because the nexus package (a dep of gargoyle) is higher in sys.pth when run with "python setup.py test", and it erroneously includes its example_project directory, meaning that the test_suite in setup.py ("example_project.run.tests") was finding the wrong example_project module and failing.

Pull request to exclude example_project upstream: https://github.com/disqus/nexus/pull/5

> - l.428: since you renamed the db, why add the old name to bzrignore? (l. 9)

Done - a left over from earlier

26. By Simon Davy

addressing review comments

Revision history for this message
Simon Davy (bloodearnest) wrote :
Revision history for this message
Michael Foord (mfoord) wrote :

Good work. Thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2012-05-02 21:37:14 +0000
3+++ .bzrignore 2013-06-03 10:45:34 +0000
4@@ -4,3 +4,4 @@
5 .tox
6 *.egg-info
7 doc/_build
8+preflight.wp*
9
10=== modified file 'doc/conf.py'
11--- doc/conf.py 2011-02-09 16:26:04 +0000
12+++ doc/conf.py 2013-06-03 10:45:34 +0000
13@@ -18,7 +18,7 @@
14 # documentation root, use os.path.abspath to make it absolute, like shown here.
15 sys.path.append(os.path.abspath('..'))
16
17-os.environ['DJANGO_SETTINGS_MODULE'] = 'example_project.settings'
18+os.environ['DJANGO_SETTINGS_MODULE'] = 'preflight_example_project.settings'
19
20 # -- General configuration -----------------------------------------------------
21
22
23=== modified file 'doc/install.rst'
24--- doc/install.rst 2013-05-17 14:52:42 +0000
25+++ doc/install.rst 2013-06-03 10:45:34 +0000
26@@ -56,10 +56,10 @@
27
28 import preflight
29 import preflight.urls
30-
31-
32+
33+
34 preflight.autodiscover()
35-
36+
37 urlpatterns = patterns('',
38 (r'^preflight/', include(preflight.urls)),
39 )
40
41=== modified file 'preflight/models.py'
42--- preflight/models.py 2013-02-20 21:15:50 +0000
43+++ preflight/models.py 2013-06-03 10:45:34 +0000
44@@ -142,3 +142,39 @@
45 To be able to access you need to have permission from every preflight class.
46 """
47 return all(class_().authenticate(request) for class_ in REGISTRY)
48+
49+
50+def gather_switches():
51+ """Gather all switches into one list."""
52+ switches = {}
53+ gargoyle_switches = gather_gargoyle()
54+ if gargoyle_switches:
55+ switches['gargoyle'] = gargoyle_switches
56+ return switches
57+
58+
59+def gather_gargoyle():
60+ try:
61+ from gargoyle.models import Switch
62+ except ImportError:
63+ return None
64+ return [_format_gargoyle(switch) for switch in Switch.objects.all()]
65+
66+
67+def _format_gargoyle(switch):
68+ """Formats a gargoyle switch into std format for display"""
69+ from gargoyle import gargoyle
70+ d = switch.to_dict(gargoyle)
71+ conditions = []
72+ for condition in d['conditions']:
73+ params = ','.join('%s=%s' % (c[0], c[2])
74+ for c in condition['conditions'])
75+ conditions.append("%s(%s)" % (condition['label'], params))
76+
77+ return dict(
78+ name=d['key'],
79+ description=d['description'],
80+ status=d['status'],
81+ status_text=d['statusLabel'],
82+ conditions=conditions,
83+ )
84
85=== modified file 'preflight/templates/preflight/overview.html'
86--- preflight/templates/preflight/overview.html 2012-05-02 21:37:14 +0000
87+++ preflight/templates/preflight/overview.html 2013-06-03 10:45:34 +0000
88@@ -11,8 +11,21 @@
89 <div id="preflight">
90 <h1>{% trans "Pre-flight Applications Checks" %}</h1>
91
92+ <ul id="links">
93+ {% for application in applications %}
94+ <li><a href="#{{ application.name|slugify }}">{{ application.name }}</a></li>
95+ {% endfor %}
96+ <li><a href="#versions">Versions</a></li>
97+ <li><a href="#settings">Settings</a></li>
98+ {% if switches %}
99+ <li><a href="#switches">Switches</a></li>
100+ {% endif %}
101+ </ul>
102+
103 {% for application in applications %}
104- <h2>{{ application.name }}</h2>
105+ <a href="#links" style="text-decoration: none;">
106+ <h2 id="{{ application.name|slugify }}">{{ application.name }}</h2>
107+ </a>
108 <table class="{{ preflight_table_class }}">
109 <thead>
110 <tr>
111@@ -39,7 +52,9 @@
112 </table>
113 {% endfor %}
114
115- <h1>{% trans "Versions" %}</h1>
116+ <a href="#links" style="text-decoration: none;">
117+ <h1 id="versions">{% trans "Versions" %}</h1>
118+ </a>
119 <table class="{{ preflight_table_class }}">
120 <thead>
121 <tr>
122@@ -49,15 +64,17 @@
123 </thead>
124 <tbody>
125 {% for item in versions %}
126- <tr>
127- <td>{{ item.name }}</td>
128- <td>{{ item.version }}</td>
129- </tr>
130+ <tr>
131+ <td>{{ item.name }}</td>
132+ <td>{{ item.version }}</td>
133+ </tr>
134 {% endfor %}
135 </tbody>
136 </table>
137
138- <h1>{% trans "Settings" %}</h1>
139+ <a href="#links" style="text-decoration: none;">
140+ <h1 id="settings">{% trans "Settings" %}</h1>
141+ </a>
142 <dl class="{{ preflight_table_class }}">
143 {% for setting in settings %}
144 <dt><b>{{ setting.name }}</b></dt>
145@@ -65,6 +82,50 @@
146 {% if setting.location %}<dd>Location: {{ setting.location}}</dd>{% endif %}
147 {% endfor %}
148 </dl>
149+
150+ {% if switches %}
151+ <a href="#links" style="text-decoration: none;">
152+ <h1 id="switches">{% trans "Switches" %}</h1>
153+ </a>
154+ <table id="switches-table" class="{{ preflight_table_class }}">
155+ <thead>
156+ <tr>
157+ <th>{% trans "Name" %}</th>
158+ <th>{% trans "Description" %}</th>
159+ <th>{% trans "Conditions" %}</th>
160+ <th>{% trans "Status" %}</th>
161+ </tr>
162+ </thead>
163+ <tbody>
164+ {% for type, type_switches in switches.items %}
165+ <tr><th colspan="4">{{ type }}</th></tr>
166+ {% for switch in type_switches %}
167+ <tr class="switch">
168+ <td>{{ switch.name }}</td>
169+ <td>{{ switch.description }}</td>
170+ <td>
171+ {% if switch.conditions %}
172+ <ul>
173+ {% for condition in switch.conditions %}
174+ <li>{{ condition|first }}: {{ condition|last }}</li>
175+ {% endfor %}
176+ </ul>
177+ {% endif %}
178+ </td>
179+ <td>{{ switch.status_text }}</td>
180+ {% endfor %}
181+ </tr>
182+ {% endfor %}
183+ </tbody>
184+ </table>
185+ <p>Switch data is included in JSON format in a script tag with id
186+ "switches-json" to aid automated scripting</p>
187+ <script id="switches-json">
188+ {{ switches_json|safe }}
189+ </script>
190+ {% else %}
191+ <p>No switches.</p>
192+ {% endif %}
193 </div>
194
195 <p>View generated at: {{ now }}</p>
196
197=== modified file 'preflight/tests.py'
198--- preflight/tests.py 2013-05-17 14:51:54 +0000
199+++ preflight/tests.py 2013-06-03 10:45:34 +0000
200@@ -1,18 +1,26 @@
201 # Copyright 2010 Canonical Ltd. This software is licensed under the
202 # GNU Affero General Public License version 3 (see the file LICENSE).
203 import sys
204+import json
205+try:
206+ from unittest import skipIf
207+except ImportError:
208+ from unittest2 import skipIf # NOQA
209
210+from django.conf import settings
211 from django.core.management import call_command
212 from django.core.urlresolvers import reverse
213 from django.http import HttpResponse
214 from django.test import TestCase
215-from django.template import RequestContext, TemplateDoesNotExist
216+from django.template import RequestContext
217+from django.template.loader import render_to_string
218 from mock import (
219 Mock,
220 patch,
221 )
222
223 from cStringIO import StringIO
224+from pyquery import PyQuery
225
226 from . import Preflight
227 from .models import (
228@@ -21,9 +29,12 @@
229 REGISTRY,
230 authenticate,
231 gather_checks,
232+ gather_switches,
233+ gather_gargoyle,
234 gather_settings,
235 gather_versions,
236 )
237+from preflight.conf import BASE_TEMPLATE, TABLE_CLASS
238
239
240 class CheckTestCase(TestCase):
241@@ -142,6 +153,7 @@
242 def authenticate(self, request):
243 return False
244
245+
246 def clear_registry():
247 while REGISTRY:
248 REGISTRY.pop()
249@@ -182,6 +194,7 @@
250 def versions(self):
251 return [{'name': 'spam', 'version': 'ni'}]
252
253+
254 class ExtraVersionAsList(Preflight):
255
256 versions = [{'name': 'eggs', 'version': 'peng'}]
257@@ -294,3 +307,100 @@
258 mock_settings.PREFLIGHT_HIDDEN_SETTINGS = ''
259 settings = gather_settings()
260 self.assertEqual([], settings)
261+
262+
263+@skipIf(not settings.USE_GARGOYLE, 'skipping for Django 1.1')
264+class GargoyleTestCase(TestCase):
265+ from gargoyle import gargoyle # NOQA
266+ from gargoyle.builtins import IPAddressConditionSet # NOQA
267+ from gargoyle.models import ( # NOQA
268+ Switch,
269+ DISABLED,
270+ SELECTIVE,
271+ GLOBAL,
272+ INHERIT
273+ )
274+
275+ def setUp(self):
276+ super(GargoyleTestCase, self).setUp()
277+ self.gargoyle.register(self.IPAddressConditionSet())
278+
279+ def get_switches(self):
280+ switches = [
281+ self.Switch(key='DISABLED', status=self.DISABLED,
282+ description='switch 1'),
283+ self.Switch(key='SELECTIVE_1', status=self.SELECTIVE),
284+ self.Switch(key='SELECTIVE_2', status=self.SELECTIVE),
285+ self.Switch(key='GLOBAL', status=self.GLOBAL),
286+ self.Switch(key='INHERIT', status=self.INHERIT),
287+ ]
288+ selective = switches[2]
289+ selective.add_condition(
290+ self.gargoyle,
291+ condition_set='gargoyle.builtins.IPAddressConditionSet',
292+ field_name='ip_address',
293+ condition='127.0.0.1',
294+ )
295+ return switches
296+
297+ @patch.dict(sys.modules, **{'gargoyle.models': None})
298+ def test_gather_switches_no_gargoyle(self):
299+ self.assertEqual(gather_gargoyle(), None)
300+
301+ def assert_switches_dict(self, actual):
302+ expected = [
303+ dict(name='DISABLED',
304+ status=self.DISABLED,
305+ description='switch 1',
306+ status_text=self.Switch.STATUS_LABELS[self.DISABLED],
307+ conditions=[]),
308+ dict(name='SELECTIVE_1',
309+ status=self.SELECTIVE,
310+ description=None,
311+ status_text=self.Switch.STATUS_LABELS[self.GLOBAL],
312+ conditions=[]),
313+ dict(name='SELECTIVE_2', status=self.SELECTIVE,
314+ description=None,
315+ status_text=self.Switch.STATUS_LABELS[self.SELECTIVE],
316+ conditions=['IP Address(ip_address=127.0.0.1)']),
317+ dict(name='GLOBAL', status=self.GLOBAL,
318+ description=None,
319+ status_text=self.Switch.STATUS_LABELS[self.GLOBAL],
320+ conditions=[]),
321+ dict(name='INHERIT', status=self.INHERIT,
322+ description=None,
323+ status_text=self.Switch.STATUS_LABELS[self.INHERIT],
324+ conditions=[]),
325+ ]
326+ self.assertEqual(actual, expected)
327+
328+ @patch('gargoyle.models.Switch.objects.all')
329+ def test_gather_switches(self, mock_all):
330+ mock_all.return_value = self.get_switches()
331+ self.assert_switches_dict(gather_gargoyle())
332+
333+ @patch('gargoyle.models.Switch.objects.all')
334+ def test_gargoyle_template(self, mock_all):
335+ switches = self.get_switches()
336+ mock_all.return_value = switches
337+ the_switches = gather_switches()
338+ context = {
339+ "switches": the_switches,
340+ "switches_json": json.dumps(the_switches),
341+ "preflight_base_template": BASE_TEMPLATE,
342+ "preflight_table_class": TABLE_CLASS,
343+ }
344+ response = render_to_string('preflight/overview.html', context)
345+ dom = PyQuery(response)
346+ table = dom.find('#switches-table tbody')
347+ self.assertEqual(table.find('tr')[0][0].text, 'gargoyle')
348+
349+ for row, switch in zip(table.find('tr.switch'), switches):
350+ self.assertEqual(row[0].text, switch.key)
351+ self.assertEqual(row[1].text, str(switch.description))
352+ self.assertEqual(row[3].text, switch.get_status_label())
353+
354+ data = json.loads(dom.find('#switches-json').text())
355+ self.assertTrue('gargoyle' in data)
356+ json_switches = data['gargoyle']
357+ self.assert_switches_dict(json_switches)
358
359=== modified file 'preflight/views.py'
360--- preflight/views.py 2013-05-17 14:51:29 +0000
361+++ preflight/views.py 2013-06-03 10:45:34 +0000
362@@ -2,6 +2,7 @@
363 # GNU Affero General Public License version 3 (see the file LICENSE).
364
365 from datetime import datetime
366+import json
367
368 from django.views.decorators.cache import never_cache
369 from django.http import Http404
370@@ -13,6 +14,7 @@
371 gather_checks,
372 gather_settings,
373 gather_versions,
374+ gather_switches,
375 )
376 from .conf import BASE_TEMPLATE, TABLE_CLASS
377
378@@ -22,10 +24,13 @@
379 if not authenticate(request):
380 raise Http404
381
382+ switches = gather_switches()
383 context = RequestContext(request, {
384 "applications": gather_checks(),
385 "versions": gather_versions(),
386 "settings": gather_settings(),
387+ "switches": switches,
388+ "switches_json": json.dumps(switches),
389 "now": datetime.now(),
390 "preflight_base_template": BASE_TEMPLATE,
391 "preflight_table_class": TABLE_CLASS,
392
393=== renamed directory 'example_project' => 'preflight_example_project'
394=== modified file 'preflight_example_project/run.py'
395--- example_project/run.py 2011-02-07 16:06:51 +0000
396+++ preflight_example_project/run.py 2013-06-03 10:45:34 +0000
397@@ -1,8 +1,8 @@
398 import os
399 import sys
400
401-os.environ['DJANGO_SETTINGS_MODULE'] = 'example_project.settings'
402-sys.path.insert(0, 'example_project')
403+os.environ['DJANGO_SETTINGS_MODULE'] = 'preflight_example_project.settings'
404+sys.path.insert(0, 'preflight_example_project')
405
406 try:
407 from django.test.utils import get_runner
408
409=== modified file 'preflight_example_project/settings.py'
410--- example_project/settings.py 2013-05-17 14:51:54 +0000
411+++ preflight_example_project/settings.py 2013-06-03 10:45:34 +0000
412@@ -1,4 +1,5 @@
413 import os
414+import django
415
416 DEBUG = True
417 TEMPLATE_DEBUG = DEBUG
418@@ -9,7 +10,7 @@
419 DATABASES = {
420 'default': {
421 'ENGINE': 'django.db.backends.sqlite3',
422- 'NAME': 'database.sqlit3',
423+ 'NAME': 'database.sqlite3',
424 }
425 }
426
427@@ -25,7 +26,7 @@
428 ADMIN_MEDIA_PREFIX = '/media/'
429 SECRET_KEY = 'j0-wo!y4zwi)tejv1u(0skc41_379ls^@qbh91%#5o=greje6('
430
431-ROOT_URLCONF = 'example_project.urls'
432+ROOT_URLCONF = 'preflight_example_project.urls'
433
434 TEMPLATE_DIRS = (
435 os.path.join(os.path.realpath(os.path.dirname(__file__)), 'templates'),
436@@ -52,4 +53,8 @@
437 'app',
438 )
439
440+USE_GARGOYLE = django.VERSION[:2] > (1, 1)
441+if USE_GARGOYLE:
442+ INSTALLED_APPS = INSTALLED_APPS + ('gargoyle',)
443+
444 PREFLIGHT_BASE_TEMPLATE = 'base.html'
445
446=== modified file 'setup.py'
447--- setup.py 2013-05-17 14:55:49 +0000
448+++ setup.py 2013-06-03 10:45:34 +0000
449@@ -1,6 +1,8 @@
450 # -*- encoding: utf-8 -*-
451 # Copyright 2010 Canonical Ltd. This software is licensed under the
452 # GNU Affero General Public License version 3 (see the file LICENSE).
453+import sys
454+
455 try:
456 from ast import PyCF_ONLY_AST
457 except ImportError:
458@@ -14,6 +16,14 @@
459 for line in open('preflight/__init__.py')
460 if line.startswith('__version__')][0]
461
462+tests_require = [
463+ 'mock > 0.6',
464+ 'gargoyle >= 0.6.0',
465+ 'pyquery',
466+]
467+
468+if sys.version_info[:2] < (2, 7):
469+ tests_require.append('unittest2')
470
471 setup(
472 name='django-preflight',
473@@ -46,13 +56,11 @@
474 'preflight': ['templates/preflight/*.html'],
475 },
476 install_requires=[
477- 'django >= 1.1'
478- ],
479- tests_require=[
480- 'mock > 0.6'
481- ],
482+ 'django >= 1.1',
483+ ],
484+ tests_require=tests_require,
485 extras_require={
486 'docs': ['Sphinx'],
487 },
488- test_suite='example_project.run.tests',
489+ test_suite='preflight_example_project.run.tests',
490 )

Subscribers

People subscribed via source and target branches