天天看點

第26天,自定制CRUD元件

目錄

一、定制一個啟動檔案
二、建立一個類,用以封裝model class
三、動态生成URL(一)
四、動态生成URL(二)
五、定制特殊的視圖函數
六、預留URL的鈎子函數
七、定制清單頁面
八、定制添加頁面
           

CURD也就是增加(Create)、讀取查詢(Retrieve)、更新(Update)和删除(Delete)幾個單詞的首字母簡寫。也就是自定制一個具有增删改查功能的背景管理元件。

一、定制一個啟動檔案

先建立一個app,名為stark,然後修改

stark/apps.py

如下:

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

class StarkConfig(AppConfig):
    name = 'stark'
    def ready(self):
        # 去在settings.py的INSTALLED_APPS中已經注冊的APP目錄下尋找stark.py檔案并執行
        autodiscover_modules('stark')
           
注意:在StarkConfig類中定義一個方法,名稱必須為ready,這個ready()方法會覆寫掉父類AppConfig中的ready方法(),AppConfig類中的ready方法如下,是一個空的。
第26天,自定制CRUD元件
image.png
提示:在子類中覆寫此方法以在Django啟動時運作代碼。

然後再建立一個app,名為app01,再在app01目錄下建立一個stark.py檔案,在檔案中随便寫入一句代碼

print('django啟動自動執行stark.py')
           

注意:需要確定settings.py的INSTALLED_APPS中已經注冊app01和stark這兩個建立的app

INSTALLED_APPS = [
    ...
    'stark.apps.StarkConfig',
    'app01.apps.App01Config',
]
           

啟動項目,就可以看到app01/stark.py被立即執行。

二、建立一個類,用以封裝model class

在stark應用目錄下建立一個目錄service (目錄名稱可自定義),然後建立檔案

service/selfadmin.py

,内容如下:

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

class StarkConfig(object):
    '''用于被繼承時,自定義一些方法''
    def __init__(self,mcls):
        '''mcls應該是一個model class'''
        self.mcls = mcls

class StarkSite(object):
    def __init__(self):
        self._registry = {}

    def register(self,model_class):
        self._registry[model_class] = StarkConfig(model_class)

site = StarkSite()
           

register()

方法,将傳入的model_class封裝成字典,key就是model_class本身,value是

StarkConfig

類的對象。這個對象在動态生成URL時會用到。

app01/stark.py

中注冊model class

from stark.service import selfadmin
from app01 import models

selfadmin.site.register(models.UserInfo)
selfadmin.site.register(models.Role)
           

而此時

service/selfadmin.py

中的

self._registry

就等于

{models.UserInfo: StarkConfig(models.UserInfo), models.Role: StarkConfig(models.Role)}

三、動态生成URL(一)

動态生成URL,是根據注冊的model class不同生成不同的URL.

service/selfadmin.py

StarkSite

類中增加一個urls()方法:

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

class StarkConfig(object):
    def __init__(self,mcls):
        '''mcls應該是一個model class'''
        self.mcls = mcls

class StarkSite(object):
    def __init__(self):
        self._registry = {}

    def register(self,model_class):
        self._registry[model_class] = StarkConfig(model_class)
        '''
        此時self._registry = {models.UserInfo: StarkConfig(models.UserInfo)}
        '''
    @property
    def urls(self):
        patterns = []

        for model_class,config_obj in self._registry.items():
            # 假設第一個循環{models.UserInfo: StarkConfig(models.UserInfo)}進來
            app_name = model_class._meta.app_label      # 擷取models.UserInfo所在的app名稱
            model_name = model_class._meta.model_name   # 擷取models.UserInfo的類名小寫
            temp = url(r'^%s/%s/' %(app_name,model_name), self.login)
            patterns.append(temp)

        return patterns,None,'stark'

    def login(self,request):
        return HttpResponse('登入頁面')

site = StarkSite()
           
注意:

urls()

方法傳回三個值用逗号隔開,就相當于傳回一個元組,而路由分發時用到的

include()

方法實際上也是傳回一個元組,是以這裡就用自定義的

urls()

方法代替

include()

方法。詳見 Include()的本質

urls.py中:

# 先導入自定義的selfadmin子產品
from stark.service import selfadmin

urlpatterns = [
    ...
    url(r'^stark/', selfadmin.site.urls),
]
           

由于步驟二中已經注冊了

models.UserInfo

models.Role

兩個model_class類,是以現在可以生成兩個URL,分别是 /stark/app01/userinfo/和/stark/app01/role/

四、動态生成URL(二)

步驟三,隻為每個model class生成了一個URL,要想做增删改查,就必須至少生成四個URL。那麼就需要在步驟三中動态生成的 URL中再做一次子路由分發。

以models.Role類為例,需要生成如下4個URL:

  • /stark/app01/role/
  • /stark/app01/role/add/
  • /stark/app01/role/1/change/
  • /stark/app01/role/1/delete/

修改service/selfadmin.py檔案中的

StarkConfig

類如下:

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

class StarkConfig(object):
    def __init__(self,mcls):
        '''mcls應該是一個model class'''
        self.mcls = mcls
        self.app_name = self.mcls._meta.app_label       # 擷取model class的app名稱
        self.model_name = self.mcls._meta.model_name    # 擷取model class的類名小寫
            # app名稱和類名生成每個URL唯一的name,如下urls()方法中應用

    @property
    def urls(self):
        patterns = [
            url(r'^$', self.list_view, name='%s_%s_list' %(self.app_name,self.model_name,)),
            url(r'^add/', self.add_view,  name='%s_%s_add' %(self.app_name,self.model_name,)),
            url(r'^(\d+)/change/', self.change_view,  name='%s_%s_change' %(self.app_name,self.model_name,)),
            url(r'^(\d+)/delete/', self.delete_view,  name='%s_%s_delete' %(self.app_name,self.model_name,)),
        ]

        return patterns, None, None

    def list_view(self,request):
        return HttpResponse('清單頁面')

    def add_view(self,request):
        return HttpResponse('增加頁面')

    def change_view(self,request,id):
        return HttpResponse('修改頁面')

    def delete_view(self,request,id):
        return HttpResponse('删除頁面')
           
為每個URL定義一個name,用以在視圖函數中反向生成URL。

StarkSite

類的

urls()

方法,将

temp = url(r'^%s/%s/' %(app_name,model_name), self.login)
           

改為:

temp = url(r'^%s/%s/' %(app_name,model_name), config_obj.urls)

# 此處的config_obj就是StarkConfig類的對象,此對象具有urls方法。
           

這樣就可以為每個注冊的model class生成4個URL了。

五、定制特殊的視圖函數

如果你在以後使用CRUD元件時,覺得已經寫好的視圖函數不能滿足你的需求,這時,應該可以重寫視圖函數,并覆寫之前寫好的視圖函數。就需要這樣來做:

service/selfadmin.py

子產品中的

StarkSite

register

方法修改為下面這樣:

class StarkSite(object):
    def __init__(self):
        self._registry = {}

    def register(self,model_class,self_class_config=None):
        if not self_class_config:
            self_class_config = StarkConfig
        self._registry[model_class] = self_class_config(model_class)
        '''
        當self_class_config沒有傳值時,self_class_config就等于StarkConfig
        當self_class_config有值,就使用self_class_config自己,self_class_config應該是使用者自定義的一個類,\
        它繼承了StarkConfig類。
        '''

    @property
    def urls(self):
        patterns = []

        for model_class,config_obj in self._registry.items():
            # 假設第一個循環{models.UserInfo: StarkConfig(models.UserInfo)}進來
            app_name = model_class._meta.app_label      # 擷取models.UserInfo所在的app名稱
            model_name = model_class._meta.model_name   # 擷取models.UserInfo的類名小寫
            temp = url(r'^%s/%s/' %(app_name,model_name), config_obj.urls)
            patterns.append(temp)

        return patterns,None,'stark'

site = StarkSite()
           
注意:重新定義了

register()方法

  • 當self_class_config沒有傳值時,self_class_config就等于StarkConfig;
  • 當self_class_config有值,就使用self_class_config自己,self_class_config應該是使用者自定義的一個類,它繼承了StarkConfig類。

這時,如果你在

app01/stark.py

中注冊

models.UserInfo

時,自己定義一個

UserInfoConfig

類,并将其作為第2個參數傳給

register

方法,那

UserInfoConfig

類中定義的屬性和方法,隻要與

StarkConfig

類中定義的屬性和方法名稱一緻,就會将

StarkConfig

類中定義的屬性和方法覆寫,這樣就可以達到你自定制的效果。

這裡隻是用

models.UserInfo

來舉例而已。

app01/stark.py

from stark.service import selfadmin
from app01 import models
from django.shortcuts import HttpResponse

class UserInfoConfig(selfadmin.StarkConfig):
    def list_view(self,request):
        return HttpResponse('自定制的使用者清單頁面')


selfadmin.site.register(models.UserInfo,UserInfoConfig)
           
注意:自定義的

UserInfoConfig

類必須要繼承

StarkConfig

六、預留URL的鈎子函數

前面的代碼,我們隻是固定為每個注冊的model class生成4個URL。如果,你有額外增加子URL的需求,就需要修改一下

service/selfadmin.py

StarkConfig

類,如下:

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

class StarkConfig(object):
    def __init__(self,mcls):
        '''mcls應該是一個model class'''
        self.mcls = mcls
        self.app_name = self.mcls._meta.app_label  

    @property
    def urls(self):
        patterns = [
            url(r'^$', self.list_view, name='%s_%s_list' %(self.app_name,self.model_name,)),
            url(r'^add/', self.add_view,  name='%s_%s_add' %(self.app_name,self.model_name,)),
            url(r'^(\d+)/change/', self.change_view,  name='%s_%s_change' %(self.app_name,self.model_name,)),
            url(r'^(\d+)/delete/', self.delete_view,  name='%s_%s_delete' %(self.app_name,self.model_name,)),
        ]
        patterns.extend(self.extra_urls())
        return patterns, None, None

    def extra_urls(self):
        '''
        自定制額外url的鈎子函數
        :return: 
        '''
        return []

    # ...其他方法不變,省略
           
注意:以上修改的代碼中,新定義了一個extra_urls()方法,此方法傳回一個空清單,并且在

urls()

中通過

patterns.extend(self.extra_urls())

擴充新增的url。

然後在

app01/stark.py

的自定制類UserInfoConfig中定義一個

extra_urls()

方法去覆寫

stark.service.selfadmin.StarkConfig.extra_urls

方法,如下:

from stark.service import selfadmin
from app01 import models
from django.shortcuts import HttpResponse
from django.conf.urls import url

class UserInfoConfig(selfadmin.StarkConfig):
    def list_view(self,request):
        return HttpResponse('自定制的使用者清單頁面')

    def extra_urls(self):
        patterns = [
            url(r'^test/', self.test),
        ]
        return patterns

    def test(self,request):
        return HttpResponse('test頁面')

selfadmin.site.register(models.UserInfo,UserInfoConfig)
           

這樣的話,注冊的

models.UserInfo

類,除了會生成增、删、改、查4個URL,還會額外生成一個test的URL:/stark/app01/userinfo/test/

七、定制清單頁面

清單頁面也就是将資料庫裡的資料讀出來,以表格的形式展示到頁面上。

修改stark/service/selfadmin.py的StarkConfig類的list_view()視圖函數如下:

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

class StarkConfig(object):
    list_display = []

    def __init__(self,mcls):
        '''mcls應該是一個model class'''
        self.mcls = mcls
        self.app_name = self.mcls._meta.app_label       # 擷取model class的app名稱
        self.model_name = self.mcls._meta.model_name    # 擷取model class的類名小寫
            # app名稱和類名生成每個URL唯一的name,如下urls()方法中應用

    @property
    def urls(self):
        patterns = [
            url(r'^$', self.list_view, name='%s_%s_list' %(self.app_name,self.model_name,)),
            url(r'^add/', self.add_view,  name='%s_%s_add' %(self.app_name,self.model_name,)),
            url(r'^(\d+)/change/', self.change_view,  name='%s_%s_change' %(self.app_name,self.model_name,)),
            url(r'^(\d+)/delete/', self.delete_view,  name='%s_%s_delete' %(self.app_name,self.model_name,)),
        ]
        patterns.extend(self.extra_urls())
        return patterns, None, None

    def extra_urls(self):
        '''
        自定制額外url的鈎子函數
        :return: 
        '''
        return []

    def list_view(self,request):
        obj_list = self.mcls.objects.all()

        header_list = []
        if self.list_display:
            for item in self.list_display:
                title = self.mcls._meta.get_field(item).verbose_name
                header_list.append(title)

        body_dict = {}
        if self.list_display:
            for obj in obj_list:
                val_list = []
                for item in self.list_display:
                    val = getattr(obj,item)
                    val_list.append(val)
                body_dict[obj.id] = val_list
                print(body_dict)
                '''
                需要将body_dict傳給模闆檔案進行渲染,之是以要将每個表的id做為Key,是因為在修改和删除資料時,
                前端頁面需要回傳id。沒有id就不知道要修改或删除表中的哪條資料
                '''
        else:
            for obj in obj_list:
                val_list = [obj,]
                body_dict[obj.id] = val_list
            print(body_dict)

        app_name = self.app_name
        model_name = self.model_name
        '''
        app_name和model_name傳給模闆,用以拼接删除和修改按鈕的URL
        '''
        add_url = reverse('stark:%s_%s_add' %(app_name,model_name,))

        return render(request, 'stark/list.html', locals())

           
解析:
  • StarkConfig類的list_display 預設是一個空清單,可以在繼承StarkConfig類的子類中根據需求重新定義,清單中應該包含你想展示在頁面上的字段;
  • list_view()是清單頁面的視圖函數;
  • self.mcls,如果注冊的是models.UserInfo類,那麼此時,

    self.mcls = models.UserInfo

  • add_url = reverse('stark:%s_%s_add' %(app_name,model_name,))

    此代碼是反向生成URL,以注冊models.UserInfo為例,生成的URL是/stark/app01/userinfo/add

app01/stark.py注冊UserInfo類

from stark.service import selfadmin
from app01 import models

class UserInfoConfig(selfadmin.StarkConfig):
    list_display = ['id','username','email']

selfadmin.site.register(models.UserInfo,UserInfoConfig)
           

清單頁面的模闆檔案stark/list.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
</head>
<body>

<div class="container" style="margin-top: 30px;">
    <div class="table-responsive">
        <a href="{{ add_url }}" class="btn btn-primary">添加</a>
        <table class="table table-bordered table-striped table-hover">
            {% if header_list %}
                <!-- 展示表格title -->
                <thead>
                    <tr>
                        {% for header in header_list %}
                            <th>{{ header }}</th>
                        {% endfor %}
                        <th>操作</th>
                    </tr>
                </thead>
            {% endif %}
            <tbody>
                {% for id,body_list in body_dict.items %}
                    <!-- 循環出每行資料 -->
                    <tr>
                        {% for body in body_list %}
                            <!-- 循環出每行資料中的每個表格資料 -->
                            <td>{{ body }}</td>
                        {% endfor %}
                        <td>
                            <a href='/stark/{{ app_name }}/{{ model_name }}/{{ id }}/change/' class="btn btn-warning">修改</a>
                            <a href='/stark/{{ app_name }}/{{ model_name }}/{{ id }}/delete/' class="btn btn-danger">删除</a>
                        </td>
                    </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
</div>
</body>
</html>
           

web頁面清單展示:

第26天,自定制CRUD元件

八、定制添加頁面

添加、修改頁面是利用Django的ModelForm來生成form表單和表單資料驗證。

編輯service/selfadmin.py

from django.shortcuts import HttpResponse,render,redirect
from django.conf.urls import url
from django.urls import reverse
from django.forms import ModelForm

class StarkConfig(object):
    list_display = []
    ModelFormCls= None

    def __init__(self,mcls):
        '''mcls應該是一個model class'''
        self.mcls = mcls
        self.app_name = self.mcls._meta.app_label       # 擷取model class的app名稱
        self.model_name = self.mcls._meta.model_name    # 擷取model class的類名小寫
            # app名稱和類名生成每個URL唯一的name,如下urls()方法中應用

    def get_modelform_cls(self):
        if self.ModelFormCls:
            return self.ModelFormCls
        else:
            class TempModelForm(ModelForm):
                class Meta:
                    model = self.mcls
                    fields = "__all__"
            return TempModelForm

    def add_view(self,request):
        ModelFormCls= self.get_modelform_cls()
        forms = ModelFormCls()
        if request.method == 'POST':
            forms = ModelFormCls(request.POST)
            if forms.is_valid():
                forms.save()
                name = 'stark:%s_%s_list' %(self.app_name,self.model_name,)
                return redirect(reverse(name))
        return render(request,'stark/add.html',locals())
           
  • modelform_cls = None

    可以在繼承StarkConfig類的子類中根據需求重新定義名為modelform_cls的類,用以覆寫預設值;
  • get_model_form_cls()

    方法判斷使用者是否自定制了modelform_cls類;有,就用使用者自定制的modelform_cls類;沒有,就用預設的TempModelForm類。

app01/stark.py注冊UserInfo類:

from stark.service import selfadmin
from app01 import models
from django.shortcuts import HttpResponse
from django.conf.urls import url
from django.forms import ModelForm

class UserInfoConfig(selfadmin.StarkConfig):
    list_display = ['id','username','email']

    class ModelFormCls(ModelForm):
        class Meta:
            model = models.UserInfo
            fields = ['id','username','password','email']
            labels = {
                'ip':'IP',
                'username': '使用者名',
                'password': '密碼',
                'email': '郵箱',
            }

# 注意model class
selfadmin.site.register(models.UserInfo,UserInfoConfig)
           

添加頁面的模闆檔案stark/add.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="" method="post" novalidate>
    {% csrf_token %}
    {% for form in forms %}
        <p>{{ form.label }}: {{ form }} {{ form.errors.0 }}</p>
    {% endfor %}
    <p><input type="submit"></p>
</form>

</body>
</html>
           

添加頁面展示效果:

第26天,自定制CRUD元件