第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方法如下,是一个空的。

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.pyStarkSite类中增加一个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.UserInfomodels.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。

修改service/selfadmin.py文件中的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页面列表展示:


image.png

八、定制添加页面

添加、修改页面是利用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>

添加页面展示效果:


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343