Python入门自学进阶-Web框架——26DjangoAdmin项目应用-数据记录操作
Posted kaoa000
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python入门自学进阶-Web框架——26DjangoAdmin项目应用-数据记录操作相关的知识,希望对你有一定的参考价值。
对于每个表显示的数据,点击其中一条,进入这条数据的修改页面,显示此条数据的具体内容,并提供修改、删除等功能。主要是ModelForm的应用。
一、记录数据修改
首先是路由项的添加,点击一条记录后,进入相应的记录显示修改页面:
urls.py中增加路由项:path('<str:app_name>/<str:table_name>/<int:id_num>/change/',views.rec_obj_change,name='rec_change'),
视图函数中增加对应的函数rec_obj_change:
def rec_obj_change(req,app_name,table_name,id_num):
print(app_name,table_name,id_num)
return render(req,'mytestapp/rec_change.html')
模板中增加rec_change.html:
% extends 'base.html' %
% load tags %
% block mybody %
<body>
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">我的客户管理系统</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="#"> request.user.userprofile.name </a>
</li>
</ul>
</nav>
<div class="container-fluid" style="margin-top: 20px;">
<div class="flex-column bd-highlight">
<div class="p-3 mb-2 bg-info text-white">数据表:</div>
<div class="p-2 mb-2">数据项111</div>
</div>
</div>
</body>
% endblock %
在前一步骤显示数据的基础上,将数据的第一列增加上a标签,最终形成<str:app_name>/<str:table_name>/<int:id_num>/change/格式的href,跳转到rec_change.html页面。
首先改造数据显示列表,将第一列做成a标签:
@register.simple_tag
def build_table_row(obj,admin_class,url_path):
row_ele = ""
print("===///:::",obj)
for row_data in obj:
row_ele = row_ele +"<tr>"
for index_ele,column in enumerate(admin_class.list_display):
field_obj = row_data._meta.get_field(column)
if field_obj.choices:
column_data = getattr(row_data,"get_%s_display"%column)()
else:
column_data = getattr(row_data,column)
field_obj1 = getattr(row_data,column)
if hasattr(field_obj1,'values'):
s = ""
dic1 = field_obj1.values()[0]
print(type(dic1),dic1)
for v in dic1.values():
s = s + str(v) + ';'
column_data = s
if index_ele == 0: # 若果是第一列,则加上a标签,可以跳转到修改页
row_ele += '<td colspan="6"><a href="%s%s/change/">%s</a></td>'%(url_path,row_data.id,column_data)
else:
row_ele += "<td colspan='6'>%s</td>"%column_data
row_ele = row_ele + "</tr>"
print(row_ele)
return mark_safe(row_ele)
在遍历admin_class中的list_display时,使用enumerate,生成index及数据,对于index为0的,即显示数据的第一列加上a标签,a标签的href为mytestapp/customer/3/change/格式,其中3是id的值。这样,点击对应的数据的第一列,就进入rec_change.html页。
rec_change.html页对每条记录的数据进行详细显示,并提供修改、验证功能。
修改、验证用到前面学过的ModelForm类,自动生成前端标签,提供基本的验证功能。
温习一下ModelForm:
class CustomerModelForm(forms.ModelForm):
class Meta:
model = models.Customer
fields = "__all__"
def rec_obj_change(req,app_name,table_name,id_num):
print(app_name,table_name,id_num)
obj = CustomerModelForm()
return render(req,'mytestapp/rec_change.html','obj':obj)
前端将“数据项111”改成 obj ,将显示如下
先定义CustomerModelForm类,在视图函数中生成这个类的实例,返回给前端,前端就能自动生成对应的标签,并具有基本的验证功能。ModelForm类主要是指定Meta中的model以及fields。
因为我们是针对所有的Model类,即所有的表都会进行数据修改,不能定义一个Model,就定义一个ModelForm,这里要动态生成ModelForm。
def create_model_form(req,admin_class):
# 动态生成ModelForm类,主要使用type函数
class Meta:
model = admin_class.model
fields = "__all__"
model_form_class = type("DynamicModelForm",(ModelForm,),'Meta':Meta)
# setattr(model_form_class,'Meta',Meta) # 以这种方式给动态生成的类加Meta不好用
return model_form_class
def rec_obj_change(req,app_name,table_name,id_num):
admin_class = mytestapp_admin.enable_admins[app_name][table_name]
model_form_class = myutils.create_model_form(req,admin_class)
obj = admin_class.model.objects.get(id = id_num)
form_obj = model_form_class(instance=obj)
return render(req,'mytestapp/rec_change.html','obj':form_obj)
前端:
<div class="container-fluid" style="margin-top: 20px;">
<div class="flex-column bd-highlight">
<div class="p-3 mb-2 bg-info text-white">数据表:</div>
<div class="p-2 mb-2">
obj.as_ul
</div>
</div>
</div>
obj.as_ul 显示:
美化一下:
<div class="form-group row">
% for f in obj %
<label for="inputPassword" class="col-sm-2 col-form-label"> f.label </label>
<div class="col-sm-10">
f
</div>
% endfor %
</div>
将上述字段包入form标签中,并增加修改保存按钮:
<form role="form" method="post">% csrf_token %
<div class="form-group row">
% for f in obj %
% if f.field.required % <!-- 字段是否必填,如果必填,标签加粗显示 -->
<label class="col-sm-2 col-form-label"><b> f.label </b></label>
% else %
<label class="col-sm-2 col-form-label"> f.label </label>
% endif %
<div class="col-sm-10">
f
</div>
% endfor %
</div>
<div class="form-group row" style="float: right;margin-right: 100px">
<div class="col-sm-10 pull-right">
<button type="submit" class="btn btn-success pull-right">Save</button>
</div>
</div>
</form>
使用post方法提交到本页面,后端进行修改保存
后端修改:
def rec_obj_change(req,app_name,table_name,id_num):
admin_class = mytestapp_admin.enable_admins[app_name][table_name]
model_form_class = myutils.create_model_form(req,admin_class)
obj = admin_class.model.objects.get(id=id_num)
if req.method == "POST":
form_obj = model_form_class(req.POST,instance=obj)
# ModelForm参数为一个时,是新建一条记录,当有两个参数时,就是修改
# POST方法就是进行记录修改的,所以需要两个参数,第二个是instance=参数
if form_obj.is_valid():
form_obj.save()
else: # 这是GET请求,所以是新建一个ModelForm,进行显示
form_obj = model_form_class(instance=obj)
return render(req,'mytestapp/rec_change.html','obj':form_obj)
在生成ModelForm时,就对相关字段添加前端生成标签时的样式进行设置,即进行class属性设置:
def create_model_form(req,admin_class):
# 动态生成ModelForm类,主要使用type函数
def __new__(cls, *args, **kwargs):
# 対生成ModelForm中的字段添加前端样式,即在前端自动生成标签时带上class属性
cls.base_fields['qq'].widget.attrs['class'] = 'form-control'
return ModelForm.__new__(cls)
class Meta:
model = admin_class.model
fields = "__all__"
model_form_class = type("DynamicModelForm",(ModelForm,),'Meta':Meta)
setattr(model_form_class,'__new__',__new__) # 以这种方式给动态生成的类加__new__方法
return model_form_class
base_fields是所有字段的列表。
二、记录数据增加
修改数据显示前端文件,增加一个Add标签:
<div class="p-3 mb-2 bg-info text-white">表数据管理: model_class_name
<div style="float: right"><a class="pull-right" href=" url_path add/">Add</a> </div>
</div>
增加路由项:
path('<str:app_name>/<str:table_name>/add/',views.rec_obj_add,name='rec_add'),
增加视图函数rec_obj_add:
def rec_obj_add(req,app_name,table_name):
admin_class = mytestapp_admin.enable_admins[app_name][table_name]
model_form_class = myutils.create_model_form(req, admin_class)
if req.method == "POST":
form_obj = model_form_class(req.POST)
# ModelForm参数为一个时,是新建一条记录,POST方法提交,是新建一条记录
if form_obj.is_valid():
form_obj.save()
return redirect(req.path.replace("/add/","/")
else: # 这是GET请求,所以是新建一个空ModelForm
form_obj = model_form_class()
return render(req,"mytestapp/rec_add.html",'obj':form_obj,'model_name':admin_class.model.__name__)
前端显示页面rec_add.html:
% extends 'base.html' %
% load tags %
% block mybody %
<body>
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">我的客户管理系统</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="#"> request.user.userprofile.name </a>
</li>
</ul>
</nav>
<div class="container-fluid" style="margin-top: 20px;">
<div class="flex-column bd-highlight">
<div class="p-3 mb-2 bg-info text-white">数据表: model_name ————数据添加</div>
<div class="p-2 mb-2" style="margin-left: 30px"> obj.errors </div>
<div class="p-2 mb-2" style="margin-left: 30px">
<form role="form" method="post">% csrf_token %
<div class="form-group row">
% for f in obj %
% if f.field.required %
<label class="col-sm-2 col-form-label"><b> f.label </b></label>
% else %
<label class="col-sm-2 col-form-label"> f.label </label>
% endif %
<div class="col-sm-10">
f
</div>
% endfor %
</div>
<div class="form-group row" style="float: right;margin-right: 100px">
<div class="col-sm-10 pull-right">
<button type="submit" class="btn btn-success pull-right">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
% endblock %
三、记录数据修改、增加中多选下拉框不同样式设计
在Django的admin应用中,如果配置了filter_horizontal = ['tags'],则显示如下的样式:
自己实现类似的功能。
首先在admin_class类中增加配置filter_horizontal = ['tags']
在前端显示表字段时,要进行判断,即如果字段值在filter_horizontal中,就要使用上述的格式显示。
首先要写两个标签,来获取多选下拉框中没有被选中的项和已经选中的项的数据集:
@register.simple_tag
def get_m2m_obj_list(admin_class,f): # 获取没有被选择的所有项
field_obj = getattr(admin_class.model,f.name)
all_obj_list = field_obj.rel.model.objects.all()
selected_list = f.initial
print('================================all=======')
print(all_obj_list)
print('=========================selected=======')
print(selected_list)
noselect_list = []
for obj in all_obj_list:
if obj not in selected_list:
noselect_list.append(obj)
return noselect_list
@register.simple_tag
def get_m2m_selected_list(form_obj,f): # 获取所有已选择的项
selected_obj = getattr(form_obj.instance,f.name)
print(selected_obj.all())
return selected_obj.all()
前端循环,填充进入不同的下拉框:
<div class="p-2 mb-2" style="margin-left: 30px">
<form role="form" method="post">% csrf_token %
<div class="form-group row">
% for f in form_obj %
% if f.field.required %
<label class="col-sm-2 col-form-label"><b> f.label </b></label>
% else %
<label class="col-sm-2 col-form-label"> f.label </label>
% endif %
<div class="col-sm-10" style="padding-top: 5px">
<div class="row">
% if f.name in admin_class.filter_horizontal %
<div class="col-md-2" >
% get_m2m_obj_list admin_class f as m2m_obj_list %
<select id="mynoselect" multiple class="select-box">
% for obj in m2m_obj_list %
<option value=" obj.id "> obj </option>
% endfor %
</select>
</div>
<div class="col-md-1">
===》<br>
《===
</div>
<div class="col-md-2">
<select id="myselected" multiple class="select-box">>
% get_m2m_selected_list form_obj f as selected_list %
<!-- 上面是使用自定义标签获取已选择项数据-->
<!-- 下面是经过测试,使用f的initial也能获取到,不需要再定义标签 -->
% for obj in f.initial %
<option value=" obj.id "> obj </option>
% endfor %
</select>
</div>
% else %
f
% endif %
</div>
</div>
% endfor %
</div>
<div class="form-group row" style="float: right;margin-right: 100px">
<div class="col-sm-10 pull-right">
<button type="submit" class="btn btn-success pull-right">Save</button>
</div>
</div>
</form>
</div>
关键点是如何将对应记录的多选下拉框填充上正确的数据,model类的字段的rel.model属性指向字段关联的外键表对应的model,通过这个model,即可查询出下拉框的所有数据,在此基础上,找出未选择和已选择数据。ModelForm每个字段的initial属性,也指出了已选择的数据,可以直接在前端使用。最终样式:
数据填上后,就是要前端实现双击数据实现数据在两个多选下拉框之间移动,这主要是前端技术,与python就无关了:
<form role="form" method="post">% csrf_token %
<div class="form-group row">
% for f in form_obj %
% if f.field.required %
<label class="col-sm-2 col-form-label"><b> f.label </b></label>
% else %
<label class="col-sm-2 col-form-label"> f.label </label>
% endif %
<div class="col-sm-10" style="padding-top: 5px">
<div class="row">
% if f.name in admin_class.filter_horizontal %
<div class="col-md-2" >
% get_m2m_obj_list admin_class f as m2m_obj_list %
<select id="noselected_ f.name " multiple class="select-box">
% for obj in m2m_obj_list %
<option value=" obj.id " ondblclick="MoveToSelected(this,'selected_ f.name ','noselected_ f.name ')"> obj </option>
% endfor %
</select>
</div>
<div class="col-md-1">
===》<br>
《===
</div>
<div class="col-md-2">
<select id="selected_ f.name " multiple class="select-box" name="f.name" my_id="selectedalloption">
% get_m2m_selected_list form_obj f as selected_list %
<!-- 上面是使用自定义标签获取已选择项数据-->
<!-- 下面是经过测试,使用f的initial也能获取到,不需要再定义标签 -->
% for obj in f.initial %
<option value=" obj.id " ondblclick="MoveToSelected(this,'noselected_ f.name ','selected_ f.name ')"> obj </option>
% endfor %
</select>
</div>
% else %
f
% endif %
</div>
</div>
% endfor %
</div>
<div class="form-group row" style="float: right;margin-right: 100px">
<div class="col-sm-10 pull-right">
<button type="submit" class="btn btn-success pull-right">Save</button>
</div>
</div>
</form>
<script>
function MoveToSelected(ele,target_id,self_id)
var opt_ele = '<option value="' + $(ele).val() +'"' + ' ondblclick=MoveToSelected(this,"' +self_id +'","' +target_id + '")>' + $(ele).text() + '</option>';
console.log(opt_ele)
$("#" + target_id).append(opt_ele);
$(ele).remove()
</script>
然后在提交的时候,需要再对应选择的多选下拉框中的数据做全部选中状态,这样提交时才能带过去数据:<form role="form" method="post" οnsubmit="return SelectedAll();">,增加onsubmit
function SelectedAll()
$("select[my_id='selectedalloption'] option").each(function ()
$(this).prop("selected",true)
);
return true
有一个BUG,在前端使用f.initial遍历已选数据项时,提交后数据没有刷新,修改使用自定义标签
<form role="form" method="post" onsubmit="return SelectedAll();">% csrf_token %
<div class="form-group row">
% for f in form_obj %
% if f.field.required %
<label class="col-sm-2 col-form-label"><b> f.label </b></label>
% else %
<label class="col-sm-2 col-form-label"> f.label </label>
% endif %
<div class="col-sm-10" style="padding-top: 5px">
<div class="row">
% if f.name in admin_class.filter_horizontal %
<div class="col-md-2" >
% get_m2m_obj_list admin_class f form_obj as m2m_obj_list %
<select id="noselected_ f.name " multiple class="select-box" name=" f.name ">
% for obj in m2m_obj_list %
<option value=" obj.id " ondblclick="MoveToSelected(this,'selected_ f.name ','noselected_ f.name ')"> obj </option>
% endfor %
</select>
</div>
<div class="col-md-1">
===》<br>
《===
</div>
<div class="col-md-2">
<select my_id="selectedalloption" id="selected_ f.name " multiple class="select-box" name=" f.name ">
% get_m2m_selected_list form_obj f as selected_list %
<!-- 上面是使用自定义标签获取已选择项数据-->
<!-- 下面是经过测试,使用f的initial也能获取到,不需要再定义标签 -->
% for obj in selected_list %
<option value=" obj.id " ondblclick="MoveToSelected(this,'noselected_ f.name ','selected_ f.name ')"> obj </option>
% endfor %
</select>
</div>
% else %
f
% endif %
</div>
</div>
% endfor %
</div>
<div class="form-group row" style="float: right;margin-right: 100px">
<div class="col-sm-10 pull-right">
<button type="submit" class="btn btn-success pull-right">Save</button>
</div>
</div>
</form>
@register.simple_tag
def get_m2m_obj_list(admin_class,f,form_obj): # 获取没有被选择的所有项
field_obj = getattr(admin_class.model,f.name)
all_obj_list = field_obj.rel.model.objects.all()
if field_obj.instance.id:
selected_list = getattr(form_obj.instance,f.name).all()
else: # 对于新增记录,没有id,直接返回所有
return all_obj_list
print('================================all=======')
print(all_obj_list)
print('=========================selected=======')
print(selected_list)
noselect_list = []
for obj in all_obj_list:
if obj not in selected_list:
noselect_list.append(obj)
print('=====没有选择的====',noselect_list)
return noselect_list
@register.simple_tag
def get_m2m_selected_list(form_obj,f): # 获取所有已选择的项
selected_obj = getattr(form_obj.instance,f.name)
print('标签,已选:',selected_obj.all())
return selected_obj.all()
以上是关于Python入门自学进阶-Web框架——26DjangoAdmin项目应用-数据记录操作的主要内容,如果未能解决你的问题,请参考以下文章
Python入门自学进阶-Web框架——18FormModelForm
Python入门自学进阶-Web框架——18FormModelForm
Python入门自学进阶-Web框架——20Django其他相关知识2