stark——pop功能(admin中添加功能)
一、pop功能需要实现的功能和问题
1、如何在一对多和多对多字段后渲染 +
2、+对应的跳转路径是什么
3、保存添加记录同时,将原页面的对应的下拉菜单中添加该记录
二、window.open()方法详解
open() 方法用于打开一个新的浏览器窗口或查找一个已命名的窗口。
1、window.open()支持环境:
JavaScript1.0+/JScript1.0+/Nav2+/IE3+/Opera3+
重要事项:请不要混淆方法 Window.open() 与方法 Document.open(),这两者的功能完全不同。为了使您的代码清楚明白,请使用 Window.open(),而不要使用 open()。
2、基本语法:
window.open(URL,name,features,replace)
参数介绍:
URL:一个可选的字符串,声明了要在新窗口中显示的文档的 URL。如果省略了这个参数,或者它的值是空字符串,那么新窗口就不会显示任何文档。
name:一个可选的字符串,该字符串是一个由逗号分隔的特征列表,其中包括数字、字母和下划线,该字符声明了新窗口的名称。这个名称可以用作标记 <a> 和 <form> 的属性 target 的值。如果该参数指定了一个已经存在的窗口,那么 open() 方法就不再创建一个新窗口,而只是返回对指定窗口的引用。在这种情况下,features 将被忽略。
features:一个可选的字符串,声明了新窗口要显示的标准浏览器的特征。如果省略该参数,新窗口将具有所有标准特征。在窗口特征这个表格中,我们对该字符串的格式进行了详细的说明。
replace:一个可选的布尔值。规定了装载到窗口的 URL 是在窗口的浏览历史中创建一个新条目,还是替换浏览历史中的当前条目。支持下面的值:
true - URL 替换浏览历史中的当前条目。
false - URL 在浏览历史中创建新的条目。
3、窗口特征(Window Features)
channelmode=yes|no|1|0 是否使用剧院模式显示窗口。默认为 no。
directories=yes|no|1|0 是否添加目录按钮。默认为 yes。
fullscreen=yes|no|1|0 是否使用全屏模式显示浏览器。默认是 no。处于全屏模式的窗口必须同时处于剧院模式。
height=pixels 窗口文档显示区的高度。以像素计。
left=pixels 窗口的 x 坐标。以像素计。
location=yes|no|1|0 是否显示地址字段。默认是 yes。
menubar=yes|no|1|0 是否显示菜单栏。默认是 yes。
resizable=yes|no|1|0 窗口是否可调节尺寸。默认是 yes。
scrollbars=yes|no|1|0 是否显示滚动条。默认是 yes。
status=yes|no|1|0 是否添加状态栏。默认是 yes。
titlebar=yes|no|1|0 是否显示标题栏。默认是 yes。
toolbar=yes|no|1|0 是否显示浏览器的工具栏。默认是 yes。
top=pixels 窗口的 y 坐标。
width=pixels 窗口的文档显示区的宽度。以像素计。
4、应用示例:
<body>
<p><button class="add" onclick="foo()">+</button></p>
<script>
function foo() {
window.open("/addbook/", "", "width=400,height=400,top=100,left=200")
}
</script>
</body>
三、在一对多和多对多字段后渲染 +
1、调整form.html模板样式
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form action="" method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div style="position: relative">
<label for="">{{ field.label }}</label>
{{ field }} <span class="error pull-right">{{ field.errors.0 }}</span>
<a href="" style="position: absolute; right: -30px; top: 20px;"><span style="font-size: 28px">+</span></a>
</div>
{% endfor %}
<button type="submit" class="btn btn-default pull-right">提交</button>
</form>
</div>
</div>
</div>
注意:
(1)绝对定位以父盒子为参考点
父辈元素设置相对定位,子元素设置绝对定位,那么会以父辈元素左上角为参考点。
因此在这里a标签以父级盒子div标签为参考点。top属性时,以父盒子左上角为参考点调整位置。
2、分析利用ModelForm组件构建的表单对象
这个组件的功能就是把model和form组合起来。查看分析service/stark.py代码如下:
class ModelStark(object):
"""默认类,定制配置类"""
def get_modelform_class(self):
"""用来获取modelform类"""
if not self.modelform_class:
# 如果没有值
from django.forms import ModelForm
from django.forms import widgets as wid class ModelFormDemo(ModelForm):
class Meta:
model = self.model
fields = "__all__" return ModelFormDemo
else:
# 如果有值说明在用户已经自己定制过了,直接取值
return self.modelform_class def add_view(self, request):
"""添加页面视图"""
ModelFormDemo = self.get_modelform_class()
form = ModelFormDemo() # 实例化步骤提前不管是post请求还是get请求都会传递到模板中 if request.method == "POST":
form = ModelFormDemo(request.POST)
if form.is_valid(): # 校验字段全部合格
form.save()
return redirect(self.get_list_url()) # 跳转到当前访问表的查看页面
# 校验有错误返回页面,且包含了错误信息 return render(request, "add_view.html", locals())
ModelFormDemo是ModelForm的子类,form是ModelFormDemo实例对象。分析这个form对象:
for bound_field in form: # 拿到每一个字段
# print(type(bound_field)) # <class 'django.forms.boundfield.BoundField'> django封装的数据
# 通过这种方式查看这个数据类:from django.forms.boundfield import BoundField
print(bound_field.field)
print(type(bound_field.field))
"""
<django.forms.fields.CharField object at 0x10d73a160>
<class 'django.forms.fields.CharField'>
<django.forms.fields.DateField object at 0x10d73a1d0>
<class 'django.forms.fields.DateField'>
<django.forms.fields.DecimalField object at 0x10d73a240>
<class 'django.forms.fields.DecimalField'>
<django.forms.models.ModelChoiceField object at 0x10d73a2b0>
<class 'django.forms.models.ModelChoiceField'>
<django.forms.models.ModelMultipleChoiceField object at 0x10d73a320>
<class 'django.forms.models.ModelMultipleChoiceField'>
"""
可以看到它的类型是<class 'django.forms.boundfield.BoundField'>一种django的封装数据。查看BoundField类可知它具有field属性。
可以看出这个field属性的类型是CharField、ModelChoiceField、ModelMultipleChoiceField等Django内置字段类型。
在forms组件中,ChoiceField是负责渲染select标签的;ModelChoiceField继承ChoiceField常用于渲染一对多的select标签;ModelMultipleChoiceField继承ModelChoiceField常用于渲染多对多的select标签。
3、调整仅给一对多和多对多字段添加 +
service/stark.py:
class ModelStark(object):
"""默认类,定制配置类"""
def add_view(self, request):
"""添加页面视图"""
ModelFormDemo = self.get_modelform_class()
form = ModelFormDemo() # 实例化步骤提前不管是post请求还是get请求都会传递到模板中 for bound_field in form: # 拿到每一个字段
print(bound_field.field)
print(type(bound_field.field))
from django.forms.models import ModelChoiceField # ModelMultipleChoiceField继承ModelChoiceField
if isinstance(bound_field.field, ModelChoiceField): # 通过这个判断是否是一对多或多对多的字段对象
bound_field.is_pop = True # 给所有一对多、多对多对象添加is_pop这个属性 if request.method == "POST":
form = ModelFormDemo(request.POST)
if form.is_valid(): # 校验字段全部合格
form.save()
return redirect(self.get_list_url()) # 跳转到当前访问表的查看页面
# 校验有错误返回页面,且包含了错误信息 return render(request, "add_view.html", locals())
注意:ModelMultipleChoiceField是ModelChoiceField的子类,因此只要引入ModelChoiceField,判断bound_field.field是否是ModelChoiceField的对象就可以判断是否是一对一或一对多字段。其次通过bound_field.is_pop = True的方式为bound_filed对象添加属性。在form页面中可以通过判断字段对象的is_pop属性是否为真判断是否需要添加“+”。
form.html:
-------代码部分省略
<form action="" method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div style="position: relative">
<label for="">{{ field.label }}</label>
{{ field }} <span class="error pull-right">{{ field.errors.0 }}</span>
{% if field.is_pop %}
{# 判断是一对多、多对多字段 #}
<a href="" style="position: absolute; right: -30px; top: 20px;"><span style="font-size: 28px">+</span></a>
{% endif %}
</div>
{% endfor %}
<button type="submit" class="btn btn-default pull-right">提交</button>
</form>
显示效果:
四、“+”对应的跳转路径
1、将“+”的a标签href属性换位onclick
form.html
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form action="" method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div style="position: relative">
<label for="">{{ field.label }}</label>
{{ field }} <span class="error pull-right">{{ field.errors.0 }}</span>
{% if field.is_pop %}
{# 判断是一对多、多对多字段 #}
<a onclick="pop('{{ field.url }}')" style="position: absolute; right: -30px; top: 20px;"><span style="font-size: 28px">+</span></a>
{% endif %}
</div>
{% endfor %}
<button type="submit" class="btn btn-default pull-right">提交</button>
</form>
</div>
</div>
</div>
<script>
function pop(url) {
window.open(url, "", "width=600, height=400, top=100, left=100")
}
</script>
注意window.open()方法的使用。注意{{ field.url }}拿到访问路径。
2、在add_view视图函数中得到访问路径url
class ModelStark(object):
"""默认类,定制配置类"""
def add_view(self, request):
"""添加页面视图"""
ModelFormDemo = self.get_modelform_class()
form = ModelFormDemo() # 实例化步骤提前不管是post请求还是get请求都会传递到模板中 for bound_field in form: # 拿到每一个字段
# print(bound_field.field) # 字段对象
print(bound_field.name) # title\publishDate\publish 字段名称
# print(type(bound_field.field)) # 字段类型
from django.forms.models import ModelChoiceField # ModelMultipleChoiceField继承ModelChoiceField
if isinstance(bound_field.field, ModelChoiceField): # 通过这个判断是否是一对多或多对多的字段对象
bound_field.is_pop = True # 给所有一对多、多对多对象添加is_pop这个属性 # 需要拿到的不是当前表而是字段关联表
print("===》", bound_field.field.queryset.model)
"""
一对多或者多对多字段的关联模型表
<class 'app01.models.Publish'>
<class 'app01.models.Author'>
"""
# 拿到模型名和应用名
related_model_name = bound_field.field.queryset.model._meta.model_name
related_app_label = bound_field.field.queryset.model._meta.app_label
# 拼出添加页面地址
_url = reverse("%s_%s_add" % (related_app_label, related_model_name))
bound_field.url = _url if request.method == "POST":
form = ModelFormDemo(request.POST)
if form.is_valid(): # 校验字段全部合格
form.save()
return redirect(self.get_list_url()) # 跳转到当前访问表的查看页面
# 校验有错误返回页面,且包含了错误信息 return render(request, "add_view.html", locals())
注意:
(1)拿到一对多、多对多字段关联表
bound_field.field是字段对象;bound_filed.name拿到的是字段名称:title、publishDate、publish、authors等字符串;
bound_field.field是字段类型;bound_field.field.queryset.model拿到是字段关联模型表:<class 'app01.models.Publish'> 、<class 'app01.models.Author'>。
拿到模型表后,再通过._meta.model_name和._meta.app_label就可以获取模型名和应用名来拼接对应模型表的add页面路径。
(2)拼接添加页面地址,让form.html通过模板获取
# 拼出添加页面地址
_url = reverse("%s_%s_add" % (related_app_label, related_model_name))
bound_field.url = _url ######form.html#######
<a onclick="pop('{{ field.url }}')" style="position: absolute; right: -30px; top: 20px;"><span style="font-size: 28px">+</span></a>
(3)显示效果
(4)__str__返回值改为字符串
models.py:
class AuthorDetail(models.Model):
nid = models.AutoField(primary_key=True)
birthday = models.DateField()
telephone = models.BigIntegerField()
addr = models.CharField(max_length=64) def __str__(self):
# 返回的不能是一个数字一定要强制转为一个字符串
return str(self.telephone)
五、保存添加记录同时,将原页面的对应的下拉菜单中添加该记录
1、get_body方法,field类型判断调整
class ShowList(object):
"""展示页面类"""
def get_body(self):
"""构建表单数据"""
new_data_list = []
# for obj in self.data_list:
for obj in self.page_data: # 当前页面的数据
temp = [] for field in self.config.new_list_display(): # ["__str__", ] ["pk","name","age",edit]
if callable(field):
val = field(self.config, obj)
else:
try: # 如果是普通字段
field_obj = self.config.model._meta.get_field(field) # 拿到字段对象
if isinstance(field_obj, ManyToManyField): # 判断是否是多对多
# 反射处理 增加.all
# 多对多的情况 obj.field.all()
ret = getattr(obj, field).all() # <QuerySet [<Author: alex>, <Author: egon>]>
t = []
for obj in ret:
t.append(str(obj))
val = ",".join(t) # 用join方法实现拼接 alex,egon
else:
# 非多对多的情况
val = getattr(obj, field) # 拿到的关联对象 处理不了多对多
if field in self.config.list_display_links:
# _url = reverse("%s_%s_change" % (app_label, model_name), args=(obj.pk,))
_url = self.config.get_change_url(obj)
val = mark_safe("<a href='%s'>%s</a>" % (_url, val))
except Exception as e: # 如果是__str__
val = getattr(obj, field) temp.append(val)
new_data_list.append(temp)
return new_data_list
if callable() 判断对象是否可调用,如果为true说明是field是函数对象。当判断为false时,filed的值也分两种情况。一种是字段字符串,一种是“__str__”。如果是__str__情况,程序会报错(self.config.model._meta.get_field(field) 这句出错),这里通过try来做异常处理,通过反射拿到对象__str__函数的返回值 self.name 如:武汉大学出版社。
显示效果:
2、改写add_view视图POST请求处理
改写POST请求前,先修改add_view视图中isinstance判断是一对多、多对多对象后的bound_field.url:
# url拿到后,再在后面拼接字段名称
bound_field.url = _url + "?pop_res_id=id_%s" % bound_field.name # /?pop_res_id=id_authors
之前的add_view视图在处理POST请求时,如果字段校验合格是直接页面重定向到当前模型表的查看页面。但现在需要区分两种情况:直接在页面访问添加页面、通过window.open()打开网页访问添加页面。
def add_view(self, request):
"""省略代码"""
if request.method == "POST":
form = ModelFormDemo(request.POST)
if form.is_valid(): # 校验字段全部合格
obj = form.save() # 将数据保存到数据库
print(obj) # 拿到返回值:当前生成的记录
pop_res_id = request.GET.get("pop_res_id") # 拿到window.open打开页面后面的get请求 if pop_res_id:
# 当属于window.open页面post请求
res = {"pk": obj.pk, "text": str(obj), "pop_res_id": pop_res_id} return render(request, "pop.html", {"res": res})
else:
# 跳转到当前访问表的查看页面
return redirect(self.get_list_url())
# 校验有错误返回页面,且包含了错误信息 return render(request, "add_view.html", locals())
注意:
(1)pop_res_id判断是否是window.open()打开窗口
根据get请求数据(新的add页面)中是否包含pop_res_id判断是来自一般页面访问还是来自window.open()打开的子页面访问。
(2)如果是window.open子页面提交的post请求渲染pop.html
3、pop.html中的script脚本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script type="text/javascript">
// opener 属性是一个可读可写的属性,可返回对创建该窗口的 Window 对象的引用。
// 当使用window.open()打开一个窗口,您可以使用此属性返回来自目标窗口源(父)窗口的详细信息。
window.opener.pop_response('{{ res.pk }}','{{ res.text }}','{{ res.pop_res_id }}')
// close() 方法用于关闭浏览器窗口
window.close()
</script>
</body>
</html>
执行pop.html中的两条script脚本。
(1)window.opener 的用法
返回打开当前窗口的那个窗口的引用。如果当前窗口是由另一个窗口打开的, window.opener保留了那个窗口的引用. 如果当前窗口不是由其他窗口打开的, 则该属性返回 null.
pop.html是由add_view.html中的pop函数打开的。因此会去add_view.html去找pop_response函数。
(2)window.close()方法
该方法用于关闭浏览器窗口。
4、add_view.html的pop_response
<body>
<h3>添加页面</h3>
{% include 'form.html' %} <script>
function pop(url) {
window.open(url, "", "width=600, height=400, top=100, left=100")
} function pop_response (pk, text, id) {
console.log(pk, text, id); // 10 人民邮电出版社 id_publish
console.log(typeof text); // string
// 选择哪一个select标签
// option文本值和value值
var $option = $('<option>'); // 创建标签:<option></option>
$option.html(text); // 给标签添加文本:<option>南京出版社</option>
$option.val(pk); // 给标签添加value:<option value=111>南京出版社</option>
$option.attr("selected", "selected"); // 添加属性selected:<option value="111" selected="selected">南京出版社</option>
$("#" + id).append($option); // 将标签添加到id="id_publish"的标签中
}
</script>
</body>
注意:
(1)pop_response函数的三个参数
这三个参数先由stark.py中的add_view视图函数传递给pop.html模板
res = {"pk": obj.pk, "text": str(obj), "pop_res_id": pop_res_id}
再在模板中通过模板语法将数据传递给pop_response函数:
window.opener.pop_response('{{ res.pk }}','{{ res.text }}','{{ res.pop_res_id }}')
最终这三个参数分别是:当前关联对象的主键值(11)、文本值(北京教育出版社)、get请求值(id_publish)
(2)创建option标签并添加到对应的添加页面select标签中
5、运用pop添加显示效果
六、项目代码
https://github.com/hqs2212586/stark_demo
stark——pop功能(admin中添加功能)的更多相关文章
- Java并发编程(十三)在现有的线程安全类中添加功能
重用现有的类而不是创建新的类,可以降低工作量,开发风险以及维护成本. 有时候线程安全类可以支持我们所有的操作,但更多时候,现有的了类只能支持大部分的操作,此时就需要在不破坏线程安全性的情况下添加一个新 ...
- onethink后台边栏,添加新的方法后不显示,是需要在后台系统中添加功能,如下图
- 如何在 Blazor WebAssembly中 使用 功能开关
微软Azure 团队开发的 功能管理 (Feature Management) 包 Microsoft.FeatureManagement可用于实现 功能开关,可以通过 功能开关 特性动态的改变应用程 ...
- dgango中admin下添加搜索功能
admin下添加搜索功能: 在表单中加入search_fields = ['ip','hostname'] 可模糊匹配 当有人在管理搜索框中进行搜索时,Django将搜索查询分解成单词,并返回包含 ...
- stark组件开发之添加功能实现
添加功能,还是使用, form 组件来完成! 并且 完成添加之后,需要保留原搜索条件. def memory_url(self): '''用于反向生成url, 并且携带,get请求的参数,跳转到下一 ...
- Django 中自定义 Admin 样式与功能
目录 自定义 Admin 样式与功能 1 页面修改中文 1.1 语言设置为中文 1.2 应用管理设置为中文 1.3 数据库表设置为中文 1.4 数据库表字段名称修改为中文 2 修改后台样式 2.1 安 ...
- FastReport 中添加二维码功能.(Delphi)
http://www.cnblogs.com/fancycloud/archive/2011/07/24/2115240.html FastReport 中添加二维码功能.(Delphi) 在实际 ...
- TogetherJS – 酷!在网站中添加在线实时协作功能
TogetherJS是一个免费.开源的 JavaScript 库,来自 Mozilla 实验室,可以实现基于 Web 的在线协作功能.把 TogetherJS 添加到您的网站中,您的用户可以在实时的互 ...
- freemarker中修改和添加功能中包含checkbox复选框默认选中需求的解决方式
今天做的公司ERP系统上线第一天内部使用的,各种BUG铺天盖地,[虽然只是技术总监一个人在测试……],其中有一个就是其中部门管理页面中的修改和添加功能 一个人做一套ERP总是有点疏漏的,虽然里面的东西 ...
随机推荐
- docker构建mysql容器及Navicat 远程连接
1. MySQL部署 1.1拉取MySQL镜像 docker pull mysql 查看镜像 docker images 1.2创建MySQL容器 首先建立所需要的 文件夹: docker run - ...
- SDUT OJ 数据结构实验之串一:KMP简单应用 && 浅谈对看毛片算法的理解
数据结构实验之串一:KMP简单应用 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Discuss Problem Descr ...
- 自旋锁Spin lock与互斥锁Mutex的区别
POSIX threads(简称Pthreads)是在多核平台上进行并行编程的一套常用的API.线程同步(Thread Synchronization)是并行编程中非常重要的通讯手段,其中最典型的应用 ...
- 一些很有意思的JS现象
关于JS对象的 . 和 [] []除了属性名可以比 .天马行空以外(比如我们要添加一个为'33-abc'的属性,一定得用[])),还有一个实际操作中的区别 Object.is的作用和两个奇特的现象 还 ...
- C++_新特性总结与未来的路
了解C++之后,可以阅读一些高级主题和面向对象编程相关的书籍: OOP有助于开发大型的项目,并提高其可靠性: OOP方法的基本活动之一就是发明能够模拟当前情况的类.当前情况被统称为问题域. 由于实际问 ...
- mfix的Negative gas density报错解决
错误很难定位,因为编译正常,而是运行过程中会告知出现Negative gas density,并且不收敛,没有其他错误信息.最后通过一步步定位发现是由于 IC_EP_g(1) < EP_star ...
- HDU 3783
读入一个字符串,字符串中包含ZOJ三个字符,个数不一定相等,按ZOJ的顺序输出,当某个字符用完时,剩下的仍然按照ZOJ的顺序输出. MY:(OUTPUT LIMIT EXCEED) #include& ...
- HDU6396 (贪心+fread 挂)
题意:初始值你有k个属性的攻击vi,有n个怪兽,每个怪兽有k种属性的血量ai,并且有k种属性的加成bi,当你的k种属性的值全部大于等于某只怪兽的k种属性的血量,你可以杀死他,并且你的攻击力vi会升级, ...
- UVA - 11825 状压DP
该题目是EMAXX推荐的练习题,刘汝佳的书也有解说 如果S0属于全集,那S0就可以作为一个分组,那么S分组数可以是best{当前S中S0的补集+1} 对于集合类的题目我觉得有点抽象,希望多做多理解把 ...
- apche 配置
http://www.tootoogo.org/wordpress/?p=230 apache的配置由httpd.conf 文件配置. 1.基本配置 ServerRoot "/mnt/sof ...