一、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中添加功能)的更多相关文章

  1. Java并发编程(十三)在现有的线程安全类中添加功能

    重用现有的类而不是创建新的类,可以降低工作量,开发风险以及维护成本. 有时候线程安全类可以支持我们所有的操作,但更多时候,现有的了类只能支持大部分的操作,此时就需要在不破坏线程安全性的情况下添加一个新 ...

  2. onethink后台边栏,添加新的方法后不显示,是需要在后台系统中添加功能,如下图

  3. 如何在 Blazor WebAssembly中 使用 功能开关

    微软Azure 团队开发的 功能管理 (Feature Management) 包 Microsoft.FeatureManagement可用于实现 功能开关,可以通过 功能开关 特性动态的改变应用程 ...

  4. dgango中admin下添加搜索功能

    admin下添加搜索功能: 在表单中加入search_fields = ['ip','hostname']   可模糊匹配 当有人在管理搜索框中进行搜索时,Django将搜索查询分解成单词,并返回包含 ...

  5. stark组件开发之添加功能实现

    添加功能,还是使用, form 组件来完成!  并且 完成添加之后,需要保留原搜索条件. def memory_url(self): '''用于反向生成url, 并且携带,get请求的参数,跳转到下一 ...

  6. Django 中自定义 Admin 样式与功能

    目录 自定义 Admin 样式与功能 1 页面修改中文 1.1 语言设置为中文 1.2 应用管理设置为中文 1.3 数据库表设置为中文 1.4 数据库表字段名称修改为中文 2 修改后台样式 2.1 安 ...

  7. FastReport 中添加二维码功能.(Delphi)

    http://www.cnblogs.com/fancycloud/archive/2011/07/24/2115240.html FastReport 中添加二维码功能.(Delphi)   在实际 ...

  8. TogetherJS – 酷!在网站中添加在线实时协作功能

    TogetherJS是一个免费.开源的 JavaScript 库,来自 Mozilla 实验室,可以实现基于 Web 的在线协作功能.把 TogetherJS 添加到您的网站中,您的用户可以在实时的互 ...

  9. freemarker中修改和添加功能中包含checkbox复选框默认选中需求的解决方式

    今天做的公司ERP系统上线第一天内部使用的,各种BUG铺天盖地,[虽然只是技术总监一个人在测试……],其中有一个就是其中部门管理页面中的修改和添加功能 一个人做一套ERP总是有点疏漏的,虽然里面的东西 ...

随机推荐

  1. java开发系统内核:让内核从严重错误中恢复

    更详细的讲解和代码调试演示过程,请参看视频 用java开发C语言编译器 更详细的讲解和代码调试演示过程,请参看视频 如何进入google,算法面试技能全面提升指南 如果你对机器学习感兴趣,请参看一下链 ...

  2. MyBatis与JDBC的对比

    //JDBC的步骤,1.加载驱动.2.获取连接.3.执行sql语句.4.处理结果集.5.关闭资源 Class.forName("com.mysql.jdbc.Driver").ne ...

  3. [jvm] JVM体系结构

    一.jvm 运行时数据区域 程序计数器 java虚拟机栈 本地方法栈 java堆 方法区 运行时常量池 直接内存(非JVM区域) 二.深入探讨HotSpot虚拟机在Java堆中对象分配.布局和访问的全 ...

  4. 关于 Linq to EF 的内存泄漏问题

    查到一些解决方案:             1, http://www.codethinked.com/keep-your-iqueryable-in-check 自定义常用方法,屏蔽IQuery功能 ...

  5. Centos文章列表

    1.Linux 中将用户添加到组的指令:https://cnzhx.net/blog/linux-add-user-to-group/ 2.CentOS7为firewalld添加开放端口及相关操作:h ...

  6. 用qt creator创建可继承ui类

    https://jingyan.baidu.com/article/5d368d1efa2dd73f60c05786.html 用qt creator创建可继承ui类 听语音 | 浏览:1657 | ...

  7. 网络控制芯片AX88796B系列使用简介

    目录 1. 特性 2. 结构框图 3. 接收 3.1 缓存空间 3.2 Receiver Buffer Ring 3.3 接收机制 4. 发送 5. 编程过程简要说明 5.1 初始化配置 5.2 接收 ...

  8. In linux shell, How to cp/rm files by time?

    find /path/to/folder/ -mtime 1 -exec rm {} \; // Deletes all Files modified yesterday

  9. 让Nginx支持pathinfo

    # 典型配置 location ~ \.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_ ...

  10. 将Eclipse的Java Project转换为Dynamic Web Project

    在用Eclipse做JavaEE开发时经常遇到Web工程被识别为Java工程的问题,导致很多功能无法使用. 只需做以下操作便可解决该问题. 1.右击Java工程选择Properties 2.选择左边目 ...