web框架前言与学生数据库系统(附1.0源码)
对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端。
import socket def f1(request): """ 处理用户请求,并返回相应的内容 :param request: 用户请求的所有信息 :return: """ f = open('index.fsw','rb') data = f.read() f.close() return data """ <body> <h1>用户登录</h1> <form> <p><input type="text" placeholder="用户名" /></p> <p><input type="password" placeholder="密码" /></p> </form> </body> """ def f2(request): f = open('aricle.tpl','rb') data = f.read() f.close() print(data) return data """ <body> <table border="1"> <thead> <tr> <th>ID</th> <th>用户名</th> <th>邮箱</th> </tr> </thead> <tbody> <tr> <th>1</th> <th>root</th> <th>root@qq.com</th> </tr> </tbody> </table> </body> """ routers = [ ('/xxx', f1), ('/ooo', f2), ]#手动写的网址 def run(): sock = socket.socket() sock.bind(('127.0.0.1',8080)) sock.listen(5) while True: conn,addr = sock.accept() # hang住 #print(conn)#获得的两个套接字,我去charm自己会发送请求一个/favicon.ico页面的报文 # print(addr) # 有人来连接了 # 获取用户发送的数据 data = conn.recv(8096) data = str(data,encoding='utf-8') #print(data)#get报文 headers,bodys = data.split('\r\n\r\n') #print("head:",headers) #print("body:",bodys) body是空的 temp_list = headers.split('\r\n') # print(temp_list) method,url,protocal = temp_list[0].split(' ') # print(method) GET # print(url) /ooo /favicon.ico # print(protocal) /HTTP/1.1 conn.send(b"HTTP/1.1 200 OK\r\n\r\n") func_name = None for item in routers: if item[0] == url: func_name = item[1] break if func_name: response = func_name(data) print(data) else: response = b"404" conn.send(response) conn.close() if __name__ == '__main__': run()
这种静态页面不能与数据库连接交互,所以也是非常的low。
import socket def f1(request): """ 处理用户请求,并返回相应的内容 :param request: 用户请求的所有信息 :return: """ f = open('index.fsw','rb') data = f.read() f.close() return data """ <body> <h1>用户登录</h1> <form> <p><input type="text" placeholder="用户名" /></p> <p><input type="password" placeholder="密码" /></p> </form> </body> """ def f2(request): f = open('aricle.tpl','r',encoding='utf-8') data = f.read() f.close() import time ctime = time.time() data = data.replace('@@sw@@',str(ctime)) return bytes(data,encoding='utf-8') """ <body> <table border="1"> <thead> <tr> <th>ID</th> <th>用户名</th> <th>邮箱</th> </tr> </thead> <tbody> <tr> <th>1</th> <th>@@sw@@</th> <th>root@qq.com</th> </tr> </tbody> </table> </body> """ def f3(request): import pymysql # 创建连接 conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='jeff@123', db='db1') cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute("select id,username,password from userinfo") user_list = cursor.fetchall() #print(user_list) cursor.close() conn.close() content_list = [] for row in user_list: tp = "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" %(row['id'],row['username'],row['password']) content_list.append(tp) content = "".join(content_list) f = open('userlist.html','r',encoding='utf-8') template = f.read() f.close() # 模板渲染(模板+数据) data = template.replace('@@sdfsdffd@@',content) return bytes(data,encoding='utf-8') """ mysql> select * from userinfo; +----+----------+----------+ | id | username | password | +----+----------+----------+ | 1 | alex | 123 | +----+----------+----------+ <body> <table border="1"> <thead> <tr> <th>ID</th> <th>用户名</th> <th>邮箱</th> </tr> </thead> <tbody> @@sdfsdffd@@ </tbody> </table> </body> """ def f4(request): import pymysql # 创建连接 conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='jeff@123', db='db1') cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute("select id,username,password from userinfo") user_list = cursor.fetchall() cursor.close() conn.close() f = open('hostlist.html','r',encoding='utf-8') data = f.read() f.close() # 基于第三方工具实现的模板渲染 from jinja2 import Template template = Template(data) data = template.render(xxxxx=user_list,user='sdfsdfsdf') return data.encode('utf-8') """ {% for row in xxxxx %} <tr> <td>{{row.id}}</td> <td>{{row.username}}</td> <td>{{row.password}}</td> </tr> {% endfor %} </tbody> </table> {{user}} """ routers = [ ('/xxx', f1), ('/ooo', f2), ('/userlist.html', f3), ('/host.html', f4), ] def run(): sock = socket.socket() sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('127.0.0.1',8080)) sock.listen(5) while True: conn,addr = sock.accept() # hang住 # 有人来连接了 # 获取用户发送的数据 data = conn.recv(8096) data = str(data,encoding='utf-8') headers,bodys = data.split('\r\n\r\n') temp_list = headers.split('\r\n') method,url,protocal = temp_list[0].split(' ') conn.send(b"HTTP/1.1 200 OK\r\n\r\n") func_name = None for item in routers: if item[0] == url: func_name = item[1] break if func_name: response = func_name(data) else: response = b"404" conn.send(response) conn.close() if __name__ == '__main__': run()
这里要说两点,首先这里使用了jinjia2模块,所以要简单的介绍一下这个模块。
渲染模板(使用render_template方法)
@app.route('/about/') def about(): # return render_template('about.html',user='username') return render_template('about.html',**{'user':'username'})
渲染模版时有两种传递参数的方式:用 var='value' 传递一个参数;使用字典组织多个参数,并且加两个*
号转换成关键字参数传入。
在jinja2模板中:
{{ ... }}
:装载一个变量,模板渲染的时候,会使用传进来的同名参数这个变量代表的值替换掉。
{% ... %}
:装载一个控制语句。
{# ... #}
:装载一个注释,模板渲染的时候会忽视这中间的值。
变量:
设置全局变量:{% set name='xx' %},之后就可以使用此变量了。
设置局部变量:
{% with foo = 42 %}
{{ foo }}
{% endwith %}
这里的foo变量只能在with标签中使用。
{% if kenny.sick %} Kenny is sick. {% elif kenny.dead %} You killed Kenny! You bastard!!! {% else %} Kenny looks okay --- so far {% endif %}
#一般循环 <ul> {% for user in users %} <li>{{ user.username|e }}</li> {% endfor %} </ul> #遍历字典 {% for key, value in my_dict.iteritems() %} <dt>{{ key|e }}</dt> <dd>{{ value|e }}</dd> {% endfor %}
jinja2模块最重要的部分是宏,宏相当于一个搭建好的页面一部分,可以被引入,可以往宏传递参数。可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量,在使用宏时传递参数,从而将宏渲染成为页面的一部分。
更多关于此模块的操作,可以查看博客https://www.cnblogs.com/ygj0930/p/7170621.html。
要说的第二点就是这种方法还是太low了。
import socket def handle_request(client): buf = client.recv(1024) client.send("HTTP/1.1 200 OK\r\n\r\n".encode("utf8")) client.send("<h1 style='color:red'>Hello, yuan</h1>".encode("utf8")) def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost',8001)) sock.listen(5) while True: connection, address = sock.accept() handle_request(connection) connection.close() if __name__ == '__main__': main()
最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。
如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。
正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。这个接口就是WSGI:Web Server Gateway Interface。
# from wsgiref.simple_server import make_server # # # def application(environ, start_response): # start_response('200 OK', [('Content-Type', 'text/html')]) # return [b'<h1>Hello, web!</h1><h2>Hello, py!</h2>'] # # # httpd = make_server('127.0.0.2', 8080, application)#(ip,pork,func) # # print('Serving HTTP on port 8080...') # # 开始监听HTTP请求: # httpd.serve_forever()
django入门
django是一个基于python的高级web开发框架,因为他的高度集成,将会在今后的web开发里给予我们很大的帮助。
首先创建一个django工程(加不加.py都可以):
django-admin.py startproject project_name #django-admin.py startproject myblog
工程下面有几个核心测文件:
manage.py Django项目里面的管理工具,通过它可以调用django shell和数据库等。
settings.py 包含了项目的默认设置,包括数据库信息,调试标志以及其他一些工作的变量。
urls.py 负责把URL模式映射到应用程序,路由(就是url与函数的对应关系)。
wsgi.py 调用python内置的wsgiref模块,web服务网关接口。他定义django用什么socket实现,默认就是wsgiref模块。
注:除了命令的方式pycharm也可以进行django工程的搭建。
HttpResponse模块
from django.conf.urls import url from django.shortcuts import HttpResponse def index(request):#request用户请求相关的所有信息 return HttpResponse('whatever') urlpatterns = [ url(r'^index/', index), ]
启动django自带的服务器,
python manage.py runserver 8080
在浏览器访问127.0.0.1:8080/index即可查看到django渲染后的网页。
render模块
from django.conf.urls import url from django.shortcuts import render def index(request):#request用户请求相关的所有信息 return render(request,"a.html")#默认要加request参数 urlpatterns = [ url(r'^index/', index), ]
接下来在浏览器访问127.0.0.1:8080/index即可查看到django渲染后的网页(服务器在改变了代码的情况下会自动重启)。还有,访问的前提是在templates目录下有一个a.html的文件。那么django是如何找到这个路径的呢,因为在settings.py下有一个TEMPLATES列表,其中'DIRS': [os.path.join(BASE_DIR, 'templates')]指明了render需要从这个目录下拿到。
静态文件的配置
在工程文件夹下创建一个static文件夹里面存放静态文件,并将路径写入settings.py下。
STATIC_URL = '/static/' STATICFILES_DIRS=(os.path.join(BASE_DIR,'static'),)
然后在导入文件时一律使用/static引入。
request相关
request.method获得当前请求的方法。request.GET与request.POST可以取到用户提交的数据。
from django.conf.urls import url from django.shortcuts import render,redirect def index(request):#request用户请求相关的所有信息 if request.method =='GET':#浏览器默认传get,区分返回来的信息 return render(request,"a.html") else: u=request.POST.get('user')#取出post方式传回来的字典的值 p=request.POST.get('pwd')#get取不到会转化为none if u=='jeff' and p=='123': return redirect('http://www.baidu.com')#当然也可以重定向到自己的目录 else: return render(request, "a.html") urlpatterns = [ url(r'^index/', index), ]
a.html中的修改:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="/static/as.css"> <title>Title</title> </head> <body> <h1>用户登录</h1> <form method="post" action="/index/">{# 发送以post方式发到index下 #} <input type="text" name="user"/> <input type="password" name="pwd"/> <input type="submit" value="login"/> </form> </body> </html>
这样用户访问127.0.0.1:8080/index会使用get方法返回a.html页面,输入用户名和密码提交会用post方法返回给index页面经判断是重定向还是重新输入。
django的渲染模板
django基本的html的模板与jinja2很相似,我们可以在form表单里加入一个{{ msg }}的模板,然后在render里添加一个msg:value用于自动传入。
django的模板取序列的值也是简单粗暴,比如取列表就是{{ list.index }}例如{{ s.0 }}{{ s.1 }},字典就是{{ dict.key }}例如{{row.id}}{{ row.name }}。
from django.conf.urls import url
from django.contrib import admin
from django.shortcuts import HttpResponse,render,redirect
def index(request):#request用户请求相关的所有信息
if request.method =='GET':
return render(request,"a.html")
else:
u=request.POST.get('user')
p=request.POST.get('pwd')
print(u)
print(p)
if u=='jeff' and p=='123':
return render(request, "b.html",{'user':[{'id':1,'name':'jeff','age':0},
{'id': 2, 'name': 'frank', 'age': 1},
{'id': 3, 'name': 'xixi', 'age': 2}]})
else:
return render(request, "a.html")
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', index),
]
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="/static/as.css"> <title>Title</title> </head> <body> <h1>用户登录</h1> <form method="post" action="/index/">{# 发送以post方式发到index下 #} <input type="text" name="user"/> <input type="password" name="pwd"/> <input type="submit" value="login"/> </form> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <table border="1"> {% for item in user %} <tr> <td>{{ item.id }}</td> <td>{{ item.name }}</td> <td>{{ item.age }}</td> <td><a href="/del/?nid=={{ item.id }}"></a></td>{# 跳转到专门的del页面 #} </tr> {% endfor %}{#循环结束 #} </table> </body> </html>
这里render传入的字典可以使用pymsql导入,这样就与数据库紧密的连接到一起了。
模板传入字典:
def func(request): v = {'name':'jeff', 'age':25} return render(request, 'test.html', {'v': v})
{% for item in v %} <h6>{{ item }}</h6> {% endfor %}
结果只显示key。
{% for item in v.keys %} <h6>{{ item }}</h6> {% endfor %}
与上显示一致。
{% for item in v.values %} <h6>{{ item }}</h6> {% endfor %}
只显示values。
{% for k,v in v.items %} <h6>{{ item }}</h6> {% endfor %}
显示键值。
班级管理之单表操作
C:\USERS\JEFFD\PYCHARMPROJECTS\DAY65
│ db.sqlite3
│ manage.py
├─app_name
│ └─views.py
├─day65
│ │ settings.py
│ │ urls.py
│ │ wsgi.py
│ └─__init__.py
├─static
└─templates
add_clas.html
class.html
增加与查看单表数据
创建过程:在urls.py创建路由
from app_name import views
url(r'^class/', views.clas),
访问127.0.0.1:8000/class/会直接跳转到views下的clas函数。
def clas(request): conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='jeff@123', db='db3', charset='utf8') cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)#字典方式取数据 cursor.execute("select cid,caption from class") class_list = cursor.fetchall() cursor.close() conn.close() return render(request,'class.html',{'class_list':class_list})#将mysql的数据传给模板语言
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>班级列表</h1> <table> <thead> <tr> <th>ID</th> <th>班级名称</th> <th>操作</th> </tr> </thead> <tbody> {% for row in class_list %} <tr> <td>{{ row.cid }}</td> <td>{{ row.caption }}</td> <td> <a href="/add_clas/">添加</a> <a href="/edit_class/?nid={{ row.id }}">编辑</a> <a href="/del_class/?nid={{ row.id }}">删除</a> </td> </tr> {% endfor %} </tbody> </table> </body> </html>
点击class.html的a标签,跳转到"/add_clas/",此时我们要在urls.py中增加一条路由。
url(r'^add_clas/', views.add_clas),
def add_clas(request): if request.method=='GET': return render(request,'add_clas.html') else: cap=request.POST.get('title')#传值的name参数 print(cap) conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='jeff@123', db='db3', charset='utf8') cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute("insert into class(caption) values(%s)",[cap,]) conn.commit() cursor.close() conn.close() return redirect('/class/')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>添加班级</h1> <form method="POST" action="/add_clas/"> <p>班级名称:<input type="text" name="title" /></p>{# title给request.post取值 #} <input type="submit" value="提交" /> </form> </body> </html>
重定向会第二次再请求‘/class/’页面,从而获取新的数据库中的值。
删除单表数据
在class.html上加上跳转链接:
<a href="/del_clas/?nid={{ row.cid }}">删除</a>
添加路由:
url(r'^del_clas/', views.del_clas),
增加del函数:
def del_clas(request): nid=request.GET.get('nid') conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='jeff@123', db='db3', charset='utf8') cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute("delete from class where cid=%s", [nid, ]) conn.commit() cursor.close() conn.close() return redirect('/class/')
post可以在链接上传数据,nid由此返回到del_clas函数,取出id并在数据库删除,重定向到初始页面,数据就被删掉了。
修改单表数据
同理class.html需要链接到新的页面,urls.py要增加路由。
def edit_clas(request): if request.method == "GET":#第一次get请求,获取id的参数 nid = request.GET.get('nid') conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='jeff@123', db='db3', charset='utf8') cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute("select cid,caption from class where cid = %s", [nid, ]) result = cursor.fetchone() cursor.close() conn.close() return render(request, 'edit_clas.html', {'result': result}) else:#第二次post请求,将修改值更新到数据库 nid = request.GET.get('nid')#post在url可以传参,但是要用get取url的参数 # nid = request.POST.get('nid') print(nid) title = request.POST.get('title') conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='jeff@123', db='db3', charset='utf8') cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute("update class set caption=%s where cid = %s", [title,nid, ]) conn.commit() cursor.close() conn.close() return redirect('/class/')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>编辑班级</h1> <form method="POST" action="/edit_clas/?nid={{ result.cid }}"> <p>班级名称:<input type="text" name="title" value="{{ result.caption }}" /></p> <input type="submit" value="提交" /> </form> </body> </html>
注:get请求信息在请求头里,就是url上,post请求信息在请求体中,如果要使用post在url中传递参数,那么要用request.GET来获取参数。
这就是单表的数据完成的增删改查。
单表操作之模态对话框
这次以学生表为例,先创建查看学生表。
先创建工程,添加路由,创建app_name文件夹和views.py文件以及students函数。
import pymysql from django.shortcuts import render, redirect,HttpResponse def students(request): """ 学生列表 :param request: 封装请求相关的所有信息 :return: """ # conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='jeff@123', db='db3', charset='utf8') cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute( "select student.sid,student.sname,class.caption from student left JOIN class on student.class_id = class.cid") student_list = cursor.fetchall() cursor.close() conn.close() return render(request, 'students.html', {'student_list': student_list})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>学生列表</h1> <div> <a href="/add_student/">添加</a> </div> <table> <thead> <tr> <th>ID</th> <th>学生姓名</th> <th>所属班级</th> <th>操作</th> </tr> </thead> <tbody> {% for row in student_list %} <tr> <td>{{ row.sid }}</td> <td>{{ row.sname }}</td> <td>{{ row.caption }}</td> <td> <a href="/edit_student/?nid={{ row.sid }}">编辑</a> | <a>删除</a> </td> </tr> {% endfor %} </tbody> </table> </body> </html>
学生表之add
先url再函数,
def add_student(request):
if request.method == "GET":
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='jeff@123', db='db3', charset='utf8')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute("select cid,caption from class")
class_list = cursor.fetchall()
cursor.close()
conn.close()
return render(request, 'add_student.html', {'class_list': class_list})
else:
name = request.POST.get('name')
class_id = request.POST.get('class_id')
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='jeff@123', db='db3', charset='utf8')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute("insert into student(sname,class_id) values(%s,%s)", [name, class_id, ])
conn.commit()
cursor.close()
conn.close()
return redirect('/students/')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>添加学生</h1> <form method="POST" action="/add_student/"> <p>学生姓名<input type="text" name="name" /></p> <p> 所属班级 <select name="class_id"> {% for row in class_list %} <option value="{{ row.cid }}">{{ row.caption }}</option> {% endfor %} </select> </p> <input type="submit" value="提交" /> </form> </body> </html>
在之前的的增删改查中有很多pymsql的操作,大量的重复代码,我们可以将数据库查询单独拿出来做成一个工具包。我们可以在项目中增加一个utils的包作为sql操作包。
import pymysql
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='jeff@123', db='db3', charset='utf8')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
def get_list(sql,args):#查找数据库中所有数据
cursor.execute(sql,args)
result = cursor.fetchall()
cursor.close()
conn.close()
return result
def get_one(sql,args):#查找一条数据
cursor.execute(sql,args)
result = cursor.fetchone()
cursor.close()
conn.close()
return result
def modify(sql,args):#更新数据
cursor.execute(sql,args)
conn.commit()
cursor.close()
conn.close()
注:conn和cursor拿出来可能会导致在同一个函数中操作时可能会导致conn被关闭第二次取不到值。
编辑学生表:
from utils import sqlhelper def edit_student(request): if request.method == "GET": nid = request.GET.get('nid') class_list = sqlhelper.get_list("select cid,caption from class", []) current_student_info = sqlhelper.get_one('select sid,sname,class_id from student where sid=%s', [nid, ]) print(current_student_info) return render(request, 'edit_student.html', {'class_list': class_list, 'current_student_info': current_student_info}) else: nid = request.GET.get('nid') name = request.POST.get('name') class_id = request.POST.get('class_id') sqlhelper.modify('update student set sname=%s,class_id=%s where sid=%s', [name, class_id, nid, ]) return redirect('/students/')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>编辑学生</h1> <form method="POST" action="/edit_student/?nid={{ current_student_info.sid }}"> <p>学生姓名<input type="text" name="name" value="{{ current_student_info.sname }}" /></p> <p> 所属班级 <select name="class_id"> <!-- 循环所有的班级 --> {% for row in class_list %} <!-- 如果是当前学生所在班级,则默认选中 --> {% if row.cid == current_student_info.class_id %} <option selected value="{{ row.cid }}">{{ row.caption }}</option> {% else %} <option value="{{ row.cid }}">{{ row.caption }}</option> {% endif %} {% endfor %} </select> </p> <input type="submit" value="提交" /> </form> </body> </html>
为了确保编辑时现实的select就是此id的班级,所以这里使用的if判断来完成。只有row.cid == current_student_info.class_id时,默认被selected即可。
到这里开始和之前的班级表并没有什么差异,但是,每次编辑和添加都使用新的链接并不是一件很可靠的事,在很多情况下我们都需要在当前页进行增删改查,这个时候我们就需要使用到模态对话框了。而submit的提交一定会导致页面的刷新(后台指定),所以有需要使用的前端的ajax技术。
一句话介绍ajax就是页面不刷新并悄悄地往后台发数据,然后在后台进行简单的操作。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .hide{ display: none; } .shadow{ position: fixed; left: 0; top: 0; right: 0; bottom: 0; background-color: black; opacity: 0.4; z-index: 999; } .modal{ z-index: 1000; position: fixed; left: 50%; top: 50%; height: 300px; width: 400px; background-color: white; margin-left: -200px; margin-top: -150px; } </style> </head> <body> <h1>学生列表</h1> <div> <a href="/add_student/">添加</a> <a onclick="showModal();">对话框添加</a> </div> <table> <thead> <tr> <th>ID</th> <th>学生姓名</th> <th>所属班级</th> <th>操作</th> </tr> </thead> <tbody> {% for row in student_list %} <tr> <td>{{ row.sid }}</td> <td>{{ row.sname }}</td> <td>{{ row.caption }}</td> <td> <a href="/edit_student/?nid={{ row.sid }}">编辑</a> | <a>删除</a> </td> </tr> {% endfor %} </tbody> </table> <div id="shadow" class="shadow hide"></div> <div id="modal" class="modal hide"> <h2>添加学生</h2> <p> 学生姓名<input id="sname" type="text" name="sname" /> </p> <p> 所属班级 <select name="class_id" id="add_classid">{# 相当于提交了一个class_id=value回后台 #} {% for row in class_list %} <option value="{{ row.cid }}">{{ row.caption }}</option> {% endfor %} </select> </p> <input type="button" value="提交" onclick="AjaxSend();" /><span id="errormsg"></span> <input type="button" value="取消" onclick="cancleModal();" /> </div> <script src="/static/jquery-3.2.1.js"></script> <script> function showModal(){ document.getElementById('shadow').classList.remove('hide'); document.getElementById('modal').classList.remove('hide'); } function cancleModal(){ document.getElementById('shadow').classList.add('hide'); document.getElementById('modal').classList.add('hide'); } function AjaxSend(){ $.ajax({ url: '/modal_add_student/', type: 'POST', data: {'sname': $('#sname').val(),'class_id':$('#add_classid').val()},//返回的是节点下value的值 success: function(data){ // 当服务端处理完成后,返回数据时,该函数自动调用 // data=服务端返回的值 console.log(data); if(data == "ok"){ location.href= "/students/"; }else{ $('#errormsg').text(data); } } }) } </script> </body> </html>
def modal_add_student(request): sname = request.POST.get('sname') class_id=request.POST.get('class_id') if len(sname) > 0: sqlhelper.modify('insert into student(sname,class_id) values(%s,%s)',[sname,class_id,]) return HttpResponse('ok') else: return HttpResponse('班级标题不能为空')
所以,总的来说Ajax的步骤就三部:
1.url,发送的后端路径,
2.type,发送到后端的方式(GET/POST),
3.data,发送到后端的数据。
后端处理完以后会触发Ajax的success里的函数,location.href指向要跳转的地址。
模态对话框与新url的方式各自的优缺点:
模态对话框:处理少量输入框并且输入的部分比较少时使用,比如登陆界面等;
新url方式:处理操作较多,数据量偏大时使用,比如博客的编辑页面等。
所以我们要根据实际的操作情况来选择合适的方式。
学生表之对话框编辑
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .hide{ display: none; } .shadow{ position: fixed; left: 0; top: 0; right: 0; bottom: 0; background-color: black; opacity: 0.4; z-index: 999; } .modal{ z-index: 1000; position: fixed; left: 50%; top: 50%; height: 300px; width: 400px; background-color: white; margin-left: -200px; margin-top: -150px; } .editModal{ z-index: 1000; position: fixed; left: 50%; top: 50%; height: 300px; width: 400px; background-color: white; margin-left: -200px; margin-top: -150px; } </style> </head> <body> <h1>学生列表</h1> <div> <a href="/add_student/">添加</a> <a onclick="showModal();">对话框添加</a> </div> <table> <thead> <tr> <th>ID</th> <th>学生姓名</th> <th>所属班级</th> <th>操作</th> </tr> </thead> <tbody> {% for row in student_list %} <tr> <td>{{ row.sid }}</td> <td>{{ row.sname }}</td> <td clsId="{{ row.cid }}">{{ row.caption }}</td> <td> <a href="/edit_student/?nid={{ row.sid }}">编辑</a> | <a onclick="modelEdit(this);">对话框编辑</a> | <a>删除</a> </td> </tr> {% endfor %} </tbody> </table> <div id="shadow" class="shadow hide"></div> <div id="modal" class="modal hide"> <h2>添加学生</h2> <p> 学生姓名<input id="sname" type="text" name="sname" /> </p> <p> 所属班级 <select name="class_id" id="add_classid">{# 相当于提交了一个class_id=value回后台 #} {% for row in class_list %} <option value="{{ row.cid }}">{{ row.caption }}</option> {% endfor %} </select> </p> <input type="button" value="提交" onclick="AjaxSend();" /> <input type="button" value="取消" onclick="cancleModal();" /><span id="errormsg"></span> </div> <div id="editModal" class="editModal hide"> <h2>编辑学生</h2> <p> <input type="text" style="display: none" id="stuId"> 学生姓名<input id="stuname" type="text" name="sname" /> </p> <p> 所属班级 <select name="classId" id="edit_classid">{# 相当于提交了一个class_id=value回后台 #} {% for row in class_list %} <option value="{{ row.cid }}">{{ row.caption }}</option> {% endfor %} </select> </p> <input type="button" value="提交" id="btnEdit" /> <input type="button" value="取消" onclick="cancleModal();" /><span id="errorm"></span> </div> <script src="/static/jquery-3.2.1.js"></script> <script> function showModal(){ document.getElementById('shadow').classList.remove('hide'); document.getElementById('modal').classList.remove('hide'); } function cancleModal(){ document.getElementById('shadow').classList.add('hide'); document.getElementById('modal').classList.add('hide'); $('.editModal').hide(); } function AjaxSend(){ $.ajax({ url: '/modal_add_student/', type: 'POST', data: {'sname': $('#sname').val(),'class_id':$('#add_classid').val()}, success: function(data){ // 当服务端处理完成后,返回数据时,该函数自动调用 // data=服务端返回的值 //console.log(data); if(data == "ok"){ location.href= "/students/";{# js实现页面跳转#} }else{ $('#errormsg').text(data); } } }) } function modelEdit(ths){ document.getElementById('shadow').classList.remove('hide'); document.getElementById('modal').classList.add('hide'); $('.editModal').show(); /* 1. 获取当前点击标签 2. 当前标签父标签,再找其上方标签 3. 获取当前行班级名称,赋值到编辑对话框中 */ var row = $(ths).parent().prevAll(); var clas = $(row[0]).attr('clsId'); $('#edit_classid').val(clas); //要在对话框中取到classid那么在获取数据的时候就需要从数据库中查询到, // 以attr或隐藏方式传入原始表单,才能取到。 var stuname = $(row[1]).text(); $('#stuname').val(stuname); var stu_id = $(row[2]).text();//修改的学生的id也应该传给后台用于sql更新 $('#stuId').val(stu_id); } $('#btnEdit').click(function(){ $.ajax({ url:'/modal_edit_student/', type: 'POST', data: {'nid': $('#stuId').val(), 'name':$('#stuname').val(),'class_id': $('#edit_classid').val()}, dataType: 'JSON', //JSON.parse(arg) success:function(arg){ if(arg.status){ location.reload(); }else{ $('#errorm').text(arg.message); } } }) }) </script> </body> </html>
我们在模态对话框中显示的编辑对象,需要显示学生姓名以及学生班级因为学生班级要用select表单方式展示所以必须要拿到学生对应的classid号,但是页面只需要显示课程的名称,所以可以采取两种方案,都要现在查询联表时查询到classid传到模板,第一种方式是添加隐藏的列来取到id,另一种就是这里使用的增加自定义属性clsId="{{ row.cid }}"来取到编辑用户的id。
取classid的时候使用的是row = $(ths).parent().prevAll();取出点击的标签之前所有的兄弟标签元素(td),$(row[index]).attr('clsId')来取出classid。
location.reload()//页面重新加载
注:JSON.parse(arg)只能操作可序列化元素,我们这里使用dataType: 'JSON'相当于在回调函数中进行了JSON.parse(arg)操作。
import json def modal_edit_student(request): ret = {'status': True,'message': None} try: nid = request.POST.get('nid') name = request.POST.get('name') class_id = request.POST.get('class_id') sqlhelper.modify('update student set sname=%s,class_id=%s where sid=%s',[name,class_id,nid,]) except Exception as e: ret['status'] = False ret['message'] = str(e) return HttpResponse(json.dumps(ret))
注:Ajax向后台发数据时更新学生信息所以需要获取学生id。
教师表之多对多操作
查询操作和一对一,一对多基本相同,唯一注意的就是,查询出来的teacher_list是列表中套字典的格式:
[{'tid': 1, 'tname': '波多', 'student_id': 2, 'class_id': 3, 'caption': '三年一班'},]
在页面中显示的时候我们希望看到的友好的界面是一名老师对应多门课程,多门课程在同一个单元格中显示。
result = {} for row in teacher_list: tid =row['tid'] if tid in result: result[tid]['captions'].append(row['caption']) else: result[tid] = {'tid': row['tid'],'name':row['tname'],'captions': [row['caption'],]}
sql辅助模块
前面使用了sql的工具包存在着一些一些问题,每次连接都需要拿链接和cursor,并且不停的打开关闭数据库会浪费很多时间,所以有什么方法可以只拿到一次conn和cursor就往里面写,写完再断开呢?这很容易就联想到python里的类属性。
class SqlHelper(object): def __init__(self): # 读取配置文件 self.connect() def connect(self): self.conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='', db='s4db65', charset='utf8') self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor) def get_list(self,sql,args): self.cursor.execute(sql,args) result = self.cursor.fetchall() return result def get_one(self,sql,args): self.cursor.execute(sql,args) result = self.cursor.fetchone() return result def modify(self,sql,args): self.cursor.execute(sql,args) self.conn.commit() def multiple_modify(self,sql,args): # self.cursor.executemany('insert into bd(id,name)values(%s,%s)',[(1,'alex'),(2,'eric')]) self.cursor.executemany(sql,args) self.conn.commit() def create(self,sql,args): self.cursor.execute(sql,args) self.conn.commit() return self.cursor.lastrowid def close(self): self.cursor.close() self.conn.close()
添加老师的课程,因为班级是可以多选的,首先前端往后端发送的课程是一个列表,后端使用request.POST.getlist(''),获取提交的列表,同时我们需要获得老师的id号用于insert,cursor.lastrowid是最后一条记录插入的记录(非并发)。
相对于优化过的数据库一次链接多次提交,我们使用一次链接一次提交更为高效。
# self.cursor.executemany('insert into bd(id,name)values(%s,%s)',[(1,'alex'),(2,'eric')])
data_list = [] for cls_id in class_ids: temp = (teacher_id,cls_id,) data_list.append(temp) obj = sqlheper.SqlHelper() obj.multiple_modify('insert into teacher2class(teacher_id,class_id) values(%s,%s)',data_list) obj.close()
如果数据很多,查询数据库需要等待,等待的界面就是shadow和一个屏幕正中间的div(填充了背景为gif的图片)show,表格div为hide,在Ajax收到后台返回的数据后在success里将gif的div改为hide,表格div为show就是这样的效果了。
在这里使用对话框进行添加和编辑老师时,对于获取所有的课程我们一直是在页面加载的时候就进行数据库查询,然后隐藏起来,需要操作的时候show,但实际上某些时候表基本上不会改动,那么等于每次都进行了额外的数据库查询,这里我们还提供使用Ajax的方式动态展现。
$.ajax({ url:'/get_all_class/', type:'GET', dataType: 'JSON', success:function(arg){ /* arg = [ {id:1,title:xx} {id:1,title:xx} {id:1,title:xx} ] */ //console.log(arg); // 将所有的数据添加到select,option $.each(arg,function(i,row){ var tag = document.createElement('option'); tag.innerHTML = row.title; tag.setAttribute('value',row.id); $('#classIds').append(tag); }); $('#loading').hide(); $('#addModal').show(); } })
Ajax在发送data时,data数据中需要传递列表类型的数据则要使用traditional: true,后台才可以接收到数据。
function bindAddSubmit(){ $('#addSubmit').click(function(){ var name = $('#addName').val(); var class_id_list = $('#classIds').val(); console.log(name,class_id_list); $.ajax({ url:'/modal_add_teacher/', type: 'POST', data: {'name':name, 'class_id_list': class_id_list}, dataType:'JSON', traditional: true,// 如果提交的数据的值有列表,则需要添加此属性 success: function (arg) { if(arg.status){ location.reload(); }else{ alert(arg.message); } } }) }); } 后台: class_id_list = request.POST.getlist('class_id_list') #如果没有 traditional: true,那么后台接收到的就是一个空列表。
表单提交中的input、button、submit的区别
http://blog.csdn.net/ldc5306590/article/details/54376417
简而言之就是:
<input type="button" /> 这就是一个按钮。如果你不写javascript 的话,按下去什么也不会发生。
<input type="submit" /> 这样的按钮用户点击之后会自动提交 form,除非你写了javascript 阻止它或者绑定onsubmit="return false;"。
<button> 这个按钮放在 form 中也会点击自动提交,比前两个的优点是按钮的内容不光可以有文字,还可以有图片等多媒体内容。
附:源码的git地址:https://gitee.com/gouliguojiashengsiyi/jeffsys1.0/tree/master/