diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 1563c25043..4a5887b117 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -15,7 +15,8 @@ Extensive contribution guidelines are available in the repository at https://docs.djangoproject.com/en/dev/internals/contributing/ -**Warning: pull requests are ignored!** `File a ticket`__ to suggest changes. +**Warning: non-trivial pull requests (anything more than fixing a typo) without +Trac tickets will be closed!** `Please file a ticket`__ to suggest changes. __ https://code.djangoproject.com/newticket diff --git a/README.rst b/README.rst index ca2cd5e5a9..95029a6fce 100644 --- a/README.rst +++ b/README.rst @@ -2,8 +2,8 @@ Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Thanks for checking it out. All documentation is in the "docs" directory and online at -http://docs.djangoproject.com/en/dev/. If you're just getting started, here's -how we recommend you read the docs: +https://docs.djangoproject.com/en/stable/. If you're just getting started, +here's how we recommend you read the docs: * First, read docs/intro/install.txt for instructions on installing Django. @@ -19,11 +19,9 @@ how we recommend you read the docs: * See docs/README for instructions on building an HTML version of the docs. -Docs are updated rigorously. If you find any problems in the docs, or think they -should be clarified in any way, please take 30 seconds to fill out a ticket -here: - -http://code.djangoproject.com/newticket +Docs are updated rigorously. If you find any problems in the docs, or think +they should be clarified in any way, please take 30 seconds to fill out a +ticket here: https://code.djangoproject.com/newticket To get more help: @@ -31,11 +29,11 @@ To get more help: there. Read the archives at http://django-irc-logs.com/. * Join the django-users mailing list, or read the archives, at - http://groups.google.com/group/django-users. + https://groups.google.com/group/django-users. To contribute to Django: -* Check out http://www.djangoproject.com/community/ for information about +* Check out https://www.djangoproject.com/community/ for information about getting involved. To run Django's test suite: diff --git a/django/__init__.py b/django/__init__.py index 5b4034bfed..14c941601a 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -6,3 +6,16 @@ def get_version(*args, **kwargs): # Only import if it's actually called. from django.utils.version import get_version return get_version(*args, **kwargs) + + +def setup(): + """ + Configure the settings (this happens as a side effect of accessing the + first setting), configure logging and populate the app registry. + """ + from django.apps import apps + from django.conf import settings + from django.utils.log import configure_logging + + configure_logging(settings.LOGGING_CONFIG, settings.LOGGING) + apps.populate(settings.INSTALLED_APPS) diff --git a/django/apps/__init__.py b/django/apps/__init__.py new file mode 100644 index 0000000000..074a448660 --- /dev/null +++ b/django/apps/__init__.py @@ -0,0 +1,2 @@ +from .base import AppConfig # NOQA +from .registry import apps # NOQA diff --git a/django/apps/base.py b/django/apps/base.py new file mode 100644 index 0000000000..47f0a57711 --- /dev/null +++ b/django/apps/base.py @@ -0,0 +1,165 @@ +from importlib import import_module + +from django.core.exceptions import ImproperlyConfigured +from django.utils.module_loading import module_has_submodule +from django.utils._os import upath + + +MODELS_MODULE_NAME = 'models' + + +class AppConfig(object): + """ + Class representing a Django application and its configuration. + """ + + def __init__(self, app_name, app_module): + # Full Python path to the application eg. 'django.contrib.admin'. + self.name = app_name + + # Root module for the application eg. . + self.module = app_module + + # The following attributes could be defined at the class level in a + # subclass, hence the test-and-set pattern. + + # Last component of the Python path to the application eg. 'admin'. + # This value must be unique across a Django project. + if not hasattr(self, 'label'): + self.label = app_name.rpartition(".")[2] + + # Human-readable name for the application eg. "Admin". + if not hasattr(self, 'verbose_name'): + self.verbose_name = self.label.title() + + # Filesystem path to the application directory eg. + # u'/usr/lib/python2.7/dist-packages/django/contrib/admin'. May be + # None if the application isn't a bona fide package eg. if it's an + # egg. Otherwise it's a unicode on Python 2 and a str on Python 3. + if not hasattr(self, 'path'): + try: + self.path = upath(app_module.__path__[0]) + except AttributeError: + self.path = None + + # Module containing models eg. . Set by import_models(). + # None if the application doesn't have a models module. + self.models_module = None + + # Mapping of lower case model names to model classes. Initally set to + # None to prevent accidental access before import_models() runs. + self.models = None + + def __repr__(self): + return '<%s: %s>' % (self.__class__.__name__, self.label) + + @classmethod + def create(cls, entry): + """ + Factory that creates an app config from an entry in INSTALLED_APPS. + """ + try: + # If import_module succeeds, entry is a path to an app module. + # Otherwise, entry is a path to an app config class or an error. + module = import_module(entry) + + except ImportError: + # Avoid django.utils.module_loading.import_by_path because it + # masks errors -- it reraises ImportError as ImproperlyConfigured. + mod_path, _, cls_name = entry.rpartition('.') + + # Raise the original exception when entry cannot be a path to an + # app config class. + if not mod_path: + raise + + mod = import_module(mod_path) + try: + cls = getattr(mod, cls_name) + except AttributeError: + # Emulate the error that "from import " + # would raise when exists but not , with + # more context (Python just says "cannot import name ..."). + raise ImportError( + "cannot import name '%s' from '%s'" % (cls_name, mod_path)) + + # Check for obvious errors. (This check prevents duck typing, but + # it could be removed if it became a problem in practice.) + if not issubclass(cls, AppConfig): + raise ImproperlyConfigured( + "'%s' isn't a subclass of AppConfig." % entry) + + # Obtain app name here rather than in AppClass.__init__ to keep + # all error checking for entries in INSTALLED_APPS in one place. + try: + app_name = cls.name + except AttributeError: + raise ImproperlyConfigured( + "'%s' must supply a name attribute." % entry) + + # Ensure app_names points to a valid module. + app_module = import_module(app_name) + + # Entry is a path to an app config class. + return cls(app_name, app_module) + + else: + # Entry is a path to an app module. + return cls(entry, module) + + def get_model(self, model_name): + """ + Returns the model with the given case-insensitive model_name. + + Raises LookupError if no model exists with this name. + """ + if self.models is None: + raise LookupError( + "App '%s' doesn't have any models." % self.label) + try: + return self.models[model_name.lower()] + except KeyError: + raise LookupError( + "App '%s' doesn't have a '%s' model." % (self.label, model_name)) + + def get_models(self, include_auto_created=False, + include_deferred=False, include_swapped=False): + """ + Returns an iterable of models. + + By default, the following models aren't included: + + - auto-created models for many-to-many relations without + an explicit intermediate table, + - models created to satisfy deferred attribute queries, + - models that have been swapped out. + + Set the corresponding keyword argument to True to include such models. + Keyword arguments aren't documented; they're a private API. + """ + for model in self.models.values(): + if model._deferred and not include_deferred: + continue + if model._meta.auto_created and not include_auto_created: + continue + if model._meta.swapped and not include_swapped: + continue + yield model + + def import_models(self, all_models): + # Dictionary of models for this app, primarily maintained in the + # 'all_models' attribute of the Apps this AppConfig is attached to. + # Injected as a parameter because it gets populated when models are + # imported, which might happen before populate() imports models. + self.models = all_models + + if module_has_submodule(self.module, MODELS_MODULE_NAME): + models_module_name = '%s.%s' % (self.name, MODELS_MODULE_NAME) + self.models_module = import_module(models_module_name) + + def ready(self): + """ + Override this method in subclasses to run code when Django starts. + """ diff --git a/django/apps/registry.py b/django/apps/registry.py new file mode 100644 index 0000000000..a0cd5e0abf --- /dev/null +++ b/django/apps/registry.py @@ -0,0 +1,413 @@ +from collections import Counter, defaultdict, OrderedDict +import os +import sys +import warnings + +from django.core.exceptions import ImproperlyConfigured +from django.utils import lru_cache +from django.utils.module_loading import import_lock +from django.utils._os import upath + +from .base import AppConfig + + +class Apps(object): + """ + A registry that stores the configuration of installed applications. + + It also keeps track of models eg. to provide reverse-relations. + """ + + def __init__(self, installed_apps=()): + # installed_apps is set to None when creating the master registry + # because it cannot be populated at that point. Other registries must + # provide a list of installed apps and are populated immediately. + if installed_apps is None and hasattr(sys.modules[__name__], 'apps'): + raise RuntimeError("You must supply an installed_apps argument.") + + # Mapping of app labels => model names => model classes. Every time a + # model is imported, ModelBase.__new__ calls apps.register_model which + # creates an entry in all_models. All imported models are registered, + # regardless of whether they're defined in an installed application + # and whether the registry has been populated. Since it isn't possible + # to reimport a module safely (it could reexecute initialization code) + # all_models is never overridden or reset. + self.all_models = defaultdict(OrderedDict) + + # Mapping of labels to AppConfig instances for installed apps. + self.app_configs = OrderedDict() + + # Stack of app_configs. Used to store the current state in + # set_available_apps and set_installed_apps. + self.stored_app_configs = [] + + # Whether the registry is populated. + self.ready = False + + # Pending lookups for lazy relations. + self._pending_lookups = {} + + # Populate apps and models, unless it's the master registry. + if installed_apps is not None: + self.populate(installed_apps) + + def populate(self, installed_apps=None): + """ + Loads application configurations and models. + + This method imports each application module and then each model module. + + It is thread safe and idempotent, but not reentrant. + """ + if self.ready: + return + # Since populate() may be a side effect of imports, and since it will + # itself import modules, an ABBA deadlock between threads would be + # possible if we didn't take the import lock. See #18251. + with import_lock(): + if self.ready: + return + + # app_config should be pristine, otherwise the code below won't + # guarantee that the order matches the order in INSTALLED_APPS. + if self.app_configs: + raise RuntimeError("populate() isn't reentrant") + + # Load app configs and app modules. + for entry in installed_apps: + if isinstance(entry, AppConfig): + app_config = entry + else: + app_config = AppConfig.create(entry) + if app_config.label in self.app_configs: + raise ImproperlyConfigured( + "Application labels aren't unique, " + "duplicates: %s" % app_config.label) + + self.app_configs[app_config.label] = app_config + + # Check for duplicate app names. + counts = Counter( + app_config.name for app_config in self.app_configs.values()) + duplicates = [ + name for name, count in counts.most_common() if count > 1] + if duplicates: + raise ImproperlyConfigured( + "Application names aren't unique, " + "duplicates: %s" % ", ".join(duplicates)) + + # Load models. + for app_config in self.app_configs.values(): + all_models = self.all_models[app_config.label] + app_config.import_models(all_models) + + self.clear_cache() + self.ready = True + + for app_config in self.get_app_configs(): + app_config.ready() + + def check_ready(self): + """ + Raises an exception if the registry isn't ready. + """ + if not self.ready: + raise RuntimeError("App registry isn't ready yet.") + + def get_app_configs(self): + """ + Imports applications and returns an iterable of app configs. + """ + self.check_ready() + return self.app_configs.values() + + def get_app_config(self, app_label): + """ + Imports applications and returns an app config for the given label. + + Raises LookupError if no application exists with this label. + """ + self.check_ready() + try: + return self.app_configs[app_label] + except KeyError: + raise LookupError("No installed app with label '%s'." % app_label) + + # This method is performance-critical at least for Django's test suite. + @lru_cache.lru_cache(maxsize=None) + def get_models(self, app_mod=None, include_auto_created=False, + include_deferred=False, include_swapped=False): + """ + Returns a list of all installed models. + + By default, the following models aren't included: + + - auto-created models for many-to-many relations without + an explicit intermediate table, + - models created to satisfy deferred attribute queries, + - models that have been swapped out. + + Set the corresponding keyword argument to True to include such models. + """ + self.check_ready() + if app_mod: + warnings.warn( + "The app_mod argument of get_models is deprecated.", + PendingDeprecationWarning, stacklevel=2) + app_label = app_mod.__name__.split('.')[-2] + try: + return list(self.get_app_config(app_label).get_models( + include_auto_created, include_deferred, include_swapped)) + except LookupError: + return [] + + result = [] + for app_config in self.app_configs.values(): + result.extend(list(app_config.get_models( + include_auto_created, include_deferred, include_swapped))) + return result + + def get_model(self, app_label, model_name): + """ + Returns the model matching the given app_label and model_name. + + model_name is case-insensitive. + + Raises LookupError if no application exists with this label, or no + model exists with this name in the application. + """ + self.check_ready() + return self.get_app_config(app_label).get_model(model_name.lower()) + + def register_model(self, app_label, model): + # Since this method is called when models are imported, it cannot + # perform imports because of the risk of import loops. It mustn't + # call get_app_config(). + model_name = model._meta.model_name + app_models = self.all_models[app_label] + if model_name in app_models: + raise RuntimeError( + "Conflicting '%s' models in application '%s': %s and %s." % + (model_name, app_label, app_models[model_name], model)) + app_models[model_name] = model + self.clear_cache() + + def is_installed(self, app_name): + """ + Checks whether an application with this name exists in the registry. + + app_name is the full name of the app eg. 'django.contrib.admin'. + + It's safe to call this method at import time, even while the registry + is being populated. It returns False for apps that aren't loaded yet. + """ + app_config = self.app_configs.get(app_name.rpartition(".")[2]) + return app_config is not None and app_config.name == app_name + + def get_containing_app_config(self, object_name): + """ + Look for an app config containing a given object. + + object_name is the dotted Python path to the object. + + Returns the app config for the inner application in case of nesting. + Returns None if the object isn't in any registered app config. + + It's safe to call this method at import time, even while the registry + is being populated. + """ + candidates = [] + for app_config in self.app_configs.values(): + if object_name.startswith(app_config.name): + subpath = object_name[len(app_config.name):] + if subpath == '' or subpath[0] == '.': + candidates.append(app_config) + if candidates: + return sorted(candidates, key=lambda ac: -len(ac.name))[0] + + def get_registered_model(self, app_label, model_name): + """ + Similar to get_model(), but doesn't require that an app exists with + the given app_label. + + It's safe to call this method at import time, even while the registry + is being populated. + """ + model = self.all_models[app_label].get(model_name.lower()) + if model is None: + raise LookupError( + "Model '%s.%s' not registered." % (app_label, model_name)) + return model + + def set_available_apps(self, available): + """ + Restricts the set of installed apps used by get_app_config[s]. + + available must be an iterable of application names. + + set_available_apps() must be balanced with unset_available_apps(). + + Primarily used for performance optimization in TransactionTestCase. + + This method is safe is the sense that it doesn't trigger any imports. + """ + available = set(available) + installed = set(app_config.name for app_config in self.get_app_configs()) + if not available.issubset(installed): + raise ValueError("Available apps isn't a subset of installed " + "apps, extra apps: %s" % ", ".join(available - installed)) + + self.stored_app_configs.append(self.app_configs) + self.app_configs = OrderedDict( + (label, app_config) + for label, app_config in self.app_configs.items() + if app_config.name in available) + self.clear_cache() + + def unset_available_apps(self): + """ + Cancels a previous call to set_available_apps(). + """ + self.app_configs = self.stored_app_configs.pop() + self.clear_cache() + + def set_installed_apps(self, installed): + """ + Enables a different set of installed apps for get_app_config[s]. + + installed must be an iterable in the same format as INSTALLED_APPS. + + set_installed_apps() must be balanced with unset_installed_apps(), + even if it exits with an exception. + + Primarily used as a receiver of the setting_changed signal in tests. + + This method may trigger new imports, which may add new models to the + registry of all imported models. They will stay in the registry even + after unset_installed_apps(). Since it isn't possible to replay + imports safely (eg. that could lead to registering listeners twice), + models are registered when they're imported and never removed. + """ + self.check_ready() + self.stored_app_configs.append(self.app_configs) + self.app_configs = OrderedDict() + self.ready = False + self.clear_cache() + self.populate(installed) + + def unset_installed_apps(self): + """ + Cancels a previous call to set_installed_apps(). + """ + self.app_configs = self.stored_app_configs.pop() + self.ready = True + self.clear_cache() + + def clear_cache(self): + """ + Clears all internal caches, for methods that alter the app registry. + + This is mostly used in tests. + """ + self.get_models.cache_clear() + + ### DEPRECATED METHODS GO BELOW THIS LINE ### + + def load_app(self, app_name): + """ + Loads the app with the provided fully qualified name, and returns the + model module. + """ + warnings.warn( + "load_app(app_name) is deprecated.", + PendingDeprecationWarning, stacklevel=2) + app_config = AppConfig.create(app_name) + app_config.import_models(self.all_models[app_config.label]) + self.app_configs[app_config.label] = app_config + self.clear_cache() + return app_config.models_module + + def app_cache_ready(self): + warnings.warn( + "app_cache_ready() is deprecated in favor of the ready property.", + PendingDeprecationWarning, stacklevel=2) + return self.ready + + def get_app(self, app_label): + """ + Returns the module containing the models for the given app_label. + """ + warnings.warn( + "get_app_config(app_label).models_module supersedes get_app(app_label).", + PendingDeprecationWarning, stacklevel=2) + try: + models_module = self.get_app_config(app_label).models_module + except LookupError as exc: + # Change the exception type for backwards compatibility. + raise ImproperlyConfigured(*exc.args) + if models_module is None: + raise ImproperlyConfigured( + "App '%s' doesn't have a models module." % app_label) + return models_module + + def get_apps(self): + """ + Returns a list of all installed modules that contain models. + """ + warnings.warn( + "[a.models_module for a in get_app_configs()] supersedes get_apps().", + PendingDeprecationWarning, stacklevel=2) + app_configs = self.get_app_configs() + return [app_config.models_module for app_config in app_configs + if app_config.models_module is not None] + + def _get_app_package(self, app): + return '.'.join(app.__name__.split('.')[:-1]) + + def get_app_package(self, app_label): + warnings.warn( + "get_app_config(label).name supersedes get_app_package(label).", + PendingDeprecationWarning, stacklevel=2) + return self._get_app_package(self.get_app(app_label)) + + def _get_app_path(self, app): + if hasattr(app, '__path__'): # models/__init__.py package + app_path = app.__path__[0] + else: # models.py module + app_path = app.__file__ + return os.path.dirname(upath(app_path)) + + def get_app_path(self, app_label): + warnings.warn( + "get_app_config(label).path supersedes get_app_path(label).", + PendingDeprecationWarning, stacklevel=2) + return self._get_app_path(self.get_app(app_label)) + + def get_app_paths(self): + """ + Returns a list of paths to all installed apps. + + Useful for discovering files at conventional locations inside apps + (static files, templates, etc.) + """ + warnings.warn( + "[a.path for a in get_app_configs()] supersedes get_app_paths().", + PendingDeprecationWarning, stacklevel=2) + self.check_ready() + app_paths = [] + for app in self.get_apps(): + app_paths.append(self._get_app_path(app)) + return app_paths + + def register_models(self, app_label, *models): + """ + Register a set of models as belonging to an app. + """ + warnings.warn( + "register_models(app_label, *models) is deprecated.", + PendingDeprecationWarning, stacklevel=2) + for model in models: + self.register_model(app_label, model) + + +apps = Apps(installed_apps=None) diff --git a/django/conf/__init__.py b/django/conf/__init__.py index ac074d5166..e10d4b1afb 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -7,16 +7,12 @@ a list of all possible variables. """ import importlib -import logging import os -import sys import time # Needed for Windows -import warnings from django.conf import global_settings from django.core.exceptions import ImproperlyConfigured from django.utils.functional import LazyObject, empty -from django.utils.module_loading import import_by_path from django.utils import six ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" @@ -44,34 +40,12 @@ class LazySettings(LazyObject): % (desc, ENVIRONMENT_VARIABLE)) self._wrapped = Settings(settings_module) - self._configure_logging() def __getattr__(self, name): if self._wrapped is empty: self._setup(name) return getattr(self._wrapped, name) - def _configure_logging(self): - """ - Setup logging from LOGGING_CONFIG and LOGGING settings. - """ - if not sys.warnoptions: - # Route warnings through python logging - logging.captureWarnings(True) - # Allow DeprecationWarnings through the warnings filters - warnings.simplefilter("default", DeprecationWarning) - - if self.LOGGING_CONFIG: - from django.utils.log import DEFAULT_LOGGING - # First find the logging configuration function ... - logging_config_func = import_by_path(self.LOGGING_CONFIG) - - logging_config_func(DEFAULT_LOGGING) - - # ... then invoke it with the logging settings - if self.LOGGING: - logging_config_func(self.LOGGING) - def configure(self, default_settings=global_settings, **options): """ Called to manually configure the settings. The 'default_settings' @@ -84,7 +58,6 @@ class LazySettings(LazyObject): for name, value in options.items(): setattr(holder, name, value) self._wrapped = holder - self._configure_logging() @property def configured(self): @@ -104,11 +77,6 @@ class BaseSettings(object): elif name == "ALLOWED_INCLUDE_ROOTS" and isinstance(value, six.string_types): raise ValueError("The ALLOWED_INCLUDE_ROOTS setting must be set " "to a tuple, not a string.") - elif name == "INSTALLED_APPS": - value = list(value) # force evaluation of generators on Python 3 - if len(value) != len(set(value)): - raise ImproperlyConfigured("The INSTALLED_APPS setting must contain unique values.") - object.__setattr__(self, name, value) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index d29dd5ec34..d86a4272cd 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -290,7 +290,7 @@ MEDIA_URL = '' # Absolute path to the directory static files should be collected to. # Example: "/var/www/example.com/static/" -STATIC_ROOT = '' +STATIC_ROOT = None # URL that handles the static files served from STATIC_ROOT. # Example: "http://example.com/static/", "http://static.example.com/" diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py index d215aa6259..158c436c71 100644 --- a/django/contrib/admin/actions.py +++ b/django/contrib/admin/actions.py @@ -71,7 +71,6 @@ def delete_selected(modeladmin, request, queryset): "perms_lacking": perms_needed, "protected": protected, "opts": opts, - "app_label": app_label, 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, } diff --git a/django/contrib/admin/apps.py b/django/contrib/admin/apps.py new file mode 100644 index 0000000000..980ff0c369 --- /dev/null +++ b/django/contrib/admin/apps.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig + +from django.utils.translation import ugettext_lazy as _ + + +class AdminConfig(AppConfig): + name = 'django.contrib.admin' + verbose_name = _("administration") diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 4c5adaf0f8..263d1f4a87 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -46,9 +46,12 @@ from django.views.decorators.csrf import csrf_protect IS_POPUP_VAR = '_popup' TO_FIELD_VAR = '_to_field' + HORIZONTAL, VERTICAL = 1, 2 -# returns the
    class for a given radio_admin field -get_ul_class = lambda x: 'radiolist%s' % (' inline' if x == HORIZONTAL else '') + + +def get_ul_class(radio_style): + return 'radiolist' if radio_style == VERTICAL else 'radiolist inline' class IncorrectLookupParameters(Exception): @@ -1308,7 +1311,6 @@ class ModelAdmin(BaseModelAdmin): media=media, inline_admin_formsets=inline_admin_formsets, errors=helpers.AdminErrorList(form, formsets), - app_label=opts.app_label, preserved_filters=self.get_preserved_filters(request), ) context.update(extra_context or {}) @@ -1532,7 +1534,6 @@ class ModelAdmin(BaseModelAdmin): media=media, has_add_permission=self.has_add_permission(request), opts=cl.opts, - app_label=app_label, action_form=action_form, actions_on_top=self.actions_on_top, actions_on_bottom=self.actions_on_bottom, @@ -1627,7 +1628,6 @@ class ModelAdmin(BaseModelAdmin): action_list=action_list, module_name=capfirst(force_text(opts.verbose_name_plural)), object=obj, - app_label=app_label, opts=opts, preserved_filters=self.get_preserved_filters(request), ) diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index 7b633bef89..8b81f6c68e 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -6,6 +6,7 @@ from django.contrib.auth import logout as auth_logout, REDIRECT_FIELD_NAME from django.contrib.contenttypes import views as contenttype_views from django.views.decorators.csrf import csrf_protect from django.db.models.base import ModelBase +from django.apps import apps from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.core.urlresolvers import reverse, NoReverseMatch from django.template.response import TemplateResponse @@ -156,20 +157,16 @@ class AdminSite(object): """ Check that all things needed to run the admin have been correctly installed. - The default implementation checks that LogEntry, ContentType and the - auth context processor are installed. + The default implementation checks that admin and contenttypes apps are + installed, as well as the auth context processor. """ - from django.contrib.admin.models import LogEntry - from django.contrib.contenttypes.models import ContentType - - if not LogEntry._meta.installed: + if not apps.is_installed('django.contrib.admin'): raise ImproperlyConfigured("Put 'django.contrib.admin' in your " "INSTALLED_APPS setting in order to use the admin application.") - if not ContentType._meta.installed: + if not apps.is_installed('django.contrib.contenttypes'): raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in " "your INSTALLED_APPS setting in order to use the admin application.") - if not ('django.contrib.auth.context_processors.auth' in settings.TEMPLATE_CONTEXT_PROCESSORS or - 'django.core.context_processors.auth' in settings.TEMPLATE_CONTEXT_PROCESSORS): + if 'django.contrib.auth.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS: raise ImproperlyConfigured("Put 'django.contrib.auth.context_processors.auth' " "in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.") @@ -383,7 +380,7 @@ class AdminSite(object): app_dict[app_label]['models'].append(model_dict) else: app_dict[app_label] = { - 'name': app_label.title(), + 'name': apps.get_app_config(app_label).verbose_name, 'app_label': app_label, 'app_url': reverse('admin:app_list', kwargs={'app_label': app_label}, current_app=self.name), 'has_module_perms': has_module_perms, @@ -392,7 +389,7 @@ class AdminSite(object): # Sort the apps alphabetically. app_list = list(six.itervalues(app_dict)) - app_list.sort(key=lambda x: x['name']) + app_list.sort(key=lambda x: x['name'].lower()) # Sort the models alphabetically within each app. for app in app_list: @@ -410,6 +407,7 @@ class AdminSite(object): def app_index(self, request, app_label, extra_context=None): user = request.user + app_name = apps.get_app_config(app_label).verbose_name has_module_perms = user.has_module_perms(app_label) if not has_module_perms: raise PermissionDenied @@ -444,7 +442,7 @@ class AdminSite(object): # something to display, add in the necessary meta # information. app_dict = { - 'name': app_label.title(), + 'name': app_name, 'app_label': app_label, 'app_url': '', 'has_module_perms': has_module_perms, @@ -455,7 +453,7 @@ class AdminSite(object): # Sort the models alphabetically within each app. app_dict['models'].sort(key=lambda x: x['name']) context = dict(self.each_context(), - title=_('%s administration') % capfirst(app_label), + title=_('%(app)s administration') % {'app': app_name}, app_list=[app_dict], app_label=app_label, ) diff --git a/django/contrib/admin/templates/admin/auth/user/change_password.html b/django/contrib/admin/templates/admin/auth/user/change_password.html index 2a1b4d3c90..f37f35c6a3 100644 --- a/django/contrib/admin/templates/admin/auth/user/change_password.html +++ b/django/contrib/admin/templates/admin/auth/user/change_password.html @@ -6,12 +6,12 @@ {% endblock %} {% block extrastyle %}{{ block.super }}{% endblock %} -{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %} +{% block bodyclass %}{{ opts.app_label }}-{{ opts.model_name }} change-form{% endblock %} {% if not is_popup %} {% block breadcrumbs %}