自定义admin管理工具(stark组件)

创建项目

了解了admin的功能后,我们可以开始仿照admin编写我们自己的管理工具stark组件

首先创建一个新的项目,并创建三个app

stark就是我们要编写的组件,我们的主要逻辑就写在里面

在settings配置中分别注册这三个app

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
'app02.apps.App02Config',
'stark.apps.StarkConfig'
]

在app01和app02的models文件中创建数据类

# app01
from django.db import models # Create your models here. class Book(models.Model):
title = models.CharField(max_length=32) def __str__(self):
return self.title # app02
class Menu(models.Model):
caption = models.CharField(max_length=32) def __str__(self):
return self.caption class UserInfo(models.Model):
name = models.CharField(max_length=32)
pwd = models.CharField(max_length=32, default=123)
email = models.EmailField()
roles = models.ManyToManyField(to="Role") def __str__(self):
return self.name class Role(models.Model):
title = models.CharField(max_length=32)
permissions = models.ManyToManyField(to="Permission") def __str__(self):
return self.title class Permission(models.Model):
url = models.CharField(max_length=32)
title = models.CharField(max_length=32)
permission_group = models.ForeignKey("PermissionGroup", default=1)
code = models.CharField(max_length=32, default="")
parent = models.ForeignKey("self", default=1, null=True, blank=True) def __str__(self):
return self.title class PermissionGroup(models.Model):
caption = models.CharField(max_length=32)
menu = models.ForeignKey("Menu", default=1) def __str__(self):
return self.caption

在app01和app02下分别创建一个stark.py文件

使用admin时,我们知道启动项目时,会扫描每个app下的admin.py文件并执行

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

其实就是上面的方法实现了该功能,现在我们也仿照该方法,让项目启动时,扫描每个app下的stark.py文件并执行

在stark的apps.py中配置

from django.apps import AppConfig
from django.utils.module_loading import autodiscover_modules class StarkConfig(AppConfig):
name = 'stark' def ready(self):
autodiscover_modules('stark')

这个ready函数的内容就实现了我们的需求

仿照admin设置相关类

首先创建下面的文件

在执行admin.py文件时我们发现其实第一步就是导入admin,导入时通过单例模式生成了一个site对象,现在我们也来写一个类,生成一个单例对象

class StarkSite(object):

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

在app01和app02的stark.py文件中导入

from stark.service.sites import site

这样我们也就得到了一个单例对象site,在注册时admin使用的是site对象的register方法,我们也学着他写一个register方法

class StarkSite(object):

    def __init__(self):
self._registry = {} def register(self, model, model_config=None):
if not model_config:
model_config = ModelStark self._registry[model] = model_config(model, self)

这个方法的本质其实就是往self._registry这个字典中添加键值对,键就是我们的数据类(如Book类),值是一个类的对象,这个类就是我们要创建的第二个类,样式类

class ModelStark(object):

    def __init__(self, model, site):
self.model = model
self.site = site

通过这个类我们控制页面展示的内容和样式

做完这几步我们就可以在app01和app02的stark.py文件中开始注册了

# app01
from stark.service.sites import site
from .models import * site.register(Book) # app02
from stark.service.sites import site
from .models import * site.register(UserInfo)
site.register(Role)

注册完成后,我们的site._registry字典中就有了我们注册类对应的键值对,接下来就要配置url了

url配置

admin中的url配置

from django.conf.urls import url
from django.contrib import admin urlpatterns = [
url(r'^admin/', admin.site.urls), ]

可以看到所有的url都是在admin.site.urls这个方法中生成的,我可以看看这个方法的源码

@property
def urls(self):
return self.get_urls(), 'admin', self.name

其实就是做了一个分发,url是在self.get_urls()这个函数中生成的,接着看这个函数的主要代码

    def get_urls(self):
from django.conf.urls import url, include
# Since this module gets imported in the application's root package,
# it cannot import models from other applications at the module level,
# and django.contrib.contenttypes.views imports ContentType.
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'),
] # Add in each model's views, and create a list of valid URLS for the
# app_index
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 there were ModelAdmins registered, we should have a list of app
# labels for which we need to allow access to the app_index view,
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

这里我们需要知道到是我们生成的url的格式都是admin/app名/表名,所以我们要想办法取到app名和表名拼接起来

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)),
]

这里的model就是我们的数据类(如Book),如何通过他取到我们想要的呢

model._meta.app_label 取类所在的app名

model._meta.model_name 取类的名字

这样我们就成功拼接出了我们要的url,但是每个url下又有增删改查不同的url,这时又要再次进行分发,admin中使用了include方法,通过model_admin我们注册时样式类生成的对象下的url方法得到我们想要的

这个方法的内容如下

def get_urls(self):
from django.conf.urls import url
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
wrapper.model_admin = self
return update_wrapper(wrapper, view)
info = self.model._meta.app_label, self.model._meta.model_name
urlpatterns = [
url(r'^$', wrap(self.changelist_view), name='%s_%s_changelist' % info),
url(r'^add/$', wrap(self.add_view), name='%s_%s_add' % info),
url(r'^(.+)/history/$', wrap(self.history_view), name='%s_%s_history' % info),
url(r'^(.+)/delete/$', wrap(self.delete_view), name='%s_%s_delete' % info),
url(r'^(.+)/change/$', wrap(self.change_view), name='%s_%s_change' % info),
# For backwards compatibility (was the change url before 1.9)
url(r'^(.+)/$', wrap(RedirectView.as_view(
pattern_name='%s:%s_%s_change' % ((self.admin_site.name,) + info)
))),
]
return urlpatterns
@property
def urls(self):
return self.get_urls()

其实和之前一样,只是做了又一次分发,并且对应了视图函数,这里我们先不看视图函数的内容,值得注意的是这一次的分发和视图函数都是写在样式类中的,而不是写在生成site的AdminStie类中

这样有什么好处呢,我们知道当我们要注册时,是可以自己定义一些属性的,其实要显示的页面也是可以自己定义的,所以讲这最后一层url分发和对应的函数写在样式类中可以方便我们进行自定义

看完了admin的做法,我们可以来写我们自己的代码了,首先在urls文件中配置

from django.conf.urls import url
from django.contrib import admin
from stark.service.sites import site urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^stark/', site.urls),
]

然后在我们创建的两个类中添加相关的代码,这里url对应的函数我们先简写

from django.conf.urls import url
from django.shortcuts import HttpResponse, render class ModelStark(object): def __init__(self, model, site):
self.model = model
self.site = site def change_list(self, request):
ret = self.model.objects.all()
return render(request, "stark/change_list.html", locals()) def add_view(self, request):
return HttpResponse("add_view") def del_view(self, request, id):
return HttpResponse("del_view") def change_view(self, request, id):
return HttpResponse("change_view") def get_url_func(self):
temp = []
temp.append(url("^$", self.change_list))
temp.append(url("^add/$", self.add_view))
temp.append(url("^(\d+)/delete/$", self.del_view))
temp.append(url("^(\d+)/change/$", self.change_view))
return temp @property
def urls(self):
return self.get_url_func(), None, None class StarkSite(object): def __init__(self):
self._registry = {} def register(self, model, model_config=None):
if not model_config:
model_config = ModelStark self._registry[model] = model_config(model, self) def get_urls(self):
temp = []
for model, model_config in self._registry.items():
model_name = model._meta.model_name
app_label = model._meta.app_label
u = url("^%s/%s/" % (app_label, model_name), model_config.urls)
temp.append(u)
return temp @property
def urls(self):
return self.get_urls(), None, None site = StarkSite()

别名的使用

在设置url对应的视图函数时,我们可以给这个url添加一个别名,在使用时可以通过这个别名来反向生成url,这样即使url有修改,这样别名不变我们都不需要修改代码

增加别名时要注意,由于每个数据类我们都生成了增删改查4条url,所以在写别名时应该有些区别,不然会引起混淆,所以我们设计别名的格式为app名_表名_增

def get_url_func(self):
temp = []
model_name = self.model._meta.model_name
app_label = self.model._meta.app_label
app_model = (app_label, model_name)
temp.append(url("^$", self.change_list, name="%s_%s_list" % app_model))
temp.append(url("^add/$", self.add_view, name="%s_%s_add" % app_model))
temp.append(url("^(\d+)/delete/$", self.del_view, name="%s_%s_delete" % app_model))
temp.append(url("^(\d+)/change/$", self.change_view, name="%s_%s_change" % app_model))
return temp
@property
def urls(self):
return self.get_url_func(), None, None

列表展示页面

url设计完成后,我们就需要来设计每个url对应的页面了,我们注意到,其实不管是访问哪张表,增删改查都只对应相同的四个视图函数,那么应该如何区分我们访问的表呢

在样式类ModelStark中,我们定义了self.model,这里的model其实就是我们访问表的数据类,通过他我们就能拿到我们需要的数据显示到页面上,访问不同的表时这个model是不同的,这时就做到了访问什么表显示什么表的内容

list_display

在使用admin时,默认给我们展示的是一个个的类对象,当我们想要看到其它内容时,可以通过list_display属性设置

from django.contrib import admin
from .models import *
# Register your models here. admin.site.register(UserInfo) class RoleConfig(admin.ModelAdmin):
list_display = ["id", "title"]
admin.site.register(Role, RoleConfig)

通过上面的方法,在访问admin页面时点击Role表就能看到id和title两个字段的内容了,现在我们也来仿照admin写一个list_display属性

首先,这个属性应该是可以自定制的,如果用户没有定制,那么他应该有一个默认值,所以我们可以在ModelStark样式类中先自己定义一个list_display静态属性

class ModelStark(object):
list_display = [] def __init__(self, model, site):
self.model = model
self.site = site

如果用户需要定制他,可以在app对应的stark.py文件中做如下配置

class BookConfig(ModelStark):
list_display = ["id", "title", "price"]
site.register(Book, BookConfig)

这里我们写在list_display中的内容都是表中有的字段,其实里面还可以写我们自己定义的函数,用来将我们自己需要的内容显示到页面上

from stark.service.sites import site, ModelStark
from .models import *
from django.utils.safestring import mark_safe class BookConfig(ModelStark): def edit(self, obj=None, is_header=False):
if is_header:
return "操作"
return mark_safe("<a href='/stark/app01/book/%s/change'>编辑</a>" % obj.pk) def delete(self, obj=None, is_header=False):
if is_header:
return "操作"
return mark_safe("<a href='/stark/app01/book/%s/delete'>删除</a>" % obj.pk)
list_display = ["id", "title", "price", edit, delete]
site.register(Book, BookConfig) class AuthorConfig(ModelStark):
list_display = ["name", "age"]
site.register(Author)

这里我们增加了编辑和删除两个函数,可以看到他们的返回值是一个a标签,这样就可以在页面上显示一个可以点击的编辑和删除,这里的mark_safe和前端渲染时用的safe是一样的功能,可以使标签正确的显示在页面上

这样我们就可以让页面显示成下面的样子

当我们处理列表页面对应的函数时就可以拿到list_display的值,再通过self.model取到对应的数据对象,从对象中拿到我们想要的数据,放到页面上进行显示

class ModelStark(object):
list_display = [] def __init__(self, model, site):
self.model = model
self.site = site def change_list(self, request):
# 生成表标头
header_list = []
for field in self.list_display:
if callable(field):
# header_list.append(field.__name__)
val = field(self, is_header=True)
header_list.append(val)
else:
field_obj = self.model._meta.get_field(field)
header_list.append(field_obj.verbose_name) # 生成表数据列表
data_list = self.model.objects.all()
new_data_list = []
for obj in data_list:
temp = []
for field in self.list_display:
if callable(field):
val = field(self, obj)
else:
val = getattr(obj, field)
temp.append(val)
new_data_list.append(temp)
return render(request, "stark/change_list.html", locals())

表头数据

首先,我们要生成表头,表头的内容应该根据list_display中写到的内容进行显示,这里要注意,如果我们在stark.py里自己写了样式类,那么list_display会优先从我们自己写的样式类中取,如果里面没有才会找到ModelStark中的

取到list_display的值后我们对他进行循环,如果值为可调用的,说明值为一个函数,那么我们就执行函数,取到我们要的结果,这里要注意执行函数时,我们给函数传了一个is_header=True,说明我们这次是取表头,在函数中我们给这个参数定义一个默认值为False

进入函数时,首先对他进行判断,如果为True,那么我们直接返回一个表头的信息就行了

class BookConfig(ModelStark):

    def edit(self, obj=None, is_header=False):
if is_header:
return "操作"
return mark_safe("<a href='/stark/app01/book/%s/change'>编辑</a>" % obj.pk) def delete(self, obj=None, is_header=False):
if is_header:
return "操作"
return mark_safe("<a href='/stark/app01/book/%s/delete'>删除</a>" % obj.pk)
list_display = ["id", "title", "price", edit, delete]
site.register(Book, BookConfig)

上面的内容可以看到我们返回的表头内容为操作

如果我们循环list_display得到的值是一个字符串,那么说明这应该是表中的一个字段,这时我们可以通过self.model._meta.get_field(字段名)的方法取到这个字段的对象,这个对象有一个verbose_name的属性,这个属性是用来描述一个字段的,在models中可以进行定义

class Book(models.Model):
title = models.CharField(verbose_name="标题", max_length=32)
price = models.DecimalField(verbose_name="价格", decimal_places=2, max_digits=5, default=12) def __str__(self):
return self.title

我们可以通过self.model._meta.get_field(字段名).verbose_name取到这个属性,将他作为表头,如果没有定义这个属性,那么默认值为字段名

表内容数据

取表内容数据时,和表头一样要做判断,判断list_display中的每一个值,如果是可调用的就执行函数取值,这里执行时,我们要将对应的数据对象传进去,这样在生成url时才能使用相关的id值

如果这个值是一个字符串,那么我们可以通过反射,取到数据对象中的值,最后将这些值组成下面形式的数据格式发给前端渲染

'''
[
[1, "python", 12],
[2, "linux", 12],
[3,"php"], 12 ]
'''

前端页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
</head>
<body>
<h3>数据展示</h3> <div class="container">
<div class="row">
<div class="col-md-8">
<table class="table table-striped table-hover">
<thead>
<tr>
{% for foo in header_list %}
<td>{{ foo }}</td>
{% endfor %}
</tr>
</thead>
<tbody>
{% for data in new_data_list %}
<tr>
{% for item in data %}
<td>{{ item }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
 
 
 
 
 

添加checkbox选择框

在使用admin时可以看到展示页面上每条记录前都有一个选择框,可以选择多条记录进行批量操作,我们也给我们的组件增加这一功能,其实实现方法和编辑按钮类似

我们先自己定义一个checkbox函数,返回一个checkbox类型的input标签,然后将这个函数添加到list_display中即可

class BookConfig(ModelStark):

    def edit(self, obj=None, is_header=False):
if is_header:
return "操作"
return mark_safe("<a href=%s>编辑</a>" % reverse("%s_%s_change" % self.app_model, args=(obj.pk,))) def delete(self, obj=None, is_header=False):
if is_header:
return "操作"
return mark_safe("<a href=%s>删除</a>" % reverse("%s_%s_delete" % self.app_model, args=(obj.pk,))) def select(self, obj=None, is_header=False):
if is_header:
return "选择"
return mark_safe("<input type='checkbox' value=%s />" % obj.pk) list_display = [select, "id", "title", "price", edit, delete]
site.register(Book, BookConfig)

这里checkbox标签的value值可以设置为该记录的主键值,方便以后使用,当我们点击最上面的复选框时应该还有全选和全部取消的功能,这里只需要添加一段Js代码即可

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
<script src="/static/jquery-3.2.1.min.js"></script>
</head>
<body>
<h3>数据展示</h3> <div class="container">
<div class="row">
<div class="col-md-8">
<table class="table table-striped table-hover">
<thead>
<tr>
{% for foo in header_list %}
<td>{{ foo }}</td>
{% endfor %}
</tr>
</thead>
<tbody>
{% for data in new_data_list %}
<tr>
{% for item in data %}
<td>{{ item }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div> <script>
$("#action-toggle").click(function () {
if ($(this).prop("checked")){
$("tbody :checkbox").prop("checked",true)
}else{
$("tbody :checkbox").prop("checked",false)
}
})
</script>
</body>
</html>

我们还注意到,在编辑和删除函数中我们在生成url时采用了反向解析,利用我们之前使用的别名来反向生成url,这样就不会把url写死了

list_display的默认情况

上面的内容我们都是考虑了用户自己定制了list_display的情况,如果用户没用进行自定制呢,那么我们所使用的list_display就应该是ModelStark中定义好的

我们仿照admin将默认的list_display设置为__str__,这样在生成表头时我们需要多做一步判断,当为__str__时,直接将表名的大写添加到header_list中即可

class ModelStark(object):

    list_display = ["__str__",]

    def __init__(self, model, site):
self.model = model
self.site = site
self.app_model = (self.model._meta.app_label, self.model._meta.model_name) # 查看数据视图
def change_list(self, request):
# 生成表标头
header_list = []
for field in self.list_display:
if callable(field):
# header_list.append(field.__name__)
val = field(self, is_header=True)
header_list.append(val)
else:
if field == "__str__":
header_list.append(self.model._meta.model_name.upper())
else:
field_obj = self.model._meta.get_field(field)
header_list.append(field_obj.verbose_name) # 生成表数据列表
data_list = self.model.objects.all()
new_data_list = []
for obj in data_list:
temp = []
for field in self.list_display:
if callable(field):
val = field(self, obj)
else:
val = getattr(obj, field)
print(val)
temp.append(val)
new_data_list.append(temp)
return render(request, "stark/change_list.html", locals())

这样我们就完成了默认情况的设置,但是我们发现在admin中不论用户如何设置list_display,其实我们都能看到复选框和编辑删除功能,所以我们也将编辑、删除和复选框的函数直接放入到ModelStark中作为默认配置,然后设置一个get_list_display函数,对所有的list_play都增加这三个功能

class ModelStark(object):
# 编辑按钮
def edit(self, obj=None, is_header=False):
if is_header:
return "操作"
name = "%s_%s_change" % self.app_model
return mark_safe("<a href=%s>编辑</a>" % reverse(name, args=(obj.pk,))) # 删除按钮
def delete(self, obj=None, is_header=False):
if is_header:
return "操作"
name = "%s_%s_delete" % self.app_model
return mark_safe("<a href=%s>删除</a>" % reverse(name, args=(obj.pk,))) # 复选框
def checkbox(self, obj=None, is_header=False):
if is_header:
return mark_safe("<input type='checkbox' id='action-toggle'>")
return mark_safe("<input type='checkbox' value=%s>" % obj.pk) def get_list_display(self):
new_list_display = []
new_list_display.extend(self.list_display)
new_list_display.append(ModelStark.edit)
new_list_display.append(ModelStark.delete)
new_list_display.insert(0, ModelStark.checkbox)
return new_list_display list_display = ["__str__",] def __init__(self, model, site):
self.model = model
self.site = site
self.app_model = (self.model._meta.app_label, self.model._meta.model_name) # 查看数据视图
def change_list(self, request):
# 生成表标头
header_list = []
for field in self.get_list_display():
if callable(field):
# header_list.append(field.__name__)
val = field(self, is_header=True)
header_list.append(val)
else:
if field == "__str__":
header_list.append(self.model._meta.model_name.upper())
else:
field_obj = self.model._meta.get_field(field)
header_list.append(field_obj.verbose_name) # 生成表数据列表
data_list = self.model.objects.all()
new_data_list = []
for obj in data_list:
temp = []
for field in self.get_list_display():
if callable(field):
val = field(self, obj)
else:
val = getattr(obj, field)
print(val)
temp.append(val)
new_data_list.append(temp)
return render(request, "stark/change_list.html", locals())

我们还注意到通过自定制get_list_display函数我们可以实现一些我们自己的逻辑,比如根据权限判断是否需要加入编辑按钮等

list_display_links

使用admin时,我们还可以通过list_display_links设置一些字段,点击这些字段也能进入编辑页面

我们也来实现一下这个功能,首先在ModelStark中定义一个默认的list_display_links,当用户自己定制了这个属性时,我们只要在生成表数据时多做一步判断,如果字段在list_display_links中,则在返回时给字段加上一个a标签,使他可以跳转到编辑页即可

由于我们经常要用到增删改查的url,所以我们在ModelStark中定义4个方法,分别获取增删改查的url

class ModelStark(object):
list_display = ["__str__", ] list_display_links = [] def __init__(self, model, site):
self.model = model
self.site = site
self.app_model = (self.model._meta.app_label, self.model._meta.model_name) # 获取当前查看表的编辑url
def get_edit_url(self, obj):
edit_url = reverse("%s_%s_change" % self.app_model, args=(obj.pk,))
return edit_url # 获取当前查看表的删除url
def get_delete_url(self, obj):
del_url = reverse("%s_%s_delete" % self.app_model, args=(obj.pk,))
return del_url # 获取当前查看表的增加url
def get_add_url(self):
add_url = reverse("%s_%s_add" % self.app_model)
return add_url # 获取当前查看表的查看url
def get_list_url(self):
list_url = reverse("%s_%s_list" % self.app_model)
return list_url # 查看数据视图
def change_list(self, request):
add_url = self.get_add_url()
# 生成表标头
header_list = []
for field in self.get_list_display():
if callable(field):
# header_list.append(field.__name__)
val = field(self, is_header=True)
header_list.append(val)
else:
if field == "__str__":
header_list.append(self.model._meta.model_name.upper())
else:
field_obj = self.model._meta.get_field(field)
header_list.append(field_obj.verbose_name) # 生成表数据列表
data_list = self.model.objects.all()
new_data_list = []
for obj in data_list:
temp = []
for field in self.get_list_display():
if callable(field):
val = field(self, obj)
else:
val = getattr(obj, field)
if field in self.list_display_links:
val = mark_safe("<a href=%s>%s</a>" % (self.get_edit_url(obj), val))
temp.append(val)
new_data_list.append(temp)
return render(request, "stark/change_list.html", locals())

这样当用户在stark.py中自己定义了list_display_links属性时,我们就能看到下面的效果了

from stark.service.sites import site, ModelStark
from .models import * class BookConfig(ModelStark):
list_display = ["id", "title", "price"]
list_display_links = ["id"]
site.register(Book, BookConfig)

如果能够点击字段内容进入编辑页面,那么我们自己定义的编辑按钮就可以不用显示了,所以可以在get_list_display中再做一次判断

def get_list_display(self):
new_list_display = []
new_list_display.extend(self.list_display)
if not self.list_display_links:
new_list_display.append(ModelStark.edit)
new_list_display.append(ModelStark.delete)
new_list_display.insert(0, ModelStark.checkbox)
return new_list_display

添加和编辑页面

编辑页面和添加页面的功能我们通过ModelForm实现,但是生成ModelForm时,由于用户访问的表可能是不一样的,所以里面的详细字段我们不能写死,所以我们只能定义一个简单的ModelForm类,然后在ModelStark中设置一个model_form_class,默认为None

用户如果想要对ModelForm的详细字段做设置,可以自己定制一个类,并将该类设置为model_form_class的值

class ModelStark(object):
list_display = ["__str__", ] model_form_class = None list_display_links = [] def get_modelform_class(self):
class ModelFormClass(ModelForm):
class Meta:
model = self.model
fields = "__all__"
if not self.model_form_class:
return ModelFormClass
else:
return self.model_form_class

可以看到当用户未设置model_form_class时,我们用自己的类,当用户设置了,则使用用户自己的类

from stark.service.sites import site, ModelStark
from django.forms import ModelForm
from .models import *
from django.forms import widgets as wid class BookModelForm(ModelForm):
class Meta:
model = Book
fields = "__all__"
error_messages = {
"title": {"required": "不能为空"},
"price": {"required": "不能为空"}
} class BookConfig(ModelStark):
list_display = ["id", "title", "price"]
model_form_class = BookModelForm
list_display_links = ["id"]
site.register(Book, BookConfig)

用户设置时就可以设置明确的字段信息了

添加和编辑的函数

    # 添加数据视图
def add_view(self, request):
ModelFormClass = self.get_modelform_class()
if request.method == "GET":
form = ModelFormClass()
return render(request, "stark/add_view.html", locals())
else:
form = ModelFormClass(data=request.POST)
if form.is_valid():
form.save()
return redirect(self.get_list_url())
else:
return render(request, "stark/add_view.html", locals()) # 编辑数据视图
def change_view(self, request, id):
edit_obj = self.model.objects.filter(pk=id).first()
ModelFormClass = self.get_modelform_class()
if request.method == "GET":
form = ModelFormClass(instance=edit_obj)
return render(request, "stark/change_view.html", locals())
else:
form = ModelFormClass(data=request.POST, instance=edit_obj)
if form.is_valid():
form.save()
return redirect(self.get_list_url())
else:
return render(request, "stark/change_view.html", locals())

就是通过ModelForm来实现添加和编辑

前端页面,由于前端的页面基本相同,所以我们可以把相同的部分写到一个页面中,然后应include调用

<div class="container">
<div class="row">
<div class="col-md-6">
<form action="" method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label for="">{{ field.label }}</label>
<div>
{{ field }}
<span class="error pull-right">
{{ field.errors.0 }}
</span>
</div> </div>
{% endfor %} <p><input type="submit" class="btn btn-default"></p>
</form>
</div>
</div>
</div>

添加页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>添加</title>
<link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
<script src="/static/jquery-3.2.1.min.js"></script>
<style>
.form-group input{
display: block;
width: 100%;
height: 34px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
color: #555;
background-color: #fff;
background-image: none;
border: 1px solid #ccc;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
-o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
} </style>
</head>
<body>
<h3>添加数据</h3> {% include 'stark/form.html' %} </body>
</html>

编辑页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>添加</title>
<link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
<script src="/static/jquery-3.2.1.min.js"></script>
<style>
.form-group input{
display: block;
width: 100%;
height: 34px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
color: #555;
background-color: #fff;
background-image: none;
border: 1px solid #ccc;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
-o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
} </style>
</head>
<body>
<h3>编辑数据</h3> {% include 'stark/form.html' %} </body>
</html>

删除页面

当点击删除时,我们不直接将数据删除,而是给用户返回一个确认页面,用户点击确认才真的删除,点击取消还跳回列表页面

# 删除数据视图
def del_view(self, request, id):
del_obj = self.model.objects.filter(pk=id).first()
if request.method == "GET":
list_url = self.get_list_url()
return render(request, "stark/del_view.html", locals())
else:
del_obj.delete()
return redirect(self.get_list_url())
 

前端页面

 
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>删除</title>
<link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
<script src="/static/jquery-3.2.1.min.js"></script>
</head>
<body>
<div>
<p>{{ del_obj }}</p>
</div>
<form action="" method="post">
{% csrf_token %}
<input type="submit" value="确认删除" class="btn btn-danger">
<a href="{{ list_url }}" class="btn btn-primary">取消</a>
</form>
</body>
</html>

自定义admin管理工具(stark组件)的更多相关文章

  1. Django框架 之 admin管理工具(组件使用)

    Django框架 之 admin管理工具(组件使用) 浏览目录 激活管理工具 使用管理工具 admin的定制 admin组件使用 Django 提供了基于 web 的管理工具. Django 自动管理 ...

  2. 10.5Djang admin 管理工具

    2018-10-5 17:30:57 Django admin 管理工具  参考连接: https://www.cnblogs.com/yuanchenqi/articles/8323452.html ...

  3. Django 之 admin管理工具

    -------------------------------------------------------------------------妄尝恶果,苦果自来. admin组件使用 Django ...

  4. Django admin 管理工具

    admin 组件的使用 Django 提供了基于 web 的管理工具.Django 自动管理工具是 django.contrib 的一部分. INSTALLED_APPS = [ 'django.co ...

  5. Django的 admin管理工具

    admin组件使用 Django 提供了基于 web 的管理工具. Django 自动管理工具是 django.contrib 的一部分.你可以在项目的 settings.py 中的 INSTALLE ...

  6. admin管理工具

    admin组件使用 Django 提供了基于 web 的管理工具. Django 自动管理工具是 django.contrib 的一部分.你可以在项目的 settings.py 中的 INSTALLE ...

  7. django之admin管理工具

    admin组件 admin是django提供的基于web的管理工具 >如何使用: 1.激活管理工具 通常会在生成项目时在urls.py中自动设置好 from django.urls import ...

  8. Django——admin管理工具

    一.admin组件使用 Django 提供了基于 web 的管理工具. Django 自动管理工具是 django.contrib 的一部分.你可以在项目的 settings.py 中的 INSTAL ...

  9. Django 测试开发3 数据模型models和admin管理工具

    参考:https://blog.csdn.net/weixin_44510615/article/details/89425412 1.Django模型字段常用类型: IntegerField : 整 ...

随机推荐

  1. notepad++程序员开发插件

    1. sftp 2. explore 3. XBrackets 括号自动补全(引号,方括号)

  2. Linux网卡命名enp3s0说明

    用了很多年Linux的我在升级Ubuntu 16.04之后竟然发现我的以太网卡的名字竟然不是eth0,变成了enp3s0,每次想要修改什么配置,都要先ifconfig查一下网卡名,真是让我很郁闷! 去 ...

  3. js实现二分搜索法

    二分搜索法: 也称折半搜索,是一种在有序数组中查找特定元素的搜索算法. 实现步骤: 1. 首先从数组中间开始查找对比,若相等则找到,直接返回中间元素的索引. 2. 若查找值小于中间值,则在小于中间值的 ...

  4. 论坛模块__发帖时使用FCKeditor

    论坛模块__发帖时使用FCKeditor 测试 <html> <head> <meta http-equiv="content-type" conte ...

  5. Echarts 的悬浮框tooltip显示自定义格式化

    最近做的项目用到echarts雷达图,但是由于地市过多,遇到悬浮框显示问题被遮住 如图: 可以看到上面从兴安开始数据就被遮住了 为了解决这个被遮住的悬浮框,达到tooltip自定义格式 完成后的效果如 ...

  6. centos中调整tmpfs分区的大小

    tmpfs是Linux/Unix系统上的一种基于内存的文件系统.tmpfs可以使用系统的内存或swap分区来存储文件.由此可见,tmpfs主要存储暂存的文件. tmpfs默认的大小是RM的一半,假如你 ...

  7. 170413、怎么精确理解leader布置的任务(技术外的话)

    [缘起] 和一个同学交代了一个很重要的事情,结果执行的结果并不是自己想要的,微微生气之余,简单的聊聊“如何精确的理解leader布置的任务”. [员工角度的潜在困惑] 1)leader讲了很多,脑子记 ...

  8. Python IDE软件PyCharm通用激活方法

    1,打开软件点击help-Register 2,输入地址http://xidea.online激活

  9. JAR包中的MANIFEST.MF文件详解以及编写规范

    参考百度百科的解释如下: http://baike.baidu.com/item/MANIFEST.MF MANIFEST.MF:这个 manifest 文件定义了与扩展和包相关的数据.单词“mani ...

  10. timepicker php strtotime 8hours

    https://jqueryui.com/datepicker/ w timepicker datepicker 日期 时间 选择器 <script src="static/jquer ...