1. 程式人生 > >Django中自定義admin---Xadmin的實現

Django中自定義admin---Xadmin的實現

在Django框架中,自帶一個後臺管理頁面admin,這個管理頁面很全,但是,有些並不是我們需要的,所以我們可以根據admin的實現流程來自定義自己的需求,即根據admin的實現方式來實現自定製--Xadmin

首先,我們先解析admin的流程,在Django中,我們在建立專案的時候,Django自帶一個admin的url,實現了不同模型表的增刪改查,那麼admin是如何實現url的分發的?

我們可以從三部分來看admin的路由分發實現

1,啟動

我們可以通過from django.contrib import admin來看admin是如何啟動的

Django啟動後,會在manage.py檔案中載入配置檔案settings.py  ,在settings.py中有一個INSTALLED_APPS這個配置項,Django會按照配置項的內容一次載入每一個app.

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'Xadmin.apps.XadminConfig',
    'blogs.apps.BlogsConfig',
    'bigs.apps.BigsConfig',
]

載入到django.contrib.admin時,會載入admin相關的,我們點選admin,進入admin的__init__.py檔案,

from django.contrib.admin.sites import AdminSite, site
from django.utils.module_loading import autodiscover_modules

def autodiscover():
    autodiscover_modules('admin', register_to=site)

執行auto_discover這個介面,會自動的載入所有APP中的admin.py這個檔案,這就是admin的啟動檔案

2,註冊

自動載入所有APP下的admin.py檔案時,會一次記錄所有執行了admin.site.register(模型類)這個方法的模型類,從而完成模型類的註冊。

所有的模型類都執行admin.site.register()這個方法後完成的註冊,那麼這個方法內部都做了些什麼?

我們可以點選site進入admin的原始碼中,得到這個:site = AdminSite()  site是一個物件,是AdminSite這個類的一個物件,並且是一個單例物件,那麼我們就可以肯定,register是這個單例物件的一個方法,那這個方法中到底做了什麼?

class AdminSite(object):
    """
    An AdminSite object encapsulates an instance of the Django admin application, ready
    to be hooked in to your URLconf. Models are registered with the AdminSite using the
    register() method, and the get_urls() method can then be used to access Django view
    functions that present a full admin interface for the collection of registered
    models.
    """


    def __init__(self, name='admin'):
        self._registry = {}  # model_class class -> admin_class instance
        self.name = name
 

    # 關於register的方法

    def register(self, model_or_iterable, admin_class=None, **options):

        if not admin_class:
            admin_class = ModelAdmin

        # Instantiate the admin class to save in the registry
        self._registry[model] = admin_class(model, self)

  #備註:擷取的原始碼中的一部分  
        

示例site物件時,會生成一個self._registry={}這個物件屬性,如果我們沒有指定admin_class,那麼admin_class就是ModelAdmin,由此可以看出,admin_class是admin提供給我們定製頁面的一個自定義類,這個類必須繼承ModelAdmin這個類,以下是:

class ModelAdmin(BaseModelAdmin):
    "Encapsulates all admin options and functionality for a given model."

    list_display = ('__str__',)
    list_display_links = ()
    list_filter = ()
    list_select_related = False
    list_per_page = 100
    list_max_show_all = 200
    list_editable = ()
    search_fields = ()
    date_hierarchy = None
    save_as = False
    save_as_continue = True
    save_on_top = False
    paginator = Paginator
    preserve_filters = True
    inlines = []

    # Custom templates (designed to be over-ridden in subclasses)
    add_form_template = None
    change_form_template = None
    change_list_template = None
    delete_confirmation_template = None
    delete_selected_confirmation_template = None
    object_history_template = None
    popup_response_template = None

    # Actions
    actions = []
    action_form = helpers.ActionForm
    actions_on_top = True
    actions_on_bottom = False
    actions_selection_counter = True
    checks_class = ModelAdminChecks

    def __init__(self, model, admin_site):
        self.model = model
        self.opts = model._meta
        self.admin_site = admin_site
        super(ModelAdmin, self).__init__()

    def __str__(self):
        return "%s.%s" % (self.model._meta.app_label, self.__class__.__name__)

所以不管有沒有自定製樣式類,都會執行self._registry[model] = admin_class(model, self),也就是說,註冊一個模型類,就會在物件的_registry這個字典中新增一個鍵值對,這個鍵是我們註冊的這個模型類 model,值是繼承ModelAdmin的樣式類或者ModelAdmin這個類的一個物件。

3,設計url

因為site是一個單例物件,所以admin在執行完所有admin.py檔案後,就會得到整個全域性的一個包含所有註冊模型的字典_registry,得到這個self._registry(也就是admin.site物件)字典,我們可以for迴圈這個admin.site._registry字典,得到這些鍵值對,那麼得到這些鍵值對有什麼用呢?這就是admin設計url時會用到的。

我們訪問admin這個後臺管理頁面會發現,這個頁面關於我們註冊的所有模型都會實現增刪改查功能,每個功能介面對應不同的模型類時,除了資料不同外,是一樣的,也就是說,admin對於不同的模型類用到是一套url,一套模板,那麼這個url是怎麼設計的呢?

我們分別訪問不同的模型類的這四個功能頁面,會發現一個規律:

查詢頁面url: http://IP:PORT/admin/app名/模型類的名字(全部小寫)/

新增頁面url:   http://IP:PORT/admin/app名/模型類的名字(全部小寫)/add

編輯頁面url:http://IP:PORT/admin/app名/模型類的名字(全部小寫)/id值/change

刪除頁面url:http://IP:PORT/admin/app名/模型類的名字(全部小寫)/id值/delete

通過這個規律可以看出url分發的實現

但是在這個實現的過程中,我們有一個需要注意的時,使用者訪問的請求攜帶的路徑是一個字串的型別,我們可以通過request.path得到使用者訪問的路徑,也就能得到使用者訪問的是哪個APP下的哪個模型類,但是這兩個引數都是字串的型別,如何通過字串得到使用者訪問的哪張表,這是個麻煩,這時,可能我們會想到使用importlib模組來得到這個類,但是,這樣這個過程很麻煩,Django為我們封裝好了相應的方法:我們可以通過這個模型類的類名,通過._meta.model_name這個方法得到對應的字串形式的類名,同樣的我們也可以通過這個類名._meta.app_label得到字串格式的相應的APP名

# 使用model代指模型類的類名
model._meta.model_name   #得到字串格式的類名(全小寫的)

model._meta.app_label   #可以得到當前類所在APP的字串的名字

# 補充
model._meta.get_field("字串格式的欄位屬性") # 得到一個欄位屬性的物件field_obj,這樣我們就可以利用這個欄位物件取得屬性
                            # 比如: field_obj.verbose_name

通過這兩個封裝的方法,就完美的解決了我們頭疼的問題

from django.conf.urls import url

from django.contrib import admin

def get_urls_operate():
    emp = []
    emp.append(url(r'^$',查詢頁面的檢視函式))
    emp.append(url(r'^add/$',新增頁面的檢視函式))
    emp.append(url(r'^(\d+)/change/$',編輯頁面的檢視函式))
    emp.append(url(r'^(\d+)/delete/$',刪除頁面的檢視函式))
    return emp


def get_urls():
    temp = []
    for model,main_class_obj in admin.site._registry.items():
        app_name = model._meta.app_label
        model_name = model._meta.model_name
        temp.append(url(r'^{}/{}/'.format(app_name,model_name),(get_urls_operate(),None,None)))   #實現第二層路由的分發
    return temp

urlpatterns = [
     url(r'^Xadmin/',(get_urls(),None,None))     #自定義一個字首  實現第一層路由的分發
]

這樣我們就實現一個通過一個路由實現不同場景的分發

通過這三部分,我們可以按照admin的實現方式,來自定製Xadmin

我們瞭解了admin內部的實現流程,我們可以將這個實現過程封裝到一個類中。

  我們自定製Xadmin時,也要按照admin的流程實現,首先是啟動項,admin中,Django啟動時,會自動的執行每個app下的admin.py檔案,我們可以自定製為啟動時,自動執行app下的每個自定製的.py檔案,比如Xadmin.py 檔案,那麼如何達到Django啟動的時候幫我們自動掃描載入我們自定製的Xadmin.py檔案呢?這是個問題

  我們觀察可以發現,Django在啟動時,會載入配置檔案settings.py ,在settings.py檔案中,有一個INSTALLED_APPS這個列表,這個列表中,放置著我們在Django專案中的所有app的配置資訊,我們觀察這個列表:我們自己開啟的APP,在設定配置資訊時,會在APP名字後加一個.apps.(app名首字母大寫)Config這個東西,而對應的在我們的app下,Django會給我們自動的配置一個apps.py檔案,那麼這個apps.py檔案有什麼作用呢,我們開啟這個apps.py檔案,看看裡面的配置資訊:

以blogs這個APP為例:

from django.apps import AppConfig


class BlogsConfig(AppConfig):
    name = 'blogs'

我們可以發現,裡面定義了一個類,這個類的類名就是settings配置資訊中apps後面跟的那個東東,這個類中有一個靜態屬性name是當前的APP名,這個類繼承了AppConfig這個類。這就是我們從這個.py檔案中所能得的所有東西,乍一看,沒有什麼有用的資訊,那麼我們只能從它繼承的類中找了

MODELS_MODULE_NAME = 'models'


class AppConfig(object):
    """
    Class representing a Django application and its configuration.
    """

    def __init__(self, app_name, app_module):
        # Full Python path to the application eg. 'django.contrib.admin'.
        self.name = app_name

    def ready(self):
        """
        Override this method in subclasses to run code when Django starts.
    #這句話的語義為:在子類中重寫此方法,以便在Django啟動時執行程式碼 """

檢視整個AppConfig,我們可以把我們的要啟動的程式碼放置在重寫的ready方法中即可

from django.apps import AppConfig
from django.utils.module_loading import autodiscover_modules


class XadminConfig(AppConfig):
    name = 'Xadmin'
    def ready(self):
        autodiscover_modules('Xadmin')   #Django啟動時會自動掃描每個app下的Xadmin.py檔案

這樣,我們就完成了自定製Xadmin的啟動階段,啟動後我們就要進行下一步註冊

 在每個app下的Xadmin.py 檔案中註冊模型

以blogs這個APP下的UserInfo、Book為例:

from blogs import models
from Xadmin.service.Xadmin import site

site.register(models.UserInfo)
site.register(models.Book)

接下了就是設計url了,我們可以仿照admin的方式,在單例物件admin.site的這個類中封裝好這些方法

from django.conf.urls import url
from django.shortcuts import HttpResponse,render

class ModelXadmin(object):
    def __init__(self,model,site):
        self.model = model
        self.site = site

    def show(self,request):
        data_list = self.model.objects.all()
        return render(request,'show.html',locals())  #locals()  請函式內部所有的鍵值對儲存 == {"data_list":data_list}

    def add(self,request):
        return HttpResponse('新增頁面')

    def edit(self,request, pk):
        return HttpResponse('編輯頁面')

    def delete(self,request, pk):
        return HttpResponse('刪除頁面')
    
    @property
    def get_urls_operate(self):
        emp = []
        emp.append(url(r'^$', self.show))
        emp.append(url(r'^add/$', self.add))
        emp.append(url(r'^(\d+)/change/$', self.edit))
        emp.append(url(r'^(\d+)/delete/$', self.delete))
        return emp
    
    @property
    def urls(self):
        return self.get_urls_operate,None,None


class XadminSite(object):
    def __init__(self,name='xadmin'):
        self._registry = {}
        
    def register(self,model,class_main=None,**option):
        if not class_main:
            class_main = ModelXadmin
        self._registry[model] = class_main(model,self)

    @property
    def get_urls(self):
        # print(admin.site._registry)
        temp = []
        for model, model_admin_object in self._registry.items():
            model_name = model._meta.model_name
            model_app = model._meta.app_label
            temp.append(url(r'^{}/{}/'.format(model_app, model_name), model_admin_object.urls))
        return temp

    @property
    def urls(self):
        return self.get_urls,None,None


site = XadminSite()

 

備註:一個關鍵點,為什麼把第二層分發設定在了ModelXadmin這個類中,那肯定是放在這個類中能有什麼好處,如果我們把這個二級分發放在XadminSite中,那麼我們要取得每一個模型的資料是很麻煩的,但是,如果我們把這個放在ModelXadmin中,由於,在register(註冊)時,我們給class_admin(XadminSite)傳了每一個模型類,所以放在這個類中,我們可以通過self.model這個屬性獲得每個模型類的資料(self.model.objects.all()),這樣就很容易得到這個模型表。

&n