1. 程式人生 > >django---admin模組原始碼解析

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.ModelAdminPermission模型類,定製樣式,這樣在我們訪問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模型類,另外預設還有GroupUser

<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)的程式碼執行流程
倉促完成,如有問題,評論留言,感謝~_~