自定义 Django admin 组件
摘要:学习 Django admin 组件,仿照源码的逻辑,自定义了一个简易的 stark 组件,实现类似 admin 的功能。
可自动生成 url 路由,对于model 有与之相应的配置类对象,可进行增、删、改、查的操作。
通过继承 ModelStark 配置类,重写类参数和方法,可自定义查找显示信息,模糊查询字段,批量操作方法等
那么让我们开始吧~
思路:
在启动时------> 1、遍历所有app的stark.py文件,注册model
2、自动生成,注册的model对应的url
执行时 -------> 1、url路由分发,调用配置类中相应的方法
2、在 ModelStark 配置类中处理数据、渲染页面
3、返回 Responce 对象
具体实现:
一、先创建一个组件 stark

二、并在组件的 app.py 文件中,写下遍历的其他APP的方法
from django.apps import AppConfig
from django.utils.module_loading import autodiscover_modules class StarkConfig(AppConfig):
name = 'stark' def ready(self):
#这句表示 自动遍历所有app 的 stark.py 文件
autodiscover_modules('stark')
注意:在settings.py 文件中 ,注册 stark 组件时,一定要写上,只写 ‘stark’ 的话,不会执行ready的
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
"stark.apps.StarkConfig"
]
三、 在 组件的 stark.py文件中
定义一个 单例的 全局类 StrakSite ,该类控制以及路由的生成,并调用配置类,生成第二级的路由
class StrakSite:
def __init__(self, name='stark'):
self.name = name
# 注册了的所有表与表的配置类对象
self._registry = {} def register(self, model, model_config=None):
if not model_config:
#如果没有自定义配置类,就用父类的配置类
model_config = ModelStark self._registry[model] = model_config(model) # URL分发 第一级
def get_urls(self):
temp = []
for model, model_config in self._registry.items():
app_label = model._meta.app_label
model_name = model._meta.model_name
temp.append(url(r'^%s/%s/' % (app_label, model_name), model_config.urls))
return temp @property
def urls(self):
#路由分发
return self.get_urls(), None, None
# 单例模式 所有的model共享同一个StrakSite对象
site = StrakSite()
配置类,在python代码里生成前端HTML标签:
class ModelStark:
list_display = ['__str__'] # 列表时,定制显示的列。
modelfoem_class = [] # 自定义配置类
list_display_links = [] # list页面点击可以跳转到编辑页面的字段
search_fields = [] # 搜索框,自定义的搜索字段
actions = [] # 自定义批处理函数列表
list_filter = [] # 自定义 右侧筛选字段 def __init__(self, model):
self.model = model
self.app_label = model._meta.app_label
self.model_name = model._meta.model_name meta= model._meta
print(meta) # 生成复选框
def checkbox_col(self, obj=None, is_header=False):
'''
生成复选框
:param is_hander:
:return:
'''
if is_header:
return '选择'
# _url=
return mark_safe('<input type="checkbox" name="selected_action" value="%s">' % obj.pk) # 反向解析,获取URL
def get_url(self, type, id=None):
'''
反向解析,获取URL
:param type:
:param id:
:return:
'''
name = '%s_%s_%s' % (self.app_label, self.model_name, type)
if id:
return reverse(name, args=(id,))
else:
return reverse(name) # 生成删除按钮
def del_col(self, obj=None, is_header=False):
'''
生成删除按钮
:param is_hander:
:return:
'''
if is_header:
return '删除'
_url = self.get_url('delete', obj.pk)
return mark_safe('<a id="%s_delete" href="%s">删除</a>' % (self.model_name, _url)) # 生成添加a标签
def add_col(self):
'''
生成添加按钮
:param is_hander:
:return:
'''
_url = self.get_url('add')
return mark_safe('<a id="%s_add" href="%s"><span class="glyphicon glyphicon-plus pull-lift"></span></a>'
% (self.model_name, _url)) # 生成编辑按钮
def edit_col(self, obj=None, is_header=False):
'''
生成编辑按钮
:param is_hander:
:return:
'''
if is_header:
return '编辑'
_url = self.get_url('edit', obj.pk)
return mark_safe('<a id="%s_edit" href="%s">编辑</a>' % (self.model_name, _url)) # 按照表格显示需要的列格式,将每个字段按顺序放进列表里
def get_new_list_display(self):
new_display = []
new_display.extend(self.list_display)
# 这里一定要传 类名.方法名 如果用self.方法名的话
# 调用的时候 这里传入的方法会自动传self值,而我们自定义的display方法并不会,所以在通用方法处理时会报错
new_display.insert(0, ModelStark.checkbox_col)
new_display.extend([ModelStark.edit_col, ModelStark.del_col])
return new_display # 查
def list(self, request): self.data_list = self.model.objects.all()
# 列表展示页面类
show = ShowList(self, request, self.data_list) # 如果有自定义查询字段且输入了查询内容 该操作要写在分页之前,因为对data_list进行了修改
self.search_info = request.GET.get("search")
if self.search_fields and self.search_info:
show.get_search(self.search_info) # 批量操作
action_list = show.get_actions()
if request.method == "POST":
action_info = request.POST.get("action")
if self.actions and action_info:
id_list = request.POST.getlist("selected_action")
data = self.model.objects.filter(pk__in=id_list)
action = getattr(self, action_info)
action(request,data) # 生成左侧筛选栏 该操作要写在分页之前,因为对data_list进行了修改
filter_html = None
if self.list_filter:
filter_html = show.get_filter() # 分页HTML
page_html = show.get_page_html()
# 表头
title_list = show.get_hander()
# 表格内容
table_list = show.get_body()
# 生成 添加a标签
add_opp = self.add_col() return render(request, 'stark/list.html',
{'title_list': title_list,
'table_list': table_list,
'page_html': page_html,
'add_opp': add_opp,
'action_list': action_list,
'search_about': (self.search_fields, self.search_info),
'filter_html': filter_html,
}) # 增
def add(self, request): if request.method == 'GET':
# 生成modelform对象
form = self.modelform_class() from django.forms.boundfield import BoundField
# bfield 是 BoundField类的子类
for bfield in form:
if isinstance(bfield.field, ModelChoiceField):
bfield.is_pop = True
# 得到该字段的关联表
filed_rel_model = self.model._meta.get_field(bfield.name).rel.to
app_label = filed_rel_model._meta.app_label
model_name = filed_rel_model._meta.model_name
name = '%s_%s_add' % (app_label, model_name)
_url = reverse(name)
bfield.url = _url + "?pop_back_id=" + bfield.auto_id
return render(request, 'stark/add.html', {'form': form})
else:
# 得到带参数的modelform对象
form = self.modelform_class(request.POST) from django.forms.boundfield import BoundField
# bfield 是 BoundField类的子类
for bfield in form:
if isinstance(bfield.field, ModelChoiceField):
bfield.is_pop = True
# 得到该字段的关联表
filed_rel_model = self.model._meta.get_field(bfield.name).rel.to
app_label = filed_rel_model._meta.app_label
model_name = filed_rel_model._meta.model_name
name = '%s_%s_add' % (app_label, model_name)
_url = reverse(name)
bfield.url = _url + "?pop_back_id=" + bfield.auto_id # 校验页面填值
if form.is_valid():
# 保存添加的数据
obj = form.save() try:
pop_back_id = request.GET.get('pop_back_id')
if pop_back_id:
return render(request, 'stark/close.html',
{'pop_back_id': pop_back_id, 'text': str(obj), 'pk': obj.pk})
except Exception:
pass # 跳转回list页面
_url = self.get_url('list')
return redirect(_url)
else:
return render(request, 'stark/add.html', {'form': form}) # 删
def delete(self, request, id):
self.model.objects.get(pk=id).delete()
_url = self.get_url('list')
return redirect(_url) # 改
def edit(self, request, id):
model_obj = self.model.objects.get(pk=id)
if request.method == 'GET':
# 生成一个带有model对象内容的modelform对象
form = self.modelform_class(instance=model_obj)
return render(request, 'stark/edit.html', {'form': form})
else:
form = self.modelform_class(request.POST, instance=model_obj)
if form.is_valid():
form.save()
_url = self.get_url('list')
return redirect(_url)
else:
return render(request, 'stark/edit.html', {'form': form})
def extra_urls(self):
return [] # URL分发 第二级
def get_urls(self):
temp = [] # 增
temp.append(url(r'add/$', self.add,
name='%s_%s_add' % (self.app_label, self.model_name)))
# 删
temp.append(url(r'(?P<id>\d+)/delete/$', self.delete,
name='%s_%s_delete' % (self.app_label, self.model_name)))
# 改
temp.append(url(r'(?P<id>\d+)/edit/$', self.edit,
name='%s_%s_edit' % (self.app_label, self.model_name)))
# 查
temp.append(url(r'$', self.list,
name='%s_%s_list' % (self.app_label, self.model_name))) temp.extend(self.extra_urls()) print(temp) return temp @property
def urls(self):
return self.get_urls(), None, None # 获取modelform类
@property
def modelform_class(self):
if self.modelfoem_class:
return self.modelfoem_class
else:
class ModelFormClass(forms.ModelForm):
class Meta:
model = self.model
fields = '__all__' return ModelFormClass
因为查询页面太大,所以单独抽成一个ShowList类,其中封装了list页面的表头、表格内容、分页、搜索栏、批量操作栏、标签查找栏等
class ShowList(object):
def __init__(self, conf_obj, request, data_list):
self.conf_obj = conf_obj
self.data_list = data_list
self.new_list_display = self.conf_obj.get_new_list_display()
self.request = request # 获取列表的表头
def get_hander(self):
# 表头
title_list = []
for field in self.new_list_display:
if isinstance(field, str):
# 如果没有自己传list_display进来,默认的是'__str__',则打印大写的表名
if field == '__str__':
field = self.conf_obj.model_name.upper() else:
# 获取字段的对象
field_obj = self.conf_obj.model._meta.get_field(field)
# 从字段对象中获取字段的verbose_name,在model模型类里写的
field = field_obj.verbose_name
else:
# 如果不是表字段的话,执行对应的方法 eg: edit delete
field = field(self.conf_obj, is_header=True)
title_list.append(field)
return title_list # 获取表格内容
def get_body(self):
table_list = []
for obj in self.data_list:
list_info = []
for field in self.new_list_display:
if isinstance(field, str):
try:
if self.conf_obj.model._meta.get_field(field).choices:
field_val = getattr(obj, 'get_%s_display' %field )
else:
field_val = getattr(obj, field)
if field in self.conf_obj.list_display_links:
_url = self.conf_obj.get_url('edit', obj.pk)
field_val = mark_safe(
'<a id="%s_edit" href="%s">%s</a>' % (self.conf_obj.model_name, _url, field_val))
except Exception as e:
# __str__ 的字段
field_val = getattr(obj, field)
else:
field_val = field(self.conf_obj, obj=obj)
list_info.append(field_val)
table_list.append(list_info)
return table_list # 获取自定义分页的THML
def get_page_html(self):
# 来源url
url_prefix = self.request.get_full_path()
# 数据总量
total_num = self.data_list.count()
# 当前页
current_page = self.request.GET.get('page')
# 生成页面对象
self.page_obj = Page(total_num, current_page, url_prefix, per_page=10, show_page_num=9)
# 得到当前页展示的数据列表
self.data_list = self.data_list[self.page_obj.data_start:self.page_obj.data_end]
# 得到对应的分页HTML
page_html = self.page_obj.page_html()
return page_html # 搜索栏操作
def get_search(self, search_info):
search_condition = Q()
search_condition.connector = "or"
for field in self.conf_obj.search_fields:
# 模糊查询
search_condition.children.append((field + "__icontains", search_info))
self.data_list = self.conf_obj.model.objects.filter(search_condition) # 自定义批量操作
def get_actions(self):
temp = []
for action in self.conf_obj.actions:
temp.append({
'name': action.__name__,
'desc': action.desc
})
return temp # 右侧筛选栏
def get_filter(self): self.params = copy.deepcopy(self.request.GET)
self.params['page'] = None
self.params.pop("page") filter_dic = {}
filter_condition = Q()
for k, v in self.params.items():
if k in self.conf_obj.list_filter and v != 'all':
filter_dic[k] = v
filter_condition.children.append((k, v))
self.data_list = self.data_list.filter(filter_condition) temp = {}
for filter in self.conf_obj.list_filter:
filter_field_obj = self.conf_obj.model._meta.get_field(filter)
val_html = []
queryset = filter_field_obj.rel.to.objects.all() self.params[filter] = 'all'
val_html.append('<a href="?%s">ALL</a>' % (self.params.urlencode())) for obj in queryset:
self.params[filter] = obj.pk
if (filter in filter_dic) and (filter_dic[filter] == str(obj.pk)):
val_html.append('<a class="red href="?%s">%s</a>' % (self.params.urlencode(), str(obj)))
else:
val_html.append('<a href="?%s">%s</a>' % (self.params.urlencode(), str(obj))) temp[filter] = val_html
return temp
ShowList
四、HTML页面和,静态文件,以及工具文件(mypage分页)也都放在组件里,在查找时,最外层的找不到,会直接到组件里找对应的文件,所以可以放在组件里

Django 之 modelForm (edit.html页面的编写)
使用方法:
在app的 stark.py 文件里,
1、 注册 model,生成对应的url
2、 #自定义配置类
list_display = ["title", "price", "publish"] #配置该表显示的字段列
list_display_links = ["title"] #配置点击哪些字段可以跳转到编辑页面
search_fields = ["price"] #配置哪些字段可以作为可模糊的字段
list_filter = ["publish"] #右侧边分类栏,可分类现实数据
#自定义 批量操作方法
from django.shortcuts import HttpResponse, render, redirect from app01.models import *
from django.http import JsonResponse #注册 model,生成对应的url
site.register(User)
site.register(Role) #自定义配置类
class BookConfig(ModelStark):
list_display = ["title", "price", "publish"] #配置该表显示的字段列
list_display_links = ["title"] #配置点击哪些字段可以跳转到编辑页面
search_fields = ["price"] #配置哪些字段可以作为可模糊的字段
list_filter = ["publish"] #右侧边分类栏,可分类现实数据 #自定义 批量操作方法
def delete_action(self, queryset):
queryset.delete() #批量操作方法的名称
delete_action.desc = "批量删除" def init_price_action(self, queryset):
queryset.update(price=100.0) init_price_action.desc = "批量初始化" #批量操作方法列表
actions = [delete_action, init_price_action] #注册该类,将对应的配置类传入
site.register(models.Book, BookConfig)
#自定义列
#自定义视图
#自定义url
class StudentConfig(ModelStark):
#自定义列 方法
def class_display(self, obj=None, is_header=False):
if is_header:
return "已报班级"
temp = []
for i in obj.class_list.all():
temp.append(str(i))
return ",".join(temp) #自定义列
def score_display(self, obj=None, is_header=False):
if is_header:
return "学习详情"
return mark_safe("<a href='/stark/app01/student/score/%s'>学习详情</a>" % obj.pk) #自定义列 方法列表
list_display = ["customer", class_display, score_display]
#自定义视图
def score_view(self, request, sid): if request.is_ajax():
sid = request.GET.get("sid")
cid = request.GET.get("cid")
# 查询学生sid在班级cid在的所有成绩
ret = StudentStudyRecord.objects.filter(student=sid, classstudyrecord__class_obj=cid).values_list(
"classstudyrecord__day_num", "score")
print("ret", ret) data = [["day%s" % i[0], i[1]] for i in ret]
return JsonResponse(data, safe=False) student_obj = Student.objects.filter(pk=sid).first()
class_list = student_obj.class_list.all() return render(request, "score_view.html", locals()) #自定义url
def extra_urls(self):
temp = []
temp.append(
url("score/(\d+)", self.score_view)
) return temp site.register(Student, StudentConfig)
自定义 Django admin 组件的更多相关文章
- Django admin 组件 原理分析与扩展使用 之 sites.py (一)
一 . 前言 Django 提供了admin 组件 为项目提供基本的管理后台功能(对数据表的增删改查). 本篇文章通过 admin源码 简单分析admin 内部原理 ,扩展使用方式,为以后进行定制和自 ...
- Django admin组件使用
ADMIN 组件 介绍 admin 组件实现了更方便的WEB后台数据管理方式 settings.py 中第一个组件就是 : INSTALLED_APPS = [ 'django.contrib.adm ...
- Django admin组件源码流程
admin 组件 Django 自带的用户后台组件 用于用户便携的操作 admin 组件核心 启动 注册 设计url 启动核心代码 每个app 通过 apps.py 扫描 admin.py 文件 并执 ...
- 自定义django admin及其界面
1.在项目目录下新创建一个app,命名为kingadmin,在templates目录下新建kingadmin目录,用来存放相关页面的模板文件,新建一个templatetags目录,用来存放处理前端模板 ...
- Django——admin组件
Django提供了基于web的管理工具. Django自动管理工具是django.contrib的一部分.你可以在项目的settings.py中的INSTALLED_APPS看到它: # Applic ...
- day 82 Django Admin组件.
一.先建表环境 modules文件 from django.db import models # Create your models here. from django.contrib.auth.m ...
- 自定义Django Admin界面
目录 模型 注册模型 定制页面 模型 # app/model.py class Question(models.Model): question_text = models.CharField(max ...
- 自定义admin组件
配置路由 1 新建一个项目, 创建一个app01和stark应用,stark创建一个service包,并在service下创建stark.py.然后注册app 2 仿照site.py的注册代码,写st ...
- python框架之Django(13)-admin组件
使用 Django 提供了基于 web 的管理工具. Django 自动管理工具是 django.contrib 的一部分.你可以在项目的 settings.py 中的 INSTALLED_APPS ...
随机推荐
- LVS的跨网络DR实现
一.网络配置 1.1 客户端 #客户端配置 [root@client ~]#cat /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0 NAME ...
- 使用纯swift代码文件制作framework
因为最近我们公司的一个客户要求我们使用swift编写程序并且将API封装成framework的形式提供给他们,所以我就开始了swift实践之路. 程序编写完之后,我就琢磨怎么封装成framework的 ...
- 教你写Spring组件
前言 原文地址:https://www.cnblogs.com/qnlcy/p/15905682.html 一.宗旨 在如日中天的 Spring 架构体系下,不管是什么样的组件,不管它采用的接入方式如 ...
- Solution -「USACO 2020.12 P」Sleeping Cows
\(\mathcal{Description}\) Link. 有 \(n\) 个牛棚,大小为 \(t_{1..n}\),\(n\) 头奶牛,大小为 \(s_{1..n}\),奶牛只能住进不小 ...
- suse 12 二进制部署 Kubernetets 1.19.7 - 第13章 - 部署metrics-server插件
文章目录 1.13.0.创建metrics-server证书和私钥 1.13.1.生成metrics-server证书和私钥 1.13.2.开启kube-apiserver聚合配置 1.13.3.分发 ...
- 大厂偏爱的Agent技术究竟是个啥
搜索关注微信公众号"捉虫大师",后端技术分享,架构设计.性能优化.源码阅读.问题排查.踩坑实践. hello大家好,我是小楼,今天给大家分享一个关于Agent技术的话题,也是后端启 ...
- 【Java8新特性】Optional类在处理空值判断场景的应用 回避空指针异常 编写健壮的应用程序
一.序言 空值异常是应用运行时常见的异常,传统方式为了编写健壮的应用,常常使用多层嵌套逻辑判断回避空指针异常.Java8新特性之Optional为此类问题提供了优雅的解决方式. 广大程序员朋友对空值异 ...
- Ubuntu20.04 Focal Cloudimage扩容以及KVM安装的问题记录
运行Ubuntu20.04的KVM虚机遇到一些问题, 单独总结一下 镜像扩容 不能用virt-resize --expand /dev/sda1 old.qcow2 new.qcow2这样的命令, 这 ...
- 【基础篇】js对本地文件增删改查
[基础篇] js对本地文件增删改查--增 js对本地文件增删改查--删 js对本地文件增删改查--改 js对本地文件增删改查--查
- WPF中RichTextBox中添加文字的两种方法
RichTextBox控件不同于TextBox控件,后者可以直接通过其Text属性绑定或者在后台动态添加文字. (一)使用数据绑定 <RichTextBox FontSize="12& ...