自定义 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 ...
随机推荐
- SpringMVC5中,@ModelAttribute注解详解
看这个注解的前提最好熟悉一下SpringMVC的model组件,该注解可以有五种使用方式: ①②③为 @ModelAttribute 跟@RequestMapping 分开修饰方法,被@ModelAt ...
- linxu 查看运行日志
journalctl - 检索 systemd 日志 journalctl 可用于检索 systemd(1) 日志(由 systemd-journald.service(8) 记录). 如果不带任何参 ...
- 动手写一个LRU缓存
前言 LRU 是 Least Recently Used 的简写,字面意思则是最近最少使用. 通常用于缓存的淘汰策略实现,由于缓存的内存非常宝贵,所以需要根据某种规则来剔除数据保证内存不被占满. 在r ...
- 5、前端--js常量、变量、5种基本数据类型(number string boolean undefined object)、运算符、流程控制、三元运算符、函数、自定义对象、内置对象、BOM操作
变量与常量 在JS中声明变量需要使用关键字 老版本 var(全部都是全局变量) 新版本 let(可以声明局部变量) # 推荐使用let(其实问题不大) 在JS中声明常量也需要使用关键字 const # ...
- KC705E增强版基于FMC接口的 Kintex-7 XC7K325T PCIeX8 接口卡
一.板卡概述 本板卡基于Xilinx公司的FPGAXC7K325T-2FFG900 芯片,pin_to_pin兼容FPGAXC7K410T-2FFG900 ,支持PCIeX8.64bit DDR3容量 ...
- JavaScript ==原理与分析
JavaScript原始类型 ECMAScript 有 5 种原始类型(primitive type),即 Undefined.Null.Boolean.Number 和 String. typeof ...
- 防世界之NaNNaNNaNNaN-Batman
题目: 只有一个附件,下载解压放到桌面. web应该是个html文件,改下后缀打开看看 发现就一个框和按钮,测试发现也没注入点,应该不是考sql.打开源码查看一下,发现是个js脚本,但是,代码是乱码, ...
- 在 TIME_WAIT 状态的 TCP 连接,收到 SYN 后会发生什么?
周末跟朋友讨论了一些 TCP 的问题,在查阅<Linux 服务器高性能编程>这本书的时候,发现书上写了这么一句话: 书上说,处于 TIME_WAIT 状态的连接,在收到相同四元组的 SYN ...
- 【C# .Net GC】后台垃圾回收
在后台垃圾回收 (GC) 中,在进行第 2 代回收的过程中,将会根据需要收集暂时代(第 0 代和第 1 代). 后台垃圾回收是在一个或多个专用线程上执行的,具体取决于它是后台还是服务器 GC,它只适用 ...
- 【C# 线程】IOCP IO完成端口-Windows系统下常见的7种I/O模型
一.IOCP(I/O Completion Ports)简介 要实现异步通信,必须要用到一个很风骚的I/O数据结构 ,叫重叠结构"Overlapped",Window ...