Source code for django_autoconfig.autoconfig

'''Automatic configuration for Django project.'''

import collections
import copy
from django.core.exceptions import ImproperlyConfigured
from django.conf import global_settings
from django.conf.urls import include, url
try:
    from django.conf.urls import patterns
except ImportError:
    def patterns(_, *args):
        return list(args)
from django.utils.functional import Promise
from django.utils.module_loading import module_has_submodule
import imp
import importlib
import operator

import logging
LOGGER = logging.getLogger(__name__)

MAX_ITERATIONS = 1000

[docs]class OrderingRelationship(object): ''' This class defines a relationship between an element in a setting that's a list and one or more other entries. It's intended to be used in an autoconfig.py file like so:: RELATIONSHIPS = [ OrderingRelationship( 'INSTALLED_APPS', 'my.app', before = [ 'django.contrib.admin', ], after = [ ], ) ] ''' def __init__( self, setting_name, setting_value, before=None, after=None, add_missing=True ): self.setting_name = setting_name self.setting_value = setting_value self.before = before or [] self.after = after or [] self.add_missing = add_missing def apply_changes(self, settings): changes = 0 if self.add_missing: for item in [self.setting_value] + self.before + self.after: if item not in settings[self.setting_name]: settings[self.setting_name] = list(settings[self.setting_name]) + [item] LOGGER.debug("Added %r to %r.", item, self.setting_name) changes += 1 elif self.setting_value not in settings[self.setting_name]: return changes for test, related_items, list_name in ( (operator.gt, self.before, 'before'), (operator.lt, self.after, 'after'), ): current_value = settings[self.setting_name] for item in related_items: if item not in current_value: continue if test( current_value.index(self.setting_value), current_value.index(item), ): if isinstance(current_value, tuple): current_value = list(current_value) location = current_value.index(item) current_value.remove(self.setting_value) current_value.insert(location, self.setting_value) settings[self.setting_name] = current_value LOGGER.debug("Moved %r %r %r.", self.setting_value, list_name, item) changes += 1 return changes
def merge_dictionaries(current, new, only_defaults=False, template_special_case=False): ''' Merge two settings dictionaries, recording how many changes were needed. ''' changes = 0 for key, value in new.items(): if key not in current: if hasattr(global_settings, key): current[key] = getattr(global_settings, key) LOGGER.debug("Set %r to global default %r.", key, current[key]) else: current[key] = copy.copy(value) LOGGER.debug("Set %r to %r.", key, current[key]) changes += 1 continue elif only_defaults: continue current_value = current[key] if hasattr(current_value, 'items'): changes += merge_dictionaries(current_value, value) elif isinstance(current_value, (list, tuple)): for element in value: if element not in current_value: if template_special_case and key == 'TEMPLATES': existing_matches = [ template for template in current_value if template['BACKEND'] == element['BACKEND'] ] if existing_matches: changes += merge_dictionaries(existing_matches[0], element) else: current[key] = list(current_value) + [element] LOGGER.debug("Added %r to %r.", element, key) changes += 1 else: current[key] = list(current_value) + [element] LOGGER.debug("Added %r to %r.", element, key) changes += 1 elif isinstance(current_value, Promise) or isinstance(value, Promise): # If we don't know what to do with it, replace it. if current_value is not value: current[key] = value LOGGER.debug("Set %r to %r.", key, current[key]) changes += 1 else: # If we don't know what to do with it, replace it. if current_value != value: current[key] = value LOGGER.debug("Set %r to %r.", key, current[key]) changes += 1 return changes def autoconfig_module_exists(app_name): names = app_name.split('.') path = None for name in names: _, path, _ = imp.find_module(name, path) path = [path] try: imp.find_module('autoconfig', path) return True except ImportError: return False def configure_settings(settings, environment_settings=True): ''' Given a settings object, run automatic configuration of all the apps in INSTALLED_APPS. ''' changes = 1 iterations = 0 while changes: changes = 0 app_names = ['django_autoconfig'] + list(settings['INSTALLED_APPS']) if environment_settings: app_names.append('django_autoconfig.environment_settings') for app_name in app_names: import django_autoconfig.contrib if autoconfig_module_exists(app_name): module = importlib.import_module("%s.autoconfig" % (app_name,)) elif app_name in django_autoconfig.contrib.CONTRIB_CONFIGS: module = django_autoconfig.contrib.CONTRIB_CONFIGS[app_name] else: continue changes += merge_dictionaries( settings, getattr(module, 'SETTINGS', {}), template_special_case=True, ) changes += merge_dictionaries( settings, getattr(module, 'DEFAULT_SETTINGS', {}), only_defaults=True, ) for relationship in getattr(module, 'RELATIONSHIPS', []): changes += relationship.apply_changes(settings) if iterations >= MAX_ITERATIONS: raise ImproperlyConfigured( 'Autoconfiguration could not reach a consistent state' ) iterations += 1 LOGGER.debug("Autoconfiguration took %d iterations.", iterations) def configure_urls(apps, index_view=None, prefixes=None): ''' Configure urls from a list of apps. ''' prefixes = prefixes or {} urlpatterns = patterns('') if index_view: from django.views.generic.base import RedirectView urlpatterns += patterns('', url(r'^$', RedirectView.as_view(pattern_name=index_view, permanent=False)), ) for app_name in apps: app_module = importlib.import_module(app_name) if module_has_submodule(app_module, 'urls'): module = importlib.import_module("%s.urls" % app_name) if not hasattr(module, 'urlpatterns'): # Resolver will break if the urls.py file is completely blank. continue app_prefix = prefixes.get(app_name, app_name.replace("_","-")) urlpatterns += patterns( '', url( r'^%s/' % app_prefix if app_prefix else '', include("%s.urls" % app_name), ), ) return urlpatterns