Browse files

Models registering and caching added. API redesigned.

  • Loading branch information...
1 parent 12c428e commit 9245dbe20387426836b6a6a930aa16672581236b @jqb committed Nov 16, 2012
View
2 LICENSE
@@ -6,7 +6,7 @@ are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
-
+
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
View
78 README.rst
@@ -4,7 +4,7 @@ django-settings
Current version: 1.3 beta
-Simple django reusable application for storing global project settings in database.
+Django reusable application for storing global project settings in database.
By project settings I mean things like admin mail, some default values like
default_post_limit etc. Values are validated depending their type.
@@ -19,21 +19,21 @@ with it.
::
- import djsettings # or: import django_settings
+ import django_settings
# getting values
- djsettings.get('post_limit')
- djsettings.get('post_limit', default=10)
+ django_settings.get('post_limit')
+ django_settings.get('post_limit', default=10)
# set values - cache is updated for "get" and "exists" method
# values are validated using setting_value model clean_fields method
- djsettings.set('Email', 'admin_email', 'admin@admin.com')
+ django_settings.set('Email', 'admin_email', 'admin@admin.com')
- # If you want to avoid validating do this:
- djsettings.set('Email', 'admin_email', 'admin@admin.com', validate=False)
+ # If you want to avoid validation, do this:
+ django_settings.set('Email', 'admin_email', 'admin@admin.com', validate=False)
# checking if value exists
- djsettings.exists('admin_email')
+ django_settings.exists('admin_email')
@@ -47,61 +47,48 @@ Installation & setup
$> pip install django-settings
-2) Add "djsettings" to your INSTALLED_APPS
+2) Add "django_settings" to your INSTALLED_APPS
::
INSTALLED_APPS = (
+ 'django.contrib.contenttypes', # contenttypes framework is required
+
# ...
- 'djsettings',
+ 'django_settings',
# ...
)
-3) Register all settings models on your urls module:
+3) If you want to add your own settings models, please add them in one of your
+ applications models file, and register them with django_settings api:
::
- # <project>/urls.py
- from django.conf.urls.defaults import patterns, include
- from django.contrib import admin
-
- import djsettings
-
- admin.autodiscover()
- djsettings.autoregister() # NOTE: do it AFTER admin.autodiscover
+ # <project>/<app>/models.py
+ from django.db import models
- # ...
+ # ... your application models
-4) If you want to add your own setting model, create "settingsmodels.py" file in one
-of your applications and define your own solutions:
-
-::
- # <project>/<your-app>/settingsmodule.py
- from django.db import models
- import djsettings
+ import django_settings
+ class Text(django_settings.db.Model):
+ value = models.TextField()
+ class Meta:
+ abstract = True # it's IMPORTANT - it need to be abstract
+ django_settings.register(Text)
- class MyText(djsettings.Model):
- value = models.TextField()
- class Meta:
- abstract = True
-
- def register(): # create "register" function and add your own models there
- djsettings.register(MyText)
-
-
-Remember to define this model as abstract, this is important because of how django
+Remember to define model as abstract, this is important because of how django
treats model classes.
When ``register`` function will be invoked all your models will be available in
-``djsettings.models`` module, so django can treat them as regular models.
+``django_settings.models`` module, so django can treat them as regular models.
There is ability to setup some defaults via project settings.py file.
@@ -124,22 +111,17 @@ Builidin settings types: Email, Integer, String, PositiveInteger
Admin
-----
-Now you can manipulate setting via your admin interface.
-Just remember to put "admin.autodiscover" BEFORE "djsettings.autoregister"
-in your "urls.py" file.
+You can manipulate setting via your admin interface.
Changelog
---------
-1.3 beta - three big things has been changed.
+1.2 beta - two big things has been changed.
- 1) package import name changed, old one still exists for backward compatibility
- 2) from now you can extend settings with your own types
- 3) new api with caching mechanism introduced
+ 1) from now you can extend settings with your own types
+ 2) new api with caching mechanism introduced
Some tests has been added for core functionality.
-
- Really important thing is that old api through "django_settings" root
- still works, but it do not support caching.
+ It's possible to use old API but there's no cache.
View
0 djsettings/__init__.py → django_settings/__init__.py
File renamed without changes.
View
0 djsettings/admin.py → django_settings/admin.py
File renamed without changes.
View
26 django_settings/api.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Public module API
+from .moduleregistry import RegisterError
+from .dataapi import DataAPI, data
+
+
+# shortcuts
+get = data.get
+set = data.set
+exists = data.exists
+type_names = data.type_names
+
+
+# django settings-dependent parts should be loadded lazily
+from .lazyimport import lazyimport
+db = lazyimport({ # this is also part of public api
+ 'Model' : 'django_settings.models',
+ 'Setting' : 'django_settings.models',
+ 'registry' : 'django_settings.models',
+})
+
+# expose methods
+register = lambda *a, **kw: db.registry.register(*a, **kw)
+unregister = lambda *a, **kw: db.registry.unregister(*a, **kw)
+unregister_all = lambda *a, **kw: db.registry.unregister_all(*a, **kw)
+
View
12 djsettings/cache.py → django_settings/cache.py
@@ -8,19 +8,21 @@
XXX: the whole mechanism should be fixed as now it's too complicated to explain
"""
-from django.core.cache import cache
class MethodProxy(object):
- def __init__(self, instance, method, cache_client=None):
- self.key_prefix = 'djsettings'
+ def __init__(self, instance, method):
+ self.key_prefix = 'django_settings'
self.instance = instance
self.method = method # accually it's NOT bounded s it's a function!
- self.cache = cache_client or cache
# NOTE: it's proxy, so let's add at least some basic func properties
self.func_name = self.method.func_name
+ @property
+ def cache(self):
+ return self.instance.cache # ATTENTION: this may raise django ImportError
+
def origin_method(self, *args, **kwargs):
return self.method(self.instance, *args, **kwargs)
@@ -69,7 +71,7 @@ def __init__(self, method, method_proxy_class=MethodProxy):
def __get__(self, instance, instance_type=None):
proxy = getattr(instance, self.method_proxy_name, None)
if proxy is None:
- proxy = self.method_proxy_class(instance, self.method, instance.cache)
+ proxy = self.method_proxy_class(instance, self.method)
setattr(instance, self.method_proxy_name, proxy)
return proxy
View
44 djsettings/dataapi.py → django_settings/dataapi.py
@@ -2,14 +2,21 @@
# system
from operator import attrgetter
-# framework
-from django.contrib.contenttypes.models import ContentType
-from django.core.cache import cache
-from django.db.models import Q
-
# module
-from .models import registry, Setting
from .cache import cache_method, MethodProxy
+from .lazyimport import lazyimport
+
+
+# lazy imports
+django = lazyimport({
+ 'ContentType' : 'django.contrib.contenttypes.models',
+ 'cache' : 'django.core.cache',
+ 'Q' : 'django.db.models',
+})
+db = lazyimport({
+ 'registry': 'django_settings.models',
+ 'Setting': 'django_settings.models',
+})
class dataapi_set_method_proxy(MethodProxy):
@@ -27,7 +34,6 @@ def _cache_key(self, name): # should accept only "name"
return self._cache_key_for_method('get', name)
-
class DataAPIMetaclass(type):
registry = []
@@ -48,33 +54,37 @@ class DataAPI(object):
name_getter = attrgetter('name')
def __init__(self, cache_client=None):
- self.cache = cache_client or cache
+ self._client = cache_client
+
+ @property
+ def cache(self): # as lazy as possible
+ return self._client or django.cache
def contenttypes_names(self):
- ctypes = ContentType.objects.get_for_models(*registry.values()).values()
+ ctypes = django.ContentType.objects.get_for_models(*db.registry.values()).values()
return map(self.name_getter, ctypes)
contenttypes_names = cache_method(contenttypes_names)
def contenttypes_queryset(self):
- query = Q()
+ query = django.Q()
for name in self.contenttypes_names():
- query = query | Q(name=name)
- return ContentType.objects.filter(query)
+ query = query | django.Q(name=name)
+ return django.ContentType.objects.filter(query)
def type_names(self):
- return registry.names()
+ return db.registry.names()
def get(self, name, **kw):
- return Setting.objects.get_value(name, **kw)
+ return db.Setting.objects.get_value(name, **kw)
get = cache_method(get, dataapi_get_method_proxy)
def set(self, type_name, name, value, validate=True):
- setting_type = registry.elements[type_name]
- return Setting.objects.set_value(name, setting_type, value, validate=validate)
+ setting_type = db.registry.elements[type_name]
+ return db.Setting.objects.set_value(name, setting_type, value, validate=validate)
set = cache_method(set, dataapi_set_method_proxy)
def exists(self, name):
- return bool(Setting.objects.value_object_exists(name))
+ return bool(db.Setting.objects.value_object_exists(name))
exists = cache_method(exists)
def clear_cache(self):
View
8 djsettings/forms.py → django_settings/forms.py
@@ -3,27 +3,27 @@
from django.forms.models import modelform_factory
from django.utils.translation import ugettext_lazy as _
-from . import api as djsettings
+from . import api as django_settings
class SettingForm(forms.ModelForm):
class Meta:
- model = djsettings.Setting
+ model = django_settings.db.Setting
fields = ('setting_type', 'name')
value = forms.CharField()
def __init__(self, *a, **kw):
forms.ModelForm.__init__(self, *a, **kw)
- self.fields['setting_type'].queryset = djsettings.data.contenttypes_queryset()
+ self.fields['setting_type'].queryset = django_settings.data.contenttypes_queryset()
instance = kw.get('instance')
if instance:
self.fields['value'].initial = getattr(instance.setting_object, 'value', '')
self._change_callback = kw.get(
'change_callback',
- djsettings.DataAPI.setting_changed # classmethod
+ django_settings.DataAPI.setting_changed # classmethod
)
def clean(self):
View
30 django_settings/lazyimport.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+import importlib # requires python 2.7
+
+
+class lazyimport(object):
+ """
+ Usage::
+
+ from lazyimport import lazyimport
+
+ django = lazyimport({
+ 'ContentType' : 'django.contrib.contenttypes.models',
+ 'cache' : 'django.core.cache',
+ })
+
+ django.ContentType # importing takes place here
+ django.cache # importing takes place here
+ """
+
+ def __init__(self, mapping):
+ self.__mapping = mapping
+
+ def __getattr__(self, name):
+ mapping = self.__mapping
+ if name in mapping:
+ imported = getattr(importlib.import_module(mapping[name]), name)
+ setattr(self, name, imported)
+ return imported
+ return object.__getattribute__(self, name)
+
View
8 djsettings/management.py → django_settings/management.py
@@ -3,7 +3,7 @@
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
-import djsettings
+import django_settings
DEFAULT_SETTINGS = getattr(settings, 'DJANGO_SETTINGS', {})
@@ -13,7 +13,7 @@ def initialize_data(sender, **kwargs):
for name, type_name_and_value in DEFAULT_SETTINGS.items():
type_name, value = type_name_and_value
- if not djsettings.exists(type_name):
- djsettings.set(type_name, name, value)
+ if not django_settings.exists(type_name):
+ django_settings.set(type_name, name, value)
-signals.post_syncdb.connect(initialize_data, sender=djsettings.models)
+signals.post_syncdb.connect(initialize_data, sender=django_settings.models)
View
32 djsettings/models.py → django_settings/models.py
@@ -59,6 +59,7 @@ class Meta:
name = models.CharField(max_length=255)
+
# Extentions #######################################################
from .moduleregistry import new_registry
@@ -67,4 +68,35 @@ class Meta:
# cleanup
del new_registry
+# end ###############################################################
+
+
+# Builtin settings models
+class Email(Model):
+ value = models.EmailField()
+ class Meta:
+ abstract = True
+registry.register(Email)
+
+
+class String(Model):
+ value = models.CharField(max_length=254)
+ class Meta:
+ abstract = True
+registry.register(String)
+
+
+class Integer(Model):
+ value = models.IntegerField()
+ class Meta:
+ abstract = True
+registry.register(Integer)
+
+
+class PositiveInteger(Model):
+ value = models.PositiveIntegerField()
+ class Meta:
+ abstract = True
+registry.register(PositiveInteger)
+# end ###################
View
5 djsettings/moduleregistry.py → django_settings/moduleregistry.py
@@ -33,9 +33,6 @@
# system
import sys
-# framework
-from django.utils.datastructures import SortedDict
-
def subclass(class_=None, module=None):
attrs = {
@@ -50,7 +47,7 @@ class RegisterError(Exception):
class ModuleRegistry(object):
def __init__(self, module):
- self.elements = SortedDict()
+ self.elements = {}
self.module = module
def _subclass(self, model_class):
View
20 djsettings/api.py
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-# Public module API
-from .models import Model, Setting, registry
-from .moduleregistry import RegisterError
-from .discover import autoregister
-from .dataapi import DataAPI, data
-
-
-# expose methods
-register = registry.register
-unregister = registry.unregister
-unregister_all = registry.unregister_all
-
-
-# shortcuts
-get = data.get
-set = data.set
-exists = data.exists
-type_names = data.type_names
-
View
29 djsettings/discover.py
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-
-def import_django_settings():
- from django.conf import settings
- return settings
-
-
-def autoregister(module_name='settingsmodels', settings=None):
- # framework
- from django.utils.importlib import import_module
-
- # app public api
- from .api import RegisterError
-
- settings = settings or import_django_settings()
-
- for app in settings.INSTALLED_APPS:
- mod = import_module(app)
-
- try:
- rmod = import_module('%s.%s' % (app, module_name))
- except ImportError:
- pass # Ignore that one
- except RegisterError:
- raise
- else:
- rmod.register() # registering ONLY on demand
-
View
40 djsettings/settingsmodels.py
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-# Builtin settings models
-
-# framework
-from django.db import models
-
-# public api localy, normally should be: import djsettings
-from . import api as djsettings
-
-
-class Email(djsettings.Model):
- value = models.EmailField()
- class Meta:
- abstract = True
-
-
-class String(djsettings.Model):
- value = models.CharField(max_length=254)
- class Meta:
- abstract = True
-
-
-class Integer(djsettings.Model):
- value = models.IntegerField()
- class Meta:
- abstract = True
-
-
-class PositiveInteger(djsettings.Model):
- value = models.PositiveIntegerField()
- class Meta:
- abstract = True
-
-
-def register():
- djsettings.register(String)
- djsettings.register(Email)
- djsettings.register(Integer)
- djsettings.register(PositiveInteger)
-
View
3 example/app/core/models.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+
View
BIN example/dev.db
Binary file not shown.
View
2 example/settings/common.py
@@ -23,7 +23,7 @@
'django.contrib.staticfiles',
'debug_toolbar',
- 'djsettings',
+ 'django_settings',
) + apps_from('app')
View
4 example/settings/urls.py
@@ -2,13 +2,11 @@
from django.conf.urls.defaults import patterns, include
from django.contrib import admin
-import djsettings
-
admin.autodiscover()
-djsettings.autoregister()
urlpatterns = patterns('django.views.generic',
(r'^admin/', include(admin.site.urls)),
(r'^$', 'simple.direct_to_template', {'template': 'base.html'}),
)
+
View
9 setup.py
@@ -1,20 +1,18 @@
# -*- coding: utf-8 -*-
import os
-from distutils.core import setup
-import django_settings
+from setuptools import setup
+import djsettings
setup(
name='django-settings',
- version=django_settings.__version__,
+ version=djsettings.__version__,
description='Simple django reusable application for storing project settings in database.',
author='Kuba Janoszek',
author_email='kuba.janoszek@gmail.com',
url='http://github.com/jqb/django-settings',
packages=['django_settings'],
- package_dir={
- 'django_settings': 'django_settings'},
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
@@ -26,3 +24,4 @@
],
zip_safe=False,
)
+
View
2 tests/conf.py
@@ -10,7 +10,7 @@
INSTALLED_APPS = (
'django.contrib.contenttypes',
- 'djsettings',
+ 'django_settings',
'tests.moduleregistry_testapp',
)
View
12 tests/moduleregistry_testapp/for_autoregistry_test.py
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.db import models
-
-
-class Integer(object):
- value = models.IntegerField()
-
-
-def register():
- from .models import registry
- registry.register(Integer)
-
View
4 tests/moduleregistry_testapp/models.py
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
-import djsettings
+import django_settings
-registry = djsettings.moduleregistry.new_registry(__name__)
+registry = django_settings.moduleregistry.new_registry(__name__)
View
6 tests/moduleregistry_testapp/settingsmodels.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
from django.db import models
-import djsettings
+import django_settings
-class String(djsettings.Model):
+class MyString(django_settings.db.Model):
value = models.CharField(max_length=512)
class Meta:
@@ -12,5 +12,5 @@ class Meta:
def register():
from .models import registry
- registry.register(String)
+ registry.register(MyString)
View
14 tests/test_api.py
@@ -3,22 +3,22 @@
from . import DBTestCase, n
# tested app
-import djsettings
+import django_settings
# test app imports
from moduleregistry_testapp import settingsmodels
class APITest(DBTestCase):
def before_database_setup(self):
- djsettings.register(settingsmodels.String)
+ django_settings.register(settingsmodels.MyString)
def teardown(self):
- djsettings.unregister_all()
+ django_settings.unregister_all()
def test_should_set_properly(self):
- n.assert_true(hasattr(djsettings.models, 'String'))
+ n.assert_true(hasattr(django_settings.models, 'MyString'))
- assert not djsettings.exists('admin_email')
- djsettings.set('String', 'admin_email', 'admin@admin.com')
- assert djsettings.get('admin_email') == 'admin@admin.com'
+ assert not django_settings.exists('admin_email')
+ django_settings.set('MyString', 'admin_email', 'admin@admin.com')
+ assert django_settings.get('admin_email') == 'admin@admin.com'
View
14 tests/test_data_cache.py
@@ -3,10 +3,7 @@
from . import DBTestCase, n
# tested app
-import djsettings
-
-# test app imports
-from moduleregistry_testapp import settingsmodels, models
+import django_settings
def assure_db_queries(function, num):
@@ -25,13 +22,14 @@ def assure_db_queries(function, num):
class DataCachingTest(DBTestCase):
def before_database_setup(self):
- settingsmodels.register()
+ from moduleregistry_testapp import settingsmodels
def teardown(self):
+ from moduleregistry_testapp import models
models.registry.unregister_all()
def test_contenttypes_names_should_be_cached(self):
- djsettings.data.clear_cache()
- # NOTE: it clears ALL the cache not only related to djsettings
+ django_settings.data.clear_cache()
+ # NOTE: it clears ALL the cache not only related to django_settings
- assure_db_queries(lambda: djsettings.data.contenttypes_names(), 0)
+ assure_db_queries(lambda: django_settings.data.contenttypes_names(), 0)
View
37 tests/test_modelregister.py
@@ -2,50 +2,33 @@
# test stuff
from . import TestCase, n
-# tested app
-import djsettings
-
class ModelRegisterTest(TestCase):
def test_importing_special_module_should_add_all_registered_classes(self):
# before special module import
from moduleregistry_testapp import models
- n.assert_false(hasattr(models, 'String'))
- n.assert_not_in('String', models.registry)
+ n.assert_false(hasattr(models, 'MyString'))
+ n.assert_not_in('MyString', models.registry)
# import special module that triggers registering
from moduleregistry_testapp import settingsmodels
settingsmodels.register()
- n.assert_true(hasattr(models, 'String'))
- n.assert_in('String', models.registry)
+ n.assert_true(hasattr(models, 'MyString'))
+ n.assert_in('MyString', models.registry)
n.assert_not_equal(
- models.String.__module__,
- settingsmodels.String.__module__,
+ models.MyString.__module__,
+ settingsmodels.MyString.__module__,
)
# technicaly those models are not the same
- n.assert_not_equal(models.String, settingsmodels.String)
- n.assert_true(issubclass(models.String, settingsmodels.String))
+ n.assert_not_equal(models.MyString, settingsmodels.MyString)
+ n.assert_true(issubclass(models.MyString, settingsmodels.MyString))
# registered class became a part of the module
- n.assert_equal(models.String.__module__, models.__name__)
-
- models.registry.unregister_all()
- n.assert_false(hasattr(models, 'String'))
-
-
- def test_autoregister_helper_should_invoke_register_for_all_installed_apps(self):
- djsettings.autoregister('for_autoregistry_test')
-
- # after autoregistration "models" should contains Integer class
- # take a look at tests.moduleregistry_testapp.for_autoregistry_test
- from moduleregistry_testapp import models
-
- n.assert_true(hasattr(models, 'Integer'))
+ n.assert_equal(models.MyString.__module__, models.__name__)
- # check if removing works
models.registry.unregister_all()
- n.assert_false(hasattr(models, 'Integer'))
+ n.assert_false(hasattr(models, 'MyString'))

0 comments on commit 9245dbe

Please sign in to comment.