По умолчанию настройки Django?
Я ищу способ иметь настройки и настройки приложений, которые просты в использовании, трудно ошибиться и немного накладных расходов.
В настоящее время я организовал его следующим образом:
myapp/defaults.py
# application defaults
import sys
if sys.platform == 'win32':
MYAPP_HOME_ROOT = os.path.dirname(os.environ['USERPROFILE'])
else:
MYAPP_HOME_ROOT = '/home'
в моем проекте у меня есть:
mysite/settings.py
from myapp.defaults import * # import all default from myapp
MYAPP_HOME_ROOT = '/export/home' # overriding myapp.defaults
С помощью этой настройки я мог импортировать и использовать настройки в обычном режиме django (from django.conf import settings
и settings.XXX
from django.conf import settings
).
update-3 (зачем нам это нужно)
- Настройки по умолчанию ("по умолчанию"):
- Приложение более удобно использовать, если его можно настроить, переопределив набор разумных настроек по умолчанию.
- приложение "имеет знания домена", поэтому имеет смысл, если это возможно, предоставлять разумные значения по умолчанию.
- пользователю не обязательно предоставлять все настройки, необходимые для каждого приложения, этого должно быть достаточно, чтобы переопределить небольшое подмножество и оставить остальные значения по умолчанию.
- это очень полезно, если значения по умолчанию могут реагировать на окружающую среду. Вы часто захотите сделать что-то другое, когда
DEBUG
True, но могут быть полезны любые другие глобальные параметры: например MYAPP_IDENTICON_DIR = os.path.join(settings.MEDIA_ROOT, 'identicons')
(https://en.wikipedia.org/wiki/Identicon) - Project (site/global) должны переопределять параметры приложения по умолчанию, то есть пользователь, который определил
MYAPP_IDENTICON_DIR = 's3://myappbucket.example.com/identicons'
в (глобальном) файле settings.py
для своего сайта, должно получить это значение, а не по умолчанию. - любое решение, которое держится близко к обычным способам использования настроек (параметры
import.. settings; settings.FOO
) превосходит решение, которое нуждается в новом синтаксисе (поскольку новый синтаксис будет расходиться, и мы получим новые и уникальные способы использования настроек из приложения к приложению). - возможно, здесь используется zen python:
- Если внедрение трудно объяснить, это плохая идея.
- Если внедрение легко объяснить, это может быть хорошей идеей.
(Первоначальный пост поставил две ключевые проблемы ниже, оставив вышеприведенные предположения неустановленными..)
Проблема № 1: при выполнении модульных тестов для приложения нет сайта, поэтому settings
не будут иметь никаких из myapp.defaults
.
Проблема №2: Существует также большая проблема, если myapp.defaults
необходимо использовать что-либо из настроек (например, settings.DEBUG
), так как вы не можете импортировать настройки из defaults.py
(так как это будет циклический импорт).
Чтобы решить проблему №1, я создал слой косвенности:
myapp/conf.py
from . import defaults
from django.conf import settings
class Conf(object):
def __getattr__(self, attr):
try:
return getattr(settings, attr)
except AttributeError:
return getattr(defaults, attr)
conf = Conf() # !<-- create Conf instance
и использование:
myapp/views.py
from .conf import conf as settings
...
print settings.MYAPP_HOME_ROOT # will print '/export/home' when used from mysite
Это позволяет файлу conf.py
работать с "пустым" файлом настроек тоже, и код myapp может продолжать использовать привычные settings.XXX
.
Он не решает проблему №2, определяя параметры приложения на основе, например, settings.DEBUG
. Моим текущим решением является добавление в класс Conf
:
from . import defaults
from django.conf import settings
class Conf(object):
def __getattr__(self, attr):
try:
return getattr(settings, attr)
except AttributeError:
return getattr(defaults, attr)
if settings.DEBUG:
MYAPP_HOME_ROOT = '/Users'
else:
MYAPP_HOME_ROOT = '/data/media'
conf = Conf() # !<-- create Conf instance
но это не удовлетворяет, так как mysite больше не может переопределять параметр, а настройки/настройки myapp теперь распространяются на два файла...
Есть ли более простой способ сделать это?
update-4: "просто используйте django test runner.."
Приложение, которое вы тестируете, опирается на структуру Django, и вы не можете обойти тот факт, что вам нужно сначала загрузить фреймворк, прежде чем вы сможете протестировать приложение. Часть процесса начальной загрузки создает настройки по умолчанию.py и далее с помощью тестовых лент, поставляемых с помощью django, чтобы убедиться, что ваши приложения тестируются в среде, в которой они, вероятно, будут запущены.
Хотя это звучит так, как будто это должно быть правдой, на самом деле это не имеет большого смысла, например, нет такой settings.py
, как settings.py
(по крайней мере, не для многоразового приложения). Говоря об интеграционном тестировании, имеет смысл тестировать приложение с сайтом/настройками/приложениями/базами данных/кеш (ы)/ограничениями ресурсов/и т.д. что он встретится в производстве. Тем не менее, для модульного тестирования мы хотим протестировать только те единицы кода, которые мы рассматриваем, с максимально возможными внешними зависимостями (и настройками). Тестер тестирования Django делает и должен издеваться над основными частями фреймворка, поэтому нельзя сказать, что он работает в любой "реальной" среде.
Хотя тестовый бегун Django отлично работает, существует длинный список проблем, с которыми он не справляется. Для нас две большие проблемы: (i) выполнение тестов последовательно настолько медленное, что набор тестов становится неиспользуемым (<5 минут при параллельном запуске, почти час при последовательном запуске); (ii) иногда вам просто нужно запускать тесты в больших базах данных (мы восстанавливаем резервную копию прошлой ночи в тестовую базу данных, с которой могут протестировать тесты - слишком большие для светильников).
Люди, которые делали нос, py.test, twill, Selenium и любой из инструментов тестирования fuzz, действительно хорошо знают тестирование, так как это их единственное внимание. Было бы стыдно не быть в состоянии опираться на их коллективный опыт.
Я не первый, кто столкнулся с этой проблемой, и не похоже, что есть простое решение. Вот два проекта, которые имеют различное решение:
Обновление, метод python-oidc-provider:
Пакет python-oidc-provider (https://github.com/juanifioren/django-oidc-provider) имеет еще один творческий способ решения проблемы с настройками приложения/по умолчанию. Он использует свойства для определения значений по умолчанию в файле myapp/settings.py
:
from django.conf import settings
class DefaultSettings(object):
@property
def MYAPP_HOME_ROOT(self):
return ...
default_settings = DefaultSettings()
def get(name):
value = None
try:
value = getattr(default_settings, name)
value = getattr(settings, name)
except AttributeError:
if value is None:
raise Exception("Missing setting: " + name)
используя настройку внутри myapp становится:
from myapp import settings
print settings.get('MYAPP_HOME_ROOT')
good: решает проблему №2 (используя настройки при определении значений по умолчанию), решает проблему № 1 (используя настройки по умолчанию из тестов).
bad: различный синтаксис для доступа к настройкам (settings.get('FOO')
сравнению с обычными settings.FOO
), myapp не может предоставить значения по умолчанию для параметров, которые будут использоваться вне myapp (параметры, которые вы получаете from django.conf import settings
будут не содержат значений по умолчанию из myapp). Внешний код может работать from myapp import settings
чтобы получать обычные настройки и значения myapp по умолчанию, но это ломается, если более чем одно приложение хочет это сделать...
Update2, пакет django-appconf: (Примечание: не относится к Django AppConfig..)
С помощью django-appconfig
параметры приложения создаются в myapp/conf.py
(их нужно загружать раньше, поэтому вам, вероятно, следует импортировать файл conf.py с models.py - поскольку он загружен раньше):
from django.conf import settings
from appconf import AppConf
class MyAppConf(AppConf):
HOME_ROOT = '...'
Применение:
from myapp.conf import settings
print settings.MYAPP_HOME_ROOT
AppConf автоматически добавит префикс MYAPP_
, а также MYAPP_HOME_ROOT
обнаружит, было ли переопределено/переопределено MYAPP_HOME_ROOT
в настройках проекта.
pro: простой в использовании, решает проблему № 1 (доступ к настройкам приложения из тестов) и проблема №2 (с использованием настроек при определении значений по умолчанию). Пока файл conf.py загружается раньше, внешний код должен иметь возможность использовать значения по умолчанию, определенные в myapp.
con: значительно магический. Имя параметра conf.py отличается от его использования (поскольку appconf автоматически добавляет префикс MYAPP_
). Extenal/opaque dependency.
Ответы
Ответ 1
Я только что создал django-app-defaults, основанный на всех требованиях. Это в основном обобщение второго подхода, выделенного в вопросе (class Conf(object):
.
Применение:
# my_app/defaults.py
# 'django.conf.settings' or any other module can be imported if needed
# required
DEFAULT_SETTINGS_MODULE = True
# define default settings below
MY_DEFAULT_SETTING = "yey"
Тогда в любом месте вашего проекта:
from app_defaults import settings
print(settings.MY_DEFAULT_SETTING)
# yey
# All 'django.conf.settings' are also available
print(settings.DEBUG)
# True
Чтобы загрузить настройки по умолчанию для одного приложения, а не всех приложений, просто выполните:
# Note: when apps or modules are explicitly passed,
# the 'DEFAULT_SETTINGS_MODULE' var is not required
from app_defaults import Settings
settings = Settings(apps=["my_app"])
# or
from my_app import defaults
settings = Settings(modules=[defaults])
Ответ 2
Я написал пакет django для управления настройками приложения под названием django-pluggableappsettings.
Это пакет, который позволяет вам определять разумные настройки по умолчанию для ваших настроек, а также добавлять дополнительные функции, такие как проверка типов или настраиваемые параметры. Он использует метаклассы, чтобы упростить определение настроек приложений. Конечно, это добавляет внешнюю зависимость к вашему проекту.
Изменить 1:
Пример использования может быть следующим:
Установите пакет с помощью pip:
pip install django-pluggableappsettings
Создайте свой класс AppSettings в любом из ваших файлов проекта. Например, в 'app_settings.py'.
app_settings.py
from django_pluggableappsettings import AppSettings, Setting, IntSetting
class MyAppSettings(AppSettings):
MY_SETTING = Setting('DEFAULT_VALUE')
# Checks for "MY_SETTING" in django.conf.settings and
# returns 'DEFAULT_VALUE' if it is not present
ANOTHER_SETTING = IntSetting(42, aliases=['OTHER_SETTING_NAME'])
# Checks for "ANOTHER_SETTING" or "OTHER_SETTING_NAME" in
# django.conf.settings and returns 42 if it is not present.
# also validates that the given value is of type int
Импортируйте MyAppSettings
в любой файл и получите доступ к его атрибутам
from mypackage.app_settings import MyAppSettings
MyAppSettings.MY_SETTING
MyAppSettings.ANOTHER_SETTING
Обратите внимание, что настройки только инициализируются при первом доступе, поэтому, если вы никогда не получаете доступ к настройке, его присутствие в django.conf.settings
никогда не проверяется.
Ответ 3
Проблема № 1: при выполнении модульных тестов для приложения нет сайта, поэтому настройки не будут иметь никаких из myapp.defaults.
Эта проблема решается с помощью платформы тестирования, которая поставляется с django (см. Документацию), поскольку она правильно загружает тестовую среду для вас.
Имейте в виду, что тесты django всегда работают с DEBUG=False
Проблема №2: Существует также большая проблема, если myapp.defaults необходимо использовать что-либо из настроек (например, settings.DEBUG), так как вы не можете импортировать настройки из defaults.py (так как это будет циклический импорт).
Это не проблема; после импорта из myapp.defaults
в myapp.settings
все в settings
будет в области видимости. Таким образом, вы не должны импортировать DEBUG
, он уже доступен вам как глобальный.