20200318 代码发布之modelform使用
目录
昨日回顾
* ### django基于channels完成群聊功能
后端框架不一定默认支持websocket
**三步走前期配置**
```python
"""
http协议还是走 urls.py 与 views.py
/index/ index
浏览器地址栏输入网址即可
websocket协议走routing.py 与 consumers.py
/chat/ ChatConsumer
借助于js内置对象new WebSocket('ws://127.0.0.1:8080/chat/')
"""
```
需要掌握的方法
**后端**
```python
from channels.generic.websocket import WebSocketConsumer
from channels.exceptions import StopConsumer
class ChatConsumer(WebSocketConsumer):
def websocket_connect(self,message):
# 客户端请求链接自动触发
self.accept()
def websocket_receive(self,message):
# 客户端发送消息自动触发 message是一个字典{'type':'','text':客户端消息}
self.send(text_data='给客户端回复的消息')
def websocket_disconnect(self,message):
# 客户端断开链接之后自动触发
raise StopConsumer
```
**前端**
```html
<script>
var ws = new WebSocket('ws://127.0.0.1:8080/chat/');
// 1 客户端握手环节成功之后 自动触发
ws.onopen = function(){}
// 2 给服务端发送消息
ws.send()
// 3 服务端回复消息 自动触发
ws.onmessage = function(){}
// 4 主动断开链接自动触发
ws.onclose = function(){
</script>
### gojs插件
后续如果业务逻辑需要使用更多的图标,那么你可以参考官网
#### 基本概念
* TextBlock
* Shape
* Node
* Links
gojs的使用规律,先在html页面上用div占据一块区域,之后所有的渲染操作全部在该div内部进行
#### 数据绑定
* 数据格式是列表套字典
* 修改图标样式 只需要提供唯一标示key和修改参数即可
#### 去除水印
打开go.js文件,查找特定的一个字符串
注释掉或者直接删掉该字符串所在的一段代码
然后在注释或者删除的后面添加一段新的代码
```js
a.kr = function(){return false};
```
### paramiko模块
通过ssh远程连接服务器并操作服务器
执行命令
上传下载文件
该模块也是很多运维工具底层实现的原理 ansible
该模块即支持用户名密码的方式 也支持公钥私钥的方式
需要你掌握的就是我们封装之后的SSHProxy类代码,之后只要涉及到paramiko连接服务器直接拷贝使用即可
**面向对象知识点补充**
```python
# 对象执行with上下文管理的时候 会自动触发__enter__,__exit__方法
```
### gitpython模块
需要你掌握的就是我们封装之后的类代码,之后只要涉及到操作远程仓库直接拷贝使用即可
```
# modelform使用
- 服务器管理
- 项目管理
- 发布任务管理
- 发布任务执行流程动态展示到前端
扩展知识点: 当前服务器特别多的时候从平台下载数据,如何减轻平台压力
```
最新的技术点是p2p计数也叫比特流计数
所有人都可以成为数据的下载者以及上传者
下载电影时,有时快有时慢,快的时候可能因为附近有此资源,从附近进行下载
```
主要是用modelform实现服务器及项目的增删改查操作
任何一个项目毫不夸张的说80%以上都是由增删改查组成的,其次带你见证一下什么叫真正的搬砖
我们不直接创建所有的表,而是写一个创建一个
![img](http://m.qpic.cn/psc?/V12zikol0tqvgu/ChQ0KIcA.iub3F93BayOjHb8rWbEDzH1eP3s*ZWa3xvkxp9*USf3.O3pe32.pfvj9ZRXpAyT39YiK4oEIqHvRw!!/b&bo=FAg4BAAAAAARBxA!&rf=viewer_4)
## 服务器管理
初始配置
- 为了解耦合以及项目逻辑更加清晰
- 将全局的templates文件夹剔除,在应用内创建自己的templates文件夹
- 将应用里面的views.py删除,创建views文件夹,里面分局功能的不同创建不同的py文件
数据校验
- 前端字段标签渲染
- 展示错误信息
- forms组件 >>> modelform组件
```
from django.forms import ModelForm
```
### 查询及新增
```python
class ServerModelForm(ModelForm):
# 将是否添加样式,做成可配置的
exclude_bootstrap = []
class Meta:
model = models.Server
fields = '__all__'
# 重写init方法 当你不知道一个方法是否有参数或者有几个参数的时候,建议使用args kwargs
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
# 额外的操作
# print(self.fields) # 有序字典 OrderedDict([('hostname', <django.forms.fields.CharField object at 0x1092abf60>)])
# 给所有的字段添加样式(前端显示自动添加)
for k,field in self.fields.items():
# 判断当前字段是否需要加样式
if k in self.exclude_bootstrap:
continue
field.widget.attrs['class'] = 'form-control'
```
查询新增
```python
def server_list(request):
# 将当前的所有服务器展示到前端
serverlist = models.Server.objects.all()
return render(request,'server_list.html',locals())
def server_add(request):
# 先产生一个空的modelform对象
form_obj = ServerModelForm()
if request.method == 'POST':
# 获取数据并进行校验
form_obj = ServerModelForm(data=request.POST)
# 判断数据是否合法
if form_obj.is_valid():
# 通过校验,操作数据库
form_obj.save() # 保存数据
# 跳转到展示页 (redirect可以直接写url,也可以写反向解析的别名,但是如果带有名无名的情况下则必须使用reverse )
return redirect('server_list')
return render(request,'form.html',locals())
```
html展示
```html
{#继承模板#}
{% extends 'base.html' %}
{% block content %}
<h1>服务器列表</h1>
<a href="{% url 'server_add' %}" class="btn btn-primary" style="margin: 10px">添加服务器</a>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>主机名</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for server_obj in serverList %}
<tr>
<td>{{ server_obj.pk }}</td>
<td>{{ server_obj.hostname }}</td>
<td>
<a href="{% url 'server_edit' edit_id=server_obj.pk %}">编辑</a>
<a href="#" onclick="removeData(this,{{ server_obj.pk }})">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
{% block js %}
<script>
function removeData(ths,delete_id) {
var res = confirm('是否确定删除?');
// 返回值 判断用户点击的确定还是取消
if(res){
// 朝后端发送删除数据的请求
$.ajax({
url:'/server/delete/' + delete_id + '/',
type:'get',
dataType:'JSON',
success:function (args) {
if(args.status){
// 通过DOM操作 来删除页面数据
$(ths).parent().parent().remove()
}
}
})
}
}
</script>
{% endblock %}
********************form组件校验************************
{#form表单 添加页#}
{% extends 'base.html' %}
{% block content %}
<form class="form-horizontal" method="post" novalidate>
{% csrf_token %}
{% for foo in form_obj %}
<div class="form-group">
<label for="{{ foo.id_for_label }}" class="col-sm-2 control-label">{{ foo.label }}</label>
<div class="col-sm-10">
{{ foo}}
<span style="color: red">{{ foo.errors.0 }}</span>
</div>
</div>
{% endfor %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-success">提交</button>
</div>
</div>
</form>
{% endblock %}
```
### 修改功能
编辑功能非常简单,只需要在序列化时添加`instance`参数
```python
def server_edit(request,edit_id):
# 获取编辑对象 展示到页面给用户看 之后用户再编辑 提交
edit_obj = models.Server.objects.filter(pk=edit_id).first()
"""
其实编辑页面和添加页面是一样的 不同的在于是否需要渲染默认数据
所以我们直接使用用一个页面
"""
form_obj = ServerModelForm(instance=edit_obj)
if request.method == 'POST':
form_obj = ServerModelForm(data=request.POST,instance=edit_obj)
if form_obj.is_valid():
form_obj.save() # 编辑
# 新增和编辑都是save方法 区分就依据与instance参数
return redirect('server_list')
return render(request,'form.html',locals())
```
### 删除功能
删除功能需要做二次确认
并且删除完成后需要让用户能够直观的在浏览器上看到数据被删除了
不能直接刷新页面,因为如果是分页的情况,刷新页面之后肯定是在第一页
如果我是在第99页删除了1条数据,数据删除并且还在99页
```
后端返回一个标识符 status,判断是否删除成功
通过DOM操作,来删除页面数据
```
```python
def server_delete(request,delete_id):
models.Server.objects.filter(pk=delete_id).delete()
# 返回给前端进行判断删除
return JsonResponse({'status':True})
```
```html
<script>
function removeData(ths,delete_id) {
var res = confirm('是否确定删除?');
// 返回值 判断用户点击的确定还是取消
if(res){
// 朝后端发送删除数据的请求
$.ajax({
url:'/server/delete/' + delete_id + '/',
type:'get',
dataType:'JSON',
success:function (args) {
if(args.status){
// 通过DOM操作 来删除页面数据
$(ths).parent().parent().remove()
}
}
})
}
}
</script>
```
### 扩展知识点
反向解析
```
return redirect('server_list')
# redirect可以直接写url,也可以写反向解析的别名,但是如果带有名无名的情况下则必须使用reverse
```
django默认的语言环境是英文环境,但他内部提供了很多国家的语言环境,你可以修改
```python
LANGUAGE_CODE = 'zh-hans'
# 可以从django默认的全局配置查看支持的相应语言
# from django.conf import global_settings
```
## 项目管理
```python
# 项目相关所有业务逻辑
from django.shortcuts import HttpResponse,render,redirect,reverse
from app01 import models
from django.http import JsonResponse
from app01.myforms.project import ProjectModelForm
def project_list(request):
# 将当前所有的服务器展示到前端页面
projectList = models.Project.objects.all()
# return render(request,'server_list.html',{'serverList':serverList})
return render(request,'project_list.html',locals())
def project_add(request):
# 先产生一个空的modelform对象
form_obj = ProjectModelForm()
if request.method == 'POST':
# 获取数据并校验
form_obj = ProjectModelForm(data=request.POST)
# 判断数据是否合法
if form_obj.is_valid():
# 通过校验 操作数据库
form_obj.save() # 保存数据
# 跳转到展示页
return redirect('project_list')
# redirect括号内可以直接写url 其实也可以写反向解析的别名 但是如果带有名无名分组的情况 则必须使用reverse
# 将该对象传递给form页面
return render(request,'form.html',locals())
def project_edit(request,edit_id):
# 获取编辑对象 展示到页面给用户看 之后用户再编辑 提交
edit_obj = models.Project.objects.filter(pk=edit_id).first()
"""
其实编辑页面和添加页面是一样的 不同的在于是否需要渲染默认数据
所以我们直接使用用一个页面
"""
form_obj = ProjectModelForm(instance=edit_obj)
if request.method == 'POST':
form_obj = ProjectModelForm(data=request.POST,instance=edit_obj)
if form_obj.is_valid():
form_obj.save() # 编辑
# 新增和编辑都是save方法 区分就依据与instance参数
return redirect('project_list')
return render(request,'form.html',locals())
def project_delete(request,delete_id):
models.Project.objects.filter(pk=delete_id).delete()
return JsonResponse({'status':True})
```
## 项目优化
可以先用简单的方式将代码写好之后,再考虑代码优化封装
- 视图函数与modelform应该区分开
```
app01
-myform
--project
--server
```
- 整合服务器的modelform与项目的modelform类相同的代码,抽成基类
```
什么是类
类是对象公共的属性与技能的结合体
什么是基类
基类是类公共的属性与技能的结合体
```
base.py
```python
from django.forms import ModelForm
class BaseModelForm(ModelForm):
# 将是否添加样式 做成可配置的
exclude_bootstrap = []
# 重写init方法 当你不知道一个方法是否有参数或者有几个参数的时候 建议你写*args,**kwargs
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 额外的操作
# print(self.fields) # 有序字典 OrderedDict([('hostname', <django.forms.fields.CharField object at 0x1092abf60>)])
# 给所有的字段添加样式form-control
for k, field in self.fields.items():
# 判断当前字段是否需要加
if k in self.exclude_bootstrap:
# 直接跳过
continue
field.widget.attrs['class'] = 'form-control'
```
server.py
```python
from app01 import models
from app01.myforms.base import BaseModelForm
class ServerModelForm(BaseModelForm):
class Meta:
model = models.Server
fields = "__all__"
```
project.py
```python
from app01 import models
from app01.myforms.base import BaseModelForm
class ProjectModelForm(BaseModelForm):
class Meta:
model = models.Project
fields = "__all__"
```
## model表设计
```python
from django.db import models
# Create your models here.
from django.db import models
class Server(models.Model):
'''服务器表'''
hostname = models.CharField(verbose_name='主机名',max_length=32)
class Project(models.Model):
'''项目表'''
# luffy cmdb crm
title = models.CharField(verbose_name='项目名',max_length=32)
# www.GitHub.com/xxx.git
repo = models.CharField(verbose_name='仓库地址',max_length=32)
env_choices = (
('prod','正式'),
('test','测试')
)
env = models.CharField(verbose_name='环境',max_length=16,choices=env_choices,default='test')
# /data/temp/..
path = models.CharField(verbose_name='线上项目地址',max_length=32)
# 项目泡在服务器上,那么项目和服务器应该是有关系的
'''
一个项目可以跑在多台服务器
一台服务器可以跑多个项目
所以是多对多
'''
servers = models.ManyToManyField(verbose_name='关联服务器',to='Server')
def __str__(self):
return self.title
class DeployTask(models.Model):
'''
发布任务单
项目主键 项目版本
1 v1
1 v2
'''
# 项目名-环境-版本-日期
uid = models.CharField(verbose_name='标识',max_length=32)
# 任务与项目是一对多的关系,并且任务是多,项目是一
project = models.ForeignKey(verbose_name='项目',to='Project')
tag = models.CharField(verbose_name='版本',max_length=32)
status_choices = (
(1,'待发布'),
(2,'发布中'),
(3,'成功'),
(4,'失败'),
)
status = models.IntegerField(verbose_name='状态',choices=status_choices)
"""预留了一些钩子功能"""
before_download_script = models.TextField(verbose_name='下载前脚本', null=True, blank=True)
after_download_script = models.TextField(verbose_name='下载后脚本', null=True, blank=True)
before_deploy_script = models.TextField(verbose_name='发布前脚本', null=True, blank=True)
after_deploy_script = models.TextField(verbose_name='发布后脚本', null=True, blank=True)
```