diff --git a/.travis.yml b/.travis.yml index 26bd0c5c55a..dfbe05aae57 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ env: - DJANGO=1.8 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='customuserapp.User' before_script: + - pip freeze - sh -c "if [ '$DATABASE_URL' = 'postgres://postgres@127.0.0.1/djangocms_test' ]; then psql -c 'DROP DATABASE IF EXISTS djangocms_test;' -U postgres; fi" - sh -c "if [ '$DATABASE_URL' = 'postgres://postgres@127.0.0.1/djangocms_test' ]; @@ -38,16 +39,15 @@ before_script: before_install: - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" + # xvfb is started in this way to ensure the screen size is 1280x1024x8 + - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/cucumber_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x8" install: - - pip install -q -r "test_requirements/django-$DJANGO.txt" xvfbwrapper - - if [ $DATABASE_URL == 'postgres://postgres@127.0.0.1/djangocms_test' ]; then pip - install -q psycopg2 ; fi - - if [ $DATABASE_URL == 'mysql://root@127.0.0.1/djangocms_test' ]; then pip install - -q mysqlclient ; fi + - pip install -r "test_requirements/django-$DJANGO.txt" + - if [ $DATABASE_URL == 'postgres://postgres@127.0.0.1/djangocms_test' ]; then pip install psycopg2 ; fi + - if [ $DATABASE_URL == 'mysql://root@127.0.0.1/djangocms_test' ]; then pip install mysqlclient ; fi -script: coverage run --rcfile=.coverage.rc develop.py test --migrate --xvfb +script: coverage run --rcfile=.coverage.rc manage.py test after_success: coverallscustomuserapp.User --config_file=.coverage.rc diff --git a/cms/api.py b/cms/api.py index da15ffb2502..0fd2de168da 100644 --- a/cms/api.py +++ b/cms/api.py @@ -19,7 +19,6 @@ from django.utils.translation import activate from cms import constants -from cms.admin.forms import save_permissions from cms.app_base import CMSApp from cms.apphook_pool import apphook_pool from cms.constants import TEMPLATE_INHERITANCE_MAGIC @@ -363,6 +362,7 @@ def create_page_user(created_by, user, See docs/extending_cms/api_reference.rst for more info """ + from cms.admin.forms import save_permissions if grant_all: # just be lazy return create_page_user(created_by, user, True, True, True, True, diff --git a/cms/apphook_pool.py b/cms/apphook_pool.py index 821235aea9c..5b4c7ea0021 100644 --- a/cms/apphook_pool.py +++ b/cms/apphook_pool.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - import warnings from django.core.exceptions import ImproperlyConfigured @@ -24,17 +23,15 @@ def clear(self): self.discovered = False def register(self, app=None, discovering_apps=False): - import os - import inspect - source_file = os.path.basename(inspect.stack()[1][1]) - if source_file == 'cms_app.py': - warnings.warn('cms_app.py filename is deprecated, ' - 'and it will be removed in version 3.4; ' - 'please rename it to cms_apps.py', DeprecationWarning) # allow use as a decorator if app is None: return lambda app: self.register(app, discovering_apps) + if app.__module__.split('.')[-1] == 'cms_app': + warnings.warn('cms_app.py filename is deprecated, ' + 'and it will be removed in version 3.4; ' + 'please rename it to cms_apps.py', DeprecationWarning) + if self.apphooks and not discovering_apps: return app diff --git a/cms/test_utils/testcases.py b/cms/test_utils/testcases.py index 8a7eb0e5516..a349fcc4b15 100644 --- a/cms/test_utils/testcases.py +++ b/cms/test_utils/testcases.py @@ -2,6 +2,7 @@ import json import sys import warnings +from cms.utils.compat import DJANGO_1_6 from django.conf import settings from django.contrib.auth import get_user_model @@ -9,7 +10,7 @@ from django.contrib.sites.models import Site from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist -from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse, clear_url_caches from django.template.context import Context, RequestContext from django.test import testcases from django.test.client import RequestFactory @@ -396,9 +397,25 @@ def render_template_obj(self, template, context, request): template_obj = Template(template) return template_obj.render(RequestContext(request, context)) + class CMSTestCase(BaseCMSTestCase, testcases.TestCase): pass class TransactionCMSTestCase(BaseCMSTestCase, testcases.TransactionTestCase): pass + +if DJANGO_1_6: + class ClearURLs(object): + @classmethod + def setUpClass(cls): + clear_url_caches() + super(ClearURLs, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(ClearURLs, cls).tearDownClass() + clear_url_caches() +else: + class ClearURLs(object): + pass diff --git a/cms/test_utils/util/static_analysis.py b/cms/test_utils/util/static_analysis.py index bb84e91028f..a42823a1c66 100644 --- a/cms/test_utils/util/static_analysis.py +++ b/cms/test_utils/util/static_analysis.py @@ -1,5 +1,5 @@ import os -import sys +from django.utils import six from pyflakes import api from pyflakes.checker import Checker @@ -43,6 +43,7 @@ def pyflakes(packages): Checker.___init___ = Checker.__init__ Checker.__init__ = _pyflakes_no_migrations Checker.report = _pyflakes_report_with_nopyflakes - reporter = Reporter(sys.stdout, sys.stderr) + out = six.StringIO() + reporter = Reporter(out, out) paths = [os.path.dirname(package.__file__) for package in packages] - return _check_recursive(paths, reporter) + return _check_recursive(paths, reporter), out.getvalue() diff --git a/cms/tests/__init__.py b/cms/tests/__init__.py index 40b0e830d5d..e69de29bb2d 100644 --- a/cms/tests/__init__.py +++ b/cms/tests/__init__.py @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- - -from cms.tests.static_analysis import * # nopyflakes -from cms.tests.alias import * # nopyflakes -from cms.tests.admin import * # nopyflakes -from cms.tests.api import * # nopyflakes -from cms.tests.apphooks import * # nopyflakes -from cms.tests.django_load import * # nopyflakes -from cms.tests.docs import * # nopyflakes -from cms.tests.extensions import * # nopyflakes -from cms.tests.forms import * # nopyflakes -from cms.tests.i18n import * # nopyflakes -from cms.tests.mail import * # nopyflakes -from cms.tests.menu import * # nopyflakes -from cms.tests.menu_utils import * # nopyflakes -from cms.tests.multilingual import * # nopyflakes -from cms.tests.navextender import * # nopyflakes -from cms.tests.nonroot import * # nopyflakes -from cms.tests.page import * # nopyflakes -from cms.tests.permissions import * # nopyflakes -from cms.tests.permmod import * # nopyflakes -from cms.tests.placeholder import * # nopyflakes -from cms.tests.plugins import * # nopyflakes -from cms.tests.po import * # nopyflakes -from cms.tests.publisher import * # nopyflakes -from cms.tests.rendering import * # nopyflakes -from cms.tests.reversion_tests import * # nopyflakes -from cms.tests.security import * # nopyflakes -from cms.tests.settings import * # nopyflakes -from cms.tests.site import * # nopyflakes -from cms.tests.sitemap import * # nopyflakes -from cms.tests.static_placeholder import * # nopyflakes -from cms.tests.staticfiles import * # nopyflakes -from cms.tests.templatetags import * # nopyflakes -from cms.tests.templates import * # nopyflakes -from cms.tests.toolbar import * # nopyflakes -from cms.tests.toolbar_pool import * # nopyflakes -from cms.tests.urlutils import * # nopyflakes -from cms.tests.views import * # nopyflakes -from cms.tests.management import * # nopyflakes -from cms.tests.fixture_loading import * # nopyflakes -from cms.tests.menu_page_viewperm import * # nopyflakes -from cms.tests.menu_page_viewperm_staff import * # nopyflakes -from cms.tests.nested_plugins import * # nopyflakes -from cms.tests.check import * # nopyflakes -from cms.tests.frontend import * # nopyflakes -from cms.tests.signals import * # nopyflakes -from cms.tests.no_i18n import * # nopyflakes -from cms.tests.cache import * # nopyflakes -from cms.tests.widgets import * # nopyflakes diff --git a/cms/tests/admin.py b/cms/tests/test_admin.py similarity index 98% rename from cms/tests/admin.py rename to cms/tests/test_admin.py index 5f35fdc91e6..466cc9a70a9 100644 --- a/cms/tests/admin.py +++ b/cms/tests/test_admin.py @@ -46,9 +46,9 @@ def admin_class(self): return site._registry[Page] def _get_guys(self, admin_only=False, use_global_permissions=True): - admiN_user = self.get_superuser() + admin_user = self.get_superuser() if admin_only: - return admiN_user + return admin_user USERNAME = 'test' if get_user_model().USERNAME_FIELD == 'email': @@ -73,7 +73,7 @@ def _get_guys(self, admin_only=False, use_global_permissions=True): can_move_page=True, ) gpp.sites = Site.objects.all() - return admiN_user, normal_guy + return admin_user, normal_guy class AdminTestCase(AdminTestsBase): @@ -319,8 +319,7 @@ def test_delete(self): page.publish('en') with self.login_user_context(admin_user): data = {'post': 'yes'} - with self.assertNumQueries(FuzzyInt(300, 407)): - response = self.client.post(URL_CMS_PAGE_DELETE % page.pk, data) + response = self.client.post(URL_CMS_PAGE_DELETE % page.pk, data) self.assertRedirects(response, URL_CMS_PAGE) def test_delete_diff_language(self): @@ -336,8 +335,7 @@ def test_delete_diff_language(self): page.publish('en') with self.login_user_context(admin_user): data = {'post': 'yes'} - with self.assertNumQueries(FuzzyInt(300, 394)): - response = self.client.post(URL_CMS_PAGE_DELETE % page.pk, data) + response = self.client.post(URL_CMS_PAGE_DELETE % page.pk, data) self.assertRedirects(response, URL_CMS_PAGE) def test_search_fields(self): @@ -1615,20 +1613,22 @@ def test_render_edit_mode(self): user = self.get_superuser() self.assertEqual(Placeholder.objects.all().count(), 4) with self.login_user_context(user): - with self.assertNumQueries(FuzzyInt(40, 66)): - output = force_text(self.client.get('/en/?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON')).content) + output = force_text( + self.client.get( + '/en/?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON') + ).content + ) self.assertIn('Test', output) self.assertEqual(Placeholder.objects.all().count(), 9) self.assertEqual(StaticPlaceholder.objects.count(), 2) for placeholder in Placeholder.objects.all(): add_plugin(placeholder, TextPlugin, 'en', body='Test') - with self.assertNumQueries(FuzzyInt(40, 72)): - output = force_text(self.client.get('/en/?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON')).content) + output = force_text( + self.client.get( + '/en/?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON') + ).content + ) self.assertIn('Test', output) - with self.assertNumQueries(FuzzyInt(18, 45)): - force_text(self.client.get('/en/?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON')).content) - with self.assertNumQueries(FuzzyInt(11, 29)): - force_text(self.client.get('/en/').content) def test_tree_view_queries(self): from django.core.cache import cache diff --git a/cms/tests/alias.py b/cms/tests/test_alias.py similarity index 100% rename from cms/tests/alias.py rename to cms/tests/test_alias.py diff --git a/cms/tests/api.py b/cms/tests/test_api.py similarity index 99% rename from cms/tests/api.py rename to cms/tests/test_api.py index dfd53e831dc..09821df0a1b 100644 --- a/cms/tests/api.py +++ b/cms/tests/test_api.py @@ -20,7 +20,7 @@ from cms.plugin_base import CMSPluginBase from cms.test_utils.util.menu_extender import TestMenu from cms.test_utils.util.mock import AttributeObject -from cms.tests.apphooks import APP_MODULE, APP_NAME +from cms.tests.test_apphooks import APP_MODULE, APP_NAME def _grant_page_permission(user, codename): diff --git a/cms/tests/apphooks.py b/cms/tests/test_apphooks.py similarity index 99% rename from cms/tests/apphooks.py rename to cms/tests/test_apphooks.py index 8af1ffddb70..e65b1fef9ab 100644 --- a/cms/tests/apphooks.py +++ b/cms/tests/test_apphooks.py @@ -18,8 +18,8 @@ from cms.cms_toolbars import PlaceholderToolbar from cms.models import Title, Page from cms.test_utils.project.placeholderapp.models import Example1 -from cms.test_utils.testcases import CMSTestCase -from cms.tests.menu_utils import DumbPageLanguageUrl +from cms.test_utils.testcases import CMSTestCase, ClearURLs +from cms.tests.test_menu_utils import DumbPageLanguageUrl from cms.toolbar.toolbar import CMSToolbar from cms.utils.conf import get_cms_setting from cms.utils.i18n import force_language @@ -32,7 +32,7 @@ APP_MODULE = "cms.test_utils.project.sampleapp.cms_apps" -class ApphooksTestCase(CMSTestCase): +class ApphooksTestCase(ClearURLs, CMSTestCase): def setUp(self): clear_app_resolvers() clear_url_caches() @@ -692,9 +692,7 @@ def test_nested_apphooks_urls(self): apphook_pool.clear() -class ApphooksPageLanguageUrlTestCase(CMSTestCase): - settings_overrides = {'ROOT_URLCONF': 'cms.test_utils.project.second_urls_for_apphook_tests'} - +class ApphooksPageLanguageUrlTestCase(ClearURLs, CMSTestCase): def setUp(self): clear_app_resolvers() clear_url_caches() diff --git a/cms/tests/cache.py b/cms/tests/test_cache.py similarity index 99% rename from cms/tests/cache.py rename to cms/tests/test_cache.py index 2342922bd38..d74f0cb7ef7 100644 --- a/cms/tests/cache.py +++ b/cms/tests/test_cache.py @@ -156,7 +156,7 @@ def test_cache_page(self): self.assertFalse(request.user.is_authenticated()) # Test that the page is initially uncached - with self.assertNumQueries(FuzzyInt(1, 21)): + with self.assertNumQueries(FuzzyInt(1, 22)): response = self.client.get('/en/') self.assertEqual(response.status_code, 200) diff --git a/cms/tests/check.py b/cms/tests/test_check.py similarity index 99% rename from cms/tests/check.py rename to cms/tests/test_check.py index b04642f8317..41c0047157e 100644 --- a/cms/tests/check.py +++ b/cms/tests/test_check.py @@ -125,7 +125,6 @@ def test_middlewares(self): 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.doc.XViewMiddleware', 'django.middleware.common.CommonMiddleware', 'cms.middleware.page.CurrentPageMiddleware', 'cms.middleware.toolbar.ToolbarMiddleware', diff --git a/cms/tests/django_load.py b/cms/tests/test_django_load.py similarity index 100% rename from cms/tests/django_load.py rename to cms/tests/test_django_load.py diff --git a/cms/tests/docs.py b/cms/tests/test_docs.py similarity index 100% rename from cms/tests/docs.py rename to cms/tests/test_docs.py diff --git a/cms/tests/extensions.py b/cms/tests/test_extensions.py similarity index 99% rename from cms/tests/extensions.py rename to cms/tests/test_extensions.py index 1d99de77a42..278836e8f26 100644 --- a/cms/tests/extensions.py +++ b/cms/tests/test_extensions.py @@ -15,7 +15,7 @@ from cms.test_utils.project.extensionapp.models import (MyPageExtension, MyTitleExtension) from cms.test_utils.testcases import CMSTestCase as TestCase -from cms.tests import AdminTestsBase +from cms.tests.test_admin import AdminTestsBase class ExtensionsTestCase(TestCase): diff --git a/cms/tests/fixture_loading.py b/cms/tests/test_fixture_loading.py similarity index 100% rename from cms/tests/fixture_loading.py rename to cms/tests/test_fixture_loading.py diff --git a/cms/tests/forms.py b/cms/tests/test_forms.py similarity index 100% rename from cms/tests/forms.py rename to cms/tests/test_forms.py diff --git a/cms/tests/frontend.py b/cms/tests/test_frontend.py similarity index 100% rename from cms/tests/frontend.py rename to cms/tests/test_frontend.py diff --git a/cms/tests/i18n.py b/cms/tests/test_i18n.py similarity index 100% rename from cms/tests/i18n.py rename to cms/tests/test_i18n.py diff --git a/cms/tests/mail.py b/cms/tests/test_mail.py similarity index 100% rename from cms/tests/mail.py rename to cms/tests/test_mail.py diff --git a/cms/tests/management.py b/cms/tests/test_management.py similarity index 100% rename from cms/tests/management.py rename to cms/tests/test_management.py diff --git a/cms/tests/menu.py b/cms/tests/test_menu.py similarity index 100% rename from cms/tests/menu.py rename to cms/tests/test_menu.py diff --git a/cms/tests/menu_page_viewperm.py b/cms/tests/test_menu_page_viewperm.py similarity index 100% rename from cms/tests/menu_page_viewperm.py rename to cms/tests/test_menu_page_viewperm.py diff --git a/cms/tests/menu_page_viewperm_staff.py b/cms/tests/test_menu_page_viewperm_staff.py similarity index 99% rename from cms/tests/menu_page_viewperm_staff.py rename to cms/tests/test_menu_page_viewperm_staff.py index d397f33ee90..26aee8cf8bb 100644 --- a/cms/tests/menu_page_viewperm_staff.py +++ b/cms/tests/test_menu_page_viewperm_staff.py @@ -4,7 +4,7 @@ from django.test.utils import override_settings from django.contrib.auth import get_user_model -from cms.tests.menu_page_viewperm import ViewPermissionTests +from cms.tests.test_menu_page_viewperm import ViewPermissionTests __all__ = [ 'ViewPermissionComplexMenuStaffNodeTests', diff --git a/cms/tests/menu_utils.py b/cms/tests/test_menu_utils.py similarity index 100% rename from cms/tests/menu_utils.py rename to cms/tests/test_menu_utils.py diff --git a/cms/tests/multilingual.py b/cms/tests/test_multilingual.py similarity index 100% rename from cms/tests/multilingual.py rename to cms/tests/test_multilingual.py diff --git a/cms/tests/navextender.py b/cms/tests/test_navextender.py similarity index 100% rename from cms/tests/navextender.py rename to cms/tests/test_navextender.py diff --git a/cms/tests/nested_plugins.py b/cms/tests/test_nested_plugins.py similarity index 99% rename from cms/tests/nested_plugins.py rename to cms/tests/test_nested_plugins.py index 00947f64b95..79ebc80aac8 100644 --- a/cms/tests/nested_plugins.py +++ b/cms/tests/test_nested_plugins.py @@ -9,7 +9,7 @@ from cms.models import Page from cms.models.placeholdermodel import Placeholder from cms.models.pluginmodel import CMSPlugin -from cms.tests.plugins import PluginsTestBaseCase +from cms.tests.test_plugins import PluginsTestBaseCase from cms.utils.compat.tests import UnittestCompatMixin from cms.utils.copy_plugins import copy_plugins_to diff --git a/cms/tests/no_i18n.py b/cms/tests/test_no_i18n.py similarity index 100% rename from cms/tests/no_i18n.py rename to cms/tests/test_no_i18n.py diff --git a/cms/tests/nonroot.py b/cms/tests/test_nonroot.py similarity index 93% rename from cms/tests/nonroot.py rename to cms/tests/test_nonroot.py index 0d2a3b93937..98ac4d3c147 100644 --- a/cms/tests/nonroot.py +++ b/cms/tests/test_nonroot.py @@ -5,15 +5,15 @@ from cms.api import create_page from cms.models import Page -from cms.test_utils.testcases import CMSTestCase +from cms.test_utils.testcases import CMSTestCase, ClearURLs from cms.templatetags.cms_admin import preview_link from cms.utils.i18n import force_language +from django.test.utils import override_settings from menus.base import NavigationNode -class NonRootCase(CMSTestCase): - urls = 'cms.test_utils.project.nonroot_urls' - +@override_settings(ROOT_URLCONF='cms.test_utils.project.nonroot_urls') +class NonRootCase(ClearURLs, CMSTestCase): def setUp(self): u = self._create_user("test", True, True) diff --git a/cms/tests/page.py b/cms/tests/test_page.py similarity index 98% rename from cms/tests/page.py rename to cms/tests/test_page.py index 08a21908214..121f6410830 100644 --- a/cms/tests/page.py +++ b/cms/tests/test_page.py @@ -15,7 +15,7 @@ from django.http import HttpRequest, HttpResponse, HttpResponseNotFound from django.test.utils import override_settings from django.utils.encoding import force_text -from django.utils.timezone import now as tz_now, make_aware, get_current_timezone +from django.utils.timezone import now as tz_now from cms import constants from cms.admin.forms import AdvancedSettingsForm @@ -30,7 +30,7 @@ from cms.sitemaps import CMSSitemap from cms.templatetags.cms_tags import get_placeholder_content from cms.test_utils.compat import skipIf -from cms.test_utils.testcases import (CMSTestCase, URL_CMS_PAGE, URL_CMS_PAGE_ADD) +from cms.test_utils.testcases import (CMSTestCase, ClearURLs, URL_CMS_PAGE, URL_CMS_PAGE_ADD) from cms.test_utils.util.context_managers import LanguageOverride, UserLoginContext from cms.utils import get_cms_setting from cms.utils.compat import DJANGO_1_7 @@ -442,12 +442,20 @@ def test_page_obj_change_data_from_template_tags(self): with self.login_user_context(superuser): page_data = self.get_new_page_data() change_user = str(superuser) - #some databases don't store microseconds, so move the start flag back by 1 second + # some databases don't store microseconds, so move the start flag + # back by 1 second before_change = tz_now()+datetime.timedelta(seconds=-1) self.client.post(URL_CMS_PAGE_ADD, page_data) - page = Page.objects.get(title_set__slug=page_data['slug'], publisher_is_draft=True) + page = Page.objects.get( + title_set__slug=page_data['slug'], + publisher_is_draft=True + ) self.client.post('/en/admin/cms/page/%s/' % page.id, page_data) - t = template.Template("{% load cms_tags %}{% page_attribute changed_by %} changed on {% page_attribute changed_date as page_change %}{{ page_change|date:'Y-m-d\TH:i:s' }}") + t = template.Template( + "{% load cms_tags %}{% page_attribute changed_by %} changed " + "on {% page_attribute changed_date as page_change %}" + "{{ page_change|date:'Y-m-d\TH:i:s' }}" + ) req = HttpRequest() page.save() page.publish('en') @@ -456,13 +464,20 @@ def test_page_obj_change_data_from_template_tags(self): req.REQUEST = {} actual_result = t.render(template.Context({"request": req})) - desired_result = "{0} changed on {1}".format(change_user, actual_result[-19:]) - save_time = make_aware(datetime.datetime.strptime(actual_result[-19:], "%Y-%m-%dT%H:%M:%S"), get_current_timezone()) + desired_result = "{0} changed on {1}".format( + change_user, + actual_result[-19:] + ) + save_time = datetime.datetime.strptime( + actual_result[-19:], + "%Y-%m-%dT%H:%M:%S" + ) self.assertEqual(actual_result, desired_result) - # direct time comparisons are flaky, so we just check if the page's changed_date is within the time range taken by this test - self.assertTrue(before_change <= save_time) - self.assertTrue(save_time <= after_change) + # direct time comparisons are flaky, so we just check if the + # page's changed_date is within the time range taken by this test + self.assertLessEqual(before_change, save_time) + self.assertLessEqual(save_time, after_change) def test_copy_page(self): """ @@ -1266,7 +1281,7 @@ def test_type_limit_on_plugin_move(self): @override_settings(ROOT_URLCONF='cms.test_utils.project.noadmin_urls') -class NoAdminPageTests(CMSTestCase): +class NoAdminPageTests(ClearURLs, CMSTestCase): def test_get_page_from_request_fakeadmin_nopage(self): noadmin_apps = [app for app in installed_apps() if app != 'django.contrib.admin'] diff --git a/cms/tests/permissions.py b/cms/tests/test_permissions.py similarity index 100% rename from cms/tests/permissions.py rename to cms/tests/test_permissions.py diff --git a/cms/tests/permmod.py b/cms/tests/test_permmod.py similarity index 100% rename from cms/tests/permmod.py rename to cms/tests/test_permmod.py diff --git a/cms/tests/placeholder.py b/cms/tests/test_placeholder.py similarity index 100% rename from cms/tests/placeholder.py rename to cms/tests/test_placeholder.py diff --git a/cms/tests/plugins.py b/cms/tests/test_plugins.py similarity index 99% rename from cms/tests/plugins.py rename to cms/tests/test_plugins.py index 0e1cfe9ab52..88b12199ae0 100644 --- a/cms/tests/plugins.py +++ b/cms/tests/test_plugins.py @@ -1702,6 +1702,7 @@ def test_import_broken_plugin(self): plugin_pool.discovered = False self.assertRaises(ImportError, plugin_pool.discover_plugins) + class MTIPluginsTestCase(PluginsTestBaseCase): def test_add_edit_plugin(self): from cms.test_utils.project.mti_pluginapp.models import TestPluginBetaModel diff --git a/cms/tests/po.py b/cms/tests/test_po.py similarity index 100% rename from cms/tests/po.py rename to cms/tests/test_po.py diff --git a/cms/tests/publisher.py b/cms/tests/test_publisher.py similarity index 100% rename from cms/tests/publisher.py rename to cms/tests/test_publisher.py diff --git a/cms/tests/rendering.py b/cms/tests/test_rendering.py similarity index 99% rename from cms/tests/rendering.py rename to cms/tests/test_rendering.py index d3f706c3bcf..57cc548689c 100644 --- a/cms/tests/rendering.py +++ b/cms/tests/test_rendering.py @@ -155,8 +155,8 @@ def test_details_view(self): self.assertEqual(r, u'|' + self.test_data['text_main'] + u'|' + self.test_data['text_sub'] + u'|') @override_settings( - CMS_PLUGIN_PROCESSORS=('cms.tests.rendering.sample_plugin_processor',), - CMS_PLUGIN_CONTEXT_PROCESSORS=('cms.tests.rendering.sample_plugin_context_processor',), + CMS_PLUGIN_PROCESSORS=('cms.tests.test_rendering.sample_plugin_processor',), + CMS_PLUGIN_CONTEXT_PROCESSORS=('cms.tests.test_rendering.sample_plugin_context_processor',), ) def test_processors(self): """ diff --git a/cms/tests/reversion_tests.py b/cms/tests/test_reversion_tests.py similarity index 100% rename from cms/tests/reversion_tests.py rename to cms/tests/test_reversion_tests.py diff --git a/cms/tests/security.py b/cms/tests/test_security.py similarity index 100% rename from cms/tests/security.py rename to cms/tests/test_security.py diff --git a/cms/tests/settings.py b/cms/tests/test_settings.py similarity index 100% rename from cms/tests/settings.py rename to cms/tests/test_settings.py diff --git a/cms/tests/signals.py b/cms/tests/test_signals.py similarity index 100% rename from cms/tests/signals.py rename to cms/tests/test_signals.py diff --git a/cms/tests/site.py b/cms/tests/test_site.py similarity index 100% rename from cms/tests/site.py rename to cms/tests/test_site.py diff --git a/cms/tests/sitemap.py b/cms/tests/test_sitemap.py similarity index 100% rename from cms/tests/sitemap.py rename to cms/tests/test_sitemap.py diff --git a/cms/tests/static_analysis.py b/cms/tests/test_static_analysis.py similarity index 51% rename from cms/tests/static_analysis.py rename to cms/tests/test_static_analysis.py index 249896effa4..4fd2f6f0137 100644 --- a/cms/tests/static_analysis.py +++ b/cms/tests/test_static_analysis.py @@ -1,4 +1,10 @@ -from unittest import TestCase +import sys + +if sys.version_info[0] == 2 and sys.version_info[1] == 6: + from django.test.testcases import TestCase +else: + from unittest import TestCase + from cms.test_utils.util.static_analysis import pyflakes @@ -9,4 +15,5 @@ class AboveStaticAnalysisCodeTest(TestCase): def test_pyflakes(self): import cms import menus - self.assertEqual(pyflakes((cms, menus)), 0) + errors, message = pyflakes((cms, menus)) + self.assertEqual(errors, 0, message) diff --git a/cms/tests/static_placeholder.py b/cms/tests/test_static_placeholder.py similarity index 99% rename from cms/tests/static_placeholder.py rename to cms/tests/test_static_placeholder.py index f9e53517add..dbc8dc78392 100644 --- a/cms/tests/static_placeholder.py +++ b/cms/tests/test_static_placeholder.py @@ -10,7 +10,7 @@ from cms.api import add_plugin from cms.constants import PLUGIN_MOVE_ACTION, PLUGIN_COPY_ACTION from cms.models import StaticPlaceholder, Placeholder, CMSPlugin -from cms.tests.plugins import PluginsTestBaseCase +from cms.tests.test_plugins import PluginsTestBaseCase from cms.utils.urlutils import admin_reverse diff --git a/cms/tests/staticfiles.py b/cms/tests/test_staticfiles.py similarity index 100% rename from cms/tests/staticfiles.py rename to cms/tests/test_staticfiles.py diff --git a/cms/tests/templates.py b/cms/tests/test_templates.py similarity index 100% rename from cms/tests/templates.py rename to cms/tests/test_templates.py diff --git a/cms/tests/templatetags.py b/cms/tests/test_templatetags.py similarity index 100% rename from cms/tests/templatetags.py rename to cms/tests/test_templatetags.py diff --git a/cms/tests/toolbar.py b/cms/tests/test_toolbar.py similarity index 99% rename from cms/tests/toolbar.py rename to cms/tests/test_toolbar.py index 72e4e2e37a6..a01efc84a6a 100644 --- a/cms/tests/toolbar.py +++ b/cms/tests/test_toolbar.py @@ -27,7 +27,8 @@ detail_view_multi, detail_view_multi_unfiltered, ClassDetail) from cms.test_utils.testcases import (CMSTestCase, - URL_CMS_PAGE_ADD, URL_CMS_PAGE_CHANGE) + URL_CMS_PAGE_ADD, URL_CMS_PAGE_CHANGE, + ClearURLs) from cms.test_utils.util.context_managers import UserLoginContext from cms.toolbar.items import (ToolbarAPIMixin, LinkItem, ItemSearchResult, Break, SubMenu, AjaxItem) @@ -92,33 +93,37 @@ def _fake_logentry(self, instance_id, user, text, model=Page): @override_settings(ROOT_URLCONF='cms.test_utils.project.nonroot_urls') -class ToolbarMiddlewareTest(ToolbarTestBase): - +class ToolbarMiddlewareTest(ClearURLs, ToolbarTestBase): + @override_settings(CMS_APP_NAME=None) + @override_settings(CMS_TOOLBAR_HIDE=False) def test_no_app_setted_show_toolbar_in_non_cms_urls(self): request = self.get_page_request(None, self.get_anon(), '/') - self.assertTrue(hasattr(request,'toolbar')) + self.assertTrue(hasattr(request, 'toolbar')) + @override_settings(CMS_APP_NAME=None) + @override_settings(CMS_TOOLBAR_HIDE=False) def test_no_app_setted_show_toolbar_in_cms_urls(self): - page = create_page('foo','col_two.html','en',published=True) + page = create_page('foo', 'col_two.html', 'en', published=True) request = self.get_page_request(page, self.get_anon()) - self.assertTrue(hasattr(request,'toolbar')) + self.assertTrue(hasattr(request, 'toolbar')) @override_settings(CMS_APP_NAME='cms') + @override_settings(CMS_TOOLBAR_HIDE=False) def test_app_setted_hide_toolbar_in_non_cms_urls_toolbar_hide_unsetted(self): request = self.get_page_request(None, self.get_anon(), '/') - self.assertTrue(hasattr(request,'toolbar')) + self.assertTrue(hasattr(request, 'toolbar')) @override_settings(CMS_APP_NAME='cms') @override_settings(CMS_TOOLBAR_HIDE=True) def test_app_setted_hide_toolbar_in_non_cms_urls(self): request = self.get_page_request(None, self.get_anon(), '/') - self.assertFalse(hasattr(request,'toolbar')) + self.assertFalse(hasattr(request, 'toolbar')) @override_settings(CMS_APP_NAME='cms') def test_app_setted_show_toolbar_in_cms_urls(self): - page = create_page('foo','col_two.html','en',published=True) + page = create_page('foo', 'col_two.html', 'en', published=True) request = self.get_page_request(page, self.get_anon()) - self.assertTrue(hasattr(request,'toolbar')) + self.assertTrue(hasattr(request, 'toolbar')) @override_settings(CMS_PERMISSION=False) diff --git a/cms/tests/toolbar_pool.py b/cms/tests/test_toolbar_pool.py similarity index 61% rename from cms/tests/toolbar_pool.py rename to cms/tests/test_toolbar_pool.py index ae8c96f365c..c4e51ae35e0 100644 --- a/cms/tests/toolbar_pool.py +++ b/cms/tests/test_toolbar_pool.py @@ -14,47 +14,48 @@ class TestToolbar(CMSToolbar): class ToolbarPoolTests(CMSTestCase): - - def setUp(self): - self.pool = ToolbarPool() - def test_register(self): - self.pool.register(TestToolbar) - self.pool.register(CMSToolbar) - self.assertEqual(self.pool.toolbars, { + pool = ToolbarPool() + pool.register(TestToolbar) + pool.register(CMSToolbar) + self.assertEqual(pool.toolbars, { 'cms.toolbar_base.CMSToolbar': CMSToolbar, - 'cms.tests.toolbar_pool.TestToolbar': TestToolbar}) + 'cms.tests.test_toolbar_pool.TestToolbar': TestToolbar}) self.assertRaises(ToolbarAlreadyRegistered, - self.pool.register, TestToolbar) + pool.register, TestToolbar) def test_register_type(self): - self.assertRaises(ImproperlyConfigured, self.pool.register, str) - self.assertRaises(ImproperlyConfigured, self.pool.register, object) + pool = ToolbarPool() + self.assertRaises(ImproperlyConfigured, pool.register, str) + self.assertRaises(ImproperlyConfigured, pool.register, object) def test_register_order(self): - self.pool.register(TestToolbar) - self.pool.register(CMSToolbar) + pool = ToolbarPool() + pool.register(TestToolbar) + pool.register(CMSToolbar) test_toolbar = SortedDict() - test_toolbar['cms.tests.toolbar_pool.TestToolbar'] = TestToolbar + test_toolbar['cms.tests.test_toolbar_pool.TestToolbar'] = TestToolbar test_toolbar['cms.toolbar_base.CMSToolbar'] = CMSToolbar - self.assertEqual(list(test_toolbar.keys()), list(self.pool.toolbars.keys())) + self.assertEqual(list(test_toolbar.keys()), list(pool.toolbars.keys())) def test_unregister(self): - self.pool.register(TestToolbar) - self.pool.unregister(TestToolbar) - self.assertEqual(self.pool.toolbars, {}) + pool = ToolbarPool() + pool.register(TestToolbar) + pool.unregister(TestToolbar) + self.assertEqual(pool.toolbars, {}) self.assertRaises(ToolbarNotRegistered, - self.pool.unregister, TestToolbar) + pool.unregister, TestToolbar) def test_settings(self): + pool = ToolbarPool() toolbars = toolbar_pool.toolbars toolbar_pool.clear() with self.settings(CMS_TOOLBARS=['cms.cms_toolbars.BasicToolbar', 'cms.cms_toolbars.PlaceholderToolbar']): toolbar_pool.register(TestToolbar) - self.assertEqual(len(list(self.pool.get_toolbars().keys())), 2) + self.assertEqual(len(list(pool.get_toolbars().keys())), 2) api.create_page("home", "simple.html", "en", published=True) with self.login_user_context(self.get_superuser()): response = self.client.get("/en/?%s" % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON')) diff --git a/cms/tests/urlutils.py b/cms/tests/test_urlutils.py similarity index 100% rename from cms/tests/urlutils.py rename to cms/tests/test_urlutils.py diff --git a/cms/tests/views.py b/cms/tests/test_views.py similarity index 99% rename from cms/tests/views.py rename to cms/tests/test_views.py index 0713c2c6125..ba14ea03d7f 100644 --- a/cms/tests/views.py +++ b/cms/tests/test_views.py @@ -15,7 +15,7 @@ from cms.apphook_pool import apphook_pool from cms.models import PagePermission, UserSettings, Placeholder from cms.page_rendering import _handle_no_page -from cms.test_utils.testcases import CMSTestCase +from cms.test_utils.testcases import CMSTestCase, ClearURLs from cms.test_utils.util.fuzzy_int import FuzzyInt from cms.utils.compat import DJANGO_1_7 from cms.utils.conf import get_cms_setting @@ -192,7 +192,7 @@ def test_toolbar_switch_urls(self): @override_settings(ROOT_URLCONF='cms.test_utils.project.urls') -class ContextTests(CMSTestCase): +class ContextTests(ClearURLs, CMSTestCase): def test_context_current_page(self): """ diff --git a/cms/tests/widgets.py b/cms/tests/test_widgets.py similarity index 100% rename from cms/tests/widgets.py rename to cms/tests/test_widgets.py diff --git a/cms/toolbar_pool.py b/cms/toolbar_pool.py index c5ff2014ab8..6551e95362a 100644 --- a/cms/toolbar_pool.py +++ b/cms/toolbar_pool.py @@ -37,11 +37,8 @@ def clear(self): self._discovered = False def register(self, toolbar): - import os - import inspect import warnings - source_file = os.path.basename(inspect.stack()[1][1]) - if source_file == 'cms_toolbar.py': + if toolbar.__module__.split('.')[-1] == 'cms_toolbar': warnings.warn('cms_toolbar.py filename is deprecated, ' 'and it will be removed in version 3.4; ' 'please rename it to cms_toolbar.py', DeprecationWarning) diff --git a/cms/utils/conf.py b/cms/utils/conf.py index 397e5ed1b45..74629dec199 100644 --- a/cms/utils/conf.py +++ b/cms/utils/conf.py @@ -61,8 +61,8 @@ def wrapper(): 'TOOLBAR_URL__BUILD': 'build', 'TOOLBAR_URL__DISABLE': 'toolbar_off', 'ADMIN_NAMESPACE': 'admin', - 'APP_NAME':None, - 'TOOLBAR_HIDE':False + 'APP_NAME': None, + 'TOOLBAR_HIDE': False } diff --git a/cms/utils/permissions.py b/cms/utils/permissions.py index 233d163af84..c0c06b30ef9 100644 --- a/cms/utils/permissions.py +++ b/cms/utils/permissions.py @@ -11,7 +11,6 @@ from cms.exceptions import NoPermissionsException from cms.models import (Page, PagePermission, GlobalPagePermission, MASK_PAGE, MASK_CHILDREN, MASK_DESCENDANTS) -from cms.plugin_pool import plugin_pool from cms.utils.conf import get_cms_setting @@ -420,6 +419,7 @@ def has_plugin_permission(user, plugin_type, permission_type): the action defined in permission_type permission_type should be 'add', 'change' or 'delete'. """ + from cms.plugin_pool import plugin_pool plugin_class = plugin_pool.get_plugin(plugin_type) plugin_model = plugin_class.model plugin_opts = plugin_model._meta diff --git a/develop.py b/develop.py deleted file mode 100755 index 3fdaf83d2a1..00000000000 --- a/develop.py +++ /dev/null @@ -1,437 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function, with_statement - -import contextlib -import multiprocessing -import pkgutil -import pyclbr -import subprocess -import os -import sys -import warnings - -from django.core.exceptions import DjangoRuntimeWarning -from django.core.exceptions import ImproperlyConfigured -from django.core.management import call_command, CommandError -from django.utils import autoreload -from django.utils.encoding import force_text - -from docopt import docopt - -import cms -from cms.test_utils.cli import configure -from cms.test_utils.util import static_analysis -from cms.test_utils.tmpdir import temp_dir -from cms.utils.compat import DJANGO_1_6 -import menus - -__doc__ = '''django CMS development helper script. - -To use a different database, set the DATABASE_URL environment variable to a -dj-database-url compatible value. The AUTH_USER_MODEL environment variable can -be used to change the user model in the same manner as the --user option. - -Usage: - develop.py test [--parallel | --failfast] [--migrate] [--user=] - [...] [--xvfb] - develop.py timed test [test-label...] [--xvfb] - develop.py isolated test [...] [--parallel] [--migrate] - [--xvfb] - develop.py server [--port=] [--bind=] [--migrate] - [--user=] [ ] - develop.py shell - develop.py compilemessages - develop.py makemessages - develop.py makemigrations [--merge] - develop.py squashmigrations - develop.py pyflakes - develop.py authors - -Options: - -h --help Show this screen. - --version Show version. - --parallel Run tests in parallel. - --migrate Use south migrations in test or server command. - --merge Merge migrations - --failfast Stop tests on first failure (only if not --parallel). - --port= Port to listen on [default: 8000]. - --bind= Interface to bind to [default: 127.0.0.1]. - --user= Specify which user model to run tests with (if other - than auth.User). - --xvfb Use a virtual X framebuffer for frontend testing, - requires xvfbwrapper to be installed. -''' - - -def server(bind='127.0.0.1', port=8000, migrate_cmd=False, app_name=None, migration=None): - if os.environ.get("RUN_MAIN") != "true": - from django.contrib.auth import get_user_model # must be imported lazily - if DJANGO_1_6: - from south.management.commands import syncdb, migrate - if migrate_cmd: - syncdb.Command().handle_noargs(interactive=False, verbosity=1, - database='default') - if app_name: - migrate.Command().handle(interactive=False, verbosity=1, app=app_name, - target=migration) - else: - migrate.Command().handle(interactive=False, verbosity=1) - else: - syncdb.Command().handle_noargs(interactive=False, verbosity=1, - database='default', - migrate=False, migrate_all=True) - migrate.Command().handle(interactive=False, verbosity=1, - fake=True) - else: - if app_name: - call_command("migrate", app_name, migration, database='default') - else: - call_command("migrate", database='default') - User = get_user_model() - if not User.objects.filter(is_superuser=True).exists(): - usr = User() - - if(User.USERNAME_FIELD != 'email'): - setattr(usr, User.USERNAME_FIELD, 'admin') - - usr.email = 'admin@admin.com' - usr.set_password('admin') - usr.is_superuser = True - usr.is_staff = True - usr.is_active = True - usr.save() - print('') - print("A admin user (username: admin, password: admin) " - "has been created.") - print('') - from django.contrib.staticfiles.management.commands import runserver - rs = runserver.Command() - try: - from django.core.management.base import OutputWrapper - rs.stdout = OutputWrapper(sys.stdout) - rs.stderr = OutputWrapper(sys.stderr) - except ImportError: - rs.stdout = sys.stdout - rs.stderr = sys.stderr - rs.use_ipv6 = False - rs._raw_ipv6 = False - rs.addr = bind - rs.port = port - autoreload.main(rs.inner_run, (), { - 'addrport': '%s:%s' % (bind, port), - 'insecure_serving': True, - 'use_threading': True - }) - - -def _split(itr, num): - split = [] - size = int(len(itr) / num) - for index in range(num): - split.append(itr[size * index:size * (index + 1)]) - return split - - -def _get_test_labels(): - test_labels = [] - if DJANGO_1_6: - for module in [name for _, name, _ in pkgutil.iter_modules( - [os.path.join("cms", "tests")])]: - clsmembers = pyclbr.readmodule("cms.tests.%s" % module) - for clsname, cls in clsmembers.items(): - for method, _ in cls.methods.items(): - if method.startswith('test_'): - test_labels.append('cms.%s.%s' % (clsname, method)) - else: - for module in [name for _, name, _ in pkgutil.iter_modules( - [os.path.join("cms", "tests")])]: - clsmembers = pyclbr.readmodule("cms.tests.%s" % module) - for clsname, cls in clsmembers.items(): - for method, _ in cls.methods.items(): - if method.startswith('test_'): - test_labels.append('cms.tests.%s.%s' % (clsname, method)) - test_labels = sorted(test_labels) - return test_labels - - -def _test_run_worker(test_labels, failfast=False, test_runner=None): - warnings.filterwarnings( - 'error', r"DateTimeField received a naive datetime", - RuntimeWarning, r'django\.db\.models\.fields') - from django.conf import settings - from django.test.utils import get_runner - if not test_runner: - if DJANGO_1_6: - test_runner = 'django.test.simple.DjangoTestSuiteRunner' - else: - test_runner = 'django.test.runner.DiscoverRunner' - if not test_labels: - test_labels = _get_test_labels() - settings.TEST_RUNNER = test_runner - TestRunner = get_runner(settings) - test_runner = TestRunner(verbosity=1, pattern="*.py", top_level='cms', - interactive=False, failfast=failfast) - failures = test_runner.run_tests(test_labels) - return failures - - -def _test_in_subprocess(test_labels): - return subprocess.call(['python', 'develop.py', 'test'] + test_labels) - - -def isolated(test_labels, parallel=False): - test_labels = test_labels or _get_test_labels() - if parallel: - pool = multiprocessing.Pool() - mapper = pool.map - else: - mapper = map - results = mapper( - _test_in_subprocess, - ([test_label] for test_label in test_labels) - ) - failures = [test_label for test_label, return_code in zip( - test_labels, results) if return_code != 0] - return failures - - -def timed(test_labels): - return _test_run_worker( - test_labels, - test_runner='cms.test_utils.runners.TimedTestRunner' - ) - - -def test(test_labels, parallel=False, failfast=False): - test_labels = test_labels or _get_test_labels() - if parallel: - worker_tests = _split(test_labels, multiprocessing.cpu_count()) - - pool = multiprocessing.Pool() - failures = sum(pool.map(_test_run_worker, worker_tests)) - return failures - else: - return _test_run_worker(test_labels, failfast) - - -def compilemessages(): - from django.core.management import call_command - os.chdir('cms') - call_command('compilemessages', all=True) - - -def makemessages(): - from django.core.management import call_command - os.chdir('cms') - call_command('makemessages', locale=('en',)) - call_command('makemessages', locale=('en',), domain='djangojs') - - -def shell(): - from django.core.management import call_command - call_command('shell') - - -def makemigrations(migrate_plugins=True, merge=False, squash=False): - applications = [ - # core applications - 'cms', 'menus', - # testing applications - 'meta', 'manytomany_rel', 'fileapp', 'placeholderapp', 'sampleapp', - 'fakemlng', 'one_thing', 'extensionapp', 'objectpermissionsapp', - 'bunch_of_plugins', 'mti_pluginapp', - ] - if os.environ.get("AUTH_USER_MODEL") == "emailuserapp.EmailUser": - applications.append('emailuserapp') - if os.environ.get("AUTH_USER_MODEL") == "customuserapp.User": - applications.append('customuserapp') - if migrate_plugins: - applications.extend([ - # official plugins - 'djangocms_inherit', 'djangocms_googlemap', 'djangocms_column', - 'djangocms_style', 'djangocms_link', 'djangocms_file', - 'djangocms_text_ckeditor', 'djangocms_picture', 'djangocms_teaser', - 'djangocms_file', 'djangocms_flash', 'djangocms_video', - ]) - if DJANGO_1_6: - from south.exceptions import NoMigrations - from south.migration import Migrations - - if merge: - raise DjangoRuntimeWarning( - u'Option not implemented for Django 1.6') - for application in applications: - try: - Migrations(application) - except NoMigrations: - print('ATTENTION: No migrations found for {0}, creating ' - 'initial migrations.'.format(application)) - try: - call_command('schemamigration', application, initial=True) - except SystemExit: - pass - except ImproperlyConfigured: - print('WARNING: The app: {0} could not be found.'.format( - application - )) - else: - try: - call_command('schemamigration', application, auto=True) - except SystemExit: - pass - else: - call_command('makemigrations', *applications, merge=merge) - - -def squashmigrations(application, migration): - if DJANGO_1_6: - raise CommandError(u'Command not implemented for Django 1.6') - else: - call_command('squashmigrations', application, migration) - - -def generate_authors(): - print("Generating AUTHORS") - - # Get our list of authors - print("Collecting author names") - r = subprocess.Popen( - ["git", "log", "--use-mailmap", "--format=%aN"], - stdout=subprocess.PIPE - ) - seen_authors = [] - authors = [] - with open('AUTHORS', 'r') as f: - for line in f.readlines(): - if line.startswith("*"): - author = force_text(line).strip("* \n") - if author.lower() not in seen_authors: - seen_authors.append(author.lower()) - authors.append(author) - for author in r.stdout.readlines(): - author = force_text(author).strip() - if author.lower() not in seen_authors: - seen_authors.append(author.lower()) - authors.append(author) - - # Sort our list of Authors by their case insensitive name - authors = sorted(authors, key=lambda x: x.lower()) - - # Write our authors to the AUTHORS file - print(u"Authors (%s):\n\n\n* %s" % (len(authors), u"\n* ".join(authors))) - - -def main(): - args = docopt(__doc__, version=cms.__version__) - - if args['pyflakes']: - return static_analysis.pyflakes((cms, menus)) - - if args['authors']: - return generate_authors() - - # configure django - warnings.filterwarnings( - 'error', r"DateTimeField received a naive datetime", - RuntimeWarning, r'django\.db\.models\.fields') - - default_name = ':memory:' if args['test'] else 'local.sqlite' - - db_url = os.environ.get( - "DATABASE_URL", - "sqlite://localhost/%s" % default_name - ) - migrate = (args.get('--migrate', False) or - args.get('makemigrations', False) or - args.get('squashmigrations', False)) - - with temp_dir() as STATIC_ROOT: - with temp_dir() as MEDIA_ROOT: - configs = { - 'db_url': db_url, - 'ROOT_URLCONF': 'cms.test_utils.project.urls', - 'STATIC_ROOT': STATIC_ROOT, - 'MEDIA_ROOT': MEDIA_ROOT, - 'USE_TZ': True, - 'TESTS_MIGRATE': migrate, - } - - if args['test']: - configs['SESSION_ENGINE'] = "django.contrib.sessions.backends.cache" - - # Command line option takes precedent over environment variable - auth_user_model = args['--user'] - - if not auth_user_model: - auth_user_model = os.environ.get("AUTH_USER_MODEL", None) - - if auth_user_model: - configs['AUTH_USER_MODEL'] = auth_user_model - - configure(**configs) - - # run - if args['test']: - # make "Address already in use" errors less likely, see Django - # docs for more details on this env variable. - os.environ.setdefault( - 'DJANGO_LIVE_TEST_SERVER_ADDRESS', - 'localhost:8000-9000' - ) - if args['--xvfb']: - import xvfbwrapper - context = xvfbwrapper.Xvfb(width=1280, height=720) - else: - @contextlib.contextmanager - def null_context(): - yield - context = null_context() - - with context: - if args['isolated']: - failures = isolated( - args[''], args['--parallel'] - ) - print() - print("Failed tests") - print("============") - if failures: - for failure in failures: - print(" - %s" % failure) - else: - print(" None") - num_failures = len(failures) - elif args['timed']: - num_failures = timed(args['']) - else: - num_failures = test( - args[''], - args['--parallel'], - args['--failfast'] - ) - sys.exit(num_failures) - elif args['server']: - server( - args['--bind'], - args['--port'], - args.get('--migrate', True), - args.get('', None), - args.get('', None) - ) - elif args['shell']: - shell() - elif args['compilemessages']: - compilemessages() - elif args['makemessages']: - makemessages() - elif args['makemigrations']: - makemigrations(merge=args['--merge']) - elif args['squashmigrations']: - squashmigrations( - args[''], - args[''] - ) - - -if __name__ == '__main__': - main() diff --git a/docs/contributing/testing.rst b/docs/contributing/testing.rst index 37a79ab5a8e..25ef845c217 100644 --- a/docs/contributing/testing.rst +++ b/docs/contributing/testing.rst @@ -48,7 +48,7 @@ There's more than one way to do this, but here's one to help you get started:: # note that you must be in the django-cms directory when you do this, # otherwise you'll get "Template not found" errors cd django-cms - python develop.py test + python manage.py test It can take a few minutes to run. Note that the selenium tests included in the @@ -97,120 +97,30 @@ and it should then run without errors. Advanced testing options ======================== -``develop.py`` is the django CMS development helper script. +Run ``manage.py test --help`` for full list of advanced options. -To use a different database, set the ``DATABASE_URL`` environment variable to a -dj-database-url compatible value. +Use ``--parallel`` to distribute the test cases across your CPU cores. -.. program:: develop.py +Use ``--failed`` to only run the tests that failed during the last run. -.. option:: -h, --help +Use ``--retest`` to run the tests using the same configuration as the last run. - Show help. +Use ``--vanilla`` to bypass the advanced testing system and use the built-in +Django test command. -.. option:: --version - Show CMS version. +Using Xvfb for headless frontend testing +---------------------------------------- -.. option:: --user +On Linux systems with X you can use Xvfb (X virtual frame buffer) to run +frontend tests headless (without the browser window actually showing). To do +so, it's recommended to use the ``xvfb-run`` script to run tests. - Specifies a custom user model to use for testing, the shell, or the server. The name must be in the format ., and the custom app must reside in the cms.test_utils.projects module. +.. important:: - -``develop.py test`` -------------------- - -.. program:: develop.py test - -Runs the test suite. Optionally takes test labels as arguments to limit the tests which should be run. -Test labels should be in the same format as used in ``manage.py test``. - -.. option:: --xvfb - - Use a virtual X framebuffer for frontend testing, requires `xvfbwrapper `_ to be installed. - - With this option you won't need a physical display. - -.. option:: --parallel - - Runs tests in parallel, using one worker process per available CPU core. - - Cannot be used together with :option:`develop.py test --failfast`. - - .. note:: - - The output of the worker processes will be shown interleaved, which means that you'll get the - results from each worker process individually, which might cause confusing output at the end of - the test run. - -.. option:: --failfast - - Stop running tests on the first failure or error. - - -``develop.py timed test`` -------------------------- - -.. program:: develop.py timed test - -Run the test suite and print the ten slowest tests. Optionally takes test labels as arguments to limit the tests which should be run. -Test labels should be in the same format as used in ``manage.py test``. - - -``develop.py isolated test`` ----------------------------- - -.. program:: develop.py isolated test - -Runs each test in the test suite in a new process, thus making sure that tests don't leak state. This takes a -very long time to run. Optionally takes test labels as arguments to limit the tests which should be run. -Test labels should be in the same format as used in ``manage.py test``. - -.. option:: --parallel - - Same as :option:`develop.py test --parallel`. - - -``develop.py server`` ---------------------- - -.. program:: develop.py server - -Run a server locally for testing. This is similar to ``manage.py runserver``. - -.. option:: --port - - Port to bind to. Defaults to 8000. - -.. option:: --bind - - Interface to bind to. Defaults to 127.0.0.1. - -.. option:: --migrate - - Use migrations instead of plain syncdb. - -.. option:: application-name, migration-number - - Options to specify a single migration to migrate to. When using Django 1.6 - it only works if --migrate option is specified. - - - -``develop.py shell`` --------------------- - -.. program:: develop.py shell - -Opens a Django shell. This is similar to ``manage.py shell``. - - -``develop.py compilemessages`` ------------------------------- - -.. program:: develop.py compilemessages - -Compiles the po files to mo files. This is similar to ``manage.py compilemessages``. + The frontend tests have a minimum screen size to run successfully. You must + set the screen size of the virtual frame buffer to at least 1280x720x8. + You may do so using ``xvfb-run -s"-screen 0 1280x720x8" ...``. ************* diff --git a/manage.py b/manage.py new file mode 100644 index 00000000000..875ad3fb621 --- /dev/null +++ b/manage.py @@ -0,0 +1,355 @@ +# -*- coding: utf-8 -*- +import os + +import app_manage + +from cms.utils.compat import DJANGO_1_6, DJANGO_1_7 + +gettext = lambda s: s + + +if __name__ == '__main__': + os.environ.setdefault( + 'DJANGO_LIVE_TEST_SERVER_ADDRESS', + 'localhost:8000-9000' + ) + PROJECT_PATH = os.path.abspath( + os.path.join(os.path.dirname(__file__), 'cms', 'test_utils') + ) + + INSTALLED_APPS = [ + 'debug_toolbar', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'djangocms_admin_style', + 'django.contrib.admin', + 'django.contrib.sites', + 'django.contrib.staticfiles', + 'django.contrib.messages', + 'treebeard', + 'cms', + 'menus', + 'djangocms_text_ckeditor', + 'djangocms_column', + 'djangocms_picture', + 'djangocms_file', + 'djangocms_flash', + 'djangocms_googlemap', + 'djangocms_teaser', + 'djangocms_video', + 'djangocms_inherit', + 'djangocms_style', + 'djangocms_link', + 'cms.test_utils.project.sampleapp', + 'cms.test_utils.project.placeholderapp', + 'cms.test_utils.project.pluginapp.plugins.manytomany_rel', + 'cms.test_utils.project.pluginapp.plugins.extra_context', + 'cms.test_utils.project.pluginapp.plugins.meta', + 'cms.test_utils.project.pluginapp.plugins.one_thing', + 'cms.test_utils.project.fakemlng', + 'cms.test_utils.project.fileapp', + 'cms.test_utils.project.objectpermissionsapp', + 'cms.test_utils.project.bunch_of_plugins', + 'cms.test_utils.project.extensionapp', + 'cms.test_utils.project.mti_pluginapp', + 'reversion', + 'sekizai', + 'hvad', + 'better_test', + ] + + dynamic_configs = {} + + if DJANGO_1_7: + dynamic_configs.update(dict( + TEMPLATE_CONTEXT_PROCESSORS=[ + "django.contrib.auth.context_processors.auth", + 'django.contrib.messages.context_processors.messages', + "django.core.context_processors.i18n", + "django.core.context_processors.debug", + "django.core.context_processors.request", + "django.core.context_processors.media", + 'django.core.context_processors.csrf', + "cms.context_processors.cms_settings", + "sekizai.context_processors.sekizai", + "django.core.context_processors.static", + ], + TEMPLATE_LOADERS=( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + 'django.template.loaders.eggs.Loader', + ), + TEMPLATE_DIRS=[ + os.path.abspath(os.path.join(PROJECT_PATH, 'project', 'templates')) + ], + TEMPLATE_DEBUG=True + )) + else: + dynamic_configs['TEMPLATES'] = [ + { + 'NAME': 'django', + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'APP_DIRS': True, + 'DIRS': [os.path.abspath(os.path.join(PROJECT_PATH, 'project', 'templates'))], + 'OPTIONS': { + 'context_processors': [ + "django.contrib.auth.context_processors.auth", + 'django.contrib.messages.context_processors.messages', + "django.template.context_processors.i18n", + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.template.context_processors.media", + 'django.template.context_processors.csrf', + "cms.context_processors.cms_settings", + "sekizai.context_processors.sekizai", + "django.template.context_processors.static", + ], + 'debug': True, + } + } + ] + + if DJANGO_1_6: + # South overrides the test command, thus insert it before better_test + INSTALLED_APPS.insert(0, 'south') + dynamic_configs['SOUTH_MIGRATION_MODULES'] = { + 'cms': 'cms.south_migrations', + 'menus': 'menus.south_migrations', + 'djangocms_column': 'djangocms_column.migrations', + 'djangocms_file': 'djangocms_file.migrations', + 'djangocms_flash': 'djangocms_flash.migrations', + 'djangocms_googlemap': 'djangocms_googlemap.migrations', + 'djangocms_inherit': 'djangocms_inherit.migrations', + 'djangocms_link': 'djangocms_link.migrations', + 'djangocms_picture': 'djangocms_picture.migrations', + 'djangocms_style': 'djangocms_style.migrations', + 'djangocms_teaser': 'djangocms_teaser.migrations', + 'djangocms_text_ckeditor': 'djangocms_text_ckeditor.south_migrations', + 'djangocms_video': 'djangocms_video.migrations', + 'meta': 'cms.test_utils.project.pluginapp.plugins.meta.south_migrations', + 'manytomany_rel': 'cms.test_utils.project.pluginapp.plugins.manytomany_rel.south_migrations', + 'fileapp': 'cms.test_utils.project.fileapp.south_migrations', + 'placeholderapp': 'cms.test_utils.project.placeholderapp.south_migrations', + 'sampleapp': 'cms.test_utils.project.sampleapp.south_migrations', + 'emailuserapp': 'cms.test_utils.project.emailuserapp.south_migrations', + 'customuserapp': 'cms.test_utils.project.customuserapp.south_migrations', + 'fakemlng': 'cms.test_utils.project.fakemlng.south_migrations', + 'extra_context': 'cms.test_utils.project.pluginapp.plugins.extra_context.south_migrations', + 'one_thing': 'cms.test_utils.project.pluginapp.plugins.one_thing.south_migrations', + 'bunch_of_plugins': 'cms.test_utils.project.bunch_of_plugins.south_migrations', + 'extensionapp': 'cms.test_utils.project.extensionapp.south_migrations', + 'objectpermissionsapp': 'cms.test_utils.project.objectpermissionsapp.south_migrations', + 'mti_pluginapp': 'cms.test_utils.project.mti_pluginapp.south_migrations', + } + else: + dynamic_configs['MIGRATION_MODULES'] = { + 'djangocms_column': 'djangocms_column.migrations_django', + 'djangocms_file': 'djangocms_file.migrations_django', + 'djangocms_flash': 'djangocms_flash.migrations_django', + 'djangocms_googlemap': 'djangocms_googlemap.migrations_django', + 'djangocms_inherit': 'djangocms_inherit.migrations_django', + 'djangocms_link': 'djangocms_link.migrations_django', + 'djangocms_picture': 'djangocms_picture.migrations_django', + 'djangocms_style': 'djangocms_style.migrations_django', + 'djangocms_teaser': 'djangocms_teaser.migrations_django', + 'djangocms_video': 'djangocms_video.migrations_django', + 'meta': 'cms.test_utils.project.pluginapp.plugins.meta.migrations', + 'manytomany_rel': 'cms.test_utils.project.pluginapp.plugins.manytomany_rel.migrations', + 'fileapp': 'cms.test_utils.project.fileapp.migrations', + 'placeholderapp': 'cms.test_utils.project.placeholderapp.migrations', + 'sampleapp': 'cms.test_utils.project.sampleapp.migrations', + 'emailuserapp': 'cms.test_utils.project.emailuserapp.migrations', + 'fakemlng': 'cms.test_utils.project.fakemlng.migrations', + 'extra_context': 'cms.test_utils.project.pluginapp.plugins.extra_context.migrations', + 'one_thing': 'cms.test_utils.project.pluginapp.plugins.one_thing.migrations', + 'bunch_of_plugins': 'cms.test_utils.project.bunch_of_plugins.migrations', + 'extensionapp': 'cms.test_utils.project.extensionapp.migrations', + 'objectpermissionsapp': 'cms.test_utils.project.objectpermissionsapp.migrations', + 'mti_pluginapp': 'cms.test_utils.project.mti_pluginapp.migrations', + } + + app_manage.main( + ['cms', 'menus'], + PROJECT_PATH=PROJECT_PATH, + CACHES={ + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + } + }, + SESSION_ENGINE="django.contrib.sessions.backends.cache", + CACHE_MIDDLEWARE_ANONYMOUS_ONLY=True, + DEBUG=True, + DATABASE_SUPPORTS_TRANSACTIONS=True, + DATABASES=app_manage.DatabaseConfig( + env='DATABASE_URL', + arg='--db-url', + default='sqlite://localhost/local.sqlite' + ), + USE_TZ=app_manage.Config( + env='USE_TZ', + arg=app_manage.Flag('--use-tz'), + default=False, + ), + SITE_ID=1, + USE_I18N=True, + MEDIA_ROOT=app_manage.TempDir(), + STATIC_ROOT=app_manage.TempDir(), + CMS_MEDIA_ROOT=app_manage.TempDir(), + CMS_MEDIA_URL='/cms-media/', + MEDIA_URL='/media/', + STATIC_URL='/static/', + ADMIN_MEDIA_PREFIX='/static/admin/', + EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend', + MIDDLEWARE_CLASSES=[ + 'django.middleware.cache.UpdateCacheMiddleware', + 'django.middleware.http.ConditionalGetMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.middleware.locale.LocaleMiddleware', + 'django.middleware.common.CommonMiddleware', + 'cms.middleware.language.LanguageCookieMiddleware', + 'cms.middleware.user.CurrentUserMiddleware', + 'cms.middleware.page.CurrentPageMiddleware', + 'cms.middleware.toolbar.ToolbarMiddleware', + 'django.middleware.cache.FetchFromCacheMiddleware', + ], + INSTALLED_APPS=INSTALLED_APPS, + DEBUG_TOOLBAR_PATCH_SETTINGS = False, + INTERNAL_IPS = ['127.0.0.1'], + AUTHENTICATION_BACKENDS=( + 'django.contrib.auth.backends.ModelBackend', + 'cms.test_utils.project.objectpermissionsapp.backends.ObjectPermissionBackend', + ), + LANGUAGE_CODE="en", + LANGUAGES=( + ('en', gettext('English')), + ('fr', gettext('French')), + ('de', gettext('German')), + ('pt-br', gettext('Brazilian Portuguese')), + ('nl', gettext("Dutch")), + ('es-mx', u'Español'), + ), + CMS_LANGUAGES={ + 1: [ + { + 'code': 'en', + 'name': gettext('English'), + 'fallbacks': ['fr', 'de'], + 'public': True, + }, + { + 'code': 'de', + 'name': gettext('German'), + 'fallbacks': ['fr', 'en'], + 'public': True, + }, + { + 'code': 'fr', + 'name': gettext('French'), + 'public': True, + }, + { + 'code': 'pt-br', + 'name': gettext('Brazilian Portuguese'), + 'public': False, + }, + { + 'code': 'es-mx', + 'name': u'Español', + 'public': True, + }, + ], + 2: [ + { + 'code': 'de', + 'name': gettext('German'), + 'fallbacks': ['fr'], + 'public': True, + }, + { + 'code': 'fr', + 'name': gettext('French'), + 'public': True, + }, + ], + 3: [ + { + 'code': 'nl', + 'name': gettext('Dutch'), + 'fallbacks': ['de'], + 'public': True, + }, + { + 'code': 'de', + 'name': gettext('German'), + 'fallbacks': ['nl'], + 'public': False, + }, + ], + 'default': { + 'hide_untranslated': False, + }, + }, + CMS_TEMPLATES=( + ('col_two.html', gettext('two columns')), + ('col_three.html', gettext('three columns')), + ('nav_playground.html', gettext('navigation examples')), + ('simple.html', 'simple'), + ('static.html', 'static placeholders'), + ), + CMS_PLACEHOLDER_CONF={ + 'col_sidebar': { + 'plugins': ('FilePlugin', 'FlashPlugin', 'LinkPlugin', 'PicturePlugin', + 'TextPlugin', 'SnippetPlugin'), + 'name': gettext("sidebar column") + }, + 'col_left': { + 'plugins': ('FilePlugin', 'FlashPlugin', 'LinkPlugin', 'PicturePlugin', + 'TextPlugin', 'SnippetPlugin', 'GoogleMapPlugin', 'MultiColumnPlugin', 'StylePlugin'), + 'name': gettext("left column"), + 'plugin_modules': { + 'LinkPlugin': 'Different Grouper' + }, + 'plugin_labels': { + 'LinkPlugin': gettext('Add a link') + }, + }, + 'col_right': { + 'plugins': ('FilePlugin', 'FlashPlugin', 'LinkPlugin', 'PicturePlugin', + 'TextPlugin', 'SnippetPlugin', 'GoogleMapPlugin', 'MultiColumnPlugin', 'StylePlugin'), + 'name': gettext("right column") + }, + 'extra_context': { + "plugins": ('TextPlugin',), + "extra_context": {"width": 250}, + "name": "extra context" + }, + }, + CMS_PERMISSION=True, + CMS_PUBLIC_FOR='all', + CMS_CACHE_DURATIONS={ + 'menus': 0, + 'content': 0, + 'permissions': 0, + }, + CMS_APPHOOKS=[], + CMS_PLUGIN_PROCESSORS=(), + CMS_PLUGIN_CONTEXT_PROCESSORS=(), + CMS_SITE_CHOICES_CACHE_KEY='CMS:site_choices', + CMS_PAGE_CHOICES_CACHE_KEY='CMS:page_choices', + SOUTH_TESTS_MIGRATE=False, + CMS_NAVIGATION_EXTENDERS=[ + ('cms.test_utils.project.sampleapp.menu_extender.get_nodes', + 'SampleApp Menu'), + ], + ROOT_URLCONF='cms.test_utils.project.urls', + PASSWORD_HASHERS=( + 'django.contrib.auth.hashers.MD5PasswordHasher', + ), + ALLOWED_HOSTS=['localhost'], + TEST_RUNNER='django.test.runner.DiscoverRunner', + **dynamic_configs + ) diff --git a/test_requirements/django-1.6.txt b/test_requirements/django-1.6.txt index 5b6d078c247..72fb8d72276 100644 --- a/test_requirements/django-1.6.txt +++ b/test_requirements/django-1.6.txt @@ -2,6 +2,6 @@ django>=1.6,<1.7 South==1.0.2 django-reversion<1.8.3 -https://github.com/KristianOellegaard/django-hvad/archive/master.zip#egg=hvad +https://github.com/KristianOellegaard/django-hvad/archive/ec82b2a8e144ffa6f6349f0dbd0e688c84b77af0.zip#egg=django-hvad django-classy-tags>=0.5 -django-sekizai>=0.7 \ No newline at end of file +django-sekizai>=0.7 diff --git a/test_requirements/django-1.7.txt b/test_requirements/django-1.7.txt index 2e96ca5f8da..30080710b51 100644 --- a/test_requirements/django-1.7.txt +++ b/test_requirements/django-1.7.txt @@ -1,6 +1,6 @@ -r requirements_base.txt django>=1.7,<1.8 django-reversion>=1.8,<1.9 -https://github.com/KristianOellegaard/django-hvad/archive/master.zip +https://github.com/KristianOellegaard/django-hvad/archive/ec82b2a8e144ffa6f6349f0dbd0e688c84b77af0.zip#egg=django-hvad django-classy-tags>=0.5 django-sekizai>=0.7 diff --git a/test_requirements/django-1.8.txt b/test_requirements/django-1.8.txt index 801cb08aeb5..18d810dacb6 100644 --- a/test_requirements/django-1.8.txt +++ b/test_requirements/django-1.8.txt @@ -1,6 +1,6 @@ -r requirements_base.txt -https://github.com/django/django/tarball/stable/1.8.x/django.tar.gz#egg=django +Django>=1.8,<1.9 django-reversion>=1.8,<1.9 -https://github.com/KristianOellegaard/django-hvad/archive/master.zip +https://github.com/KristianOellegaard/django-hvad/archive/ec82b2a8e144ffa6f6349f0dbd0e688c84b77af0.zip#egg=django-hvad https://github.com/yakky/django-sekizai/archive/further_18.zip https://github.com/vad/django-classy-tags/archive/fix/recursion-in-django18.zip diff --git a/test_requirements/django-trunk.txt b/test_requirements/django-trunk.txt index ceae7d2994c..a74fad151ae 100644 --- a/test_requirements/django-trunk.txt +++ b/test_requirements/django-trunk.txt @@ -1,4 +1,4 @@ -r requirements_base.txt https://github.com/django/django/tarball/master/django.tar.gz#egg=django django-reversion>=1.8 -https://github.com/gabejackson/django-hvad/archive/django_1.7_compat.zip +https://github.com/gabejackson/django-hvad/archive/django_1.7_compat.zip#egg=django-hvad diff --git a/test_requirements/requirements_base.txt b/test_requirements/requirements_base.txt index 7af7a8ee938..95744297546 100644 --- a/test_requirements/requirements_base.txt +++ b/test_requirements/requirements_base.txt @@ -22,4 +22,6 @@ django-debug-toolbar -e git+git://github.com/divio/djangocms-teaser.git#egg=djangocms-teaser -e git+git://github.com/divio/djangocms-video.git#egg=djangocms-video -e git+git://github.com/divio/djangocms-link.git#egg=djangocms-link +https://github.com/ojii/django-better-test/archive/02d4fdc44101e4759d4d1531d7f9178a304cd3d3.zip#egg=django-better-test +https://github.com/ojii/django-app-manage/archive/3fd3d5f53c27be99002d580e67cee4827107aba7.zip#egg=django-app-manage pyflakes