对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端。
import socketdef f1(request): """ 处理用户请求,并返回相应的内容 :param request: 用户请求的所有信息 :return: """ f = open('index.fsw','rb') data = f.read() f.close() return data""""""def f2(request): f = open('aricle.tpl','rb') data = f.read() f.close() print(data) return data"""用户登录
ID | 用户名 | 邮箱 |
---|---|---|
1 | root | root@qq.com |
这种静态页面不能与数据库连接交互,所以也是非常的low。
import socketdef f1(request): """ 处理用户请求,并返回相应的内容 :param request: 用户请求的所有信息 :return: """ f = open('index.fsw','rb') data = f.read() f.close() return data""""""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')"""用户登录
ID | 用户名 | 邮箱 |
---|---|---|
1 | @@sw@@ | root@qq.com |
ID | 用户名 | 邮箱 |
---|
这里要说两点,首先这里使用了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 %}
#一般循环
- { % for user in users %}
- { { user.username|e }} { % endfor %}
jinja2模块最重要的部分是宏,宏相当于一个搭建好的页面一部分,可以被引入,可以往宏传递参数。可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量,在使用宏时传递参数,从而将宏渲染成为页面的一部分。
更多关于此模块的操作,可以查看博客https://www.cnblogs.com/ygj0930/p/7170621.html。
要说的第二点就是这种方法还是太low了。
import socketdef handle_request(client): buf = client.recv(1024) client.send("HTTP/1.1 200 OK\r\n\r\n".encode("utf8")) client.send("Hello, yuan
".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'Hello, web!
Hello, py!
']### 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 urlfrom django.shortcuts import HttpResponsedef 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 urlfrom django.shortcuts import renderdef 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 urlfrom django.shortcuts import render,redirectdef 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中的修改:
Title 用户登录
这样用户访问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 urlfrom django.contrib import adminfrom django.shortcuts import HttpResponse,render,redirectdef 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),]
Title 用户登录
Title
{ { item.id }} | { { item.name }} | { { item.age }} | {# 跳转到专门的del页面 #} |
这里render传入的字典可以使用pymsql导入,这样就与数据库紧密的连接到一起了。
模板传入字典:
def func(request): v = { 'name':'jeff', 'age':25} return render(request, 'test.html', { 'v': v})
{% for item in v %}{ { item }}
{ % endfor %}
结果只显示key。
{% for item in v.keys %}{ { item }}
{ % endfor %}
与上显示一致。
{% for item in v.values %}{ { item }}
{ % endfor %}
只显示values。
{% for k,v in v.items %}{ { item }}
{ % 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的数据传给模板语言
Title 班级列表
ID | 班级名称 | 操作 |
---|---|---|
{ { row.cid }} | { { row.caption }} | 添加 编辑 删除 |
点击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/')
Title 添加班级
重定向会第二次再请求‘/class/’页面,从而获取新的数据库中的值。
删除单表数据
在class.html上加上跳转链接:
添加路由:
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/')
Title 编辑班级
注:get请求信息在请求头里,就是url上,post请求信息在请求体中,如果要使用post在url中传递参数,那么要用request.GET来获取参数。
这就是单表的数据完成的增删改查。
单表操作之模态对话框
这次以学生表为例,先创建查看学生表。
先创建工程,添加路由,创建app_name文件夹和views.py文件以及students函数。
import pymysqlfrom django.shortcuts import render, redirect,HttpResponsedef 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})
Title 学生列表
ID | 学生姓名 | 所属班级 | 操作 |
---|---|---|---|
{ { row.sid }} | { { row.sname }} | { { row.caption }} | 编辑 | 删除 |
学生表之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/')
Title 添加学生
在之前的的增删改查中有很多pymsql的操作,大量的重复代码,我们可以将数据库查询单独拿出来做成一个工具包。我们可以在项目中增加一个utils的包作为sql操作包。
import pymysqlconn = 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 resultdef get_one(sql,args):#查找一条数据 cursor.execute(sql,args) result = cursor.fetchone() cursor.close() conn.close() return resultdef modify(sql,args):#更新数据 cursor.execute(sql,args) conn.commit() cursor.close() conn.close()
注:conn和cursor拿出来可能会导致在同一个函数中操作时可能会导致conn被关闭第二次取不到值。
编辑学生表:
from utils import sqlhelperdef 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/')
Title 编辑学生
为了确保编辑时现实的select就是此id的班级,所以这里使用的if判断来完成。只有row.cid == current_student_info.class_id时,默认被selected即可。
到这里开始和之前的班级表并没有什么差异,但是,每次编辑和添加都使用新的链接并不是一件很可靠的事,在很多情况下我们都需要在当前页进行增删改查,这个时候我们就需要使用到模态对话框了。而submit的提交一定会导致页面的刷新(后台指定),所以有需要使用的前端的ajax技术。
一句话介绍ajax就是页面不刷新并悄悄地往后台发数据,然后在后台进行简单的操作。
Title 学生列表
ID | 学生姓名 | 所属班级 | 操作 |
---|---|---|---|
{ { row.sid }} | { { row.sname }} | { { row.caption }} | 编辑 | 删除 |
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方式:处理操作较多,数据量偏大时使用,比如博客的编辑页面等。
所以我们要根据实际的操作情况来选择合适的方式。
学生表之对话框编辑
Title 学生列表
ID | 学生姓名 | 所属班级 | 操作 |
---|---|---|---|
{ { row.sid }} | { { row.sname }} | { { row.caption }} | 编辑 | 对话框编辑 | 删除 |
我们在模态对话框中显示的编辑对象,需要显示学生姓名以及学生班级因为学生班级要用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 jsondef 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 阻止它或者绑定οnsubmit="return false;"。
<button> 这个按钮放在 form 中也会点击自动提交,比前两个的优点是按钮的内容不光可以有文字,还可以有图片等多媒体内容。
附:源码的git地址:https://gitee.com/gouliguojiashengsiyi/jeffsys1.0/tree/master/