Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge branch 'release/0.4'

  • Loading branch information...
commit 53c5de7e7e9d79b285565ff515ce39d6bdc02f18 2 parents 6569982 + 96130ae
Jannis Leidel authored
1  AUTHORS
... ... @@ -0,0 +1 @@
  1 +Jannis Leidel <jannis@leidel.info>
27 LICENSE
... ... @@ -0,0 +1,27 @@
  1 +Copyright (c) 2011, Jannis Leidel and individual contributors.
  2 +All rights reserved.
  3 +
  4 +Redistribution and use in source and binary forms, with or without
  5 +modification, are permitted provided that the following conditions are met:
  6 +
  7 + * Redistributions of source code must retain the above copyright
  8 + notice, this list of conditions and the following disclaimer.
  9 +
  10 + * Redistributions in binary form must reproduce the above copyright
  11 + notice, this list of conditions and the following disclaimer in the
  12 + documentation and/or other materials provided with the distribution.
  13 +
  14 + * Neither the name of django-appconf nor the
  15 + names of its contributors may be used to endorse or promote products
  16 + derived from this software without specific prior written permission.
  17 +
  18 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  19 +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  20 +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
  22 +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  23 +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  24 +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  25 +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  26 +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  27 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4 MANIFEST.in
... ... @@ -1 +1,3 @@
1   -include README.rst
  1 +include README.rst
  2 +include LICENSE
  3 +include AUTHORS
162 README.rst
Source Rendered
... ... @@ -1,10 +1,16 @@
1 1 django-appconf
2 2 ==============
3 3
4   -An app configuration object to be used for handling configuration
5   -defaults of packaged apps gracefully. Say you have an app called ``myapp``
6   -and want to define a few defaults, and refer to the defaults easily in the
7   -apps code. Add a ``settings.py`` to your app's models.py::
  4 +A helper class for handling configuration defaults of packaged Django
  5 +apps gracefully.
  6 +
  7 +Overview
  8 +--------
  9 +
  10 +Say you have an app called ``myapp`` with a few defaults, which you want
  11 +to refer to in the app's code without repeating yourself all the time.
  12 +``appconf`` provides a simple class to implement those defaults. Simply add
  13 +something like the following code somewhere in your app files::
8 14
9 15 from appconf import AppConf
10 16
@@ -14,132 +20,50 @@ apps code. Add a ``settings.py`` to your app's models.py::
14 20 "two",
15 21 )
16 22
17   - class Meta:
18   - app_label = 'myapp'
19   -
20   -The settings are initialized with the app label of where the setting is
21   -located at. E.g. if your ``models.py`` is in the ``myapp`` package,
22   -the prefix of the settings will be ``MYAPP``.
23   -
24   -The ``MyAppConf`` class will automatically look at Django's
25   -global setting to determine each of the settings. E.g. adding this to
26   -your site's ``settings.py`` will set the ``SETTING_1`` app setting
27   -accordingly::
28   -
29   - MYAPP_SETTING_1 = "uno"
30   -
31   -Usage
32   ------
33   -
34   -Instead of using ``from django.conf import settings`` as you would
35   -usually do, you can **optionally** switch to using your apps own
36   -settings module to access the settings::
37   -
38   - from myapp.models import MyAppConf
39   -
40   - myapp_settings = MyAppConf()
41   -
42   - print myapp_settings.MYAPP_SETTING_1
  23 +.. note::
43 24
44   -``AppConf`` class automatically work as proxies for the other
45   -settings, which aren't related to the app. For example the following
46   -code is perfectly valid::
  25 + ``AppConf`` classes depend on being imported during startup of the Django
  26 + process. Even though there are multiple modules loaded automatically,
  27 + only the ``models`` modules (usually the ``models.py`` file of your
  28 + app) are guaranteed to be loaded at startup. Therefore it's recommended
  29 + to put your ``AppConf`` subclass(es) there, too.
47 30
48   - from myapp.models import MyAppConf
  31 +The settings are initialized with the capitalized app label of where the
  32 +setting is located at. E.g. if your ``models.py`` with the ``AppConf`` class
  33 +is in the ``myapp`` package, the prefix of the settings will be ``MYAPP``.
49 34
50   - settings = MyAppConf()
  35 +You can override the default prefix by specifying a ``prefix`` attribute of
  36 +an inner ``Meta`` class::
51 37
52   - if "myapp" in settings.INSTALLED_APPS:
53   - print "yay, myapp is installed!"
54   -
55   -In case you want to set some settings ad-hoc, you can simply pass
56   -the value when instanciating the ``AppConf`` class::
57   -
58   - from myapp.models import MyAppConf
59   -
60   - settings = MyAppConf(SETTING_1='something completely different')
61   -
62   - if 'different' in settings.MYAPP_SETTINGS_1:
63   - print 'yay, I'm different!'
64   -
65   -Custom handling
66   ----------------
67   -
68   -Each of the settings can be individually configured with callbacks.
69   -For example, in case a value of a setting depends on other settings
70   -or other dependencies. The following example sets one setting to a
71   -different value depending on a global setting::
72   -
73   - from django.conf import settings
74 38 from appconf import AppConf
75 39
76   - class MyCustomAppConf(AppConf):
77   - ENABLED = True
78   -
79   - def configure_enabled(self, value):
80   - return value and not self.DEBUG
  40 + class MyAppConf(AppConf):
  41 + SETTING_1 = "one"
  42 + SETTING_2 = (
  43 + "two",
  44 + )
81 45
82   -The value of ``MYAPP_ENABLED`` will vary depending on the
83   -value of the global ``DEBUG`` setting.
  46 + class Meta:
  47 + prefix = 'acme'
84 48
85   -Each of the app settings can be customized by providing
86   -a method ``configure_<lower_setting_name>`` that takes the default
87   -value as defined in the class attributes as the only parameter.
88   -The method needs to return the value to be use for the setting in
89   -question.
  49 +The ``MyAppConf`` class will automatically look at Django's global settings
  50 +to determine if you've overridden it. For example, adding this to your site's
  51 +``settings.py`` would override ``SETTING_1`` of the above ``MyAppConf``::
90 52
91   -After each of the ``_configure`` method have be called, the ``AppConf``
92   -class will additionally call a main ``configure`` method, which can
93   -be used to do any further custom configuration handling, e.g. if multiple
94   -settings depend on each other. For that a ``configured_data`` dictionary
95   -is provided in the setting instance::
  53 + MYAPP_SETTING_1 = "uno"
96 54
  55 +In case you want to use a different settings object instead of the default
  56 +``'django.conf.settings'``, set the ``holder`` attribute of the inner
  57 +``Meta`` class to a dotted import path::
97 58
98   - from django.conf import settings
99 59 from appconf import AppConf
100 60
101   - class MyCustomAppConf(AppConf):
102   - ENABLED = True
103   - MODE = 'development'
104   -
105   - def configure_enabled(self, value):
106   - return value and not self.DEBUG
107   -
108   - def configure(self):
109   - mode = self.configured_data['MODE']
110   - enabled = self.configured_data['ENABLED']
111   - if not enabled and mode != 'development':
112   - print "WARNING: app not enabled in %s mode!" % mode
113   -
114   -Changelog
115   ----------
116   -
117   -0.3 (2011-08-23)
118   -^^^^^^^^^^^^^^^^
119   -
120   -* Added tests with 100% coverage.
121   -
122   -* Added ability to subclass ``Meta`` classes.
123   -
124   -* Fixed various bugs with subclassing and configuration in subclasses.
125   -
126   -0.2.2 (2011-08-22)
127   -^^^^^^^^^^^^^^^^^^
128   -
129   -* Fixed another issue in the ``configure()`` API.
130   -
131   -0.2.1 (2011-08-22)
132   -^^^^^^^^^^^^^^^^^^
133   -
134   -* Fixed minor issue in ``configure()`` API.
135   -
136   -0.2 (2011-08-22)
137   -^^^^^^^^^^^^^^^^
138   -
139   -* Added ``configure()`` API to ``AppConf`` class which is called after
140   - configuring each setting.
141   -
142   -0.1 (2011-08-22)
143   -^^^^^^^^^^^^^^^^
  61 + class MyAppConf(AppConf):
  62 + SETTING_1 = "one"
  63 + SETTING_2 = (
  64 + "two",
  65 + )
144 66
145   -* First public release.
  67 + class Meta:
  68 + prefix = 'acme'
  69 + holder = 'acme.conf.settings'
95 appconf.py
... ... @@ -1,18 +1,22 @@
1 1 import sys
2 2
3 3 # following PEP 386, versiontools will pick it up
4   -__version__ = (0, 3, 0, "final", 0)
  4 +__version__ = (0, 4, 0, "final", 0)
5 5
6 6
7 7 class AppConfOptions(object):
8 8
9   - def __init__(self, meta, app_label=None):
10   - self.app_label = app_label
  9 + def __init__(self, meta, prefix=None):
  10 + self.prefix = prefix
  11 + self.holder_path = getattr(meta, 'holder', 'django.conf.settings')
  12 + self.holder = import_attribute(self.holder_path)
  13 + self.proxy = getattr(meta, 'proxy', False)
  14 + self.configured_data = {}
11 15
12 16 def prefixed_name(self, name):
13   - if name.startswith(self.app_label.upper()):
  17 + if name.startswith(self.prefix.upper()):
14 18 return name
15   - return "%s_%s" % (self.app_label.upper(), name.upper())
  19 + return "%s_%s" % (self.prefix.upper(), name.upper())
16 20
17 21 def contribute_to_class(self, cls, name):
18 22 cls._meta = self
@@ -38,20 +42,21 @@ def __new__(cls, name, bases, attrs):
38 42 attr_meta = type('Meta', (object,), {})
39 43 meta = getattr(new_class, 'Meta', None)
40 44
41   - app_label = getattr(meta, 'app_label', None)
42   - if app_label is None:
43   - # Figure out the app_label by looking one level up.
  45 + prefix = getattr(meta, 'prefix', getattr(meta, 'app_label', None))
  46 + if prefix is None:
  47 + # Figure out the prefix by looking one level up.
44 48 # For 'django.contrib.sites.models', this would be 'sites'.
45 49 model_module = sys.modules[new_class.__module__]
46   - app_label = model_module.__name__.split('.')[-2]
  50 + prefix = model_module.__name__.split('.')[-2]
47 51
48   - new_class.add_to_class('_meta', AppConfOptions(meta, app_label))
  52 + new_class.add_to_class('_meta', AppConfOptions(meta, prefix))
49 53 new_class.add_to_class('Meta', attr_meta)
50 54
51 55 for parent in parents[::-1]:
52 56 if hasattr(parent, '_meta'):
53 57 new_class._meta.names.update(parent._meta.names)
54 58 new_class._meta.defaults.update(parent._meta.defaults)
  59 + new_class._meta.configured_data.update(parent._meta.configured_data)
55 60
56 61 for name in filter(lambda name: name == name.upper(), attrs):
57 62 prefixed_name = new_class._meta.prefixed_name(name)
@@ -59,10 +64,15 @@ def __new__(cls, name, bases, attrs):
59 64 new_class._meta.defaults[prefixed_name] = attrs.pop(name)
60 65
61 66 # Add all attributes to the class.
62   - for obj_name, obj in attrs.items():
63   - new_class.add_to_class(obj_name, obj)
  67 + for name, value in attrs.items():
  68 + new_class.add_to_class(name, value)
64 69
65   - return new_class._configure()
  70 + new_class._configure()
  71 + for name, value in new_class._meta.configured_data.iteritems():
  72 + prefixed_name = new_class._meta.prefixed_name(name)
  73 + setattr(new_class._meta.holder, prefixed_name, value)
  74 + new_class.add_to_class(name, value)
  75 + return new_class
66 76
67 77 def add_to_class(cls, name, value):
68 78 if hasattr(value, 'contribute_to_class'):
@@ -71,25 +81,37 @@ def add_to_class(cls, name, value):
71 81 setattr(cls, name, value)
72 82
73 83 def _configure(cls):
74   - from django.conf import settings
75 84 # the ad-hoc settings class instance used to configure each value
76 85 obj = cls()
77   - obj.configured_data = dict()
78 86 for name, prefixed_name in obj._meta.names.iteritems():
79 87 default_value = obj._meta.defaults.get(prefixed_name)
80   - value = getattr(settings, prefixed_name, default_value)
  88 + value = getattr(obj._meta.holder, prefixed_name, default_value)
81 89 callback = getattr(obj, "configure_%s" % name.lower(), None)
82 90 if callable(callback):
83 91 value = callback(value)
84   - obj.configured_data[name] = value
85   - obj.configured_data = obj.configure()
86   -
87   - # Finally, set the setting in the global setting object
88   - for name, value in obj.configured_data.iteritems():
89   - prefixed_name = obj._meta.prefixed_name(name)
90   - setattr(settings, prefixed_name, value)
91   -
92   - return cls
  92 + cls._meta.configured_data[name] = value
  93 + cls._meta.configured_data = obj.configure()
  94 +
  95 +
  96 +def import_attribute(import_path, exception_handler=None):
  97 + from django.utils.importlib import import_module
  98 + module_name, object_name = import_path.rsplit('.', 1)
  99 + try:
  100 + module = import_module(module_name)
  101 + except: # pragma: no cover
  102 + if callable(exception_handler):
  103 + exctype, excvalue, tb = sys.exc_info()
  104 + return exception_handler(import_path, exctype, excvalue, tb)
  105 + else:
  106 + raise
  107 + try:
  108 + return getattr(module, object_name)
  109 + except: # pragma: no cover
  110 + if callable(exception_handler):
  111 + exctype, excvalue, tb = sys.exc_info()
  112 + return exception_handler(import_path, exctype, excvalue, tb)
  113 + else:
  114 + raise
93 115
94 116
95 117 class AppConf(object):
@@ -100,13 +122,16 @@ class AppConf(object):
100 122 __metaclass__ = AppConfMetaClass
101 123
102 124 def __init__(self, **kwargs):
103   - from django.conf import settings
104   - self._holder = settings
105 125 for name, value in kwargs.iteritems():
106   - setattr(self, self._meta.prefixed_name(name), value)
  126 + setattr(self, name, value)
107 127
108 128 def __dir__(self):
109   - return sorted(list(set(dir(self._holder))))
  129 + return sorted(list(set(self._meta.names.keys())))
  130 +
  131 + # For instance access..
  132 + @property
  133 + def configured_data(self):
  134 + return self._meta.configured_data
110 135
111 136 # For Python < 2.6:
112 137 @property
@@ -114,15 +139,21 @@ def __members__(self):
114 139 return self.__dir__()
115 140
116 141 def __getattr__(self, name):
117   - return getattr(self._holder, name)
  142 + if self._meta.proxy:
  143 + return getattr(self._meta.holder, name)
  144 + raise AttributeError("%s not found. Use '%s' instead." %
  145 + (name, self._meta.holder_path))
118 146
119 147 def __setattr__(self, name, value):
120 148 if name == name.upper():
121   - return setattr(self._holder, name, value)
  149 + setattr(self._meta.holder,
  150 + self._meta.prefixed_name(name), value)
122 151 object.__setattr__(self, name, value)
123 152
124 153 def configure(self):
125 154 """
126   - Hook for doing any extra configuration.
  155 + Hook for doing any extra configuration, returning a dictionary
  156 + containing the configured data.
  157 +
127 158 """
128 159 return self.configured_data
130 docs/Makefile
... ... @@ -0,0 +1,130 @@
  1 +# Makefile for Sphinx documentation
  2 +#
  3 +
  4 +# You can set these variables from the command line.
  5 +SPHINXOPTS =
  6 +SPHINXBUILD = sphinx-build
  7 +PAPER =
  8 +BUILDDIR = _build
  9 +
  10 +# Internal variables.
  11 +PAPEROPT_a4 = -D latex_paper_size=a4
  12 +PAPEROPT_letter = -D latex_paper_size=letter
  13 +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
  14 +
  15 +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
  16 +
  17 +help:
  18 + @echo "Please use \`make <target>' where <target> is one of"
  19 + @echo " html to make standalone HTML files"
  20 + @echo " dirhtml to make HTML files named index.html in directories"
  21 + @echo " singlehtml to make a single large HTML file"
  22 + @echo " pickle to make pickle files"
  23 + @echo " json to make JSON files"
  24 + @echo " htmlhelp to make HTML files and a HTML help project"
  25 + @echo " qthelp to make HTML files and a qthelp project"
  26 + @echo " devhelp to make HTML files and a Devhelp project"
  27 + @echo " epub to make an epub"
  28 + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
  29 + @echo " latexpdf to make LaTeX files and run them through pdflatex"
  30 + @echo " text to make text files"
  31 + @echo " man to make manual pages"
  32 + @echo " changes to make an overview of all changed/added/deprecated items"
  33 + @echo " linkcheck to check all external links for integrity"
  34 + @echo " doctest to run all doctests embedded in the documentation (if enabled)"
  35 +
  36 +clean:
  37 + -rm -rf $(BUILDDIR)/*
  38 +
  39 +html:
  40 + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
  41 + @echo
  42 + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
  43 +
  44 +dirhtml:
  45 + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
  46 + @echo
  47 + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
  48 +
  49 +singlehtml:
  50 + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
  51 + @echo
  52 + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
  53 +
  54 +pickle:
  55 + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
  56 + @echo
  57 + @echo "Build finished; now you can process the pickle files."
  58 +
  59 +json:
  60 + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
  61 + @echo
  62 + @echo "Build finished; now you can process the JSON files."
  63 +
  64 +htmlhelp:
  65 + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
  66 + @echo
  67 + @echo "Build finished; now you can run HTML Help Workshop with the" \
  68 + ".hhp project file in $(BUILDDIR)/htmlhelp."
  69 +
  70 +qthelp:
  71 + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
  72 + @echo
  73 + @echo "Build finished; now you can run "qcollectiongenerator" with the" \
  74 + ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
  75 + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-appconf.qhcp"
  76 + @echo "To view the help file:"
  77 + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-appconf.qhc"
  78 +
  79 +devhelp:
  80 + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
  81 + @echo
  82 + @echo "Build finished."
  83 + @echo "To view the help file:"
  84 + @echo "# mkdir -p $$HOME/.local/share/devhelp/django-appconf"
  85 + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-appconf"
  86 + @echo "# devhelp"
  87 +
  88 +epub:
  89 + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
  90 + @echo
  91 + @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
  92 +
  93 +latex:
  94 + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
  95 + @echo
  96 + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
  97 + @echo "Run \`make' in that directory to run these through (pdf)latex" \
  98 + "(use \`make latexpdf' here to do that automatically)."
  99 +
  100 +latexpdf:
  101 + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
  102 + @echo "Running LaTeX files through pdflatex..."
  103 + make -C $(BUILDDIR)/latex all-pdf
  104 + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
  105 +
  106 +text:
  107 + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
  108 + @echo
  109 + @echo "Build finished. The text files are in $(BUILDDIR)/text."
  110 +
  111 +man:
  112 + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
  113 + @echo
  114 + @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
  115 +
  116 +changes:
  117 + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
  118 + @echo
  119 + @echo "The overview file is in $(BUILDDIR)/changes."
  120 +
  121 +linkcheck:
  122 + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
  123 + @echo
  124 + @echo "Link check complete; look for any errors in the above output " \
  125 + "or in $(BUILDDIR)/linkcheck/output.txt."
  126 +
  127 +doctest:
  128 + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
  129 + @echo "Testing of doctests in the sources finished, look at the " \
  130 + "results in $(BUILDDIR)/doctest/output.txt."
48 docs/changelog.rst
Source Rendered
... ... @@ -0,0 +1,48 @@
  1 +Changelog
  2 +=========
  3 +
  4 +0.4 (2011-08-24)
  5 +----------------
  6 +
  7 +* Renamed ``app_label`` attribute of the inner ``Meta`` class to ``prefix``.
  8 + The old form ``app_label`` will work in the meantime.
  9 +
  10 +* Added ``holder`` attribute to the inner ``Meta`` class to be able to
  11 + specify a custom "global" setting holder. Default: "'django.conf.settings'"
  12 +
  13 +* Added ``proxy`` attribute to the inner ``Meta`` class to enable proxying
  14 + of ``AppConf`` instances to the settings holder, e.g. the global Django
  15 + settings.
  16 +
  17 +* Fixed issues with ``configured_data`` dictionary available in the
  18 + ``configure`` method of ``AppConf`` classes with regard to subclassing.
  19 +
  20 +0.3 (2011-08-23)
  21 +----------------
  22 +
  23 +* Added tests with 100% coverage.
  24 +
  25 +* Added ability to subclass ``Meta`` classes.
  26 +
  27 +* Fixed various bugs with subclassing and configuration in subclasses.
  28 +
  29 +0.2.2 (2011-08-22)
  30 +------------------
  31 +
  32 +* Fixed another issue in the ``configure()`` API.
  33 +
  34 +0.2.1 (2011-08-22)
  35 +------------------
  36 +
  37 +* Fixed minor issue in ``configure()`` API.
  38 +
  39 +0.2 (2011-08-22)
  40 +----------------
  41 +
  42 +* Added ``configure()`` API to ``AppConf`` class which is called after
  43 + configuring each setting.
  44 +
  45 +0.1 (2011-08-22)
  46 +----------------
  47 +
  48 +* First public release.
220 docs/conf.py
... ... @@ -0,0 +1,220 @@
  1 +# -*- coding: utf-8 -*-
  2 +#
  3 +# django-appconf documentation build configuration file, created by
  4 +# sphinx-quickstart on Thu Aug 25 14:26:22 2011.
  5 +#
  6 +# This file is execfile()d with the current directory set to its containing dir.
  7 +#
  8 +# Note that not all possible configuration values are present in this
  9 +# autogenerated file.
  10 +#
  11 +# All configuration values have a default; values that are commented out
  12 +# serve to show the default.
  13 +
  14 +import sys, os
  15 +
  16 +# If extensions (or modules to document with autodoc) are in another directory,
  17 +# add these directories to sys.path here. If the directory is relative to the
  18 +# documentation root, use os.path.abspath to make it absolute, like shown here.
  19 +sys.path.insert(0, os.path.abspath('..'))
  20 +
  21 +# -- General configuration -----------------------------------------------------
  22 +
  23 +# If your documentation needs a minimal Sphinx version, state it here.
  24 +#needs_sphinx = '1.0'
  25 +
  26 +# Add any Sphinx extension module names here, as strings. They can be extensions
  27 +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
  28 +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.coverage']
  29 +
  30 +# Add any paths that contain templates here, relative to this directory.
  31 +templates_path = ['_templates']
  32 +
  33 +# The suffix of source filenames.
  34 +source_suffix = '.rst'
  35 +
  36 +# The encoding of source files.
  37 +#source_encoding = 'utf-8-sig'
  38 +
  39 +# The master toctree document.
  40 +master_doc = 'index'
  41 +
  42 +# General information about the project.
  43 +project = u'django-appconf'
  44 +copyright = u'2011, Jannis Leidel and individual contributors'
  45 +
  46 +# The version info for the project you're documenting, acts as replacement for
  47 +# |version| and |release|, also used in various other places throughout the
  48 +# built documents.
  49 +#
  50 +# The short X.Y version.
  51 +version = '0.4'
  52 +# The full version, including alpha/beta/rc tags.
  53 +release = '0.4'
  54 +
  55 +# The language for content autogenerated by Sphinx. Refer to documentation
  56 +# for a list of supported languages.
  57 +#language = None
  58 +
  59 +# There are two options for replacing |today|: either, you set today to some
  60 +# non-false value, then it is used:
  61 +#today = ''
  62 +# Else, today_fmt is used as the format for a strftime call.
  63 +#today_fmt = '%B %d, %Y'
  64 +
  65 +# List of patterns, relative to source directory, that match files and
  66 +# directories to ignore when looking for source files.
  67 +exclude_patterns = ['_build']
  68 +
  69 +# The reST default role (used for this markup: `text`) to use for all documents.
  70 +#default_role = None
  71 +
  72 +# If true, '()' will be appended to :func: etc. cross-reference text.
  73 +#add_function_parentheses = True
  74 +
  75 +# If true, the current module name will be prepended to all description
  76 +# unit titles (such as .. function::).
  77 +#add_module_names = True
  78 +
  79 +# If true, sectionauthor and moduleauthor directives will be shown in the
  80 +# output. They are ignored by default.
  81 +#show_authors = False
  82 +
  83 +# The name of the Pygments (syntax highlighting) style to use.
  84 +pygments_style = 'sphinx'
  85 +
  86 +# A list of ignored prefixes for module index sorting.
  87 +#modindex_common_prefix = []
  88 +
  89 +
  90 +# -- Options for HTML output ---------------------------------------------------
  91 +
  92 +# The theme to use for HTML and HTML Help pages. See the documentation for
  93 +# a list of builtin themes.
  94 +html_theme = 'default'
  95 +
  96 +# Theme options are theme-specific and customize the look and feel of a theme
  97 +# further. For a list of options available for each theme, see the
  98 +# documentation.
  99 +#html_theme_options = {}
  100 +
  101 +# Add any paths that contain custom themes here, relative to this directory.
  102 +#html_theme_path = []
  103 +
  104 +# The name for this set of Sphinx documents. If None, it defaults to
  105 +# "<project> v<release> documentation".
  106 +#html_title = None
  107 +
  108 +# A shorter title for the navigation bar. Default is the same as html_title.
  109 +#html_short_title = None
  110 +
  111 +# The name of an image file (relative to this directory) to place at the top
  112 +# of the sidebar.
  113 +#html_logo = None
  114 +
  115 +# The name of an image file (within the static path) to use as favicon of the
  116 +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
  117 +# pixels large.
  118 +#html_favicon = None
  119 +
  120 +# Add any paths that contain custom static files (such as style sheets) here,
  121 +# relative to this directory. They are copied after the builtin static files,
  122 +# so a file named "default.css" will overwrite the builtin "default.css".
  123 +html_static_path = ['_static']
  124 +
  125 +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
  126 +# using the given strftime format.
  127 +#html_last_updated_fmt = '%b %d, %Y'
  128 +
  129 +# If true, SmartyPants will be used to convert quotes and dashes to
  130 +# typographically correct entities.
  131 +#html_use_smartypants = True
  132 +
  133 +# Custom sidebar templates, maps document names to template names.
  134 +#html_sidebars = {}
  135 +
  136 +# Additional templates that should be rendered to pages, maps page names to
  137 +# template names.
  138 +#html_additional_pages = {}
  139 +
  140 +# If false, no module index is generated.
  141 +#html_domain_indices = True
  142 +
  143 +# If false, no index is generated.
  144 +#html_use_index = True
  145 +
  146 +# If true, the index is split into individual pages for each letter.
  147 +#html_split_index = False
  148 +
  149 +# If true, links to the reST sources are added to the pages.
  150 +#html_show_sourcelink = True
  151 +
  152 +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
  153 +#html_show_sphinx = True
  154 +
  155 +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
  156 +#html_show_copyright = True
  157 +
  158 +# If true, an OpenSearch description file will be output, and all pages will
  159 +# contain a <link> tag referring to it. The value of this option must be the
  160 +# base URL from which the finished HTML is served.
  161 +#html_use_opensearch = ''
  162 +
  163 +# This is the file name suffix for HTML files (e.g. ".xhtml").
  164 +#html_file_suffix = None
  165 +
  166 +# Output file base name for HTML help builder.
  167 +htmlhelp_basename = 'django-appconfdoc'
  168 +
  169 +
  170 +# -- Options for LaTeX output --------------------------------------------------
  171 +
  172 +# The paper size ('letter' or 'a4').
  173 +#latex_paper_size = 'letter'
  174 +
  175 +# The font size ('10pt', '11pt' or '12pt').
  176 +#latex_font_size = '10pt'
  177 +
  178 +# Grouping the document tree into LaTeX files. List of tuples
  179 +# (source start file, target name, title, author, documentclass [howto/manual]).
  180 +latex_documents = [
  181 + ('index', 'django-appconf.tex', u'django-appconf Documentation',
  182 + u'Jannis Leidel and individual contributors', 'manual'),
  183 +]
  184 +
  185 +# The name of an image file (relative to this directory) to place at the top of
  186 +# the title page.
  187 +#latex_logo = None
  188 +
  189 +# For "manual" documents, if this is true, then toplevel headings are parts,
  190 +# not chapters.
  191 +#latex_use_parts = False
  192 +
  193 +# If true, show page references after internal links.
  194 +#latex_show_pagerefs = False
  195 +
  196 +# If true, show URL addresses after external links.
  197 +#latex_show_urls = False
  198 +
  199 +# Additional stuff for the LaTeX preamble.
  200 +#latex_preamble = ''
  201 +
  202 +# Documents to append as an appendix to all manuals.
  203 +#latex_appendices = []
  204 +
  205 +# If false, no module index is generated.
  206 +#latex_domain_indices = True
  207 +
  208 +
  209 +# -- Options for manual page output --------------------------------------------
  210 +
  211 +# One entry per manual page. List of tuples
  212 +# (source start file, name, description, authors, manual section).
  213 +man_pages = [
  214 + ('index', 'django-appconf', u'django-appconf Documentation',
  215 + [u'Jannis Leidel and individual contributors'], 1)
  216 +]
  217 +
  218 +
  219 +# Example configuration for intersphinx: refer to the Python standard library.
  220 +intersphinx_mapping = {'http://docs.python.org/': None}
12 docs/index.rst
Source Rendered
... ... @@ -0,0 +1,12 @@
  1 +.. include:: ../README.rst
  2 +
  3 +Contents
  4 +========
  5 +
  6 +.. toctree::
  7 + :maxdepth: 1
  8 +
  9 + installation
  10 + usage
  11 + reference
  12 + changelog
20 docs/installation.rst
Source Rendered
... ... @@ -0,0 +1,20 @@
  1 +Installation
  2 +============
  3 +
  4 +* Install django-appconf with your favorite Python package manager::
  5 +
  6 + pip install django-appconf
  7 +
  8 +* Add ``'appconf'`` to your ``INSTALLED_APPS`` setting::
  9 +
  10 + INSTALLED_APPS = (
  11 + # other apps
  12 + "appconf",
  13 + )
  14 +
  15 +
  16 +.. _staticfiles: http://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/
  17 +.. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles
  18 +
  19 +.. _dependencies:
  20 +
170 docs/make.bat
... ... @@ -0,0 +1,170 @@
  1 +@ECHO OFF
  2 +
  3 +REM Command file for Sphinx documentation
  4 +
  5 +if "%SPHINXBUILD%" == "" (
  6 + set SPHINXBUILD=sphinx-build
  7 +)
  8 +set BUILDDIR=_build
  9 +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
  10 +if NOT "%PAPER%" == "" (
  11 + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
  12 +)
  13 +
  14 +if "%1" == "" goto help
  15 +
  16 +if "%1" == "help" (
  17 + :help
  18 + echo.Please use `make ^<target^>` where ^<target^> is one of
  19 + echo. html to make standalone HTML files
  20 + echo. dirhtml to make HTML files named index.html in directories
  21 + echo. singlehtml to make a single large HTML file
  22 + echo. pickle to make pickle files
  23 + echo. json to make JSON files
  24 + echo. htmlhelp to make HTML files and a HTML help project
  25 + echo. qthelp to make HTML files and a qthelp project
  26 + echo. devhelp to make HTML files and a Devhelp project
  27 + echo. epub to make an epub
  28 + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
  29 + echo. text to make text files
  30 + echo. man to make manual pages
  31 + echo. changes to make an overview over all changed/added/deprecated items
  32 + echo. linkcheck to check all external links for integrity
  33 + echo. doctest to run all doctests embedded in the documentation if enabled
  34 + goto end
  35 +)
  36 +
  37 +if "%1" == "clean" (
  38 + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
  39 + del /q /s %BUILDDIR%\*
  40 + goto end
  41 +)
  42 +
  43 +if "%1" == "html" (
  44 + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
  45 + if errorlevel 1 exit /b 1
  46 + echo.
  47 + echo.Build finished. The HTML pages are in %BUILDDIR%/html.
  48 + goto end
  49 +)
  50 +
  51 +if "%1" == "dirhtml" (
  52 + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
  53 + if errorlevel 1 exit /b 1
  54 + echo.
  55 + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
  56 + goto end
  57 +)
  58 +
  59 +if "%1" == "singlehtml" (
  60 + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
  61 + if errorlevel 1 exit /b 1
  62 + echo.
  63 + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
  64 + goto end
  65 +)
  66 +
  67 +if "%1" == "pickle" (
  68 + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
  69 + if errorlevel 1 exit /b 1
  70 + echo.
  71 + echo.Build finished; now you can process the pickle files.
  72 + goto end
  73 +)
  74 +
  75 +if "%1" == "json" (
  76 + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
  77 + if errorlevel 1 exit /b 1
  78 + echo.
  79 + echo.Build finished; now you can process the JSON files.
  80 + goto end
  81 +)
  82 +
  83 +if "%1" == "htmlhelp" (
  84 + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
  85 + if errorlevel 1 exit /b 1
  86 + echo.
  87 + echo.Build finished; now you can run HTML Help Workshop with the ^
  88 +.hhp project file in %BUILDDIR%/htmlhelp.
  89 + goto end
  90 +)
  91 +
  92 +if "%1" == "qthelp" (
  93 + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
  94 + if errorlevel 1 exit /b 1
  95 + echo.
  96 + echo.Build finished; now you can run "qcollectiongenerator" with the ^
  97 +.qhcp project file in %BUILDDIR%/qthelp, like this:
  98 + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-appconf.qhcp
  99 + echo.To view the help file:
  100 + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-appconf.ghc
  101 + goto end
  102 +)
  103 +
  104 +if "%1" == "devhelp" (
  105 + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
  106 + if errorlevel 1 exit /b 1
  107 + echo.
  108 + echo.Build finished.
  109 + goto end
  110 +)
  111 +
  112 +if "%1" == "epub" (
  113 + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
  114 + if errorlevel 1 exit /b 1
  115 + echo.
  116 + echo.Build finished. The epub file is in %BUILDDIR%/epub.
  117 + goto end
  118 +)
  119 +
  120 +if "%1" == "latex" (
  121 + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
  122 + if errorlevel 1 exit /b 1
  123 + echo.
  124 + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
  125 + goto end
  126 +)
  127 +
  128 +if "%1" == "text" (
  129 + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
  130 + if errorlevel 1 exit /b 1
  131 + echo.
  132 + echo.Build finished. The text files are in %BUILDDIR%/text.
  133 + goto end
  134 +)
  135 +
  136 +if "%1" == "man" (
  137 + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
  138 + if errorlevel 1 exit /b 1
  139 + echo.
  140 + echo.Build finished. The manual pages are in %BUILDDIR%/man.
  141 + goto end
  142 +)
  143 +
  144 +if "%1" == "changes" (
  145 + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
  146 + if errorlevel 1 exit /b 1
  147 + echo.
  148 + echo.The overview file is in %BUILDDIR%/changes.
  149 + goto end
  150 +)
  151 +
  152 +if "%1" == "linkcheck" (
  153 + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
  154 + if errorlevel 1 exit /b 1
  155 + echo.
  156 + echo.Link check complete; look for any errors in the above output ^
  157 +or in %BUILDDIR%/linkcheck/output.txt.
  158 + goto end
  159 +)
  160 +
  161 +if "%1" == "doctest" (
  162 + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
  163 + if errorlevel 1 exit /b 1
  164 + echo.
  165 + echo.Testing of doctests in the sources finished, look at the ^
  166 +results in %BUILDDIR%/doctest/output.txt.
  167 + goto end
  168 +)
  169 +
  170 +:end
76 docs/reference.rst
Source Rendered
... ... @@ -0,0 +1,76 @@
  1 +=========
  2 +Reference
  3 +=========
  4 +
  5 +.. currentmodule:: appconf
  6 +
  7 +.. class:: AppConf
  8 +
  9 + A representation of a template tag. For example::
  10 +
  11 + class MyAppConf(AppConf):
  12 + SETTING_1 = "one"
  13 + SETTING_2 = (
  14 + "two",
  15 + )
  16 +
  17 + .. method:: configure_*(value)
  18 +
  19 + Method for each of the app settings for custom configuration
  20 + which gets the value passed of the class attribute or the
  21 + appropriate override value of the :attr:`~appconf.AppConf.Meta.holder`
  22 + settings, e.g.::
  23 +
  24 + class MyAppConf(AppConf):
  25 + DEPLOYMENT_MODE = "dev"
  26 +
  27 + def configure_deployment_mode(self, value):
  28 + if on_production():
  29 + value = "prod"
  30 + return value
  31 +
  32 + The method **must return** the value to be use for the setting in
  33 + question.
  34 +
  35 + .. automethod:: configure
  36 + .. autoattribute:: configured_data
  37 +
  38 + The dictionary attribute which can be used to do any further custom
  39 + configuration handling in the :meth:`~appconf.AppConf.configure`
  40 + method, e.g. if multiple settings depend on each other.
  41 +
  42 +.. class:: AppConf.Meta
  43 +
  44 + An ``AppConf`` takes options via a ``Meta`` inner class::
  45 +
  46 + class MyAppConf(AppConf):
  47 + SETTING_1 = "one"
  48 + SETTING_2 = (
  49 + "two",
  50 + )
  51 +
  52 + class Meta:
  53 + proxy = False
  54 + prefix = 'myapp'
  55 + holder = 'django.conf.settings'
  56 +
  57 + .. attribute:: prefix
  58 +
  59 + Explicitly choose a prefix for all settings handled by the
  60 + ``AppConf`` class. If not given, the prefix will be the capitalized
  61 + class module name.
  62 +
  63 + For example, ``acme`` would turn into settings like
  64 + ``ACME_SETTING_1``.
  65 +
  66 + .. attribute:: holder
  67 +
  68 + The global settings holder to use when looking for overrides and
  69 + when setting the configured values.
  70 +
  71 + Defaults to ``'django.conf.settings'``.
  72 +
  73 + .. attribute:: proxy
  74 +
  75 + A boolean, if set to ``True`` will enable proxying attribute access
  76 + to the :attr:`~appconf.AppConf.Meta.holder`.
95 docs/usage.rst
Source Rendered
... ... @@ -0,0 +1,95 @@
  1 +Usage
  2 +=====
  3 +
  4 +It's strongly recommended to use the usual ``from django.conf import settings``
  5 +in your own code to access the configured settings.
  6 +
  7 +But you can also **OPTIONALLY** use your app's own settings object directly,
  8 +by instantiating it in place::
  9 +
  10 + from myapp.models import MyAppConf
  11 +
  12 + myapp_settings = MyAppConf()
  13 +
  14 + print myapp_settings.SETTING_1
  15 +
  16 +Note that accessing the settings that way means they don't have a prefix.
  17 +
  18 +``AppConf`` instances don't automatically work as proxies for the global
  19 +settings. But you can enable this if you want by setting the ``proxy``
  20 +attribute of the inner ``Meta`` class to ``True``::
  21 +
  22 + from appconf import AppConf
  23 +
  24 + class MyAppConf(AppConf):
  25 + SETTING_1 = "one"
  26 + SETTING_2 = (
  27 + "two",
  28 + )
  29 +
  30 + class Meta:
  31 + proxy = True
  32 +
  33 + myapp_settings = MyAppConf()
  34 +
  35 + if "myapp" in myapp_settings.INSTALLED_APPS:
  36 + print "yay, myapp is installed!"
  37 +
  38 +In case you want to override some settings programmatically, you can
  39 +simply pass the value when instantiating the ``AppConf`` class::
  40 +
  41 + from myapp.models import MyAppConf
  42 +
  43 + myapp_settings = MyAppConf(SETTING_1='something completely different')
  44 +
  45 + if 'different' in myapp_settings.SETTINGS_1:
  46 + print 'yay, I'm different!'
  47 +
  48 +Custom configuration
  49 +--------------------
  50 +
  51 +Each of the settings can be individually configured with callbacks.
  52 +For example, in case a value of a setting depends on other settings
  53 +or other dependencies. The following example sets one setting to a
  54 +different value depending on a global setting::
  55 +
  56 + from django.conf import settings
  57 + from appconf import AppConf
  58 +
  59 + class MyCustomAppConf(AppConf):
  60 + ENABLED = True
  61 +
  62 + def configure_enabled(self, value):
  63 + return value and not settings.DEBUG
  64 +
  65 +The value of ``MYAPP_ENABLED`` will vary depending on the
  66 +value of the global ``DEBUG`` setting.
  67 +
  68 +Each of the app settings can be customized by providing
  69 +a method ``configure_<lower_setting_name>`` that takes the default
  70 +value as defined in the class attributes of the ``AppConf`` subclass
  71 +or the override value from the global settings as the only parameter.
  72 +The method **must return** the value to be use for the setting in
  73 +question.
  74 +
  75 +After each of the ``*_configure`` methods have been called, the ``AppConf``
  76 +class will additionally call a main ``configure`` method, which can
  77 +be used to do any further custom configuration handling, e.g. if multiple
  78 +settings depend on each other. For that a ``configured_data`` dictionary
  79 +is provided in the setting instance::
  80 +
  81 + from django.conf import settings
  82 + from appconf import AppConf
  83 +
  84 + class MyCustomAppConf(AppConf):
  85 + ENABLED = True
  86 + MODE = 'development'
  87 +
  88 + def configure_enabled(self, value):
  89 + return value and not settings.DEBUG
  90 +
  91 + def configure(self):
  92 + mode = self.configured_data['MODE']
  93 + enabled = self.configured_data['ENABLED']
  94 + if not enabled and mode != 'development':
  95 + print "WARNING: app not enabled in %s mode!" % mode
11 setup.py
@@ -7,15 +7,16 @@
7 7 setup(
8 8 name='django-appconf',
9 9 version=":versiontools:appconf:",
10   - description='An app configuration object to be used for handling '
11   - 'configuration defaults of packaged apps gracefully.',
  10 + description='A helper class for handling configuration defaults '
  11 + 'of packaged apps gracefully.',
12 12 long_description=read(path.join(path.dirname(__file__), 'README.rst')),
13 13 author='Jannis Leidel',
14 14 author_email='jannis@leidel.info',
15   - url='https://github.com/jezdez/django-appconf/',
  15 + license = 'BSD',
  16 + url='http://django-appconf.readthedocs.org/',
16 17 py_modules=['appconf'],
17 18 classifiers=[
18   - "Development Status :: 3 - Alpha",
  19 + "Development Status :: 4 - Beta",
19 20 'Environment :: Web Environment',
20 21 'Framework :: Django',
21 22 'Intended Audience :: Developers',
@@ -28,6 +29,6 @@
28 29 'Topic :: Utilities',
29 30 ],
30 31 setup_requires=[
31   - 'versiontools >= 1.5',
  32 + 'versiontools >= 1.6',
32 33 ],
33 34 )
32 tests/testapp/models.py
... ... @@ -1,5 +1,12 @@
1 1 from appconf import AppConf
2 2
  3 +
  4 +class CustomHolder(object):
  5 + pass
  6 +
  7 +custom_holder = CustomHolder()
  8 +
  9 +
3 10 class TestConf(AppConf):
4 11
5 12 SIMPLE_VALUE = True
@@ -17,7 +24,7 @@ def configure(self):
17 24 class PrefixConf(TestConf):
18 25
19 26 class Meta:
20   - app_label = 'prefix'