Permalink
Browse files

Merge branch 'release/0.3'

  • Loading branch information...
2 parents afc21b0 + 01e2648 commit 65699821bf125099f81a178837a9a6b442fd7daf @jezdez jezdez committed Aug 23, 2011
Showing with 284 additions and 46 deletions.
  1. +10 −0 .gitignore
  2. +9 −0 README.rst
  3. +72 −46 appconf.py
  4. 0 tests/__init__.py
  5. +16 −0 tests/settings.py
  6. 0 tests/testapp/__init__.py
  7. +37 −0 tests/testapp/models.py
  8. +90 −0 tests/testapp/tests.py
  9. +50 −0 tests/tox.ini
View
@@ -0,0 +1,10 @@
+build
+dist
+MANIFEST
+*.pyc
+*.egg-info
+.tox/
+*.egg
+docs/_build/
+tests/.coverage
+tests/.tox
View
@@ -114,6 +114,15 @@ is provided in the setting instance::
Changelog
---------
+0.3 (2011-08-23)
+^^^^^^^^^^^^^^^^
+
+* Added tests with 100% coverage.
+
+* Added ability to subclass ``Meta`` classes.
+
+* Fixed various bugs with subclassing and configuration in subclasses.
+
0.2.2 (2011-08-22)
^^^^^^^^^^^^^^^^^^
View
@@ -1,72 +1,95 @@
import sys
# following PEP 386, versiontools will pick it up
-__version__ = (0, 2, 2, "final", 0)
+__version__ = (0, 3, 0, "final", 0)
class AppConfOptions(object):
- def __init__(self, meta, *args, **kwargs):
- self.configured = False
+ def __init__(self, meta, app_label=None):
+ self.app_label = app_label
def prefixed_name(self, name):
- if name.startswith(self.app_label):
+ if name.startswith(self.app_label.upper()):
return name
return "%s_%s" % (self.app_label.upper(), name.upper())
+ def contribute_to_class(self, cls, name):
+ cls._meta = self
+ self.names = {}
+ self.defaults = {}
+
class AppConfMetaClass(type):
- options_class = AppConfOptions
def __new__(cls, name, bases, attrs):
super_new = super(AppConfMetaClass, cls).__new__
parents = [b for b in bases if isinstance(b, AppConfMetaClass)]
if not parents:
return super_new(cls, name, bases, attrs)
- try:
- meta = attrs.pop('Meta')
- except KeyError:
- meta = None
-
- attrs['_meta'] = cls.options_class(meta)
- new_class = super_new(cls, name, bases, attrs)
-
- if getattr(new_class._meta, 'app_label', None) is None:
+ # Create the class.
+ module = attrs.pop('__module__')
+ new_class = super_new(cls, name, bases, {'__module__': module})
+ attr_meta = attrs.pop('Meta', None)
+ if attr_meta:
+ meta = attr_meta
+ else:
+ attr_meta = type('Meta', (object,), {})
+ meta = getattr(new_class, 'Meta', None)
+
+ app_label = getattr(meta, 'app_label', None)
+ if app_label is None:
# Figure out the app_label by looking one level up.
# For 'django.contrib.sites.models', this would be 'sites'.
model_module = sys.modules[new_class.__module__]
- new_class._meta.app_label = model_module.__name__.split('.')[-2]
+ app_label = model_module.__name__.split('.')[-2]
+
+ new_class.add_to_class('_meta', AppConfOptions(meta, app_label))
+ new_class.add_to_class('Meta', attr_meta)
+
+ for parent in parents[::-1]:
+ if hasattr(parent, '_meta'):
+ new_class._meta.names.update(parent._meta.names)
+ new_class._meta.defaults.update(parent._meta.defaults)
- names = []
- defaults = []
for name in filter(lambda name: name == name.upper(), attrs):
prefixed_name = new_class._meta.prefixed_name(name)
- names.append((name, prefixed_name))
- defaults.append((prefixed_name, attrs.pop(name)))
+ new_class._meta.names[name] = prefixed_name
+ new_class._meta.defaults[prefixed_name] = attrs.pop(name)
+
+ # Add all attributes to the class.
+ for obj_name, obj in attrs.items():
+ new_class.add_to_class(obj_name, obj)
+
+ return new_class._configure()
- new_class.defaults = dict(defaults)
- new_class.names = dict(names)
- new_class.configured_data = dict()
- new_class._configure()
+ def add_to_class(cls, name, value):
+ if hasattr(value, 'contribute_to_class'):
+ value.contribute_to_class(cls, name)
+ else:
+ setattr(cls, name, value)
def _configure(cls):
- if not cls._meta.configured:
- # the ad-hoc settings class instance used to configure each value
- from django.conf import settings
- obj = cls()
- for name, prefixed_name in obj.names.iteritems():
- default_value = obj.defaults.get(prefixed_name)
- value = getattr(settings, prefixed_name, default_value)
- callback = getattr(obj, "configure_%s" % name.lower(), None)
- if callable(callback):
- value = callback(value)
- obj.configured_data[name] = value
- obj.configured_data = obj.configure()
- # Finally, set the setting in the global setting object
- for name, value in obj.configured_data.iteritems():
- setattr(settings, obj.names[name], value)
- cls._meta.configured = True
+ from django.conf import settings
+ # the ad-hoc settings class instance used to configure each value
+ obj = cls()
+ obj.configured_data = dict()
+ for name, prefixed_name in obj._meta.names.iteritems():
+ default_value = obj._meta.defaults.get(prefixed_name)
+ value = getattr(settings, prefixed_name, default_value)
+ callback = getattr(obj, "configure_%s" % name.lower(), None)
+ if callable(callback):
+ value = callback(value)
+ obj.configured_data[name] = value
+ obj.configured_data = obj.configure()
+
+ # Finally, set the setting in the global setting object
+ for name, value in obj.configured_data.iteritems():
+ prefixed_name = obj._meta.prefixed_name(name)
+ setattr(settings, prefixed_name, value)
+
+ return cls
class AppConf(object):
@@ -76,24 +99,27 @@ class AppConf(object):
"""
__metaclass__ = AppConfMetaClass
- def __init__(self, holder=None, **kwargs):
+ def __init__(self, **kwargs):
+ from django.conf import settings
+ self._holder = settings
for name, value in kwargs.iteritems():
setattr(self, self._meta.prefixed_name(name), value)
- if holder is None:
- from django.conf import settings as holder
- self.__dict__['_holder'] = holder
def __dir__(self):
- return sorted(list(set(dir(self.__dict__['_holder']))))
+ return sorted(list(set(dir(self._holder))))
+ # For Python < 2.6:
+ @property
def __members__(self):
return self.__dir__()
def __getattr__(self, name):
- return getattr(self.__dict__['_holder'], name)
+ return getattr(self._holder, name)
def __setattr__(self, name, value):
- setattr(self.__dict__['_holder'], name, value)
+ if name == name.upper():
+ return setattr(self._holder, name, value)
+ object.__setattr__(self, name, value)
def configure(self):
"""
View
No changes.
View
@@ -0,0 +1,16 @@
+SITE_ID = 1
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': ':memory:',
+ }
+}
+
+INSTALLED_APPS = [
+ 'django.contrib.contenttypes',
+ 'django.contrib.sites',
+ 'django.contrib.auth',
+ 'django.contrib.admin',
+ 'tests.testapp',
+]
No changes.
@@ -0,0 +1,37 @@
+from appconf import AppConf
+
+class TestConf(AppConf):
+
+ SIMPLE_VALUE = True
+
+ CONFIGURED_VALUE = 'wrong'
+
+ def configure_configured_value(self, value):
+ return 'correct'
+
+ def configure(self):
+ self.configured_data['CONFIGURE_METHOD_VALUE'] = True
+ return self.configured_data
+
+
+class PrefixConf(TestConf):
+
+ class Meta:
+ app_label = 'prefix'
+
+
+class YetAnotherPrefixConf(PrefixConf):
+
+ SIMPLE_VALUE = False
+
+ class Meta:
+ app_label = 'yetanother_prefix'
+
+
+class SeparateConf(AppConf):
+
+ SEPARATE_VALUE = True
+
+ class Meta(PrefixConf.Meta):
+ pass
+
@@ -0,0 +1,90 @@
+from django.conf import settings
+from django.test import TestCase
+
+from testapp.models import (TestConf, PrefixConf, YetAnotherPrefixConf,
+ SeparateConf)
+
+
+class TestConfTests(TestCase):
+
+ def test_basic(self):
+ self.assertEquals(TestConf._meta.app_label, 'testapp')
+
+ def test_simple(self):
+ self.assertTrue(hasattr(settings, 'TESTAPP_SIMPLE_VALUE'))
+ self.assertEquals(settings.TESTAPP_SIMPLE_VALUE, True)
+
+ def test_configured(self):
+ self.assertTrue(hasattr(settings, 'TESTAPP_CONFIGURED_VALUE'))
+ self.assertEquals(settings.TESTAPP_CONFIGURED_VALUE, 'correct')
+
+ def test_configure_method(self):
+ self.assertTrue(hasattr(settings, 'TESTAPP_CONFIGURE_METHOD_VALUE'))
+ self.assertEquals(settings.TESTAPP_CONFIGURE_METHOD_VALUE, True)
+
+ def test_init_kwargs(self):
+ custom_conf = TestConf(CUSTOM_VALUE='custom')
+ self.assertEquals(custom_conf.TESTAPP_CUSTOM_VALUE, 'custom')
+ self.assertEquals(settings.TESTAPP_CUSTOM_VALUE, 'custom')
+
+ def test_init_kwargs_with_prefix(self):
+ custom_conf = TestConf(TESTAPP_CUSTOM_VALUE2='custom2')
+ self.assertEquals(custom_conf.TESTAPP_CUSTOM_VALUE2, 'custom2')
+ self.assertEquals(settings.TESTAPP_CUSTOM_VALUE2, 'custom2')
+
+ def test_dir_members(self):
+ settings = TestConf()
+ self.assertTrue('TESTAPP_SIMPLE_VALUE' in dir(settings), dir(settings))
+ self.assertTrue('TESTAPP_SIMPLE_VALUE' in settings.__members__)
+
+
+class PrefixConfTests(TestCase):
+
+ def test_app_label(self):
+ self.assertEquals(PrefixConf._meta.app_label, 'prefix')
+
+ def test_simple(self):
+ self.assertTrue(hasattr(settings, 'PREFIX_SIMPLE_VALUE'))
+ self.assertEquals(settings.PREFIX_SIMPLE_VALUE, True)
+
+ def test_configured(self):
+ self.assertTrue(hasattr(settings, 'PREFIX_CONFIGURED_VALUE'))
+ self.assertEquals(settings.PREFIX_CONFIGURED_VALUE, 'correct')
+
+ def test_configure_method(self):
+ self.assertTrue(hasattr(settings, 'PREFIX_CONFIGURE_METHOD_VALUE'))
+ self.assertEquals(settings.PREFIX_CONFIGURE_METHOD_VALUE, True)
+
+
+class YetAnotherPrefixConfTests(TestCase):
+
+ def test_app_label(self):
+ self.assertEquals(YetAnotherPrefixConf._meta.app_label,
+ 'yetanother_prefix')
+
+ def test_simple(self):
+ self.assertTrue(hasattr(settings,
+ 'YETANOTHER_PREFIX_SIMPLE_VALUE'))
+ self.assertEquals(settings.YETANOTHER_PREFIX_SIMPLE_VALUE, False)
+
+ def test_configured(self):
+ self.assertTrue(hasattr(settings,
+ 'YETANOTHER_PREFIX_CONFIGURED_VALUE'))
+ self.assertEquals(settings.YETANOTHER_PREFIX_CONFIGURED_VALUE,
+ 'correct')
+
+ def test_configure_method(self):
+ self.assertTrue(hasattr(settings,
+ 'YETANOTHER_PREFIX_CONFIGURE_METHOD_VALUE'))
+ self.assertEquals(settings.YETANOTHER_PREFIX_CONFIGURE_METHOD_VALUE,
+ True)
+
+
+class SeparateConfTests(TestCase):
+
+ def test_app_label(self):
+ self.assertEquals(SeparateConf._meta.app_label, 'prefix')
+
+ def test_simple(self):
+ self.assertTrue(hasattr(settings, 'PREFIX_SEPARATE_VALUE'))
+ self.assertEquals(settings.PREFIX_SEPARATE_VALUE, True)
View
@@ -0,0 +1,50 @@
+[tox]
+setupdir = ..
+distribute = false
+
+[testenv]
+recreate = True
+commands =
+ {envbindir}/coverage erase
+ {envbindir}/coverage run --branch --source=appconf {envbindir}/django-admin.py test {posargs:testapp} --settings=settings
+ {envbindir}/coverage report --omit=*test*
+ {envbindir}/coverage html --omit=*test* -d {envtmpdir}
+ echo "Type the following to open the coverage report: python -m webbrowser -t file://{envtmpdir}/index.html"
+downloadcache = {toxworkdir}/_download/
+
+[testenv:py25-1.2.X]
+basepython = python2.5
+deps =
+ coverage
+ django==1.2.5
+
+[testenv:py26-1.2.X]
+basepython = python2.6
+deps =
+ coverage
+ django==1.2.5
+
+[testenv:py27-1.2.X]
+basepython = python2.7
+deps =
+ coverage
+ django==1.2.5
+
+
+[testenv:py25-1.3.X]
+basepython = python2.5
+deps =
+ coverage
+ django==1.3
+
+[testenv:py26-1.3.X]
+basepython = python2.6
+deps =
+ coverage
+ django==1.3
+
+[testenv:py27-1.3.X]
+basepython = python2.7
+deps =
+ coverage
+ django==1.3

0 comments on commit 6569982

Please sign in to comment.