django---admin模組原始碼解析
django
有一套強大的admin
後臺資料庫管理工具,通過url(r'^admin/', admin.site.urls)
完成對已註冊model
的增刪改成,註冊方法是admin.site.register(Publish)
我們建立一個app
,然後建立一個model
物件,然後遷移資料庫
class Publish(models.Model):
title = models.CharField(max_length=32)
def __str__(self):
return self.title
在admin
後臺可以通過如下的url
分發,實現資料庫表的增刪改查
http://127.0.0.1:8000/admin/app01/publish/
http://127.0.0.1:8000/admin/app01/publish/1/change/
http://127.0.0.1:8000/admin/app01/publish/1/delete/
http://127.0.0.1:8000/admin/app01/publish/add/
在url.py
中我們通過url(r'^admin/', admin.site.urls)
一條配置,竟然可以實現對一個model
模型類對應的資料庫表,進行增刪改查操作,那麼django到底是如何,實現一條url配置實現眾多次分發的,今天我們就瞭解下admin
原始碼
我們在urlpatterns
配置列表處經常看到如下兩行模組匯入程式碼
from django.conf.urls import url
from django.contrib import admin
url(r'^admin/', admin.site.urls),
是呼叫了urls
模組下的url
方法
我們先從admin.site.urls
引數入手,看看這個引數做了什麼操作處理
admin
是隸屬於from django.contrib import admin
該模組,我們先看下admin
模組程式碼
from django.contrib.admin.decorators import register
from django.contrib.admin.filters import (
AllValuesFieldListFilter, BooleanFieldListFilter, ChoicesFieldListFilter,
DateFieldListFilter, FieldListFilter, ListFilter, RelatedFieldListFilter,
RelatedOnlyFieldListFilter, SimpleListFilter,
)
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
from django.contrib.admin.options import (
HORIZONTAL, VERTICAL, ModelAdmin, StackedInline, TabularInline,
)
from django.contrib.admin.sites import AdminSite, site
from django.utils.module_loading import autodiscover_modules
__all__ = [
"register", "ACTION_CHECKBOX_NAME", "ModelAdmin", "HORIZONTAL", "VERTICAL",
"StackedInline", "TabularInline", "AdminSite", "site", "ListFilter",
"SimpleListFilter", "FieldListFilter", "BooleanFieldListFilter",
"RelatedFieldListFilter", "ChoicesFieldListFilter", "DateFieldListFilter",
"AllValuesFieldListFilter", "RelatedOnlyFieldListFilter", "autodiscover",
]
def autodiscover():
autodiscover_modules('admin', register_to=site)
default_app_config = 'django.contrib.admin.apps.AdminConfig'
這個模組__init__.py
中只有autodiscover
方法,和default_app_config
變數,另外還有頭部引入的其他的模組,我們從程式碼上就能猜到autodiscover
意思大概就是自動掃描,也就是載入、匯入的意思,那麼到底是哪裡執行了autodiscover
方法呢?
django呼叫python manage.py runserver啟動專案時候,利用CommandParser構造並且解析資料,然後執行django.setup方法,呼叫django.apps模組來讀取專案檔案中的settings.py拿到這面這幾個app,然後交給django.apps的registry.py和config.py來進行統一的配置載入,然後最後self.fetch_command(subcommand).run_from_argv(self.argv)載入
那我們就從那裡入手去找找程式碼,看看能不能發現思路線索,結果在如下的程式碼中
from django.apps import apps
類下的populate
方法中找到了答案
for app_config in self.get_app_configs():
app_config.ready()
找到對應的AdminConfig
,在from django.contrib.admin.apps import AdminConfig
下
class AdminConfig(SimpleAdminConfig):
"""The default AppConfig for admin which does autodiscovery."""
def ready(self):
super(AdminConfig, self).ready()
self.module.autodiscover()
那麼我就接著上篇的部落格,來繼續跟程式碼,去研究admin
程式碼是怎麼樣的一個執行流程吧
那我們就把目光回到from django.contrib import admin
模組,看如下的掃描程式碼
def autodiscover():
autodiscover_modules('admin', register_to=site)
繼續看autodiscover_modules
方法具體程式碼,貼出如下
def autodiscover_modules(*args, **kwargs):
from django.apps import apps
register_to = kwargs.get('register_to')
for app_config in apps.get_app_configs():
for module_to_search in args:
# Attempt to import the app's module.
try:
if register_to:
before_import_registry = copy.copy(register_to._registry)
import_module('%s.%s' % (app_config.name, module_to_search))
except Exception:
if register_to:
register_to._registry = before_import_registry
if module_has_submodule(app_config.module, module_to_search):
raise
在上篇部落格我們提到過最後封裝了一個結果集(大概類似如下封裝)
app_configs odict_values([<AdminConfig: admin>, <AuthConfig: auth>, <ContentTypesConfig: contenttypes>, <SessionsConfig: sessions>, <MessagesConfig: messages>, <StaticFilesConfig: staticfiles>, <App01Config: app01>, <RbacConfig: rbac>])
迴圈遍歷結果集,在autodiscover_modules
方法中傳遞的2個引數'admin', register_to=site
,通過引數for module_to_search in args:
和if register_to
為True的使用,最後執行如下程式碼,從而匯入系列模組
import_module('%s.%s' % (app_config.name, module_to_search))
匯入的格式如下,這裡我列出格式如下:
app_config.name---- django.contrib.admin admin
app_config.name---- django.contrib.auth admin
app_config.name---- django.contrib.contenttypes admin
app_config.name---- django.contrib.sessions admin
app_config.name---- django.contrib.messages admin
app_config.name---- django.contrib.staticfiles admin
app_config.name---- app01 admin
app_config.name---- app02 admin
app_config.name---- _stark admin
我們回過頭來看最開始的配置程式碼url(r'^admin/', admin.site.urls),
以上分析的只是admin模組autodiscover
的方法,我們繼續看下from django.contrib.admin import site
做了哪些操作吧,該模組程式碼如下:
all_sites = WeakSet()
class AlreadyRegistered(Exception):
pass
class NotRegistered(Exception):
pass
class AdminSite(object):
省略n行程式碼
。。。。。。
site = AdminSite()
site = AdminSite()
是獲取一個AdminSite類物件,然後通過該物件去執行admin.site.urls
方法,我們看看這個物件初始化做了什麼,以及有哪些關鍵方法做了什麼?
def __init__(self, name='admin'):
self._registry = {} # model_class class -> admin_class instance
self.name = name
self._actions = {'delete_selected': actions.delete_selected}
self._global_actions = self._actions.copy()
all_sites.add(self)
初始化方法我們要留意下self._registry = {}
這個字典,後續我們在分析它
到此為止,我們還去看哪塊的程式碼呢?我們都知道當我們在admin
後臺,去操作一個model類對映的資料庫表時,我們都是需要在admin.py中進行如下配置
admin.site.register(Publish)
所以我們就去看看register
方法中做了哪些操作吧,我先將全部程式碼貼出如下:
def register(self, model_or_iterable, admin_class=None, **options):
if not admin_class:
admin_class = ModelAdmin
if isinstance(model_or_iterable, ModelBase):
model_or_iterable = [model_or_iterable]
for model in model_or_iterable:
if model._meta.abstract:
raise ImproperlyConfigured(
'The model %s is abstract, so it cannot be registered with admin.' % model.__name__
)
if model in self._registry:
raise AlreadyRegistered('The model %s is already registered' % model.__name__)
if not model._meta.swapped:
# If we got **options then dynamically construct a subclass of
# admin_class with those **options.
if options:
options['__module__'] = __name__
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
# Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self)
接下來我們逐行分析上述程式碼,我們先看如下程式碼
if not admin_class:
admin_class = ModelAdmin
這個ModelAdmin
是什麼東西呢?我們看如下程式碼,我們可以通過PermissionConf
繼承admin.ModelAdmin
為Permission
模型類,定製樣式,這樣在我們訪問admin
後臺時,會展現我們的樣式,每個模型類預設樣式就是ModelAdmin
class PermissionConf(admin.ModelAdmin):
list_display = ["title", "url", "permission_group", "code"]
ordering = ["id"]
admin.site.register(Permission,PermissionConf)
所以admin_class = ModelAdmin
程式碼意思就是,如果沒有為model
模型類定製樣式,那麼就使用預設的ModelAdmin
樣式,繼續執行如下程式碼
if isinstance(model_or_iterable, ModelBase):
model_or_iterable = [model_or_iterable]
在admin
模組中,是依次掃描,然後去執行每個admin.py
下的register
方法
最後執行self._registry[model] = admin_class(model, self)
_registry
就是AdminSite
初始化構造的一個空字典{}
,具體是什麼型別的字典呢?
這個字典的每個鍵值對中的鍵就是對應的model
物件模型,值就是admin_class(model, self)
值是呼叫了類ModelAdmin
,繼而構造物件構成model
物件模型對應的值
這裡我們看下封裝的資料結構吧:
<class 'django.contrib.auth.models.Group'> <django.contrib.admin.sites.AdminSite object at 0x06036DD0>
<class 'django.contrib.auth.models.User'> <django.contrib.admin.sites.AdminSite object at 0x06036DD0>
<class 'app01.models.Publish'> <django.contrib.admin.sites.AdminSite object at 0x06036DD0>
<class 'django.contrib.auth.models.Group'> <django.contrib.admin.sites.AdminSite object at 0x060C6DF0>
<class 'django.contrib.auth.models.User'> <django.contrib.admin.sites.AdminSite object at 0x060C6DF0>
<class 'app01.models.Publish'> <django.contrib.admin.sites.AdminSite object at 0x060C6DF0>
至此site = AdminSite()
物件的register
方法就分析完畢
我們接下里繼續看下如下程式碼,我再次貼出來
url(r'^admin/', admin.site.urls),
admin
模組、AdminSite
物件register
方法、均分析完畢,接下來就看看admin.site.urls
這個函式,property
特性是方法轉屬性的用法,最後返回一個<class 'tuple'>
元組型別
@property
def urls(self):
return self.get_urls(), 'admin', self.name
最後就看下方法get_urls()
,我將程式碼貼出如下:
def get_urls(self):
from django.conf.urls import url, include
from django.contrib.contenttypes import views as contenttype_views
def wrap(view, cacheable=False):
def wrapper(*args, **kwargs):
return self.admin_view(view, cacheable)(*args, **kwargs)
wrapper.admin_site = self
return update_wrapper(wrapper, view)
# Admin-site-wide views.
urlpatterns = [
url(r'^$', wrap(self.index), name='index'),
url(r'^login/$', self.login, name='login'),
url(r'^logout/$', wrap(self.logout), name='logout'),
url(r'^password_change/$', wrap(self.password_change, cacheable=True), name='password_change'),
url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True),
name='password_change_done'),
url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut),
name='view_on_site'),
]
valid_app_labels = []
for model, model_admin in self._registry.items():
urlpatterns += [
url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)),
]
if model._meta.app_label not in valid_app_labels:
valid_app_labels.append(model._meta.app_label)
if valid_app_labels:
regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$'
urlpatterns += [
url(regex, wrap(self.app_index), name='app_list'),
]
return urlpatterns
去迴圈遍歷for model, model_admin in self._registry.items():
獲取對應的鍵和值
如下是列印輸出,我只建立了一個Publish
模型類,另外預設還有Group
和User
<class 'django.contrib.auth.models.Group'> auth.GroupAdmin
<class 'django.contrib.auth.models.User'> auth.UserAdmin
<class 'app01.models.Publish'> app01.ModelAdmin
url(r'^admin/', admin.site.urls)
,分發url
,如果沒有在urlpatterns
中,那麼就拼接填入valid_app_labels.append(model._meta.app_label)
以下是get_urls
的結果集
[<RegexURLPattern index ^$>,
<RegexURLPattern login ^login/$>,
<RegexURLPattern logout ^logout/$>,
<RegexURLPattern password_change_done ^password_change/done/$>,
<RegexURLPattern view_on_site ^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$>, <RegexURLResolver <RegexURLPattern list> (None:None) ^auth/group/>, <RegexURLResolver <RegexURLPattern list> (None:None) ^auth/user/>, <RegexURLResolver <RegexURLPattern list> (None:None) ^app01/publish/>, <RegexURLPattern app_list ^(?P<app_label>auth|app01)/$>]
以上就是`url(r’^admin/’, admin.site.urls)的程式碼執行流程
倉促完成,如有問題,評論留言,感謝~_~