![](/img/trans.png)
[英]How to override Model defaults method of 3rd party installed app, django?
[英]Django app defaults?
我正在尋找一種方法來擁有易於使用、不易出錯且開銷很小的應用程序默認值和設置。
目前我組織如下:
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
)。
update-3(為什么我們需要這個)
DEBUG
為 True 時,您通常會想做一些不同的事情,但任何其他全局設置都可能有用:例如MYAPP_IDENTICON_DIR = os.path.join(settings.MEDIA_ROOT, 'identicons')
( https://en.wikipedia.org /wiki/Identicon )settings.py
文件中定義MYAPP_IDENTICON_DIR = 's3://myappbucket.example.com/identicons'
的用戶應獲得此值,而不是應用程序的默認值。import .. settings; settings.FOO
)的解決方案都優於需要新語法的解決方案(因為新語法會產生分歧,我們將獲得使用應用程序設置的新的獨特方式到應用程序)。(原帖提出了以下兩個關鍵問題,沒有說明上述假設。)
問題 #1:當為應用程序運行單元測試時,沒有站點,所以settings
不會有任何myapp.defaults
。
問題#2:如果myapp.defaults
需要使用 settings 中的任何內容(例如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
定義應用程序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 的默認值/設置現在分布在兩個文件中...
有沒有更簡單的方法來做到這一點?
更新 4:“只需使用 django 測試運行程序..”
您正在測試的應用程序依賴於 Django 框架 - 您無法避免需要先引導框架才能測試應用程序的事實。 引導過程的一部分是創建默認 settings.py 並進一步使用 django 提供的測試運行器來確保您的應用程序正在它們可能運行的環境中進行測試。
雖然這聽起來應該是真的,但實際上並沒有多大意義,例如,沒有默認settings.py
類的東西(至少對於可重用的應用程序而言)。 在談論集成測試時,使用 site/settings/apps/database(s)/cache(s)/resource-limits/etc 測試應用程序是有意義的。 它將在生產中遇到。 然而,對於單元測試,我們只想測試我們正在查看的代碼單元 - 盡可能少的外部依賴項(和設置)。 Django 測試運行器確實並且應該模擬框架的主要部分,因此不能說它在任何“真實”環境中運行。
雖然 Django 測試運行器很棒,但它無法處理的問題列表很長。 我們的兩大難題是 (i) 順序運行測試太慢以至於測試套件變得未使用(並行運行時 <5 分鍾,順序運行時將近一個小時),(ii) 有時您只需要在大型數據庫上運行測試(我們將昨晚的備份恢復到測試可以運行的測試數據庫 - 對於固定裝置來說太大了)。
制作nose、py.test、twill、Selenium 和任何模糊測試工具的人確實非常了解測試,因為這是他們唯一的關注點。 不能借鑒他們的集體經驗將是一種恥辱。
我不是第一個遇到這個問題的人,而且似乎沒有簡單或通用的解決方案。 這是兩個具有不同解決方案的項目:
更新,python-oidc-provider 方法:
python-oidc-provider 包( https://github.com/juanifioren/django-oidc-provider )有另一種創造性的方法來解決 app-settings/defaults 問題。 它使用屬性在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')
好:解決問題#2(在定義默認值時使用設置),解決問題#1(使用來自測試的默認設置)。
壞:訪問設置的不同語法( settings.get('FOO')
與普通settings.FOO
),myapp 無法為將在 myapp 之外使用的設置提供默認值(您from django.conf import settings
獲得的from django.conf import settings
將不包含來自 myapp 的任何默認值)。 外部代碼可以from myapp import settings
中獲取常規設置和 myapp 默認值,但是如果有多個應用程序想要執行此操作,則此操作會失敗...
Update2,django-appconf包:(注:與Django的AppConfig無關..)
使用django-appconfig
,在myapp/conf.py
中創建應用程序設置(需要提前加載,因此您可能應該從 models.py 導入 conf.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
是否在項目設置中被重新定義/覆蓋。
pro:使用簡單,解決了問題#1(從測試訪問應用程序設置)和問題#2(在定義默認值時使用設置)。 只要提前加載了 conf.py 文件,外部代碼就應該能夠使用 myapp 中定義的默認值。
騙局:非常神奇。 conf.py 中設置的名稱與其用法不同(因為 appconf 會自動添加MYAPP_
前綴)。 外部/不透明依賴。
我剛剛根據所有要求創建了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])
我已經編寫了一個用於管理app設置的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
存在。
問題#1:當運行應用程序的單元測試時,沒有站點,因此設置將沒有任何myapp.defaults。
通過使用django附帶的測試框架(請參閱文檔 )解決了這個問題,因為它正確地為您引導測試環境。
請記住,django測試始終以DEBUG=False
運行
問題2:如果myapp.defaults需要使用設置中的任何內容(例如settings.DEBUG),也存在一個大問題,因為你無法從defaults.py導入設置(因為那將是循環導入)。
這不是一個真正的問題; 一旦從myapp.defaults
中的myapp.settings
導入, settings
所有內容都將在范圍內。 所以你不要導入DEBUG
,它已經可以在全局范圍內使用了。
當我構建應用程序時,我嘗試以 mixin 的形式定義功能。 如果可能,每個設置都應該由一個功能 mixin 選擇。
所以在你上面的例子中:
from django.conf import settings
class AppHomeRootMixin:
home_root = getattr(settings, "MYAPP_HOME_ROOT", "/default/path/here")
用法:
class MyView(AppHomeRootMixin, TemplateView):
def dispatch(self, *args, **kwargs):
print(self.home_root)
return super().dispatch(*args, **kwargs)
這對於其他開發人員來說很容易准確地看到正在發生的事情,減輕了對第三方或第一方“魔法”的需求,並允許我們根據功能而不是個人設置來考慮事情。
我只是覺得Django的設置層應該盡量簡單,任何復雜的都應該由視圖層負責。 我遇到了很多由其他開發人員創建的令人困惑的設置配置,這些配置消耗了我很多時間,而沒有人在那里解釋幕后發生的事情。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.