昨日内容回顾
第一部分:权限相关
1. 权限基本流程
用户登录成功后获取权限信息,将【权限和菜单】信息写入到session。
以后用户在来访问,在中间件中进行权限校验。
为了提升用户体验友好度,在后台通过inclusion_tag动态生成一个二级菜单。
2. 使用权限
- 用户登陆:权限和菜单的初始化; init_permission
- 配置中间件
- 配置白名单
- 配置session中使用到的key
- load rbac
- menu ,inclusion_tag 生成菜单
- filter,可以在if后做条件,粒度控制到按钮。
第二部分:stark组件
1. 如何使用
- 在app中编写 stark.py
- 在stark.py中进行定制
- 默认配置:
site.register(models.UserInfo)
- 自定义配置:
class UserConfig(StarkConfig):
list_display = [] # 表格式列表上显示的字段
def get_list_display():
pass
order_by = [] # 排序
action_list=[] # 批量操作
search_list = [] # 模糊搜索
list_filter = [] # 组合搜索
add_btn # 是否显示添加按钮
model_form_class # 自定义ModelForm def extra_url(self): # 自定义扩展 URL
pass def get_urls(self): # 自定义URL
pass def changelist_view(self,request):
pass def add_view(self,request):
pass def change_view(self,request):
pass def del_view(self,request):
pass site.register(models.UserInfo,UserConfig)
一、popup
什么是popup
popup英文翻译叫 弹出窗口。
弹窗是一个非常流行的对话框,弹窗可以覆盖在页面上展示。
弹窗可用于显示一段文本,图片,地图或其他内容。
注意:popup弹窗,是由浏览器生成的!
在前端里面的,有一个模态框。那是由Html生成的!跟popup弹框不一样!
window.open
popup实际上,是调用了js中的window.open方法
open() 方法用于打开一个新的浏览器窗口或查找一个已命名的窗口。
语法
window.open(URL,name,features,replace)
注意:如果name重名,只会打开一个
窗口特征(Window Features)
这里只列举,下面例子中,会用到的参数
status=yes|no|1|0 | 是否添加状态栏。默认是 yes。 |
height=pixels | 窗口文档显示区的高度。以像素计。 |
width=pixels | 窗口的文档显示区的宽度。以像素计。 |
toolbar=yes|no|1|0 | 是否显示浏览器的工具栏。默认是 yes。 |
resizable=yes|no|1|0 | 窗口是否可调节尺寸。默认是 yes。 |
其他更多参数,请参考链接:
http://www.w3school.com.cn/jsref/met_win_open.asp
在django admin中,就用到了popup。比如之前写的博客系统
点击加号
它弹出了一个网页框。注意:它是单页面的!不能像浏览器一样,在当前页面,新开一个窗口。
不能收藏!URL地址也不能修改!
举例:
新建一个项目,注意:django版本为1.11
修改urls.py,增加路径
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', views.index),
]
修改views.py,增加视图函数
from django.shortcuts import render # Create your views here.
def index(request):
return render(request,"index.html")
在templates目录下,创建文件index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>添加页面</h1>
<form>
<p><input type="text"></p>
<p>
<select id="city">
<option>北京</option>
<option>上海</option>
<option value="">深圳</option>
</select>
<input type="button" value="+" onclick="popUp();">
</p>
</form>
<script>
function popUp() {
{#弹窗#}
window.open("http://www.py3study.com/",'x1',"status=1, height=500, width=500, toolbar=0, resizable=0")
}
</script>
</body>
</html>
启动django项目,访问首页
点击加号按钮,弹窗页面。它是一个单页面!
弹窗的内容,可以定制吗?当然可以!
修改urls.py,增肌路径
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', views.index),
url(r'^pop/', views.pop),
]
修改views.py,增肌视图函数
from django.shortcuts import render,HttpResponse # Create your views here.
def index(request):
return render(request,"index.html") def pop(request):
if request.method == "GET":
return render(request,"add_city.html") print(request.POST)
return HttpResponse('添加成功')
在templates目录下,创建文件add_city.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>创建城市</h1>
<form method="post">
{% csrf_token %}
<div style="width: 200px;height: 300px;border: 1px solid #dddddd">
<input type="text" name="city">
<input type="submit" value="提交">
</div>
</form>
</body>
</html>
刷新页面,重新点击加号按钮,效果如下:
点击提交之后,效果如下:
但是窗口并没有自动关闭!它应该自动关闭,并跳转到首页才对!
怎么让它自动关系呢?使用windows.close()
由于windows.close()是js代码,需要使用html文件来执行才行!
在templates目录下,创建文件pop_response.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>正在关闭</title>
</head>
<body>
<script>
{#自执行函数#}
(function () {
window.close();
})()
</script>
</body>
</html>
自执行函数,也就是能够自动立即执行的函数
看下面一段代码
(function () {
window.close();
})()
在js中声明函数,使用function关键字。函数执行,必须要加括号执行。
所以上面一段代码,就是声明之后,立刻被执行了!
还有一种用途,用于做隔离。比如多个js文件之间相互调用时!
修改views.py,渲染pop_response.html
from django.shortcuts import render,HttpResponse # Create your views here.
def index(request):
return render(request,"index.html") def pop(request):
if request.method == "GET":
return render(request,"add_city.html") print(request.POST)
return render(request, 'pop_response.html')
重启django,刷新页面,重新添加一次。效果如下:
那么问题来了,添加之后的数据,要在下拉框中展示。要实时更新,怎么搞?
注意:拉下框中的数据,是来源于数据库的。所以即使添加成功了,要刷新页面,才能加载出来!
popup,它能记住是由哪个页面触发弹窗的。它能调用原始页面的数据!使用opener调用
测试一下
修改index.html,增加tx方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>添加页面</h1>
<form>
<p><input type="text"></p>
<p>
<select id="city">
<option>北京</option>
<option>上海</option>
<option value="">深圳</option>
</select>
<input type="button" value="+" onclick="popUp();">
</p>
</form>
<script>
{#测试函数#}
function tx() {
alert('xxx');
}
function popUp() {
{#弹窗#}
window.open("/pop/",'x1',"status=1, height=500, width=500, toolbar=0, resizable=0")
}
</script>
</body>
</html>
修改pop_response.html,调用tx方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>正在关闭</title>
</head>
<body>
<script>
{#自执行函数#}
(function () {
//调用tx方法
opener.tx();
window.close();
})()
</script>
</body>
</html>
(function () {
window.close();
})()
刷新页面,重新添加一次,效果如下:
那么既然可以调用index.html的方法,就可以传值了
修改views.py,增加变量content,用来使用js传参给index.html
from django.shortcuts import render,HttpResponse # Create your views here.
def index(request):
return render(request,"index.html") def pop(request):
if request.method == "GET":
return render(request,"add_city.html") print(request.POST)
# 假设数据已经添加成功了,这里要获取添加的id和title
content = {'id':4,'title':'成都'}
# 渲染页面,用来将参数传给index.html
return render(request, 'pop_response.html',content)
修改pop_response.html,调用tx方法,并传参。
注意:参数是字符串,要用引号括起来
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>正在关闭</title>
</head>
<body>
<script>
{#自执行函数#}
(function () {
//调用tx方法,参数必须是字符串
opener.tx('{{ id }}','{{ title }}');
window.close();
})()
</script>
</body>
</html>
(function () {
window.close();
})()
修改index.html,接收参数后,操作DOM
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>添加页面</h1>
<form>
<p><input type="text"></p>
<p>
<select id="city">
<option value="">北京</option>
<option value="">上海</option>
<option value="">深圳</option>
</select>
<input type="button" value="+" onclick="popUp();">
</p>
</form>
<script>
{#测试函数#}
function tx(cityId,cityTitle) {
//创建option标签
var tag = document.createElement('option');
//设置value
tag.value = cityId;
// 设置text属性
tag.innerText = cityTitle;
// 获取ID标签
var city = document.getElementById('city');
//最后一个位置添加option标签
city.appendChild(tag);
}
function popUp() {
{#弹窗#}
window.open("/pop/",'x1',"status=1, height=500, width=500, toolbar=0, resizable=0")
}
</script>
</body>
</html>
重启django,刷新页面,重新添加一次!效果如下:
注意:这里只是添加到浏览器了,并没有到数据库。所以刷新页面,数据会丢失!
django render传输的变量。变量只会在指定的文件渲染!如果这个文件包含了js,那么即使js使用{{ }} 语法,也不会渲染!
举例:
修改views.py,给index.html传一个id参数
from django.shortcuts import render,HttpResponse # Create your views here.
def index(request):
return render(request,"index.html") def pop(request):
if request.method == "GET":
return render(request,"add_city.html",{'id':''}) print(request.POST)
# 假设数据已经添加成功了,这里要获取添加的id和title
content = {'id':4,'title':'成都'}
# 渲染页面,用来将参数传给index.html
return render(request, 'pop_response.html',content)
在app01目录下,创建static目录,在此目录下创建test.js
alert('{{id}}');
修改index.html,添加h6标签,引入js文件
{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
{#测试js文件#}
<script src="{% static 'test.js' %}"></script>
</head>
<body>
<h1>添加页面</h1>
<h6>'{{ id }}'</h6>
<form>
<p><input type="text"></p>
<p>
<select id="city">
<option value="">北京</option>
<option value="">上海</option>
<option value="">深圳</option>
</select>
<input type="button" value="+" onclick="popUp();">
</p>
</form>
<script>
{#测试函数#}
function tx(cityId,cityTitle) {
//创建option标签
var tag = document.createElement('option');
//设置value
tag.value = cityId;
// 设置text属性
tag.innerText = cityTitle;
// 获取ID标签
var city = document.getElementById('city');
//最后一个位置添加option标签
city.appendChild(tag);
}
function popUp() {
{#弹窗#}
window.open("/pop/",'x1',"status=1, height=500, width=500, toolbar=0, resizable=0")
}
</script>
</body>
</html>
重启django,刷新页面
发现id没有被渲染出来
但是index.html渲染出来了
在window.open中,name重名,只会打开1个
如果定义了多个window.open,会被浏览器拦截
只会弹出第一个,后续的都会被拦截!
总结:
主页面:
function xxxxxx(){ }
window.open('url','name','.....') popup页面:
opener.xxxxxx()
// widown.close() 补充:
js的自执行函数
用于做隔离:
(function(jq){
jq.xxx
})(jQuery)
二、Model类的继承
Django有三种继承的方式:
- 抽象基类:被用来继承的模型被称为
Abstract base classes
,将子类共同的数据抽离出来,供子类继承重用,它不会创建实际的数据表; - 多表继承:
Multi-table inheritance
,每一个模型都有自己的数据库表; - 代理模型:如果你只想修改模型的Python层面的行为,并不想改动模型的字段,可以使用代理模型。
注意!同Python的继承一样,Django也是可以同时继承两个以上父类的!
关于这3种继承的方式,详情请参考链接:
https://www.cnblogs.com/feixuelove1009/p/8420751.html
本文主要讲解 多表继承
多表继承
这种继承方式下,父类和子类都是独立自主、功能完整、可正常使用的模型,都有自己的数据库表,内部隐含了一个一对一的关系。
举例
修改models.py
from django.db import models # Create your models here.
class Author(models.Model): # 作者
name=models.CharField(verbose_name="姓名",max_length=32)
age=models.IntegerField(verbose_name="年龄") class AuthorDetail(Author): # 作者详情
gf=models.CharField(verbose_name="女朋友",max_length=32)
tel=models.CharField(verbose_name="作者电话",max_length=32)
它等同于原生sql语句
CREATE TABLE "Author" (
"id" integer NOT NULL PRIMARY KEY,
"name" varchar(32) NOT NULL,
"age" int(11) NOT NULL
); CREATE TABLE "AuthorDetail" (
"author_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "Author" ("id"),
"gf" varchar(32) NOT NULL,
"tel" varchar(32) NOT NULL
);
使用2个命令,生成表
python manage.py makemigrations
python manage.py migrate
使用Navicat打开数据库,查看表结构,并添加数据
author表
authordetail表
父类和子类都生成了单独的数据表,authordetail中存储了author的id,也就是通过OneToOneField链接在一起。继承关系通过表的JOIN操作来表示。在JPA中称作JOINED。这种方式下,每个表只包含类中定义的字段,不存在字段冗余,但是要同时操作子类和所有父类所对应的表。
author 里面的所有字段在 authordetail 中也是有效的,只不过数据保存在另外一张数据表当中。所以下面两个语句都是可以运行的:
models.Author.objects.filter(name='xiao')
models.AuthorDetail.objects.filter(name='xiao')
修改urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', views.index),
]
修改views.py
from django.shortcuts import render,HttpResponse
from app01 import models # Create your views here.
def index(request):
obj1 = models.Author.objects.filter(name='xiao').values()
obj2 = models.AuthorDetail.objects.filter(name='xiao').values()
print(obj1)
print(obj2)
return HttpResponse('ok')
重启django,访问首页
查看Pycharm控制台输出:
<QuerySet [{'age': 23, 'name': 'xiao', 'id': 1}]>
<QuerySet [{'gf': '韩雪', 'author_ptr_id': 1, 'tel': '', 'age': 23, 'name': 'xiao', 'id': 1}]>
可以看出,authordetail 表打印出本表的字段以及父表author表的字段
如果你有一个 author,那么它同时也是一个 authordetail, 那么你可以使用子 model 的小写形式从 author 对象中获得与其对应的 authordetail 对象:
修改views.py
from django.shortcuts import render,HttpResponse
from app01 import models # Create your views here.
def index(request):
obj1 = models.Author.objects.filter(name='xiao').first()
print(obj1.authordetail)
return HttpResponse('ok')
重启django,访问首页
查看Pycharm控制台输出:
AuthorDetail object
但是,如果上例中的 obj1 并不是 authordetail (比如它仅仅只是 author 对象,或者它是其他类的父类),那么在引用 p.authordetail 就会抛开RelatedObjectDoesNotExist: Author has no authordetail. 异常:
def index(request):
obj1 = models.Author.objects.create(name='zhang',age='')
print(obj1.authordetail)
return HttpResponse('ok')
也就是说,创建author实例的同时不会创建authordetail,但是创建authordetail实例的同时会创建author实例:
修改views.py
from django.shortcuts import render,HttpResponse
from app01 import models # Create your views here.
def index(request):
obj1 = models.AuthorDetail.objects.create(name="zhang",age="",gf="蒋婷婷",tel="")
obj2 = models.Author.objects.filter(name="zhang").values()
print(obj2)
return HttpResponse('ok')
刷新页面,查看Pycharm控制体输出:
<QuerySet [{'age': 25, 'name': 'zhang', 'id': 2}]>
使用Navicat打开2个表
author表
authordetail表
三、crm业务开发
新建一个项目pro_crm,应用名为crm,注意:django版本为1.11
拷贝stark app
下载代码:
链接:https://pan.baidu.com/s/1fLOGH_3G7hPTvCYKX84UdQ 密码:m8rh
将里面的stark目录拷贝至 项目根目录中
注册stark app
修改settings.py,修改INSTALLED_APPS配置项
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'crm.apps.CrmConfig',
'stark.apps.StarkConfig',
]
使用stark组件
在目标app的根目录中创建stark.py
进入crm目录,创建stark.py
修改 crm-->models.py,增加表
# from rbac.models import UserInfo as RbacUserInfo
from django.db import models class Department(models.Model):
"""
部门表
"""
title = models.CharField(verbose_name='部门名称', max_length=16) def __str__(self):
return self.title class UserInfo(models.Model):
"""
员工表
"""
name = models.CharField(verbose_name='真实姓名', max_length=16)
phone = models.CharField(verbose_name='手机号', max_length=32) gender_choices = (
(1,'男'),
(2,'女'),
)
gender = models.IntegerField(verbose_name='性别',choices=gender_choices,default=1) depart = models.ForeignKey(verbose_name='部门', to="Department") def __str__(self):
return self.name class Course(models.Model):
"""
课程表
如:
Linux基础
Linux架构师
Python自动化
Python全栈
"""
name = models.CharField(verbose_name='课程名称', max_length=32) def __str__(self):
return self.name class School(models.Model):
"""
校区表
如:
北京昌平校区
上海浦东校区
深圳南山校区
"""
title = models.CharField(verbose_name='校区名称', max_length=32) def __str__(self):
return self.title class ClassList(models.Model):
"""
班级表
如:
Python全栈 面授班 5期 10000 2017-11-11 2018-5-11
"""
school = models.ForeignKey(verbose_name='校区', to='School')
course = models.ForeignKey(verbose_name='课程名称', to='Course')
semester = models.IntegerField(verbose_name="班级(期)") #
price = models.IntegerField(verbose_name="学费")
start_date = models.DateField(verbose_name="开班日期")
graduate_date = models.DateField(verbose_name="结业日期", null=True, blank=True)
tutor = models.ForeignKey(verbose_name='班主任', to='UserInfo', related_name='classes',limit_choices_to={'depart__title':'教质部'})
teachers = models.ManyToManyField(verbose_name='任课老师', to='UserInfo', related_name='teach_classes',limit_choices_to={'depart_id__in':[6,7]})
memo = models.CharField(verbose_name='说明', max_length=256, blank=True, null=True) def __str__(self):
return "{0}({1}期)".format(self.course.name, self.semester)
使用2个命令,生成表
python manage.py makemigrations
python manage.py migrate
修改crm-->stark.py,注册表Department
from stark.service.stark import site
from crm import models site.register(models.Department)
配置路由信息
修改urls.py,增加stark路由
from django.conf.urls import url
from django.contrib import admin
from stark.service.stark import site urlpatterns = [
url(r'^admin/', admin.site.urls),
# 导入stark组件的路由
url(r'^stark/', site.urls),
]
启动django项目,访问url: http://127.0.0.1:8000/stark/crm/department/list/
默认是没有数据的,需要添加!
由于时间关系,详细的步骤略...
完整代码,请参考
链接:https://pan.baidu.com/s/1K4YG5LY89aidRWK7j51DBg 密码:30jt
未完待续...