Merge lp:~nataliabidart/django-preflight/cache-status-info into lp:django-preflight

Proposed by Natalia Bidart
Status: Merged
Approved by: Natalia Bidart
Approved revision: 33
Merged at revision: 30
Proposed branch: lp:~nataliabidart/django-preflight/cache-status-info
Merge into: lp:django-preflight
Diff against target: 759 lines (+183/-135)
12 files modified
debian/control (+1/-1)
preflight/__init__.py (+1/-14)
preflight/models.py (+58/-24)
preflight/templates/preflight/overview.html (+22/-5)
preflight/tests.py (+67/-42)
preflight/urls.py (+4/-4)
preflight/views.py (+13/-8)
preflight_example_project/app/preflight.py (+3/-2)
preflight_example_project/run.py (+0/-2)
preflight_example_project/urls.py (+3/-4)
setup.py (+1/-1)
tox.ini (+10/-28)
To merge this branch: bzr merge lp:~nataliabidart/django-preflight/cache-status-info
Reviewer Review Type Date Requested Status
Martin Albisetti (community) Approve
Review via email: mp+203601@code.launchpad.net

Commit message

- Include status information for django caches.
- Include database connection health checks.

To post a comment you must log in.
Revision history for this message
Martin Albisetti (beuno) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2011-02-07 16:06:51 +0000
3+++ debian/control 2014-01-28 17:39:58 +0000
4@@ -14,7 +14,7 @@
5 Architecture: all
6 XB-Python-Version: ${python:Versions}
7 Depends: ${misc:Depends}, ${python:Depends},
8- python-django (>= 1.0.2)
9+ python-django (>= 1.4)
10 Description: Base for creating pre-flight checks page for Django projects.
11 This project provides basic support for creating pre-flight checks page for Django
12 based projects. Such page consists with series of checks, provided by the project
13
14=== modified file 'preflight/__init__.py'
15--- preflight/__init__.py 2013-05-17 14:56:11 +0000
16+++ preflight/__init__.py 2014-01-28 17:39:58 +0000
17@@ -3,7 +3,7 @@
18
19 from django.conf import settings
20
21-from .models import REGISTRY
22+from .models import Preflight, register # NOQA
23
24 __version__ = "0.1.5"
25
26@@ -18,16 +18,3 @@
27 msg = e.args[0].lower()
28 if 'no module named' not in msg or 'preflight' not in msg:
29 raise
30-
31-
32-class Preflight(object):
33-
34- name = "Application Checks"
35-
36- def authenticate(self, request):
37- return request.user.is_staff
38-
39-
40-def register(class_):
41- if class_ not in REGISTRY:
42- REGISTRY.append(class_)
43
44=== modified file 'preflight/models.py'
45--- preflight/models.py 2014-01-24 12:21:51 +0000
46+++ preflight/models.py 2014-01-28 17:39:58 +0000
47@@ -2,12 +2,17 @@
48 # GNU Affero General Public License version 3 (see the file LICENSE).
49
50 from __future__ import absolute_import
51+
52+import platform
53 import re
54 import sys
55 import traceback
56
57 import django
58+
59+from django import db
60 from django.conf import settings
61+from django.core.cache import get_cache
62 from django.views.debug import HIDDEN_SETTINGS
63
64 from .conf import ENABLE_SETTINGS
65@@ -15,6 +20,26 @@
66 REGISTRY = []
67
68
69+def register(class_):
70+ if class_ not in REGISTRY:
71+ REGISTRY.append(class_)
72+
73+
74+class Preflight(object):
75+
76+ name = "Application Checks"
77+
78+ def authenticate(self, request):
79+ return request.user.is_staff
80+
81+ def check_database(self):
82+ """Are database connections accepted?"""
83+ cursor = db.connection.cursor()
84+ cursor.execute('SELECT 42')
85+ cursor.fetchone()
86+ return True
87+
88+
89 class Application(object):
90
91 def __init__(self, class_):
92@@ -28,8 +53,8 @@
93 if s.startswith('check_'))
94 for check_name in check_names:
95 instance = self.class_()
96- bound_method = getattr(instance, check_name)
97- check = Check(check_name[len('check_'):], bound_method)
98+ check_method = getattr(instance, check_name)
99+ check = Check(check_name[len('check_'):], check_method)
100 check.check()
101 self.checks.append(check)
102
103@@ -49,10 +74,7 @@
104
105
106 class Check(object):
107-
108- """
109- Representation of one check performed and place to store its result.
110- """
111+ """Representation of one check performed and place to store its result."""
112
113 def __init__(self, name, bound_method):
114 self.name = name
115@@ -63,14 +85,14 @@
116 self.description = ""
117
118 def check(self):
119- """
120- Returns True if check was successful and False otherwise.
121+ """Return True if check was successful and False otherwise.
122
123 This result is also stored in object's attribute ``passed``.
124+
125 """
126 try:
127 self.passed = bool(self.bound_method())
128- except Exception, exc:
129+ except Exception:
130 exc_type, exc_value, exc_traceback = sys.exc_info()
131 self.exception = u"".join(
132 traceback.format_exception(exc_type, exc_value,
133@@ -96,21 +118,12 @@
134
135
136 def gather_versions():
137- """
138- Gather list of version information to be displayed alongside the checks
139- data.
140-
141- """
142- import platform
143- import sys
144- import django
145- import preflight
146+ """Return list of version information to be shown with the checks data."""
147
148 items = [
149 {'name': 'Platform', 'version': platform.platform()},
150 {'name': 'Django', 'version': django.get_version()},
151 {'name': 'Python', 'version': sys.version},
152- {'name': 'preflight', 'version': preflight.__version__}
153 ]
154
155 for class_ in REGISTRY:
156@@ -152,7 +165,7 @@
157 else:
158 if isinstance(value, dict):
159 cleansed = dict((k, cleanse_setting(k, v, hidden))
160- for k,v in value.items())
161+ for k, v in value.items())
162 else:
163 cleansed = value
164 except TypeError:
165@@ -160,11 +173,13 @@
166 cleansed = value
167 return cleansed
168
169+
170 def gather_settings():
171 if not ENABLE_SETTINGS:
172 return []
173
174- names = sorted([x for x in settings._wrapped.__dict__
175+ names = sorted([
176+ x for x in settings._wrapped.__dict__
177 if x.isupper() and not x.startswith('_')])
178 settings_list = []
179 hidden_settings = getattr(settings, 'PREFLIGHT_HIDDEN_SETTINGS', None)
180@@ -183,9 +198,7 @@
181
182
183 def authenticate(request):
184- """
185- To be able to access you need to have permission from every preflight class.
186- """
187+ """For access, permission from every preflight class is required."""
188 return all(class_().authenticate(request) for class_ in REGISTRY)
189
190
191@@ -249,3 +262,24 @@
192 status_text=switch['statusLabel'],
193 conditions=conditions,
194 )
195+
196+
197+def gather_caches():
198+ """Expose the status of memcache on the preflight page."""
199+ caches = {}
200+
201+ for cache_name, cache_settings in settings.CACHES.iteritems():
202+ cache = get_cache(cache_name)
203+ try:
204+ stats = cache._cache.get_stats()[0][1]
205+ except Exception as exc:
206+ stats = dict(error=unicode(exc))
207+ else:
208+ stats['hit_rate'] = 100 * int(
209+ stats.get('get_hits')) / int(stats.get('cmd_get'))
210+
211+ stats['backend'] = cache_settings['BACKEND']
212+ stats['host'] = cache_settings.get('LOCATION', 'No location given')
213+ caches[cache_name] = stats
214+
215+ return caches
216
217=== modified file 'preflight/templates/preflight/overview.html'
218--- preflight/templates/preflight/overview.html 2014-01-24 14:07:11 +0000
219+++ preflight/templates/preflight/overview.html 2014-01-28 17:39:58 +0000
220@@ -5,11 +5,11 @@
221
222 {% load i18n %}
223
224-{% block title %}{% trans "Application Pre-Flight Checks" %}{% endblock %}
225+{% block title %}{% trans "Preflight Checks" %}{% endblock %}
226
227 {% block content %}
228 <div id="preflight">
229- <h1>{% trans "Pre-flight Applications Checks" %}</h1>
230+ <h1>{% trans "Preflight Checks" %}</h1>
231
232 <ul id="links">
233 {% for application in applications %}
234@@ -22,6 +22,9 @@
235 {% if switches %}
236 <li><a href="#switches">Switches</a></li>
237 {% endif %}
238+ {% if caches %}
239+ <li><a href="#caches">Caches</a></li>
240+ {% endif %}
241 </ul>
242
243 {% for application in applications %}
244@@ -60,7 +63,7 @@
245 {% endfor %}
246
247 <a href="#links" style="text-decoration: none;">
248- <h1 id="versions">{% trans "Versions" %}</h1>
249+ <h2 id="versions">{% trans "Versions" %}</h2>
250 </a>
251 <table class="{{ preflight_table_class }}">
252 <thead>
253@@ -81,7 +84,7 @@
254
255 {% if settings %}
256 <a href="#links" style="text-decoration: none;">
257- <h1 id="settings">{% trans "Settings" %}</h1>
258+ <h2 id="settings">{% trans "Settings" %}</h2>
259 </a>
260 <dl class="{{ preflight_table_class }}">
261 {% for setting in settings %}
262@@ -94,7 +97,7 @@
263
264 {% if switches %}
265 <a href="#links" style="text-decoration: none;">
266- <h1 id="switches">{% trans "Switches" %}</h1>
267+ <h2 id="switches">{% trans "Gargoyle Switches" %}</h2>
268 </a>
269 <table id="switches-table" class="{{ preflight_table_class }}">
270 <thead>
271@@ -130,6 +133,20 @@
272 {% else %}
273 <p>No switches.</p>
274 {% endif %}
275+
276+ {% for name, cache in caches.items %}
277+ <a href="#links" style="text-decoration: none;">
278+ <h2 id="caches">Cache {{ name }}</h2>
279+ </a>
280+ <dl>
281+ {% for key, value in cache %}
282+ <strong>{{ key }}</strong>: {{ value }}<br />
283+ {% endfor %}
284+ </dl>
285+ {% empty %}
286+ <p>No caches.</p>
287+ {% endfor %}
288+
289 </div>
290
291 <p>View generated at: {{ now }}</p>
292
293=== modified file 'preflight/tests.py'
294--- preflight/tests.py 2014-01-24 14:07:11 +0000
295+++ preflight/tests.py 2014-01-28 17:39:58 +0000
296@@ -1,12 +1,9 @@
297 # Copyright 2010 Canonical Ltd. This software is licensed under the
298 # GNU Affero General Public License version 3 (see the file LICENSE).
299 import sys
300-try:
301- from unittest import skipIf
302-except ImportError:
303- from unittest2 import skipIf # NOQA
304-
305-from django.conf import settings
306+
307+from cStringIO import StringIO
308+
309 from django.contrib.auth.models import AnonymousUser, User
310 from django.core.management import call_command
311 from django.core.urlresolvers import reverse
312@@ -14,20 +11,24 @@
313 from django.test import TestCase
314 from django.template import RequestContext
315 from django.template.loader import render_to_string
316+from gargoyle import gargoyle
317+from gargoyle.builtins import IPAddressConditionSet
318+from gargoyle.models import (
319+ DISABLED, GLOBAL, INHERIT, SELECTIVE,
320+ Switch,
321+)
322 from mock import (
323 Mock,
324 patch,
325 )
326-
327-from cStringIO import StringIO
328 from pyquery import PyQuery
329
330-from . import Preflight
331-from .models import (
332+from preflight.models import (
333+ HIDDEN_SETTINGS,
334+ REGISTRY,
335 Application,
336 Check,
337- HIDDEN_SETTINGS,
338- REGISTRY,
339+ Preflight,
340 authenticate,
341 cleanse_setting,
342 gather_checks,
343@@ -47,7 +48,7 @@
344 self.return_value = True
345
346 def check_method(self):
347- "description"
348+ """Some description"""
349 if self.exception:
350 raise self.exception
351 return self.return_value
352@@ -55,8 +56,8 @@
353 def test_initialisation(self):
354 check = Check("check_name", self.check_method)
355
356- self.assertEquals(check.name, "check_name")
357- self.assertEquals(check.description, "description")
358+ self.assertEqual(check.name, "check_name")
359+ self.assertEqual(check.description, "Some description")
360
361 def test_initialisation_when_doc_is_none(self):
362 def method():
363@@ -64,7 +65,7 @@
364
365 check = Check("check_name", method)
366
367- self.assertEquals(check.description, "")
368+ self.assertEqual(check.description, "")
369
370 def test_check_when_check_method_returns_true(self):
371 check = Check("check_name", self.check_method)
372@@ -89,7 +90,7 @@
373 def test_check_returns_right_return_value(self):
374 check = Check("check_name", self.check_method)
375
376- self.assertEquals(check.check(), check.passed)
377+ self.assertEqual(check.check(), check.passed)
378
379 def test_check_after_exception_has_exception_attribute(self):
380 self.exception = Exception("error")
381@@ -128,7 +129,7 @@
382 app = Application(DummyPreflight)
383
384 self.assertEqual(app.name, "preflight checks")
385- self.assertEqual(len(app.checks), 4)
386+ self.assertEqual(len(app.checks), 5)
387
388 def test_check_name_is_properly_handled(self):
389 app = Application(DummyPreflight)
390@@ -138,6 +139,7 @@
391 'check_test',
392 'maybe_fail',
393 'maybe_error',
394+ 'database',
395 ]))
396
397 def test_check_sets_passed_to_false_if_at_least_one_of_check_fails(self):
398@@ -146,14 +148,11 @@
399 self.assertFalse(app.passed)
400
401 def test_check_sets_passed_to_true_if_all_checks_passed(self):
402- DummyPreflight.fail = False
403-
404- app = Application(DummyPreflight)
405+ with patch.object(DummyPreflight, 'fail', False):
406+ app = Application(DummyPreflight)
407
408 self.assertTrue(app.passed)
409
410- DummyPreflight.fail = True
411-
412
413 class AuthenticatePass(Preflight):
414
415@@ -241,13 +240,13 @@
416
417 applications = gather_checks()
418
419- self.assertEquals(len(applications), 3)
420+ self.assertEqual(len(applications), 3)
421
422
423 class ExtraVersionAsCallable(Preflight):
424
425 def versions(self):
426- return [{'name': 'spam', 'version': 'ni'}]
427+ return [{'name': 'spam', 'version': lambda: 'ni'}]
428
429
430 class ExtraVersionAsList(Preflight):
431@@ -262,7 +261,7 @@
432
433 versions = gather_versions()
434
435- self.assertEquals(len(versions), 4)
436+ self.assertEqual(len(versions), 3)
437 for item in versions:
438 self.assertTrue('name' in item and 'version' in item)
439
440@@ -272,8 +271,8 @@
441
442 versions = gather_versions()
443
444- self.assertEquals(len(versions), 5)
445- self.assertEquals(versions[-1]['name'], 'spam')
446+ self.assertEqual(len(versions), 4)
447+ self.assertEqual(versions[-1]['name'], 'spam')
448
449 def test_get_extra_version_information_from_class_attribute(self):
450 clear_registry()
451@@ -281,8 +280,8 @@
452
453 versions = gather_versions()
454
455- self.assertEquals(len(versions), 5)
456- self.assertEquals(versions[-1]['version'], 'peng')
457+ self.assertEqual(len(versions), 4)
458+ self.assertEqual(versions[-1]['version'], 'peng')
459
460
461 class PreflightCommandTestCase(TestCase):
462@@ -330,6 +329,7 @@
463 finally:
464 sys.stdout = sys.__stdout__
465
466+
467 class OverviewView(TestCase):
468 @patch('preflight.views.authenticate')
469 def test_overview_when_not_authenticated(self, mock_authenticate):
470@@ -457,21 +457,11 @@
471 self.assertFalse('secret' in result['default']['PASSWORD'])
472
473
474-@skipIf(not settings.USE_GARGOYLE, 'skipping for Django 1.1')
475 class GargoyleTestCase(TestCase):
476
477 def setUp(self):
478- from gargoyle import gargoyle
479 self.gargoyle = gargoyle
480- from gargoyle.builtins import IPAddressConditionSet
481 self.IPAddressConditionSet = IPAddressConditionSet
482- from gargoyle.models import (
483- Switch,
484- DISABLED,
485- SELECTIVE,
486- GLOBAL,
487- INHERIT
488- )
489 self.Switch = Switch
490 self.DISABLED = DISABLED
491 self.SELECTIVE = SELECTIVE
492@@ -482,8 +472,8 @@
493
494 def get_switches(self):
495 switches = [
496- self.Switch(key='DISABLED', status=self.DISABLED,
497- description='switch 1'),
498+ self.Switch(
499+ key='DISABLED', status=self.DISABLED, description='switch 1'),
500 self.Switch(key='SELECTIVE_1', status=self.SELECTIVE),
501 self.Switch(key='SELECTIVE_2', status=self.SELECTIVE),
502 self.Switch(key='GLOBAL', status=self.GLOBAL),
503@@ -553,3 +543,38 @@
504 self.assertEqual(row[0].text, switch.key)
505 self.assertEqual(row[1].text, str(switch.description))
506 self.assertEqual(row[3].text, switch.get_status_label())
507+
508+
509+class PreflightTestCase(TestCase):
510+
511+ url = '/preflight/'
512+ email = 'email@domain.local'
513+ password = 'testpassword'
514+
515+ def setUp(self):
516+ super(PreflightTestCase, self).setUp()
517+ self.user = User.objects.create_superuser(
518+ username='pepe', email=self.email, password=self.password)
519+
520+ def test_anonymous(self):
521+ response = self.client.get(self.url)
522+
523+ self.assertEqual(404, response.status_code)
524+
525+ def test_not_staff(self):
526+ user = User.objects.create_user(
527+ username='jose', email='jose@example.com', password=self.password)
528+ assert not user.is_staff
529+ assert self.client.login(username='jose', password=self.password)
530+
531+ response = self.client.get(self.url)
532+ self.assertEqual(404, response.status_code)
533+
534+ def test_success(self):
535+ assert self.user.is_staff
536+ assert self.client.login(
537+ username=self.user.username, password=self.password)
538+
539+ response = self.client.get(self.url)
540+
541+ self.assertEqual(200, response.status_code)
542
543=== modified file 'preflight/urls.py'
544--- preflight/urls.py 2011-10-04 15:54:50 +0000
545+++ preflight/urls.py 2014-01-28 17:39:58 +0000
546@@ -1,9 +1,9 @@
547 # Copyright 2010 Canonical Ltd. This software is licensed under the
548 # GNU Affero General Public License version 3 (see the file LICENSE).
549
550-from django.conf.urls.defaults import patterns, url
551-
552-
553-urlpatterns = patterns('preflight.views',
554+from django.conf.urls import patterns, url
555+
556+urlpatterns = patterns(
557+ 'preflight.views',
558 url(r'^$', 'overview', name='preflight-overview'),
559 )
560
561=== modified file 'preflight/views.py'
562--- preflight/views.py 2013-06-10 12:57:48 +0000
563+++ preflight/views.py 2014-01-28 17:39:58 +0000
564@@ -1,24 +1,26 @@
565 # Copyright 2010 Canonical Ltd. This software is licensed under the
566 # GNU Affero General Public License version 3 (see the file LICENSE).
567
568+import functools
569+import json
570+
571 from datetime import datetime
572-import json
573-import functools
574
575 from django.contrib.auth import authenticate as django_authenticate
576-from django.views.decorators.cache import never_cache
577 from django.http import Http404, HttpResponse
578 from django.shortcuts import render_to_response
579 from django.template import RequestContext
580+from django.views.decorators.cache import never_cache
581
582-from .models import (
583+from preflight.conf import BASE_TEMPLATE, TABLE_CLASS
584+from preflight.models import (
585 authenticate,
586+ gather_caches,
587 gather_checks,
588 gather_settings,
589+ gather_switches,
590 gather_versions,
591- gather_switches,
592 )
593-from .conf import BASE_TEMPLATE, TABLE_CLASS
594
595
596 JSON_TYPE = 'application/json'
597@@ -29,11 +31,16 @@
598 if not authenticate(request):
599 raise Http404
600
601+ caches = {
602+ name: [i for i in sorted(cache.iteritems())]
603+ for name, cache in gather_caches().iteritems()
604+ }
605 data = {
606 "applications": gather_checks(),
607 "versions": gather_versions(),
608 "settings": gather_settings(),
609 "switches": gather_switches(),
610+ "caches": caches,
611 }
612 # poor man's conneg
613 if JSON_TYPE in request.META.get('HTTP_ACCEPT', ''):
614@@ -76,5 +83,3 @@
615 request.user = basic_user
616 return f(self, request)
617 return wrapper
618-
619-
620
621=== modified file 'preflight_example_project/app/preflight.py'
622--- preflight_example_project/app/preflight.py 2011-02-07 16:06:51 +0000
623+++ preflight_example_project/app/preflight.py 2014-01-28 17:39:58 +0000
624@@ -5,9 +5,10 @@
625
626 from django.conf import settings
627
628-import os.path
629+import os
630+import tempfile
631+
632 import preflight
633-import tempfile
634
635
636 class AppPreflight(preflight.Preflight):
637
638=== modified file 'preflight_example_project/run.py'
639--- preflight_example_project/run.py 2013-05-31 16:23:03 +0000
640+++ preflight_example_project/run.py 2014-01-28 17:39:58 +0000
641@@ -13,7 +13,6 @@
642 from django.conf import settings
643
644
645-
646 def tests():
647 TestRunner = get_runner(settings)
648 if hasattr(TestRunner, 'func_name'):
649@@ -26,6 +25,5 @@
650 sys.exit(bool(failures))
651
652
653-
654 if __name__ == '__main__':
655 tests()
656
657=== modified file 'preflight_example_project/urls.py'
658--- preflight_example_project/urls.py 2011-10-04 15:54:50 +0000
659+++ preflight_example_project/urls.py 2014-01-28 17:39:58 +0000
660@@ -1,15 +1,14 @@
661 # Copyright 2010 Canonical Ltd. This software is licensed under the
662 # GNU Affero General Public License version 3 (see the file LICENSE).
663
664-from django.conf.urls.defaults import (
665- handler404, handler500, include, patterns
666-)
667+from django.conf.urls import include, patterns
668
669 import preflight
670
671
672 preflight.autodiscover()
673
674-urlpatterns = patterns('',
675+urlpatterns = patterns(
676+ '',
677 (r'^preflight/', include('preflight.urls')),
678 )
679
680=== modified file 'setup.py'
681--- setup.py 2013-05-31 16:23:03 +0000
682+++ setup.py 2014-01-28 17:39:58 +0000
683@@ -56,7 +56,7 @@
684 'preflight': ['templates/preflight/*.html'],
685 },
686 install_requires=[
687- 'django >= 1.1',
688+ 'django >= 1.4',
689 ],
690 tests_require=tests_require,
691 extras_require={
692
693=== modified file 'tox.ini'
694--- tox.ini 2013-05-17 14:50:46 +0000
695+++ tox.ini 2014-01-28 17:39:58 +0000
696@@ -1,10 +1,8 @@
697 [tox]
698-envlist =
699- py2.6-django1.1, py2.7-django1.1,
700- py2.6-django1.2, py2.7-django1.2,
701- py2.6-django1.3, py2.7-django1.3,
702+envlist =
703 py2.6-django1.4, py2.7-django1.4,
704 py2.6-django1.5, py2.7-django1.5,
705+ py2.6-django1.6, py2.7-django1.6,
706 docs
707
708 [testenv]
709@@ -19,18 +17,6 @@
710 sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
711
712 # Python 2.6
713-[testenv:py2.6-django1.1]
714-basepython = python2.6
715-deps = django >= 1.1, < 1.2
716-
717-[testenv:py2.6-django1.2]
718-basepython = python2.6
719-deps = django >= 1.2, < 1.3
720-
721-[testenv:py2.6-django1.3]
722-basepython = python2.6
723-deps = django >= 1.3, < 1.4
724-
725 [testenv:py2.6-django1.4]
726 basepython = python2.6
727 deps = django >= 1.4, < 1.5
728@@ -39,19 +25,11 @@
729 basepython = python2.6
730 deps = django >= 1.5, < 1.6
731
732+[testenv:py2.6-django1.6]
733+basepython = python2.6
734+deps = django >= 1.6, < 1.7
735+
736 # Python 2.7
737-[testenv:py2.7-django1.1]
738-basepython = python2.7
739-deps = django >= 1.1, < 1.2
740-
741-[testenv:py2.7-django1.2]
742-basepython = python2.7
743-deps = django >= 1.2, < 1.3
744-
745-[testenv:py2.7-django1.3]
746-basepython = python2.7
747-deps = django >= 1.3, < 1.4
748-
749 [testenv:py2.7-django1.4]
750 basepython = python2.7
751 deps = django >= 1.4, < 1.5
752@@ -59,3 +37,7 @@
753 [testenv:py2.7-django1.5]
754 basepython = python2.7
755 deps = django >= 1.5, < 1.6
756+
757+[testenv:py2.7-django1.6]
758+basepython = python2.7
759+deps = django >= 1.6, < 1.7

Subscribers

People subscribed via source and target branches