Source code for shuup.apps

# -*- coding: utf-8 -*-
# This file is part of Shuup.
#
# Copyright (c) 2012-2021, Shuup Commerce Inc. All rights reserved.
#
# This source code is licensed under the OSL-3.0 license found in the
# LICENSE file in the root directory of this source tree.
"""
Shuup Application API
=====================

Every Shuup Application should define an app config class derived from
`shuup.apps.AppConfig`:class:.

.. _apps-settings:

Settings
--------

To define settings for a Shuup Application, add a ``settings.py`` file
to your app and define each setting as a module level variable with
uppercase name.  The values of these setting variables will be used as
the default values for the settings.  To document a setting, add a
special comment block using '#: ' prefixed lines just before the
setting assignment line.

Default values can then be changed normally by defining the changed
value in your Django settings module.  To read a value of a setting use
the `django.conf.settings` interface.

For example, if a fancy app lives in a Python package named `fancyapp`,
its settings will be in module `fancyapp.settings` and if it contains
something like this

.. code-block:: python

   #: Number of donuts to use
   #:
   #: Must be less than 42.
   FANCYAPP_NUMBER_OF_DONUTS = 3

then this would define a setting `FANCYAPP_NUMBER_OF_DONUTS` with a
default value of 3.

See also source code of :obj:`shuup.core.settings`.

.. _apps-naming-settings:

Naming Settings
^^^^^^^^^^^^^^^

Applications in :term:`Shuup Base` distribution should use the following
rules for naming their settings.

 1. Each setting should be prefixed with the string `SHUUP_`
 2. Boolean toggle settings should have a verb in imperative mood as
    part of the name, e.g. `SHUUP_ALLOW_ANONYMOUS_ORDERS`,
    `SHUUP_ENABLE_ATTRIBUTES` or `SHUUP_ENABLE_MULTIPLE_SHOPS`.
 3. Setting that is used to locate a replaceable module should have
    suffix `_SPEC` or `_SPECS` (if the setting is a list or mapping of
    those), e.g. `SHUUP_PRICING_MODULE_SPEC`.
 4. Setting names do NOT have to be prefixed with the application name.
    For example, `SHUUP_BASKET_VIEW_SPEC` which is not prefixed with
    `SHUUP_FRONT` even though it is from `shuup.front` application.
 5. Setting names should be unique; if two applications define a setting
    with a same name, they cannot be enabled in the same installation.

.. _apps-settings-override-warning:

.. warning::

   When you have a settings file :file:`your_app/settings.py`, do not
   import Django's settings in :file:`your_app/__init__.py` with

   .. code-block:: python

      from django.conf import settings

   since that will make ``your_app.settings`` ambiguous. It may point to
   :obj:`django.conf.settings` when ``your_app.settings`` is not yet
   imported, or when it is imported, it will point to module defined by
   :file:`your_app/settings.py`.
"""
from __future__ import unicode_literals

import importlib

import django.apps
import django.conf
from django.core.exceptions import ImproperlyConfigured

from .settings import collect_settings_from_app, get_known_settings

__all__ = ["AppConfig", "get_known_settings"]


[docs]class AppConfig(django.apps.AppConfig): #: Name of the settings module for this app default_settings_module = ".settings" #: Apps that are required to be in INSTALLED_APPS for this app #: #: This may also be a dict of the form {app_name: reason} #: where the reason will then be listed in the `ImproperlyConfigured` #: exception. required_installed_apps = () #: See :doc:`/provides` for details about the ``provides`` variable. provides = {} def __init__(self, *args, **kwargs): super(AppConfig, self).__init__(*args, **kwargs) collect_settings_from_app(self) self._check_required_installed_apps()
[docs] def get_default_settings_module(self): """ Get default settings module. :return: the settings module. :raises: ImportError if no such module exists. """ mod_name = self.default_settings_module pkg_name = self.module.__name__ return importlib.import_module(mod_name, package=pkg_name)
def _get_app_require_reason(self, app_name): try: return self.required_installed_apps[app_name] except (TypeError, KeyError): return "required" def _check_required_installed_apps(self): required_apps = set(self.required_installed_apps) installed_apps = set(django.conf.settings.INSTALLED_APPS) missing_apps = required_apps - installed_apps if missing_apps: information = ", ".join( "%s (%s)" % (app_name, self._get_app_require_reason(app_name)) for app_name in sorted(missing_apps) ) raise ImproperlyConfigured( "Error! `%s` requires the following INSTALLED_APPS: `%s`" % (self.name, information) )