一、Hello Django
1、查看django命令
D:> django-admin
2、创建项目
D:> django-admin startproject guest
- guest/
- guest/
- __init__.py:一个空的文件,用它标识一个目录为 Python 的标准包。
- settings.py:Django 项目的配置文件,包括 Django 模块应用配置,数据库配置,模板配置等。
- urls.py:Django 项目的 URL 声明。
- wsgi.py:为 WSGI 兼容的 Web 服务器服务项目的切入点。
- manage.py:一个命令行工具,可以让你在使用 Django 项目时以不同的方式进行交互。
3、查看 manage 所提供的命令
D:\guest> python manage.py
4、创建应用
D:\guest> python manage.py startapp sign
- guest/
- sign/
- migrations/:用于记录 models 中数据的变更。
- admin.py:映射 models 中的数据到 Django 自带的 admin 后台。
- apps.py:在新的 Django 版本中新增,用于应用程序的配置。
- models.py:创建应用程序数据表模型(对应数据库的相关操作)。
- tests.py:创建 Django 测试。
- views.py:控制向前端显示哪些数据。
5、运行项目
D:\guest>
python manage.py runserver
默认8000端口启动项目,也可以指定IP和端口:D:\guest>
python manage.py runserver 127.0.0.1:8001
6、使用视图显示 Hello Django
(1)首先配置 guest/guest/settings.py ,将 sign 应用添加到项目中。
1
2
3
4 INSTALLED_APPS = [
...
'sign', # 添加sign应用
](2)再配置 guest/guest/urls.py ,添加 index/ 目录。
1
2
3
4
5
6 from django.conf.urls import url, include # url控制需要导入
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^index/$', views.index), # 添加 index/ 路径配置
](3)接着编辑 guest/sign/views.py ,创建 index() 视图函数。
1
2
3
4 from django.http import HttpResponse
def index(request):
return HttpResponse('Hello Django')(4)运行项目,访问127.0.0.1:8000,即可显示’Hello Django’。
7、使用模板显示 Hello Django
(1)在应用 guest/sign/ 目录下,创建 templates/index.html 。(注意需要创建templates目录)
1
2
3
4
5
6
7
8 <html>
<head>
<title>Django Page</title>
</head>
<body>
<h1>Hello Django</h1>
</body>
</html>(2)编辑 guest/sign/views.py,用 index() 视图函数返回 index.html。(注意html文件路径不需要写templates)
1
2
3
4 from django.shortcuts import render
def index(request):
return render(request, 'index.html') # 这里抛弃 HttpResponse 类,转而使用 Django 的 render 函数(3)运行项目,访问127.0.0.1:8000,即可显示’Hello Django’。
8、MTV模式
(1)首先理解 Model-View-Controller(MVC)模式:数据存取逻辑、表现逻辑、业务逻辑。
(2)由于 Controller 由框架自行处理,而 Django 里更关注的是模型(Model)、模板(Template)和视图(Views), Django 也被称为 MTV 框架 :
- M(Model),即数据存取层。该层处理与数据相关的所有事务: 如何存取、如何验证有效。
- T(Template),即表现层。该层处理与表现相关的决定: 如何在页面或其他类型文档中进行显示。
- V(View),即业务逻辑层。该层包含存取模型及调取恰当模板的相关逻辑。 你可以把它看作模型与模板之间的桥梁。
二、Views 视图
1、写个简单的登录
(1)…/templates/index.html编写登录表单代码代码。表单方法使用 method=”post”,提交路径为 action=”/login_action/“。
(2)注意页面需要添加跨站请求伪造( CSRF)令牌,在Html表单中使用Django模板标签:{
% csrf_token %}
(3)打开 …/guest/urls.py 文件添加 login_action/的路由。
(4)打开 …/sign/views.py 文件,创建 login_action 视图函数。
(5)在 …/templates/index.html 页面中添加 Django 模板:。
(6)刷新网页,填入账号密码,验证效果。
2、添加登录成功页
(1)首先创建 …/templates/event_manage.html 页面,编写html代码。
(2)修改 …/sign/views.py 中的 login_action 函数,登录成功后重定向到 /event_manage/ 路径。
(3)在 …/guest/urls.py 文件中添加路径 event_manage/的路由:views.event_manage。
(4)在 …/sign/views.py 中添加event_manage视图函数,返回event_manage.html。
3、Cookie 的使用
(1)修改…/sign/views.py 文件login_action函数设置cookie:
response.set_cookie('user', username)
(2)修改…/sign/views.py 文件 event_manage函数获取cookie:request.COOKIES.get('user', '', 3600)
(3)修改…/templates/event_manage.html 页面,添加标签来显示用户登录的用户名。
4、Session 的使用
(1)修改…/sign/views.py文件,在 login_action 函数中将 session 信息记录到浏览器:
request.session['user']=username
。
(2)修改…/sign/views.py文件,在 event_manage 函数中读取浏览器 session:username=request.session.get('user','')
。
(3)既然要服务器端记录用户的数据,需要创建 django_session 表,不过Django已经准备好了,我们需要生成他们,进行数据迁移:D:\guest>python3 manage.py migrate
5、登录 Admin 后台
执行 manage.py 的“migrate”命令时,Django 同时也帮我们生成了 auth_user 表。同时,我们可以通过 URL 地址:http://127.0.0.1:8000/admin/ 来访问 Django 自带的 Admin 管理后台。在此之前,先来创建登录 Admin 后台的管理员账号:
- D:\guest>
python3 manage.py createsuperuser
- Username (leave blank to use ‘fnngj’):
admin
#输入用户名- Email address:
admin@mail.com
#输入邮箱- Password:
admin123456
#输入密码- Password (again):
admin123456
#重复输入密码
6、引用 Django 认证登录
(1)打开…/sign/views.py 文件修改 login_action 函数,认证给出的用户名和密码:
user = auth.authenticate(username=username, password=password)
(2)通过 if 语句判断 authenticate()返回(即user)如果不为 None,说明用户认证通过。
(3)来调用 login()函数进行 登录:auth.login(request, user)
7、关上窗户
(1)直接访问:http://127.0.0.1:8000/event_manage/, 我们不需要通过登录也可以直接访问到登录成功的页面。我们要把这些“窗户”都关上,使用户只能通过登录来访问系统:
- 如果想限制某个视图函数必须登录才能访问,只需要在这个函数的前面加上:
@login_required
。(2)仔细看,会发布在访问被@login_required 装饰的视图时,默认会跳转的 URL 中会包含 “/accounts/login/”,为什么不让它直接跳转到登录页面呢?不但要告诉你窗户是关着的,还要帮你指引到门的位置:
- 修改…/urls.py 文件,添加以下路径:url(r’^accounts/login/$’, views.index)。
- 当用户访问:http://127.0.0.1:8000/ 、 http://127.0.0.1:8000/index/ 、 http://127.0.0.1:8000/event_manage/ ,都会跳转到登录页面。
三、Model 模型
1、设计系统表
Django 提供完善的模型(model)层主要用来创建和存取数据,不需要我们直接对数据库操作。每个模型是一个 Python 类,继承 django.db.models.model 类。该模型的每个属性表示一个数据库表字段。
(1)打开…/sign/models.py 文件,完成表的创建。
(2)进行数据库迁移:
- D:\guest>
python3 manage.py makemigrations sign
- D:\guest>
python3 manage.py migrate
2、admin 后台管理
创建的发布会和嘉宾表 同样可以通过 Admin 后台去管理:
- 打开…/sign/admin.py 文件,通知 admin 管理工具为这些模块逐一提供界面:
admin.site.register()
。- 打开…/sign/admin.py 文件,定义 list_display 要在列表中显示哪些字段,当然,这些字段名称必须是模型中的数据表类中定义的。
- 打开…/sign/admin.py 文件,search_fields 创建表字段的搜索器,设置搜索关键字匹配多个表字段。 list_filter 用于创建字段过滤器。
3、基本数据访问
python manage.py shell
# dos下进入django的shell模式from sign.models import Event,Guest
table.objects.all()
# 获得 table(Event、Gues 表)中的所有对象quit()
# 退出shell
(1)插入数据
1
2
3 from datetime import datetime
e1 = Event(id=2,name='红米 Pro 发布会',limit=2000,status=True,address='北京水立方',start_time=datetime(2016,8,10,14,0,0))
e1.save()ps:修改 settings.py 文件保存后,需要执行 quit() 命令退出 shell 模式,并重新执行 Python3 manage.py shell 进入,刚才的设置才会生效。
也可以通过 table.objects.create()方法将两步合为一步:
1
2 Event.objects.create(id=3,name='红米 MAX发布会',limit=2000,status=True, address='北京会展中心',start_time=datetime(2016,9,22,14,0,0)) <Event: 红米MAX发布会>
Guest.objects.create(realname='andy',phone=13611001101,email= 'andy@mail.com',sign=False,event_id=3)(2)查询数据
1
2
3
4
5
6
7
8 table.objects.get() # 从数据库表中取得一条匹配的结果,返回一个对象,如果记录不存在的话,那么它会报DoesNotExist类型错误:
e1 = Event.objects.get(name='红米MAX发布会')
e1
e1.address
table.objects.filter() # 方法是从数据库的取得匹配的结果,返回一个对象列表,如果记录不存在的话,它会返回[]:
e2 = Event.objects.filter(name__contains='发布会')
e2(3)删除数据
1
2
3 #通过 delete()方法删除:
g2 = Guest.objects.get(phone='13611001101')
g2.delete()(4)更新数据
1
2
3 g3=Guest.objects.get(phone='13611001101')
g3.realname='andy2'
g3.save()
4、配置 MySQL
(1)要在…/guest/settings.py 文件中修改数据库相关配置。
(2)安装pymysql库。
(3)创建数据库。
(4)切换了数据库后,之前 Sqlite3 数据库里的数据并不能复制到 MySQL 中,所以需要重新进行数据库同步,使数据模型重新在 MySQL 数据库中生成表:D:\guest>python manage.py migrate
(5)直接进行数据迁移会报错!这是因为Django 在连接 MySQL 数据库时默认使用的是 MySQLdb 驱动,然而我们没有安装该驱动,因为它并不支持 Python3,我们现在安装的是 PyMySQL 驱动,如何让当前的 Django 通过 PyMySQL 来连接 MySQL 数据库呢?方法很简单,在…/guest/__init__.py 目录下添加:
1
2
3 import pymysql
pymysql.install_as_MySQLdb()
#添加完后,再执行数据迁移命令。(6) 因为更换了数据库,所以,Admin 后台超级管理员账号(admin/admin123456)也需要重新创建:
- D:\guest>
python3 manage.py createsuperuser
- Username (leave blank to use ‘fnngj’):
admin
#输入用户名- Email address:
admin@mail.com
#输入邮箱- Password:
admin123456
#输入密码- Password (again):
admin123456
#重复输入密码
四、Template 模板
1、Django-bootstrap3
(1)安装
pip install django-bootstrap3
(2)在…/guest/settings.py 文件中添加 bootstrap3 应用。
2、发布会管理
(1)打开…/sign/views.py 文件,修改 event_manage()视图函数:
1
2 event_list = Event.objects.all() # 从数据库查询所有发布会
return render(request,"event_manage.html",{"user": username, "events":event_list}) # 将发布会列表返回给前端(2)打开并编写…/templates/event_manage.html 页面:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 <!-- <head>中引入bootstrap:-->
{% load bootstrap3 %}
{% bootstrap_css %}
{% bootstrap_javascript %}
<!-- <body>中遍历显示出所有发布会列表:-->
{% for event in events %}
<tr>
<td>{{ event.id }}</td>
<td>{{ event.name }}</td>
<td>{{ event.status }}</td>
<td>{{ event.address }}</td>
<td>{{ event.start_time }}</td>
</tr>
{% endfor %}(3)在…/templates/event_manage.html 页面上创建搜索表单。
(4)在…/guest/urls.py 文件中添加搜索路径的路由:url(r'^search_name/$', views.search_name)
(5)打开…/sign/views.py 文件,创建 search_name()视图函数。
3、嘉宾管理
(1)创建…/templates/guest_manage.html 页面:
1
2
3
4
5
6
7
8
9
10 {% for guest in guests %}
<tr>
<td>{{ guest.id }}</td>
<td>{{ guest.realname }}</td>
<td>{{ guest.phone }}</td>
<td>{{ guest.email }}</td>
<td>{{ guest.sign }}</td>
<td>{{ guest.event }}</td>
</tr>
{% endfor %}(2)在…/guest/urls.py 文件中添加嘉宾路径的路由。
(3)打开…/sign/views.py 文件,创建 guest_manage()视图函数。
4、分页器 Paginator
Django 已经为我们准备好了 Paginator 分页类。所以,只需要调用它即可完成列表的分页功能,进入 Django 的 shell 模式,练习 Paginator 类的基本使用:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27D:\guest> python3 manage.py shell
from django.core.paginator import Paginator
from sign.models import Guest
# 查询 uest 表的所有数据 guest_list = Guest.objects.all()
2) # 创建每页 2 条数据的分页器 p = Paginator(guest_list,
# 查看共多少条数据 p.count
# 查看共分多少页 p.page_range
##########第一页#############
1) # 获取第 1 页的数据 page1 = p.page(
# 当前第几页 page1
# 当前页的对象 page1.object_list
for p in page1: # 循环打印第 1 页嘉宾的 realname
p.realname
##########第二页#############
2) page2 = p.page(
# 本页的第一条数据 page2.start_index()
# 本页的最后一条数据 page2.end_index()
# 是否有上一页 page2.has_previous()
# 是否有下一页 page2.has_next()
# 上一页是第几页 page2.previous_page_number()
# 下一页是第几页 page2.next_page_number()
##########第三页#############
3) page3 = p.page(
# 是否有其他页 page3.has_other_pages()
(1)打开…/sign/views.py 文件,修改 guest_manage()视图函数。
(2)在…/templates/guest_manage.html 页面也需要添加分页器的代码。
5、签到功能
(1)在…/templates/event_manage.html 页面,增加签到列链接。
(2)在…/guest/urls.py 文件中添加路径路由。
(3)打开…/sign/views.py 文件,创建 sign_index()视图函数。
(4)创建…/templates/sign_index.html 签到页面。
(5)打开…/guest/urls.py 文件,添加签到路径的路由。
(6)打开…/sign/views.py 文件,创建 sign_index_action()视图函数。
(7)修改…/templates/sign_index.html 页面,增加 sign_index_action()视图函数返回的提示信息的位置:
1
2
3
4
5 <font color="red">
<br>{{ hint }}
<br>{{ guest.realname }}
<br>{{ guest.phone }}
</font>
6、退出系统
(1)打开…/urls.py 文件,添加退出目录的路由。
(2)打开…/sign/views.py 文件,创建 logout()视图函数:
1
2
3 auth.logout(request) #退出登录
response = HttpResponseRedirect('/index/')
return response
五、开发 Web 接口
首先,单独创建…/sign/views_if.py文件,该项目的接口方法都在这里。
1、发布会添加接口
(1)首先,判断 eid、 name、limit、address、start_time 等字段均不能为空,否则 JsonResponse()返回相应的 状态码和提示。
(2)接下来,判断发布会 id 是否存在,以及发布会名称(name)是否存在;如果存在将返回相应的状态码和 提示信息。
(3)再接下来,判断发布会状态是否为空,如果为空,将状态设置为 1(True)。
(4)最后,将数据插入到 Event 表,在插入的过程中如果日期格式错误,将抛出 ValidationError 异常,接收 该异常并返回相应的状态和提示,否则,插入成功,返回状态码 200 和“addeventsuccess”的提示。
2、发布会查询接口
(1)通过GET请求接收发布会id和name参数。两个参数都是可选的。首先,判断当两个参数同时为空,接 口返回状态码 10021,参数错误。
(2)如果发布会 id 不为空,优先通过 id 查询,因为 id 的唯一性,所以,查询结果只会有一条,将查询结果 以 key:value 对的方式存放到定义的 event 字典中,并将数据字典作为整个返回字典中 data 对应的值返回。
(3)name 查询为模糊查询,查询数据可能会有多条,返回的数据稍显复杂;首先将查询的每一条数据放到一 个字典 event 中,再把每一个字典再放到数组 datas 中,最后再将整个数组做为返回字典中 data 对应的值返回。
3、嘉宾添加接口
(1)通过POST请求接收嘉宾参数:关联的发布会id、姓名、手机号和邮箱等参数。
(2)首先,判断 eid、realname、phone 等参数均不能为空。
(3)接下来,判断嘉宾关联的发布会 id 是否存在,以及关联的发布会状态是否为 True(即 1),如果不存在 或不为 True,将返回相应的状态码和提示信息。
(4)接下来判断发布会人数限制。
(5)再接下来的步骤是判断当前时间是否大于发布会时间,如果大于则说明发布已开始,或者早已经结束。那么该发布会就应该不能允许再添加嘉宾。
(6)最后,插入嘉宾数据,如果发布会的手机号重复则抛 IntegrityError 异常,接收该异常并返回相应的状态 码和提示信息。如果添加成功,则返回状态码 200 和“addguestsuccess”的提示。
4、嘉宾查询接口
嘉宾查询接口与发布会查询接口相似,只是参数与查询条件判断有所不同,这里就不再一一说明。
5、嘉宾签到接口
(1)签到接口通过POST请求接收发布会id和嘉宾手机号。签到接口的判断条件比较多。
(2)首先,判断两个参数均不能为空。
(3)接着,判断发布会 id 是否存在,以及发布会状态是否为 True,如果不存在或不为 True,将返回相应的状 态码和提示信息。
(4)再接着,判断当前时间是否大于发布会时间,如果大于发布会时间说明发布会已开始,不允许签到。
(5)然后,再判断嘉宾的手机号是否存在,以及嘉宾的手机号与发布会 id 是否为对应关系。否则返回相应的 错误码和提示信息。
(6)最后,判断该嘉宾的状态是否为已签到,如果已签到,返回相应的状态码和提示;如果未签到修改状态 为已签到,并返回状态码 200 和“signsuccess”的提示。
6、配置接口路径
(1)打开…/guest/urls.py文件,添加接口基本路径“/api/”:
url(r'^api/', include('sign.urls', namespace="sign"))
(2)创建…/sign/urls.py文件,配置具体接口的二级路径:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 app_name='sign' # django2.0里必须添加应用名称
urlpatterns = [
# guest system interface:
# ex : /api/add_event/
url(r'^add_event/', views_if.add_event, name='add_event'),
# ex : /api/add_guest/
url(r'^add_guest/', views_if.add_guest, name='add_guest'),
# ex : /api/get_event_list/
url(r'^get_event_list/', views_if.get_event_list, name='get_event_list'),
# ex : /api/get_guest_list/
url(r'^get_guest_list/', views_if.get_guest_list, name='get_guest_list'),
# ex : /api/user_sign/
url(r'^user_sign/', views_if.user_sign, name='user_sign'),
]
7、编写 Web 接口文档
六、接口的安全机制
1、用户认证
(1)重新创建…/sign/views_if_sec.py 视图文件,添加用户认证函数。
(2)增加发布会查询接口函数,函数首先调用用户认证函数,进行用户认证。
(3)在…/sign/urls.py 文件中添加新的安全接口指向:url(r'^sec_get_event_list/', views_if_sec.get_event_list, name='get_event_list')
2、数字签名
在使用 HTTP/SOAP 协议传输数据的时候,签名作为其中一个参数,可以起到关键作用:
1)鉴权:通过客户的密钥,服务端的密钥匹配:
- 例如一个接口传参为:http://127.0.0.1:8000/api/?a=1&b=2
- 假设签名的密钥为:@admin123
- 加上签名之后的接口参数为:http://127.0.0.1:8000/sign/?a=1&b=2&sign=@admin123
- sign 参数明文传输是不安全的,一般会通过加密算法进行加密, “@admin123” 经过 MD5 加密得到:4b9db269c5f978e1264480b0a7619eea
- 单做为鉴权,带签名的接口为:http://127.0.0.1:8000/sign/?a=1&b=2&sign=4b9db269c5f978e1264480b0a7619eea
- 服务器接收到请求后,同样需要对“@admin123”进行 MD5 加密,比对与传来的 sign 加密串是否一致,从而来鉴别调用者是否有权限方位该接口。
2)数据防篡改:参数是明文传输,将参数及密钥加密作为签名与服务器匹配:
- 同样是这样一个带参数的接口:http://127.0.0.1:8000/api/?a=1&b=2
- 假设签名的密钥为:@admin123
- 签名的明文为:a=1&b=2&api_key=@admin123
- 将签名的明文生成 MD5 加密串:786bfe32ae1d3764f208e03ca0bfaf13
- 带参数的接口串为:http://127.0.0.1:8000/sign/?a=1&b=2&sign=786bfe32ae1d3764f208e03ca0bfaf13
- 因为整个接口的参数做了加密,所以,只要任意一个参数发改变,那签名的验证就会失败。从而起到了鉴权及数据完整性的保护。
(1)打开…/sign/views_if_sec.py 视图文件,添加用户签名函数。
(2)增加添加发布会接口函数,函数首先调用用户签名函数,进行签名验证。
(3)在…/sign/urls.py 文件中添加新的安全接口路由:url(r'sec_add_event/', views_if_sec.add_event, name='add_event')
3、接口加密
PyCrypto 是一个免费的加密算法库,支持常见的 DES、AES 加密以及 MD5、SHA 各种 HASH 运算。
安装PyCryoto库: pip install Crypto
(1)客户端请求接口时,进行了AES加密,在服务端需要进行解密验证。代码详见虫师原文地址。
(2)打开…/sign/views_if_sec.py 视图文件,编写解密函数。
(3)在获取嘉宾例表的接口中调用 aes_encryption()函数进行 AES 加密字符串解密。
(4)在…/sign/urls.py 文件中添加新的安全接口指向:url(r'^sec_get_guest_list/', views_if_sec.get_guest_list, name='get_guest_list')
七、REST API
REST(即 RepresentationalStateTransfer 的缩写)中文翻译为“表现层状态转化”:
资源(Resources),所谓”资源”,就是网络上的一个实体,或者说是网络上的一个具体信息。你可以用一个 URI(统一资源定位符)指向它,每种资源对应一个特定的 URI。要获取这个资源,访问它的 URI 就可以。
表现层(Representation),把”资源”具体呈现出来的形式。URI 只代表资源的实体,不代表它的形式。严格地说,有些网址最后的“.html”后缀名是不必要的,因为这个后缀名表示格式,属于“表现层”范畴,而 URI 应该只代表“资源”的位置。它的具体表现形式,应该在 HTTP 请求的头信息中用 Accept 和 Content-Type 字段指定,这两个字段才是对”表现层”的描述。
状态转化(StateTransfer),互联网通信协议 HTTP 协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转化”(StateTransfer)。而这种转化是建立在表现层之上的,所以就是”表现层状态转化”。客户端用到的手段,只能是 HTTP 协议。具体来说,就是 HTTP 协议里面,四个表示操作方式的动词: GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源(也可 以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源。
Django-REST-Framework是一个强大而灵活的工具用于构建 WebAPIs。通过该框架可以创建 REST 风格的 WebSeriver 应用。安装:pip install djangorestframework
1、创建简单的 API
(1)>
django-admin startproject django_rest
(2)>cd django_rest
(3)\django_rest>python3 manage.py startapp api
(4)打开 settings.py 文件添加应用:’rest_framework’和’api’
(5)在 settings.py 文件末尾添加:
1
2
3
4 REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
'PAGE_SIZE': 10
}(6)执行数据库迁移 \django_rest>
python3 manage.py migrate
(7)创建超级管理员账户\django_rest>python3 manage.py createsuperuser
(8)创建数据序列化,创建…/api/serializers.py 文件,代码见文件。
(9)创建视图,打开…/api/views.py 视图文件,代码见文件。
(10)打开…/django_rest/urls.py 文件,添加路径的路由,代码见文件。
(11)启动服务 …\django_rest>python3 manage.py runserver
2、 集成发布会系统 API
(1)接下来在 django_rest 项目的基础上增加发布会和嘉宾管理的接口。
(2)打开…/api/models.py 文件,创建模型。
(3)进行数据库迁移:
1
2 \django_rest> python3 manage.py makemigrations api
\django_rest> python3 manage.py migrate(4)打开…/api/serializers.py 文件,添加发布会数据序列化。
(5)打开…/api/views.py 视图文件,定义发布会视图类。
(6)打开…/django_rest/urls.py 文件,添加 URL 配置。
(7)启动项目,使用浏览器打开:http://127.0.0.1:8000/
八、项目代码
1、guest项目
- guest/
- guest/
- __init__.py
- settings.py
- urls.py
- wsgi.py
- sign/
- migrations/
- __init__.py
- 0001_initial.py
- templates/
- event_manage.html
- guest_manage.html
- index.html
- sign_index.html
- __init__.py
- admin.py
- apps.py
- models.py
- tests.py
- urls.py
- views.py
- views_if.py
- views_if_sec.py
- db.sqlite3
- manage.py
guest/guest/__init__.py1
2
3import pymysql
pymysql.install_as_MySQLdb() # 让当前的 Django 通过 PyMySQL 来连接 MySQL 数据库
guest/guest/settings.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37import os
...
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'sign', # 添加sign应用
'bootstrap3', # 添加
]
...
# 配置mysql数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': '127.0.0.1',
'PORT': '3306',
'NAME': 'guest',
'USER': 'root',
'PASSWORD': '123456',
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
},
}
}
...
guest/guest/urls.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include # url控制需要导入
from sign import views # 导入 sign 应用 views 文件
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^index/$', views.index), # 添加 index/路径配置
url(r'^login_action/$', views.login_action),
url(r'^event_manage/$', views.event_manage),
url(r'^accounts/login/$', views.index),
url(r'^search_name/$', views.search_name),
url(r'^guest_manage/$', views.guest_manage),
url(r'^sign_index/(?P<event_id>[0-9]+)/$', views.sign_index), # 配置二级目录,发布会 id,要求必须为数字
url(r'^sign_index_action/(?P<event_id>[0-9]+)/$', views.sign_index_action),
url(r'^logout/$', views.logout),
url(r'^api/', include('sign.urls', namespace="sign")),
]
guest/sign/admin.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from django.contrib import admin
from sign.models import Event,Guest
class EventAdmin(admin.ModelAdmin):
list_display = ['id', 'name', 'status', 'address', 'start_time'] # 显示字段
search_fields = ['name'] # 搜索栏
list_filter = ['status'] # 过滤器
class GuestAdmin(admin.ModelAdmin):
list_display = ['realname', 'phone', 'email', 'sign', 'create_time', 'event'] # 显示字段
search_fields = ['realname', 'phone'] # 搜索栏
list_filter = ['sign'] # 过滤器
admin.site.register(Event, EventAdmin) # 用 EventAdmin 选项注册 Event 模块。
admin.site.register(Guest, GuestAdmin) # 用 GuestAdmin 选项注册 Guest 模块。
guest/sign/models.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31from django.db import models
# 发布会表
class Event(models.Model):
"""默认都会生成自增id,在创建模型时不需要声明该字段"""
name = models.CharField(max_length=100) # 发布会标题
limit = models.IntegerField() # 参加人数
status = models.BooleanField() # 发布会状态
address = models.CharField(max_length=200) # 地址
start_time = models.DateTimeField('event time') # 发布会时间
create_time = models.DateTimeField(auto_now=True) # 创建时间(自动获取当前时间)
def __str__(self):
return self.name # __str__()方法告诉 Python 如何将对象以 str 的方式显示出来。
# 嘉宾表
class Guest(models.Model):
"""默认都会生成自增id,在创建模型时不需要声明该字段"""
event = models.ForeignKey(Event, on_delete=models.CASCADE) # 关联发布会id
realname = models.CharField(max_length=64) # 姓名
phone = models.CharField(max_length=16) # 手机号
email = models.EmailField() # 邮箱
sign = models.BooleanField() # 签到状态
create_time = models.DateTimeField(auto_now=True) # 创建时间(自动获取当前时间)
class Meta:
"""通过发布会id+手机号来做为联合主键"""
unique_together = ("event", "phone")
def __str__(self):
return self.realname # __str__()方法告诉 Python 如何将对象以 str 的方式显示出来。
guest/sign/urls.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27from django.conf.urls import url
from sign import views_if, views_if_sec
app_name='sign' # django2.0里必须添加应用名称,和urls路由里对应。或者直接在urls路由里添加路径也可以
urlpatterns = [
# guest system interface:
# ex : /api/add_event/
url(r'^add_event/', views_if.add_event, name='add_event'),
# ex : /api/add_guest/
url(r'^add_guest/', views_if.add_guest, name='add_guest'),
# ex : /api/get_event_list/
url(r'^get_event_list/', views_if.get_event_list, name='get_event_list'),
# ex : /api/get_guest_list/
url(r'^get_guest_list/', views_if.get_guest_list, name='get_guest_list'),
# ex : /api/user_sign/
url(r'^user_sign/', views_if.user_sign, name='user_sign'),
# security interface:
# ex : /api/sec_get_event_list/
url(r'sec_get_event_list/', views_if_sec.get_event_list, name='get_event_list'),
# security interface:
# ex : /api/sec_add_event/
url(r'sec_add_event/', views_if_sec.add_event, name='add_event'),
# security interface:
# ex : /aip/sec_add_event/
url(r'^sec_get_guest_list/', views_if_sec.get_guest_list, name='get_guest_list'),
]
guest/sign/views.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib import auth
from django.contrib.auth.decorators import login_required
from sign.models import Event,Guest
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def index(request):
# return HttpResponse('Hello Django')
return render(request, 'index.html') # 这里抛弃 HttpResponse 类,转而使用 Django 的 render 函数
def login_action(request):
if request.method == 'POST':
username = request.POST.get('username', '')
password = request.POST.get('password', '')
user = auth.authenticate(username=username, password=password) # 认证给出的用户名和密码
if user is not None:
#return HttpResponse('login success!')
#response.set_cookie('user', username) # 添加浏览器cookie
auth.login(request, user) # 登录
request.session['user'] = username # 将 session 信息记录到浏览器
return HttpResponseRedirect('/event_manage/')
else:
return render(request, 'index.html', {'error': 'username or password error!'})
else:
return render(request, 'index.html', {'error': 'username or password error!'})
def event_manage(request):
event_list = Event.objects.all()
#username = request.COOKIES.get('user', '', 3600) # 读取浏览器cookie
username = request.session.get('user', '') # 读取浏览器 session
return render(request, 'event_manage.html', {"user": username, "events": event_list})
def search_name(request):
username = request.session.get('user', '')
search_name = request.GET.get('name', '')
event_list = Event.objects.filter(name__contains=search_name)
return render(request, "event_manage.html", {"user": username, "events": event_list})
def guest_manage(request):
username = request.session.get('user', '')
guest_list = Guest.objects.all()
paginator = Paginator(guest_list, 2) # 划分每页显示 2 条数据
page = request.GET.get('page') # 通过 GET 请求得到当前要显示第几页的数
try:
contacts = paginator.page(page) # 获取第 page 页的数据
except PageNotAnInteger: # 如果当前没有页数,抛 PageNotAnInteger 异常
contacts = paginator.page(1) # 返回第一页的数据
except EmptyPage: # 如果超出最大页数的范围,抛 EmptyPage 异常
contacts = paginator.page(paginator.num_pages) # 返回最后一页面的数据
return render(request, "guest_manage.html", {"user": username, "guests": contacts})
def sign_index(request, event_id):
event = get_object_or_404(Event, id=event_id)
return render(request, 'sign_index.html', {'event': event})
def sign_index_action(request, event_id):
event = get_object_or_404(Event, id=event_id)
phone = request.POST.get('phone','')
# 首先,查询 Guest 表判断用户输入的手机号是否存在,如果不存在将提示用户“手机号为空或不存在”
result = Guest.objects.filter(phone = phone)
if not result:
return render(request, 'sign_index.html', {'event': event, 'hint': 'phone error.'})
# 然后,通过手机和发布会 id 两个条件来查询 Guest 表,如果结果为空将提示用户“该用户未参加此次发布会”
result = Guest.objects.filter(phone=phone,event_id=event_id)
if not result:
return render(request, 'sign_index.html', {'event': event, 'hint':'eventidorphoneerror.'})
# 最后,再通过手机号查询 Guest 表,判断该手机号的签到状态是否为 1,如果为 1,表示已经签过到了, 返回用户“已签到”,否则,将提示用户“签到成功!”,并返回签到用户的信息。
result = Guest.objects.get(phone=phone,event_id=event_id)
if result.sign:
return render(request, 'sign_index.html', {'event': event, 'hint': "user has sign in."})
else:
Guest.objects.filter(phone=phone,event_id=event_id).update(sign = '1')
return render(request, 'sign_index.html', {'event': event, 'hint':'sign in success!', 'guest': result})
def logout(request):
auth.logout(request) # 退出登录
response = HttpResponseRedirect('/index/')
return response
guest/sign/views_if.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221from django.http import JsonResponse
from sign.models import Event
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.db.utils import IntegrityError
import time
# 添加发布会接口
def add_event(request):
# 通过POST请求接收发布会参数
eid = request.POST.get('eid','') # 发布会id
name = request.POST.get('name','') # 发布会标题
limit = request.POST.get('limit','') # 限制人数
status = request.POST.get('status','') # 状态
address = request.POST.get('address','') # 地址
start_time = request.POST.get('start_time','') # 发布会时间
# 首先,判断 eid、 name、limit、address、start_time 等字段均不能为空
if eid =='' or name == '' or limit == '' or address == '' or start_time == '':
return JsonResponse({'status':10021,'message':'parameter error'})
# 接下来,判断发布会 id 是否存在
result =Event.objects.filter(id=eid)
if result:
return JsonResponse({'status':10022,'message':'event id already exists'})
# 接下来,判断发布会名称 name 是否存在
result = Event.objects.filter(name=name)
if result:
return JsonResponse({'status':10023, 'message':'event name already exists'})
# 再接下来,判断发布会状态是否为空,如果为空,将状态设置为 1(True)。
if status == '':
status = 1
# 最后,将数据插入到 Event 表,在插入的过程中如果日期格式错误,将抛出 ValidationError 异常
try:
Event.objects.create(id=eid,name=name,limit=limit,address=address,status=int(status),start_time=start_time)
except ValidationError:
error = 'start_time format error. It must be in YYYY-MM-DD HH:MM:SS format.'
return JsonResponse({'status':10024,'message':error})
# 否则,插入成功,返回状态码 200 和“addeventsuccess”的提示。
return JsonResponse({'status':200,'message':'add event success'})
# 发布会查询
def get_event_list(request):
# 通过GET请求接收发布会id和name参数。两个参数都是可选的
eid = request.GET.get("eid", "") # 发布会id
name = request.GET.get("name", "") # 发布会名称
# 首先,判断当两个参数同时为空,接 口返回状态码 10021,参数错误。
if eid == '' and name == '':
return JsonResponse({'status':10021,'message':'parameter error'})
# 如果发布会 id 不为空,优先通过 id 查询,因为 id 的唯一性,所以,查询结果只会有一条
if eid != '':
event = {}
try:
result = Event.objects.get(id=eid)
except ObjectDoesNotExist:
return JsonResponse({'status':10022, 'message':'query result is empty'})
else:
event['eid'] = result.id
event['name'] = result.name
event['limit'] = result.limit
event['status'] = result.status
event['address'] = result.address
event['start_time'] = result.start_time
return JsonResponse({'status':200, 'message':'success', 'data':event})
# name 查询为模糊查询,查询数据可能会有多条
if name != '':
datas = []
results = Event.objects.filter(name__contains=name)
if results:
for r in results:
event = {}
event['eid'] = r.id
event['name'] = r.name
event['limit'] = r.limit
event['status'] = r.status
event['address'] = r.address
event['start_time'] = r.start_time
datas.append(event)
return JsonResponse({'status':200, 'message':'success', 'data':datas})
else:
return JsonResponse({'status':10022, 'message':'query result is empty'})
# 添加嘉宾接口
def add_guest(request):
# 通过POST请求接收嘉宾参数
eid = request.POST.get('eid','') # 关联发布会id
realname = request.POST.get('realname','') # 姓名
phone = request.POST.get('phone','') # 手机号
email = request.POST.get('email','') # 邮箱
# 首先,判断 eid、realname、phone 等参数均不能为空
if eid =='' or realname == '' or phone == '':
return JsonResponse({'status':10021,'message':'parameter error'})
# 接下来,判断嘉宾关联的发布会 id 是否存在
result = Event.objects.filter(id=eid)
if not result:
return JsonResponse({'status':10022,'message':'event id null'})
# 接下来,判断及关联的发布会状态是否为 True(即 1)
result = Event.objects.get(id=eid).status
if not result:
return JsonResponse({'status':10023,'message':'event status is not available'})
# 接下来,判断发布会人数限制
event_limit = Event.objects.get(id=eid).limit # 发布会限制人数
guest_limit = Guest.objects.filter(event_id=eid) # 发布会已添加的嘉宾数
if len(guest_limit) >= event_limit:
return JsonResponse({'status':10024,'message':'event number is full'})
# 再接下来的步骤是判断当前时间是否大于发布会时间,如果大于则说明发布已开始,或者早已经结束。
event_time = Event.objects.get(id=eid).start_time # 发布会时间
timeArray = time.strptime(str(event_time), "%Y-%m-%d %H:%M:%S")
e_time = int(time.mktime(timeArray))
now_time = str(time.time()) # 当前时间
ntime = now_time.split(".")[0]
n_time = int(ntime)
if n_time >= e_time:
return JsonResponse({'status':10025,'message':'event has started'})
# 最后,插入嘉宾数据,如果发布会的手机号重复则抛 IntegrityError 异常,接收该异常并返回相应的状态 码和提示信息
try:
Guest.objects.create(realname=realname,phone=int(phone),email=email,sign=0,event_id=int(eid))
except IntegrityError:
return JsonResponse({'status':10026,'message':'the event guest phone number repeat'})
# 如果添加成功,则返回状态码 200 和“addguestsuccess”的提示
return JsonResponse({'status':200,'message':'add guest success'})
# 嘉宾查询接口
def get_guest_list(request):
eid = request.GET.get("eid", "") # 关联发布会id
phone = request.GET.get("phone", "") # 嘉宾手机号
if eid == '':
return JsonResponse({'status':10021,'message':'eid cannot be empty'})
if eid != '' and phone == '':
datas = []
results = Guest.objects.filter(event_id=eid)
if results:
for r in results:
guest = {}
guest['realname'] = r.realname
guest['phone'] = r.phone
guest['email'] = r.email
guest['sign'] = r.sign
datas.append(guest)
return JsonResponse({'status':200, 'message':'success', 'data':datas})
else:
return JsonResponse({'status':10022, 'message':'query result is empty'})
if eid != '' and phone != '':
guest = {}
try:
result = Guest.objects.get(phone=phone,event_id=eid)
except ObjectDoesNotExist:
return JsonResponse({'status':10022, 'message':'query result is empty'})
else:
guest['realname'] = result.realname
guest['phone'] = result.phone
guest['email'] = result.email
guest['sign'] = result.sign
return JsonResponse({'status':200, 'message':'success', 'data':guest})
# 用户签到接口
def user_sign(request):
# 签到接口通过POST请求接收发布会id和嘉宾手机号
eid = request.POST.get('eid','') # 发布会id
phone = request.POST.get('phone','') # 嘉宾手机号
# 首先,判断两个参数均不能为空
if eid =='' or phone == '':
return JsonResponse({'status':10021,'message':'parameter error'})
# 接着,判断发布会 id 是否存在
result = Event.objects.filter(id=eid)
if not result:
return JsonResponse({'status':10022,'message':'event id null'})
# 接着,判断发布会状态是否为 True
result = Event.objects.get(id=eid).status
if not result:
return JsonResponse({'status':10023,'message':'event status is not available'})
# 再接着,判断当前时间是否大于发布会时间,如果大于发布会时间说明发布会已开始,不允许签到
event_time = Event.objects.get(id=eid).start_time # 发布会时间
timeArray = time.strptime(str(event_time), "%Y-%m-%d %H:%M:%S")
e_time = int(time.mktime(timeArray))
now_time = str(time.time()) # 当前时间
ntime = now_time.split(".")[0]
n_time = int(ntime)
if n_time >= e_time:
return JsonResponse({'status':10024,'message':'event has started'})
# 然后,再判断嘉宾的手机号是否存在
result = Guest.objects.filter(phone=phone)
if not result:
return JsonResponse({'status':10025,'message':'user phone null'})
# 然后,再判断嘉宾的手机号与发布会 id 是否为对应关系
result = Guest.objects.filter(phone=phone,event_id=eid)
if not result:
return JsonResponse({'status':10026,'message':'user did not participate in the conference'})
# 最后,判断该嘉宾的状态是否为已签到,如果已签到,返回相应的状态码和提示
result = Guest.objects.get(event_id=eid,phone=phone).sign
if result:
return JsonResponse({'status':10027,'message':'user has sign in'})
# 如果未签到修改状态 为已签到,并返回状态码 200 和“signsuccess”的提示。
else:
Guest.objects.filter(phone=phone).update(sign='1')
return JsonResponse({'status':200,'message':'sign success'})
guest/sign/views_if_sec.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254from django.http import JsonResponse
from sign.models import Event, Guest
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.contrib import auth as django_auth
import base64, time
import hashlib
from django.http import HttpResponse
#from Crypto.Cipher import AES # 请安装 Crypto
import json
# 用户认证
def user_auth(request):
# request.META 包含了所有本次 HTTP 请求的 Header 信息;HTTP_AUTHORIZATION 用于获取 HTTPauthorization;
# 得到的数据是这样的:Basic YWRtaW46YWRtaW4xMjM0NTY=
get_http_auth = request.META.get('HTTP_AUTHORIZATION','b')
# split()方法拆分成 list,拆分后的数据是这样的:['Basic','YWRtaW46YWRtaW4xMjM0NTY=']
auth = get_http_auth.split()
# 取出 list 中的加密串,通过 base64 对加密串进行解码。得到的数据是:('admin',':','admin123456')
try:
auth_parts = base64.b64decode(auth[1]).decode('utf-8').partition(':')
# 如果获取不到 Auth 信息,将会抛 IndexError 异常
except IndexError:
return "null"
# 取出元组中对应的用户 id 和密码。最终于数据: admin admin123456
userid, password = auth_parts[0], auth_parts[2]
# 调用 Django 的认证模块,对得到 Auth 信息进行认证。成功将返回 “success”,失败则返回“fail”
user = django_auth.authenticate(username=userid, password=password)
if user is not None and user.is_active:
django_auth.login(request, user)
return "success"
else:
return "fail"
# 发布会查询接口---增加用户认证
def get_event_list(request):
auth_result = user_auth(request) # 调用认证函数
if auth_result == "null":
return JsonResponse({'status':10011,'message':'user auth null'})
if auth_result == 'fail':
return JsonResponse({'status':10012,'message':'user auth fail'})
# 通过GET请求接收发布会id和name参数。两个参数都是可选的
eid = request.GET.get("eid", "") # 发布会id
name = request.GET.get("name", "") # 发布会名称
# 首先,判断当两个参数同时为空,接 口返回状态码 10021,参数错误。
if eid == '' and name == '':
return JsonResponse({'status':10021,'message':'parameter error'})
# 如果发布会 id 不为空,优先通过 id 查询,因为 id 的唯一性,所以,查询结果只会有一条
if eid != '':
event = {}
try:
result = Event.objects.get(id=eid)
except ObjectDoesNotExist:
return JsonResponse({'status':10022, 'message':'query result is empty'})
else:
event['eid'] = result.id
event['name'] = result.name
event['limit'] = result.limit
event['status'] = result.status
event['address'] = result.address
event['start_time'] = result.start_time
return JsonResponse({'status':200, 'message':'success', 'data':event})
# name 查询为模糊查询,查询数据可能会有多条
if name != '':
datas = []
results = Event.objects.filter(name__contains=name)
if results:
for r in results:
event = {}
event['eid'] = r.id
event['name'] = r.name
event['limit'] = r.limit
event['status'] = r.status
event['address'] = r.address
event['start_time'] = r.start_time
datas.append(event)
return JsonResponse({'status':200, 'message':'success', 'data':datas})
else:
return JsonResponse({'status':10022, 'message':'query result is empty'})
# 用户签名+时间戳
def user_sign(request):
# 通过 POST 方法获取两个参数 time 和 sign 两个参数
if request.method == "post":
client_time = request.POST.get('time', '') # 客户端时间戳
client_sign= request.POST.get('sign', '') # 客户端签名
else:
return "error"
if client_time == '' or client_sign == '':
return "sign null"
# 服务器时间
now_time = time.time() # 例:1466426831.9126902
# Python3 生成的的时间戳精度太高,我们只需要小数点前面的 10 位即可
server_time = str(now_time).split('.')[0]
# 获取时间差
time_difference = int(server_time) - int(client_time)
# 接口的请求时间是离现在超过 60 秒,返回请求超时
if time_difference >= 60:
return "timeout"
# 签名检查
md5 = hashlib.md5()
# 签名参数的生成:密钥字符串(“&Guest-Bugmaster”)和客户端发来的时间戳, 两者拼接成一个新的字符串
sign_str = client_time + "&Guest-Bugmaster"
sing_bytes_utf8 = sign_str.encode(encoding="utf-8")
# 通过 MD5 对其进行加密。从而将加密后的字符串作为 sign 的字段的参数
md5.update(sing_bytes_utf8)
server_sign = md5.hexdigest
# 两端使用相同的规则生成的加密后的字符串,进行比较
if server_sign != client_sign:
return "sign fail"
else:
return "sign right"
# 添加发布会接口---增加签名+时间戳
def add_event(request):
sign_result = user_sign(request) # 调用签名函数
if sign_result == "error":
return JsonResponse({'status':10011,'message':'request error'})
elif sign_result == "sign null":
return JsonResponse({'status':10012,'message':'user sign null'})
elif sign_result == "timeout":
return JsonResponse({'status':10013,'message':'user sign timeout'})
elif sign_result == "sign fail":
return JsonResponse({'status':10014,'message':'user sign error'})
# 通过POST请求接收发布会参数
eid = request.POST.get('eid','') # 发布会id
name = request.POST.get('name','') # 发布会标题
limit = request.POST.get('limit','') # 限制人数
status = request.POST.get('status','') # 状态
address = request.POST.get('address','') # 地址
start_time = request.POST.get('start_time','') # 发布会时间
# 首先,判断 eid、 name、limit、address、start_time 等字段均不能为空
if eid =='' or name == '' or limit == '' or address == '' or start_time == '':
return JsonResponse({'status':10021,'message':'parameter error'})
# 接下来,判断发布会 id 是否存在
result =Event.objects.filter(id=eid)
if result:
return JsonResponse({'status':10022,'message':'event id already exists'})
# 接下来,判断发布会名称 name 是否存在
result = Event.objects.filter(name=name)
if result:
return JsonResponse({'status':10023, 'message':'event name already exists'})
# 再接下来,判断发布会状态是否为空,如果为空,将状态设置为 1(True)。
if status == '':
status = 1
# 最后,将数据插入到 Event 表,在插入的过程中如果日期格式错误,将抛出 ValidationError 异常
try:
Event.objects.create(id=eid,name=name,limit=limit,address=address,status=int(status),start_time=start_time)
except ValidationError:
error = 'start_time format error. It must be in YYYY-MM-DD HH:MM:SS format.'
return JsonResponse({'status':10024,'message':error})
# 否则,插入成功,返回状态码 200 和“addeventsuccess”的提示。
return JsonResponse({'status':200,'message':'add event success'})
# AES加密算法
BS = 16
# 通过 upad 匿名函数对字符串的长度还原
unpad = lambda s : s[0: - ord(s[-1])]
def decryptBase64(src):
"""
对 Base64 字符串解密
"""
return base64.urlsafe_b64decode(src)
def decryptAES(src, key):
"""
解析AES密文
"""
# 调用 decryptBase64()方法,将 Base64 加密字符串解密为 AES 加密字符串。
src = decryptBase64(src)
iv = b"1172311105789011"
# 通过 decrypt() 对 AES 加密串进行解密。
cryptor = AES.new(key, AES.MODE_CBC, iv)
text = cryptor.decrypt(src).decode()
return unpad(text)
def aes_encryption(request):
# 服务器端与合法客户端约定的密钥 app_key。
app_key = 'W7v4D60fds2Cmk2U'
# 判断客户端请求是否为 POST,通过 POST.get()方法接收 data 参数
if request.method == 'POST':
data = request.POST.get("data", "")
else:
return "error"
# 解密,调用解密函数 decryptAES() ,传参加密字符串和 app_key
decode = decryptAES(data, app_key)
# 转化为字典,将解密后字符串通过 json.loads()方法转化成字典,并将该字典做为 aes_encryption()函数的返回值
dict_data = json.loads(decode)
return dict_data
# 嘉宾查询接口----AES算法
def get_guest_list(request):
dict_data = aes_encryption(request) # 调用加密字符串解密函数
if dict_data == "error":
return JsonResponse({'status':10011,'message':'request error'})
# 取出对应的发布会id和嘉宾手机号
eid = dict_data['eid']
phone = dict_data['phone']
if eid == '':
return JsonResponse({'status':10021,'message':'eid cannot be empty'})
if eid != '' and phone == '':
datas = []
results = Guest.objects.filter(event_id=eid)
if results:
for r in results:
guest = {}
guest['realname'] = r.realname
guest['phone'] = r.phone
guest['email'] = r.email
guest['sign'] = r.sign
datas.append(guest)
return JsonResponse({'status':200, 'message':'success', 'data':datas})
else:
return JsonResponse({'status':10022, 'message':'query result is empty'})
if eid != '' and phone != '':
guest = {}
try:
result = Guest.objects.get(phone=phone,event_id=eid)
except ObjectDoesNotExist:
return JsonResponse({'status':10022, 'message':'query result is empty'})
else:
guest['realname'] = result.realname
guest['phone'] = result.phone
guest['email'] = result.email
guest['sign'] = result.sign
return JsonResponse({'status':200, 'message':'success', 'data':guest})
guest/sign/templates/index.html1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<html>
<head>
<title>Django Page</title>
</head>
<body>
<h1>发布会管理</h1>
<form method="post" action="/login_action/">
<input name="username" type="text" placeholder="username"><br>
<input name="password" type="password" placeholder="password"><br>
{{ error }}<br>
<button id="btn" type="submit">登录</button>
{% csrf_token %}
</form>
</body>
</html>
guest/sign/templates/event_manage.html1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68<html lang="zh-CN">
<head>
{% load bootstrap3 %}
{% bootstrap_css %}
{% bootstrap_javascript %}
<title>Guest Manage</title>
</head>
<body role="document">
<!-- 导航栏 -->
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/event_manage/">Guest Manage System</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="#">发布会</a></li>
<li><a href="/guest_manage/">嘉宾</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">{{user}}</a></li>
<li><a href="/logout/">退出</a></li>
</ul>
</div>
</div>
</nav>
<!--发布会搜索表单-->
<div class="page-header" style="padding-top: 60px;">
<div id="navbar" class="navbar-collapse collapse">
<form class="navbar-form" method="get" action="/search_name/">
<div class="form-group">
<input name="name" type="text" placeholder="名称" class="form-control">
</div>
<button type="submit" class="btn btn-success">搜索</button>
</form>
</div>
</div>
<!-- 发布会列表 -->
<div class="row" style="padding-top: 80px;">
<div class="col-md-6">
<table class="table table-striped">
<thead>
<tr>
<th>id</th><th>名称</th><th>状态</th><th>地址</th><th>时间</th>
<th>签到</th>
</tr>
</thead>
<tbody>
{% for event in events %}
<tr>
<td>{{ event.id }}</td>
<td>{{ event.name }}</td>
<td>{{ event.status }}</td>
<td>{{ event.address }}</td>
<td>{{ event.start_time }}</td>
<td> <a href="/sign_index/{{ event.id }}/" target="{{ event.id }}_blank"> sign</a> </td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</body>
</html>
guest/sign/templates/guest_manage.html1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71<html lang="zh-CN">
<head>
{% load bootstrap3 %}
{% bootstrap_css %}
{% bootstrap_javascript %}
<title>Guest Manage</title>
</head>
<body role="document">
<!-- 导航栏 -->
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/event_manage/">Guest Manage System</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="/event_manage/">发布会</a></li>
<li class="active"><a href="#">嘉宾</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">{{user}}</a></li>
<li><a href="/logout/">退出</a></li>
</ul>
</div>
</div>
</nav>
<!-- 嘉宾列表 -->
<div class="row" style="padding-top: 80px;">
<div class="col-md-6">
<table class="table table-striped">
<thead>
<tr>
<th>id</th><th>名称</th><th>手机</th><th>Email</th><th>签到</th>
<th>发布会</th>
</tr>
</thead>
<tbody>
{% for guest in guests %}
<tr>
<td>{{ guest.id }}</td>
<td>{{ guest.realname }}</td>
<td>{{ guest.phone }}</td>
<td>{{ guest.email }}</td>
<td>{{ guest.sign }}</td>
<td>{{ guest.event }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- 列表分页器 -->
<div class="pagination">
<span class="step-links">
{% if guests.has_previous %}
<a href="?page={{ guests.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ guests.number }} of {{ guests.paginator.num_pages }}.
</span>
{% if guests.has_next %}
<a href="?page={{ guests.next_page_number }}">next</a>
{% endif %}
</span>
</div>
</body>
</html>
guest/sign/templates/sign_index.html1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45<html lang="zh-CN">
<head>
{% load bootstrap3 %}
{% bootstrap_css %}
{% bootstrap_javascript %}
<title>Guest Manage</title>
</head>
<body role="document">
<!-- 导航栏 -->
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<!--将页面标题设置为发布会名称-->
<a class="navbar-brand" href="#">{{ event.name }}</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="/event_manage/">发布会</a></li>
<li><a href="/guest_manage/">嘉宾</a></li>
</ul>
</div>
</div>
</nav>
<!-- 签到功能 -->
<div class="page-header" style="padding-top: 80px;">
<div id="navbar" class="navbar-collapse collapse">
<form class="navbar-form" method="post" action="/sign_index_action/{{ event.id }}/">
<div class="form-group">
<input name="phone" type="text" placeholder="输入手机号" class="form-control">
</div>
<button type="submit" class="btn btn-success">签到</button>
{% csrf_token %}
<font color="red">
<br>{{ hint }}
<br>{{ guest.realname }}
<br>{{ guest.phone }}
</font>
</form>
</div>
</div>
</body>
</html>
2、django_rest项目
- django_rest/
- django_rest/
- __init__.py
- settings.py
- urls.py
- wsgi.py
- api/
- migrations/
- __init__.py
- 0001_initial.py
- __init__.py
- admin.py
- apps.py
- models.py
- serializers.py
- tests.py
- views.py
- db.sqlite3
- manage.py
guest/django_rest/settings.py1
2
3
4
5
6
7import os
# 在文件末尾添加
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
'PAGE_SIZE': 10
}
guest/django_rest/urls.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include
from rest_framework import routers
from api import views
"""
因为我们使用的是 viewset,所以我们可以使用 routers 类自动生成 URLconf
"""
#Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'users',views.UserViewSet)
router.register(r'groups',views.GroupViewSet)
router.register(r'event', views.EventViewSet)
router.register(r'guest', views.GuestViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^', include(router.urls)),
url(r'api-auth/', include('rest_framework.urls',namespace='rest_framework'))
]
guest/api/models.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30from django.db import models
# Create your models here.
# 发布会
class Event(models.Model):
name = models.CharField(max_length=100)
limit = models.IntegerField()
status = models.BooleanField()
address = models.CharField(max_length=200)
start_time = models.DateTimeField('events time')
create_time = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
# 嘉宾
class Guest(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
realname = models.CharField(max_length=64)
phone = models.CharField(max_length=16)
email = models.EmailField()
sign = models.BooleanField()
create_time = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ('phone', 'event')
def __str__(self):
return self.realname
guest/api/serializers.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27from django.contrib.auth.models import User, Group
from rest_framework import serializers
from api.models import Event, Guest
"""
使用的是 HyperlinkedModelSerializer,也可以使用主键或者其他关系。
但使用 HyperlinkedModelSerializer 是一个好的 RESTful 设计
"""
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = ('url', 'name')
class EventSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Event
fields = ('url','name','address','start_time','limit','status')
class GuestSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Guest
fields = ('url','realname','phone','email','sign','event')
guest/api/views.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44from django.shortcuts import render
from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from api.serializers import UserSerializer, GroupSerializer
from api.serializers import EventSerializer, GuestSerializer
from api.models import Event, Guest
"""
在 django RESTframework 中,所有常见的行为都被归到 ViewSets 中。
当然我们可以将这些行为分拆出 来,但使用 ViewSets,使 view 的逻辑更为清楚。
使用 queryset 和 serializer_class 使我们能更好的控制 API 行为,这也是我们推荐的使用方式。
"""
# Create your views here.
# ViewSets define the view behavior.
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
class GroupViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
class EventViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows events to be viewed or edited.
"""
queryset = Event.objects.all()
serializer_class = EventSerializer
class GuestViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows guests to be viewed or edited.
"""
queryset = Guest.objects.all()
serializer_class = GuestSerializer
持续更新…
最后更新: 2018年08月24日 11:21